From 71d94f45f14e038b8b28ab0e6dd2fa262dc7ec27 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 3 Jun 2026 19:10:42 +0300 Subject: [PATCH] =?UTF-8?q?refactor(admin):=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=BE=D0=B2=20?= =?UTF-8?q?=C2=AB=D0=A1=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=C2=BB=20=D0=B2=20=C2=AB=D0=9E=D0=B1=D0=B7=D0=BE=D1=80?= =?UTF-8?q?=C2=BB,=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8=20=C2=AB=D0=A1=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B0=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Обзор теперь показывает и итоги за всё время (Пользователей, Тестов пройдено, Средний результат) и средний результат по предметам за всё время — данные грузятся из adminGetStats параллельно с обзором. Дублей нет: Обзор был про 24ч и контент. Убрано полностью: nav-кнопка «Статистика», панель #tab-stats, маршрут stats в ROUTE_TO_SECTION, подключение и файл sections/stats.js. #stats-хэш падает на #overview. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/admin.html | 12 ------- frontend/js/admin/admin.js | 1 - frontend/js/admin/router.js | 2 +- frontend/js/admin/sections/overview.js | 48 +++++++++++++++++++++++-- frontend/js/admin/sections/stats.js | 50 -------------------------- 5 files changed, 46 insertions(+), 67 deletions(-) delete mode 100644 frontend/js/admin/sections/stats.js diff --git a/frontend/admin.html b/frontend/admin.html index 6378e6f..3d85186 100644 --- a/frontend/admin.html +++ b/frontend/admin.html @@ -988,9 +988,6 @@ - @@ -1096,14 +1093,6 @@
- -
-
Общая статистика
-
-
По предметам
-
-
-
@@ -2125,7 +2114,6 @@ - diff --git a/frontend/js/admin/admin.js b/frontend/js/admin/admin.js index bf6c822..d07fa3f 100644 --- a/frontend/js/admin/admin.js +++ b/frontend/js/admin/admin.js @@ -53,7 +53,6 @@ // Routes that map 1:1 to a section module (Phase 2-extracted). const ROUTE_TO_SECTION = { overview: 'overview', - stats: 'stats', questions: 'questions', tests: 'tests', assignments: 'assignments', diff --git a/frontend/js/admin/router.js b/frontend/js/admin/router.js index 4074ba0..555a642 100644 --- a/frontend/js/admin/router.js +++ b/frontend/js/admin/router.js @@ -3,7 +3,7 @@ * Wraps the existing switchTab() flow without replacing it. * * Hash format: #[/[/...]] - * #stats → { route: 'stats', params: [] } + * #overview → { route: 'overview', params: [] } * #users → { route: 'users', params: [] } * #users/123 → { route: 'users', params: ['123'] } * #sessions/456/foo → { route: 'sessions', params: ['456','foo'] } diff --git a/frontend/js/admin/sections/overview.js b/frontend/js/admin/sections/overview.js index cf2fdc8..5054829 100644 --- a/frontend/js/admin/sections/overview.js +++ b/frontend/js/admin/sections/overview.js @@ -250,7 +250,7 @@ }).join(''); } - function render(data) { + function render(data, stats) { const el = document.getElementById('overview-content'); if (!el) return; ensureOvStyles(); @@ -331,6 +331,42 @@
По предметам (24ч)
${renderSubjectBar(subjects24h)}`; + /* ── all-time totals (перенесено из бывшей вкладки «Статистика») ── */ + const allTimeHtml = stats ? ` +
Итоги за всё время
+
+
+
+
${fmtNum(stats.totalUsers)}
+
Пользователей
+
+
+
+
${fmtNum(stats.totalTests)}
+
Тестов пройдено
+
+
+
+
${stats.avgScore != null ? stats.avgScore + '%' : '—'}
+
Средний результат
+
+
` : ''; + + /* ── per-subject all-time performance (перенесено из «Статистики») ── */ + const subjAllTimeHtml = (stats && Array.isArray(stats.bySubject) && stats.bySubject.length) ? ` +
Результаты по предметам (всё время)
+
+ ${stats.bySubject.map(function (b) { + const p = b.avg_pct == null ? 0 : b.avg_pct; + const barColor = p >= 75 ? 'var(--green)' : p >= 50 ? 'var(--amber)' : 'var(--pink)'; + return '
' + + '
' + e(b.name) + '
' + b.tests + ' тестов
' + + '
' + (b.avg_pct == null ? '—' : b.avg_pct) + '%
' + + '
' + + '
'; + }).join('')} +
` : ''; + /* ── results tables ────────────────────────────────────────── */ const topTableHtml = top.length ? ` @@ -386,7 +422,9 @@ ${alertsHtml} ${invHtml} + ${allTimeHtml} ${subjHtml} + ${subjAllTimeHtml}
@@ -429,9 +467,13 @@ if (!el) return; renderSkeleton(el); try { - const data = await LS.adminGetOverview(); + // Обзор + перенесённые из бывшей вкладки «Статистика» итоги за всё время. + const [data, stats] = await Promise.all([ + LS.adminGetOverview(), + LS.adminGetStats().catch(() => null), + ]); _lastLoadTs = Date.now(); - render(data); + render(data, stats); startTsInterval(); } catch (e) { LS.state.error(el, e, () => load()); diff --git a/frontend/js/admin/sections/stats.js b/frontend/js/admin/sections/stats.js deleted file mode 100644 index 432a929..0000000 --- a/frontend/js/admin/sections/stats.js +++ /dev/null @@ -1,50 +0,0 @@ -'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 = ` -
-
-
${s.totalUsers}
-
Пользователей
-
-
-
-
${s.totalTests}
-
Тестов пройдено
-
-
-
-
${s.avgScore ?? '—'}%
-
Средний результат
-
`; - if (window.lucide) lucide.createIcons(); - const subjEl = document.getElementById('subj-stats'); - if (!s.bySubject?.length) { subjEl.innerHTML = '
Нет данных
'; 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 `
-
${esc(b.name)}
${b.tests} тестов
-
-
${b.avg_pct ?? '—'}%
-
-
-
`; - }).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, - }; -})();