Files
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

161 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 3: Dashboard #overview
**Status:** ✅ Implemented (pending review)
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
**Parallelizable with:** Phase 4, Phase 5
## Objective
Сделать `#overview` дефолтным route'ом admin-панели — landing-страница "что требует внимания". Заменяет нынешние обезличенные stat-cards (totalUsers, totalTests, ...) на actionable digest за последние 24-48 часов.
## Tasks
- [x] Backend: новый endpoint `GET /api/admin/overview`:
```js
{
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'ов где возможно
- [x] 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
- [x] HTML: добавить `<div class="tab-pane" id="tab-overview">` в admin.html (перед остальными tab-pane)
- [x] Nav: добавить admin-nav-item для `overview` (icon: layout-dashboard / activity)
- [x] Регистрация в ROUTE_TO_SECTION (из Phase 2): `overview: 'overview'`
- [x] Сделать `#overview` дефолтным route'ом в router (из Phase 1) — если пустой hash, navigate to `#overview` вместо `#stats`
- [x] Старый `#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`):**
```json
{
"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 `stats` → `overview`)
**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.