Members view: Balance / All-time mode switch (period columns hide in all-time); drop redundant 'active' total card
This commit is contained in:
1 parent
52314ddfe2
commit
8c6f2a85c0
1 file changed
+29
-20
+29
-20
@@ -273,6 +273,7 @@ PAGE_HTML = """<!DOCTYPE html>
|
|||||||
.range-label { font-size: 12px; color: var(--muted); margin-right: 2px; }
|
.range-label { font-size: 12px; color: var(--muted); margin-right: 2px; }
|
||||||
.range-btn { cursor: pointer; border: 1px solid var(--border); background: #fff; color: var(--muted); border-radius: 8px; font-size: 12px; font-weight: 600; padding: 5px 12px; }
|
.range-btn { cursor: pointer; border: 1px solid var(--border); background: #fff; color: var(--muted); border-radius: 8px; font-size: 12px; font-weight: 600; padding: 5px 12px; }
|
||||||
.range-btn.active { background: var(--green-mid); color: #fff; border-color: var(--green-mid); }
|
.range-btn.active { background: var(--green-mid); color: #fff; border-color: var(--green-mid); }
|
||||||
|
.members-all-time .balance-col { display: none; }
|
||||||
.table-scroll { overflow-x: auto; border-radius: 12px; }
|
.table-scroll { overflow-x: auto; border-radius: 12px; }
|
||||||
.table-scroll table { min-width: 980px; }
|
.table-scroll table { min-width: 980px; }
|
||||||
th.sortable { cursor: pointer; user-select: none; }
|
th.sortable { cursor: pointer; user-select: none; }
|
||||||
@@ -291,7 +292,6 @@ PAGE_HTML = """<!DOCTYPE html>
|
|||||||
|
|
||||||
<div class="totals">
|
<div class="totals">
|
||||||
<div class="tot"><div class="num" id="tot-members">—</div><div class="lbl">members</div></div>
|
<div class="tot"><div class="num" id="tot-members">—</div><div class="lbl">members</div></div>
|
||||||
<div class="tot"><div class="num" id="tot-active">—</div><div class="lbl">active</div></div>
|
|
||||||
<div class="tot"><div class="num" id="tot-spend-cycle">—</div><div class="lbl">current balance used</div></div>
|
<div class="tot"><div class="num" id="tot-spend-cycle">—</div><div class="lbl">current balance used</div></div>
|
||||||
<div class="tot"><div class="num" id="tot-spend-all">—</div><div class="lbl">all-time spend</div></div>
|
<div class="tot"><div class="num" id="tot-spend-all">—</div><div class="lbl">all-time spend</div></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -302,6 +302,10 @@ PAGE_HTML = """<!DOCTYPE html>
|
|||||||
<h2 id="view-title">Members</h2>
|
<h2 id="view-title">Members</h2>
|
||||||
<button class="tab-btn active" id="tab-members" onclick="switchView('members')">Members</button>
|
<button class="tab-btn active" id="tab-members" onclick="switchView('members')">Members</button>
|
||||||
<button class="tab-btn" id="tab-models" onclick="switchView('models')">Model usage</button>
|
<button class="tab-btn" id="tab-models" onclick="switchView('models')">Model usage</button>
|
||||||
|
<span class="range-group" id="member-mode-group">
|
||||||
|
<button class="range-btn active" id="mmode-balance" onclick="setMemberMode('balance')">Balance</button>
|
||||||
|
<button class="range-btn" id="mmode-all" onclick="setMemberMode('all')">All time</button>
|
||||||
|
</span>
|
||||||
<span class="range-group" id="range-group" style="display:none;">
|
<span class="range-group" id="range-group" style="display:none;">
|
||||||
<span class="range-label">Period</span>
|
<span class="range-label">Period</span>
|
||||||
<button class="range-btn" id="range-30" onclick="setRange(30)">Last 30 days</button>
|
<button class="range-btn" id="range-30" onclick="setRange(30)">Last 30 days</button>
|
||||||
@@ -317,11 +321,11 @@ PAGE_HTML = """<!DOCTYPE html>
|
|||||||
<th class="sortable" onclick="sortMembers('active')">Status<span class="arrow" id="arrow-members-active"></span></th>
|
<th class="sortable" onclick="sortMembers('active')">Status<span class="arrow" id="arrow-members-active"></span></th>
|
||||||
<th class="sortable" onclick="sortMembers('activated')">Activated<span class="arrow" id="arrow-members-activated"></span></th>
|
<th class="sortable" onclick="sortMembers('activated')">Activated<span class="arrow" id="arrow-members-activated"></span></th>
|
||||||
<th class="sortable" onclick="sortMembers('manual')">Source<span class="arrow" id="arrow-members-manual"></span></th>
|
<th class="sortable" onclick="sortMembers('manual')">Source<span class="arrow" id="arrow-members-manual"></span></th>
|
||||||
<th class="sortable" onclick="sortMembers('balance')">Allowance<span class="arrow" id="arrow-members-balance"></span></th>
|
<th class="sortable balance-col" onclick="sortMembers('balance')">Allowance<span class="arrow" id="arrow-members-balance"></span></th>
|
||||||
<th class="sortable" onclick="sortMembers('credit_balance')">Credits<span class="arrow" id="arrow-members-credit_balance"></span></th>
|
<th class="sortable balance-col" onclick="sortMembers('credit_balance')">Credits<span class="arrow" id="arrow-members-credit_balance"></span></th>
|
||||||
<th class="sortable" onclick="sortMembers('spend')" title="Click to toggle: spend from each member's current balance ↔ all-time">Spend (<span id="spend-mode-label">current</span>)<span class="arrow" id="arrow-members-spend"></span></th>
|
<th class="sortable" onclick="sortMembers('spend')"><span id="spend-mode-label">Spend</span><span class="arrow" id="arrow-members-spend"></span></th>
|
||||||
<th class="sortable" onclick="sortMembers('remaining')">Remaining<span class="arrow" id="arrow-members-remaining"></span></th>
|
<th class="sortable balance-col" onclick="sortMembers('remaining')">Remaining<span class="arrow" id="arrow-members-remaining"></span></th>
|
||||||
<th class="sortable" onclick="sortMembers('reset_at')">Reset<span class="arrow" id="arrow-members-reset_at"></span></th>
|
<th class="sortable balance-col" onclick="sortMembers('reset_at')">Reset<span class="arrow" id="arrow-members-reset_at"></span></th>
|
||||||
<th>Adjust</th>
|
<th>Adjust</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -376,7 +380,6 @@ async function load() {
|
|||||||
if (r.status === 403) { document.body.innerHTML = '<h1>Forbidden</h1>'; return; }
|
if (r.status === 403) { document.body.innerHTML = '<h1>Forbidden</h1>'; return; }
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
$('tot-members').textContent = d.totals.members;
|
$('tot-members').textContent = d.totals.members;
|
||||||
$('tot-active').textContent = d.totals.active;
|
|
||||||
$('tot-spend-cycle').textContent = fmt(d.totals.total_spend);
|
$('tot-spend-cycle').textContent = fmt(d.totals.total_spend);
|
||||||
$('tot-spend-all').textContent = fmt(d.totals.total_all_time_spend ?? d.totals.total_spend);
|
$('tot-spend-all').textContent = fmt(d.totals.total_all_time_spend ?? d.totals.total_spend);
|
||||||
membersData = d.members || [];
|
membersData = d.members || [];
|
||||||
@@ -393,7 +396,7 @@ let currentView = 'members';
|
|||||||
let memberSort = { key: 'email', dir: 1 };
|
let memberSort = { key: 'email', dir: 1 };
|
||||||
let modelSort = { key: 'spend', dir: -1 };
|
let modelSort = { key: 'spend', dir: -1 };
|
||||||
let rangeDays = 0; // 0 = all time
|
let rangeDays = 0; // 0 = all time
|
||||||
let spendMode = 'cycle'; // 'cycle' | 'all' — Spend column in Members view ('cycle' = usage from the member's current balance period)
|
let memberMode = 'balance'; // 'balance' | 'all' — Members view: current-balance figures vs all-time spend
|
||||||
|
|
||||||
function arrowFor(key, sortState) {
|
function arrowFor(key, sortState) {
|
||||||
if (sortState.key !== key) return '';
|
if (sortState.key !== key) return '';
|
||||||
@@ -417,8 +420,9 @@ function switchView(view) {
|
|||||||
$('tab-members').className = 'tab-btn' + (view === 'members' ? ' active' : '');
|
$('tab-members').className = 'tab-btn' + (view === 'members' ? ' active' : '');
|
||||||
$('tab-models').className = 'tab-btn' + (view === 'models' ? ' active' : '');
|
$('tab-models').className = 'tab-btn' + (view === 'models' ? ' active' : '');
|
||||||
$('view-title').textContent = view === 'members' ? 'Members' : 'Model usage';
|
$('view-title').textContent = view === 'members' ? 'Members' : 'Model usage';
|
||||||
// Range buttons only apply to the models view (spend logs are model-level).
|
// Mode groups: Balance/All-time for members; Period for models.
|
||||||
$('range-group').style.display = view === 'models' ? '' : 'none';
|
$('range-group').style.display = view === 'models' ? '' : 'none';
|
||||||
|
$('member-mode-group').style.display = view === 'members' ? '' : 'none';
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,20 +433,25 @@ function setRange(days) {
|
|||||||
loadModels();
|
loadModels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setMemberMode(mode) {
|
||||||
|
memberMode = mode;
|
||||||
|
$('mmode-balance').className = 'range-btn' + (mode === 'balance' ? ' active' : '');
|
||||||
|
$('mmode-all').className = 'range-btn' + (mode === 'all' ? ' active' : '');
|
||||||
|
// Header label reflects the mode
|
||||||
|
$('spend-mode-label').textContent = mode === 'all' ? 'All-time spend' : 'Spend';
|
||||||
|
// Balance-only columns (Allowance/Credits/Remaining/Reset) hide in all-time mode
|
||||||
|
$('table-members').parentElement.classList.toggle('members-all-time', mode === 'all');
|
||||||
|
renderMembers();
|
||||||
|
}
|
||||||
|
|
||||||
function sortMembers(key) {
|
function sortMembers(key) {
|
||||||
// Clicking the Spend header toggles the value shown (since-reset ↔ all-time)
|
|
||||||
// before sorting; other headers just sort.
|
|
||||||
if (key === 'spend') {
|
|
||||||
spendMode = spendMode === 'cycle' ? 'all' : 'cycle';
|
|
||||||
$('spend-mode-label').textContent = spendMode === 'cycle' ? 'current' : 'all-time';
|
|
||||||
}
|
|
||||||
memberSort = { key, dir: memberSort.key === key ? -memberSort.dir : 1 };
|
memberSort = { key, dir: memberSort.key === key ? -memberSort.dir : 1 };
|
||||||
clearArrows('members', memberSort);
|
clearArrows('members', memberSort);
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
function memberSpend(m) {
|
function memberSpend(m) {
|
||||||
return spendMode === 'all' ? (m.all_time_spend ?? m.spend ?? 0) : (m.spend ?? 0);
|
return memberMode === 'all' ? (m.all_time_spend ?? m.spend ?? 0) : (m.spend ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortModels(key) {
|
function sortModels(key) {
|
||||||
@@ -484,11 +493,11 @@ function renderMembers() {
|
|||||||
'<td><span class="pill ' + pill + '">' + pill + '</span></td>' +
|
'<td><span class="pill ' + pill + '">' + pill + '</span></td>' +
|
||||||
'<td>' + act + '</td>' +
|
'<td>' + act + '</td>' +
|
||||||
'<td>' + src + '</td>' +
|
'<td>' + src + '</td>' +
|
||||||
'<td class="num">' + fmt(m.balance) + '</td>' +
|
'<td class="num balance-col">' + fmt(m.balance) + '</td>' +
|
||||||
'<td>' + creditsPill + '</td>' +
|
'<td class="balance-col">' + creditsPill + '</td>' +
|
||||||
'<td class="num">' + fmt(memberSpend(m)) + '</td>' +
|
'<td class="num">' + fmt(memberSpend(m)) + '</td>' +
|
||||||
'<td class="num">' + fmt(m.remaining) + '</td>' +
|
'<td class="num balance-col">' + fmt(m.remaining) + '</td>' +
|
||||||
'<td>' + esc(reset) + '</td>' +
|
'<td class="balance-col">' + esc(reset) + '</td>' +
|
||||||
'<td><div class="adjust">' +
|
'<td><div class="adjust">' +
|
||||||
'<input id="add-' + i(m.email) + '" placeholder="+$" />' +
|
'<input id="add-' + i(m.email) + '" placeholder="+$" />' +
|
||||||
'<button data-email="' + esc(m.email) + '" onclick="addBalance(this.dataset.email)" title="Add monthly allowance">Allow</button>' +
|
'<button data-email="' + esc(m.email) + '" onclick="addBalance(this.dataset.email)" title="Add monthly allowance">Allow</button>' +
|
||||||
|
|||||||
Reference in new issue
Block a user