diff --git a/app/main.py b/app/main.py index 301880a..a9348ba 100644 --- a/app/main.py +++ b/app/main.py @@ -276,7 +276,10 @@ PAGE_HTML = """ .range-btn.active { background: var(--green-mid); color: #fff; border-color: var(--green-mid); } .members-all-time .balance-col.allowance-only, .members-all-time .balance-col.remaining-only { display: none; } - .members-all-time .credits-col-head .mode-note::after { content: " (total purchased)"; } + .admins-all-time .balance-col.allowance-only, + .admins-all-time .balance-col.remaining-only { display: none; } + .members-all-time .credits-col-head .mode-note::after, + .admins-all-time .credits-col-head .mode-note::after { content: " (total purchased)"; } .table-scroll { overflow-x: auto; border-radius: 12px; } .table-scroll table { min-width: 980px; } th.sortable { cursor: pointer; user-select: none; } @@ -336,6 +339,29 @@ PAGE_HTML = """ + +
@@ -386,6 +412,7 @@ async function load() { $('tot-spend-cycle').textContent = fmt(d.totals.total_spend); $('tot-spend-all').textContent = fmt(d.totals.total_all_time_spend ?? d.totals.total_spend); membersData = d.members || []; + adminsData = d.admins || []; if (currentView === 'members') renderMembers(); } catch (e) { flash('Could not load: ' + e.message, false); @@ -394,6 +421,7 @@ async function load() { // ---- shared state ---- let membersData = []; +let adminsData = []; let modelsData = []; let currentView = 'members'; let memberSort = { key: 'spend', dir: -1 }; // leaderboard default: most spend first @@ -442,8 +470,12 @@ function setMemberMode(mode) { $('mmode-all').className = 'range-btn' + (mode === 'all' ? ' active' : ''); // Header label reflects the mode $('spend-mode-label').textContent = mode === 'all' ? 'All-time spend' : 'Spend'; + const adminLabel = document.getElementById('spend-mode-label-admin'); + if (adminLabel) adminLabel.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'); + const adminTable = document.getElementById('table-admins'); + if (adminTable) adminTable.parentElement.classList.toggle('admins-all-time', mode === 'all'); renderMembers(); } @@ -512,6 +544,35 @@ function renderMembers() { ''; }).join(''); clearArrows('members', memberSort); + + // Admin category — operator accounts (SSO owner + bot), separate from members. + const adminSection = document.getElementById('admins-section'); + if (!adminsData.length) { adminSection.style.display = 'none'; return; } + adminSection.style.display = ''; + $('admin-rows').innerHTML = adminsData.map(m => { + const reset = m.reset_at ? m.reset_at.slice(0,10) : '—'; + const credits = m.credit_balance ?? 0; + const creditsLeft = m.credits_remaining ?? credits; + const creditsShown = memberMode === 'all' ? credits : creditsLeft; + const creditsCell = creditsShown > 0 + ? fmt(creditsShown) + : '—'; + return '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + }).join(''); } async function addCredits(email) {