Files
Learn_System/plans/admin-redesign/phase-3-dashboard.md
T
Maxim Dolgolyov 41acbdd0d0 feat(admin): phase 3 — dashboard #overview landing
GET /api/admin/overview returns 24h digest (~0.08ms/call).

- adminController.getOverview: 7 prepared statements (users 24h, sessions 24h, active users, classes count, failed sessions, banned this week, top-5 sessions)

- new section frontend/js/admin/sections/overview.js (~205L): bento-grid cards, alerts (only when >0), top-5 table, quick-links

- nav-item + tab-pane reordered: #overview is now default; #stats remains routable

Auth: admin-only (inside requireRole('admin') block, sibling of /stats).

Backward compat: all 13 existing routes unchanged.

Known follow-ups (post-merge polish):

- activeClasses counts all (label could be 'Всего классов')

- failedSessions24h includes in_progress (could tighten to abandoned only)

- topSessions24h drops NULL-score completed rows

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:26:59 +03:00

8.6 KiB
Raw Blame History

Phase 3: Dashboard #overview

Status: Implemented (pending review) Parent plan: PLAN.md Domain: fullstack Parallelizable with: Phase 4, Phase 5

Objective

Сделать #overview дефолтным route'ом admin-панели — landing-страница "что требует внимания". Заменяет нынешние обезличенные stat-cards (totalUsers, totalTests, ...) на actionable digest за последние 24-48 часов.

Tasks

  • Backend: новый endpoint GET /api/admin/overview:
    {
      newUsers24h: number,      // регистрации за 24ч
      newSessions24h: number,    // запущенных тестов
      bannedThisWeek: [{ id, name, banned_at }],
      failedSessions24h: number, // тесты со статусом не completed
      topSessions24h: [{ id, user_name, subject, score, total, finished_at }],  // топ-5 за 24ч
      activeUsers24h: number,    // unique last_login за 24ч
      pendingMigrations: number, // если возможно проверить
      recentErrors: [{ id, type, message, created_at }] // если есть audit log с типом 'error'
    }
    
    • Контроллер: backend/src/controllers/adminController.js → новая функция getOverview
    • Route: добавить в backend/src/routes/admin.js
    • Auth: admin или teacher (как остальные admin/* — RBAC same)
    • Performance: один запрос для каждого поля, простые COUNT/SELECT, без JOIN'ов где возможно
  • Frontend: новый section frontend/js/admin/sections/overview.js:
    • Использует структуру из Phase 2
    • Загружает /api/admin/overview
    • Рендерит карточки в <div id="tab-overview"> секции:
      • Активность 24ч — registr, sessions, active users (число + спарклайн если есть данные)
      • Требует внимания — banned this week (если >0), failed sessions (если >5%), pending migrations
      • Топ-сессии — таблица top-5 за день с click→drilldown
      • Quick links — "Все пользователи", "Все сессии", "Создать тест" (deep-link в соответствующие routes)
    • LS.skeleton при загрузке, LS.state.error на fail
  • HTML: добавить <div class="tab-pane" id="tab-overview"> в admin.html (перед остальными tab-pane)
  • Nav: добавить admin-nav-item для overview (icon: layout-dashboard / activity)
  • Регистрация в ROUTE_TO_SECTION (из Phase 2): overview: 'overview'
  • Сделать #overview дефолтным route'ом в router (из Phase 1) — если пустой hash, navigate to #overview вместо #stats
  • Старый #stats остаётся как доступный route (legacy backend stats), но не дефолтный

Files to Modify/Create

  • backend/src/controllers/adminController.js — добавить getOverview функцию (~60-90L)
  • backend/src/routes/admin.js — добавить router.get('/overview', requireAdmin, getOverview)
  • frontend/js/admin/sections/overview.js — новый, ~250-350L
  • frontend/admin.html — добавить <div id="tab-overview"> + nav-item + <script> тег
  • frontend/js/admin/admin.js — изменить default route на #overview (если ещё не сделано через router.js config)

Acceptance Criteria

  • /admin (без hash) → редирект на #overview
  • #overview показывает реальные данные (новые регистрации видны если кто-то зарегистрировался)
  • Карточки кликабельные (click → deep-link в users / sessions с фильтром)
  • При отсутствии данных (свежая БД) — empty states, не crash
  • Endpoint выполняется <100ms на тестовой БД
  • F5 на #overview работает
  • Auth: только teacher/admin (как остальные /admin/*)

Notes

Что считать "требует внимания"

Делаем простые threshold'ы для MVP:

  • bannedThisWeek > 0 — показать жёлтую карточку с именами
  • failedSessions24h > 0 — показать список (failed = status != 'completed')
  • pendingMigrations > 0 — показать красную (но это редкий случай — миграции применяются на старте)

Можно потом расширить до "новых жалоб", "переполненных классов", и т.д. — это уже после merge.

Дизайн карточек

Bento-grid из 4-6 карточек:

+---------+---------+
|  24ч    | Внимание|
| метрики |   !     |
+---------+---------+
|   Топ сессии      |
|   (table)         |
+---------+---------+
| Quick links       |
+-------------------+

Использовать существующие стили .stat-card, .section-title из admin.html — не изобретать.

Что НЕ делать в этой фазе

  • Не делать реалтайм WebSocket-обновления (это уже Phase 7+)
  • Не делать графики/чарты (пока числа + sparkline опционально)
  • Не делать персонализацию (например "ваши классы")

Review Checklist

  • Endpoint работает и возвращает корректную форму ответа
  • Frontend handles empty state gracefully
  • Click на quick-link корректно навигирует через AdminRouter
  • Нет hardcoded date math (использовать SQL datetime('now', '-24 hours'))
  • Roles correct (admin/teacher only, не у students)
  • No SQL injection — параметры через ? placeholders
  • Build passes

Handoff to Next Phase

Endpoint shape (GET /api/admin/overview):

{
  "newUsers24h": 0,
  "newSessions24h": 0,
  "activeUsers24h": 2,
  "activeClasses": 5,
  "failedSessions24h": 0,
  "bannedThisWeek": [
    { "id": 42, "name": "...", "email": "...", "banned_at": "2026-05-15 12:30:00" }
  ],
  "topSessions24h": [
    { "id": 101, "user_name": "...", "subject_name": "Физика",
      "score": 18, "total": 20, "percent": 90.0, "finished_at": "2026-05-16 09:14:22" }
  ]
}

Performance: ~0.08ms/call avg (benchmarked 100 iters) — well under 100ms target.

Auth: uses router.use(requireRole('admin')) block (admin-only, same as /stats). /features block (teacher+admin) is above the route. Teacher access NOT granted — matches sibling /stats behavior. If Phase 4-6 wants teacher access, move /overview above the admin-only router.use(...) line in backend/src/routes/admin.js.

Quick-link wiring (Phase 4/5 extension point): Quick-link buttons live in overview.js → render() → .ov-quick-grid. They use a data-go="#hash" attribute and a delegated click → AdminRouter.navigate(...). To add a new quick-link, append a <button class="ov-quick-btn" data-go="#whatever"> to the grid.

Deep-link openings for Phase 6:

  • #users and #sessions are wired (vanilla routes — no filter params yet)
  • Future: extend AdminRouter to support #users?filter=banned / #sessions?status=failed query-style params. Currently router parses route + params (/users/123 form), no ? parsing — Phase 6 can extend router.js::parseHash().

Router defaults updated:

  • admin.js::initAdminRouter::activate() fallback route changed 'stats''overview'
  • Initial dispatch (empty hash) navigates to #overview instead of #stats
  • Initial init call: AdminSections.overview.init() instead of AdminSections.stats.init()

Files modified/created (line counts):

  • NEW: frontend/js/admin/sections/overview.js (~205 lines)
  • backend/src/controllers/adminController.js (+57 lines: new statements + getOverview)
  • backend/src/routes/admin.js (+1 line)
  • js/api.js (+1 line helper + 1 export)
  • frontend/admin.html (nav-item +3 lines, tab-pane +4 lines, script tag +1 line, swap active)
  • frontend/js/admin/admin.js (ROUTE_TO_SECTION +1 entry, default route refs swapped from statsoverview)

Unrelated to Phase 3 (do not touch): old #stats route remains functional — used as fallback in the past, can be linked from Overview as "детальная статистика" in a future polish pass.