92030b462c
Replace ~3500L admin.js monolith with thin orchestrator (~700L) + 14 IIFE-wrapped per-section modules under /js/admin/sections/. Section modules expose AdminSections.<name>.init/reload (lazy init via switchTab/router) and re-expose onclick handlers via window.X for backward compat. Shared helpers (MODES/DIFFS, fmtDate, pctClass, renderMath, qTypeBadge, pagination) live in /js/admin/_shared.js exposed on window.AdminCtx. switchTab now dispatches to AdminSections via ROUTE_TO_SECTION map; non-extracted system tabs (topics/audit/errors/health/classroom/avatars) remain inline in admin.js. user-panel overlay markup untouched — Phase 6 will remove it.
51 lines
2.4 KiB
JavaScript
51 lines
2.4 KiB
JavaScript
'use strict';
|
|
/* admin → stats section */
|
|
(function () {
|
|
'use strict';
|
|
let inited = false;
|
|
|
|
async function load() {
|
|
try {
|
|
const s = await LS.adminGetStats();
|
|
document.getElementById('stats-grid').innerHTML = `
|
|
<div class="stat-card" style="--stat-top:var(--violet)">
|
|
<div class="stat-card-icon" style="background:rgba(155,93,229,0.1)"><i data-lucide="users" class="stat-icon"></i></div>
|
|
<div class="stat-val violet">${s.totalUsers}</div>
|
|
<div class="stat-label">Пользователей</div>
|
|
</div>
|
|
<div class="stat-card" style="--stat-top:var(--cyan)">
|
|
<div class="stat-card-icon" style="background:rgba(6,214,224,0.1)"><i data-lucide="file-text" class="stat-icon"></i></div>
|
|
<div class="stat-val cyan">${s.totalTests}</div>
|
|
<div class="stat-label">Тестов пройдено</div>
|
|
</div>
|
|
<div class="stat-card" style="--stat-top:var(--green)">
|
|
<div class="stat-card-icon" style="background:rgba(6,214,100,0.1)"><i data-lucide="target" class="stat-icon"></i></div>
|
|
<div class="stat-val green">${s.avgScore ?? '—'}%</div>
|
|
<div class="stat-label">Средний результат</div>
|
|
</div>`;
|
|
if (window.lucide) lucide.createIcons();
|
|
const subjEl = document.getElementById('subj-stats');
|
|
if (!s.bySubject?.length) { subjEl.innerHTML = '<div class="empty">Нет данных</div>'; return; }
|
|
subjEl.innerHTML = s.bySubject.map(b => {
|
|
const pct = b.avg_pct ?? 0;
|
|
const barColor = pct >= 75 ? 'var(--green)' : pct >= 50 ? 'var(--amber)' : 'var(--pink)';
|
|
return `<div class="subj-stat">
|
|
<div><div class="subj-stat-name">${esc(b.name)}</div><div class="subj-stat-info">${b.tests} тестов</div></div>
|
|
<div>
|
|
<div class="subj-stat-pct">${b.avg_pct ?? '—'}%</div>
|
|
<div style="width:60px;height:3px;background:rgba(15,23,42,0.06);border-radius:99px;margin-top:5px;overflow:hidden"><div style="width:${pct}%;height:100%;background:${barColor};border-radius:99px"></div></div>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
} catch (e) {
|
|
LS.state.error(document.getElementById('stats-grid'), e, load);
|
|
}
|
|
}
|
|
|
|
window.AdminSections = window.AdminSections || {};
|
|
window.AdminSections.stats = {
|
|
init: async () => { if (inited) return; inited = true; await load(); },
|
|
reload: load,
|
|
};
|
|
})();
|