chore(plan): admin-redesign 6-phase plan
PLAN.md + 6 subplans + CONTEXT.md Strategy: Incremental | Mode: Automated | Execution: Orchestrator Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Feature Context: Admin Panel Redesign
|
||||
|
||||
## Current State
|
||||
|
||||
(будет обновляться после каждой фазы)
|
||||
|
||||
- ⬜ Phase 1 not started — старый switchTab всё ещё единственный роутер
|
||||
- ⬜ Phase 2 not started — все 13 секций в admin.js монолите
|
||||
- ⬜ Phase 3-6 not started
|
||||
|
||||
## Temporary Workarounds
|
||||
|
||||
(пусто — заполняется implementer'ом)
|
||||
|
||||
## Cross-Phase Dependencies
|
||||
|
||||
- **Phase 2 depends on Phase 1:** sections подписываются на router events, чтобы lazy-init по hashchange
|
||||
- **Phases 3, 4, 5 depend on Phase 2:** новые модули будут добавляться в `js/admin/sections/` (структура из фазы 2)
|
||||
- **Phase 6 depends on Phase 2:** deep page для user/session — это новые sections в той же структуре
|
||||
- **Phase 6 removes** старую `.user-panel` overlay из admin.html — фазы 1-5 НЕ должны её удалять
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Существующая структура (что менять / что НЕ менять)
|
||||
|
||||
**Точки входа в admin.js:**
|
||||
- `LS.initPage()` — auth + role check
|
||||
- `switchTab(btn)` — текущий tab-роутер; будет обёрнут router'ом, но не удалён до фазы 6
|
||||
- Per-tab `*Inited` флаги (`usersInited`, `sessionsInited`, ...) — переедут в section modules
|
||||
|
||||
**Backward compat обязателен:**
|
||||
- `goAddQuestion(slug)` и подобные cross-tab onclick handlers должны работать
|
||||
- Старые ссылки `<a href="#stats">` (если есть) тоже
|
||||
|
||||
### Конвенции вновь создаваемых модулей
|
||||
|
||||
Каждая section (фаза 2):
|
||||
```js
|
||||
// js/admin/sections/<name>.js
|
||||
(function () {
|
||||
'use strict';
|
||||
let inited = false;
|
||||
async function load() { /* ... */ }
|
||||
window.AdminSections = window.AdminSections || {};
|
||||
window.AdminSections.<name> = {
|
||||
init: async () => { if (inited) return; inited = true; await load(); },
|
||||
reload: load,
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
Router (фаза 1):
|
||||
```js
|
||||
// js/admin/router.js
|
||||
window.AdminRouter = {
|
||||
navigate(hash) { /* update hash + dispatch */ },
|
||||
current() { /* parse current hash */ },
|
||||
on(event, fn) { /* subscribe */ },
|
||||
};
|
||||
```
|
||||
|
||||
### Какие onclick handlers есть сейчас (выборка)
|
||||
|
||||
Из admin.html / admin.js:
|
||||
- `onclick="switchTab(this)"` — на каждой admin-nav-item
|
||||
- `onclick="openUserPanel(event, ${u.id}, '${u.role}')"` — на user row
|
||||
- `onclick="changeRole(this)"` — на role-select
|
||||
- `onclick="goAddQuestion('${slug}')"` — cross-tab
|
||||
|
||||
Эти должны работать без изменений до фазы 6.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Feature: Admin Panel Redesign
|
||||
|
||||
**Branch:** `feature/admin-redesign`
|
||||
**Base branch:** `master`
|
||||
**Created:** 2026-05-16
|
||||
**Status:** 🟡 In Progress
|
||||
**Strategy:** Incremental
|
||||
**Mode:** Automated
|
||||
**Execution:** Orchestrator
|
||||
|
||||
## Summary
|
||||
|
||||
Превратить admin-панель LearnSpace из монолитного tab-роутера (1900L HTML + 3500L JS в одном модуле) в master-detail SPA с hash-routing, lazy-loaded per-section модулями, dashboard-landing, Cmd+K command palette, per-row quick actions и deep entity pages вместо overlay-панели.
|
||||
|
||||
**Текущее состояние:**
|
||||
- `frontend/admin.html` ~1900L
|
||||
- `frontend/js/admin/admin.js` ~3500L (после недавнего extract из inline `<script>`)
|
||||
- 13 табов: stats, questions, tests, assignments, subjects, users, sessions, permissions, shop, gam, tpl, sims, games, sublog
|
||||
- `switchTab()` ручной tab-роутер, состояние теряется при F5
|
||||
- User detail = выезжающая `.user-panel` overlay внутри tab-users
|
||||
|
||||
**Цели:**
|
||||
- F5/bookmark на `#users/123` работают
|
||||
- admin.js ≤ 800L
|
||||
- Dashboard + Ctrl+K + hover-actions для частых сценариев
|
||||
- Полноценная страница user/session вместо overlay
|
||||
|
||||
## Build & Test Commands
|
||||
|
||||
- **Start:** `cd backend && npm start` (vanilla JS, нет бандлера — server раздаёт static)
|
||||
- **Dev:** `cd backend && npm run dev` (nodemon)
|
||||
- **Test:** `cd backend && npm test` (node --test)
|
||||
- **Lint:** `cd backend && npm run lint:routes` (route auth checker)
|
||||
- **Manual verify:** открыть `http://localhost:3000/admin` и пройти основные сценарии
|
||||
|
||||
## Phases
|
||||
|
||||
- [ ] Phase 1: Hash-router [domain: frontend] → [subplan](./phase-1-hash-router.md)
|
||||
- [ ] Phase 2: Split admin.html → per-section modules [domain: frontend] → [subplan](./phase-2-split-sections.md)
|
||||
- [ ] Phase 3: Dashboard #overview [domain: fullstack] → [subplan](./phase-3-dashboard.md) (parallelizable with 4, 5)
|
||||
- [ ] Phase 4: Cmd+K command palette [domain: fullstack] → [subplan](./phase-4-palette.md) (parallelizable with 3, 5)
|
||||
- [ ] Phase 5: Per-row quick actions [domain: frontend] → [subplan](./phase-5-quick-actions.md) (parallelizable with 3, 4)
|
||||
- [ ] Phase 6: Deep entity pages [domain: frontend] → [subplan](./phase-6-deep-pages.md)
|
||||
|
||||
**Параллелизация:** фазы 3, 4, 5 независимы (touch different files, no shared state) — выполняются параллельно после завершения фазы 2.
|
||||
|
||||
## Phase Progress Log
|
||||
|
||||
| Phase | Domain | Status | Review | Build | Committed |
|
||||
|-------|--------|--------|--------|-------|-----------|
|
||||
| Phase 1: Hash-router | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 2: Split sections | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 3: Dashboard | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 4: Palette | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 5: Quick actions | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 6: Deep pages | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
|
||||
## Final Review
|
||||
- [ ] Comprehensive code review (final-reviewer agent)
|
||||
- [ ] Security review (auth-touching changes in new endpoints)
|
||||
- [ ] Full build passes (server starts, no errors)
|
||||
- [ ] Manual smoke test
|
||||
- [ ] Merged to `master`
|
||||
|
||||
## Acceptance Criteria (whole feature)
|
||||
|
||||
- F5 на любом `#sub-route` восстанавливает state
|
||||
- admin.js ≤ 800L
|
||||
- Ctrl+K находит пользователя по имени за <100ms
|
||||
- Dashboard `#overview` показывает данные за 24ч
|
||||
- Per-row hover-actions на users/sessions
|
||||
- `#users/123` = полноценная страница, не overlay
|
||||
- Все existing onclick handlers продолжают работать (backward compat в фазах 1-5)
|
||||
- Нет регрессий в тестах
|
||||
|
||||
## Tech Stack & Conventions Reference
|
||||
|
||||
- **Stack:** vanilla JS, Express 4, SQLite (better-sqlite3 sync), JWT, WebSocket+SSE, KaTeX, Lucide
|
||||
- **Frontend:** pages = `frontend/*.html`, JS = `/js/*` или `frontend/js/*`, все API через `window.LS.*`
|
||||
- **UI primitives:** `LS.modal`, `LS.confirm`, `LS.toast`, `LS.state`, `LS.skeleton`, `LS.esc`
|
||||
- **localStorage prefix:** `ls_*`
|
||||
- **Icons:** inline SVG `.ic` или Lucide CDN — **эмоджи запрещены**
|
||||
- **Search в коде:** только ast-index (пользователь категорически запретил Grep)
|
||||
- **Backend:** layer-based — `controllers/`, `routes/`, `services/`, `db/migrations/NNN_*.sql`
|
||||
@@ -0,0 +1,90 @@
|
||||
# Phase 1: Hash-router
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
|
||||
Заложить фундамент для URL-роутинга admin-панели через `location.hash`. После этой фазы можно делать F5 на `#users`, делиться deep-links, использовать browser back/forward. Старая система табов (`switchTab`) продолжает работать без изменений — router её обёртывает, а не заменяет.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Создать `frontend/js/admin/router.js` с `window.AdminRouter`:
|
||||
- `parse(hash)` → `{ route: 'users', params: ['123'], raw }`
|
||||
- `navigate(routeOrHash, { replace?, silent? })` — программная навигация
|
||||
- `current()` → текущий route object
|
||||
- `on(event, fn)` / `off(event, fn)` — pub/sub для 'change' event
|
||||
- Поддержка форматов: `#stats`, `#users`, `#users/123`, `#sessions/456`
|
||||
- [ ] Подключить `router.js` в `admin.html` ДО `admin.js`
|
||||
- [ ] В `admin.js` модифицировать `switchTab(btn)`:
|
||||
- Дополнительно вызывать `AdminRouter.navigate('#' + name, { silent: true })`
|
||||
- НЕ удалять старую логику
|
||||
- [ ] Добавить листенер `AdminRouter.on('change', ...)` в admin.js:
|
||||
- При route change → найти соответствующий `.admin-nav-item[data-tab="X"]` и активировать его (через имеющийся switchTab, но с `silent`-флагом чтобы избежать рекурсии)
|
||||
- [ ] При инициализации страницы:
|
||||
- Если `location.hash` пустой → set default `#stats`
|
||||
- Если есть hash → распарсить и переключить на соответствующий tab
|
||||
- [ ] Логировать unknown routes: `console.warn('AdminRouter: unknown route', route)` + fallback на `#stats`
|
||||
- [ ] Защита от инфинит-loop'а: флаг `_routerNavigating` при programmatic-навигации, чтобы handler не реагировал на свой же hash change
|
||||
|
||||
## Files to Modify/Create
|
||||
|
||||
- `frontend/js/admin/router.js` — новый, ~80-120L
|
||||
- `frontend/admin.html` — добавить `<script src="/js/admin/router.js"></script>` в `<head>` или перед admin.js
|
||||
- `frontend/js/admin/admin.js` — модифицировать `switchTab` + добавить init-логику (~15-25L изменений)
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- F5 на `http://localhost:3000/admin#users` восстанавливает users-tab
|
||||
- Browser back/forward переключают между табами (без полного reload)
|
||||
- Клик по admin-nav-item обновляет URL (`#users` появляется в адресной строке)
|
||||
- Клик по cross-tab handler типа `goAddQuestion('bio')` — старая логика работает, URL обновляется
|
||||
- Unknown hash (например `#nonexistent`) → console.warn + fallback на `#stats`, нет crash
|
||||
- `#users/123` парсится корректно (params=['123']), но пока никто его не использует — Phase 6 подключит
|
||||
|
||||
## Notes
|
||||
|
||||
### Почему hash-router, а не history.pushState
|
||||
|
||||
Backend Express раздаёт admin.html по `/admin`. С `pushState` пришлось бы либо настраивать catch-all route на server-стороне (`/admin/*`), либо делать SPA-style роутинг. Hash-router работает out-of-the-box и не требует backend-изменений. Это критично для incremental-стратегии — мы не трогаем server в Phase 1.
|
||||
|
||||
### Защита от рекурсии
|
||||
|
||||
Сценарий: пользователь кликает на tab → switchTab вызывает navigate → navigate меняет hash → срабатывает hashchange → router emits 'change' → handler вызывает switchTab → snake eats tail.
|
||||
|
||||
Решение:
|
||||
```js
|
||||
let _navigating = false;
|
||||
function navigate(hash) {
|
||||
_navigating = true;
|
||||
location.hash = hash;
|
||||
_navigating = false;
|
||||
}
|
||||
window.addEventListener('hashchange', () => {
|
||||
if (_navigating) return;
|
||||
// emit 'change'
|
||||
});
|
||||
```
|
||||
|
||||
Или передавать `{ silent: true }` через объект-параметр и проверять его в handler'е switchTab.
|
||||
|
||||
### Существующий пример hashchange
|
||||
|
||||
В `frontend/js/textbook-tracker.js:438` уже есть `addEventListener('hashchange', handleHashNav)` — это safe-pattern, можно подсмотреть структуру.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- [ ] router.js не использует Grep / эмоджи / отсутствующие LS-помощники
|
||||
- [ ] Старый switchTab НЕ удалён, только обёрнут
|
||||
- [ ] Нет регрессий: все 13 табов переключаются, lazy-load работает
|
||||
- [ ] F5 / back / forward проверены вручную в браузере (или симуляция через subagent)
|
||||
- [ ] Default route `#stats` срабатывает при пустом hash
|
||||
- [ ] Unknown route не крашит панель
|
||||
- [ ] Код следует конвенциям проекта (no emoji, inline SVG для иконок, LS.* для API)
|
||||
- [ ] Build passes: `cd backend && npm start` → http://localhost:3000/admin загружается
|
||||
|
||||
## Handoff to Next Phase
|
||||
|
||||
<!-- Заполнит implementer после фазы.
|
||||
Должно содержать: где живёт router API, какие события эмитятся, как Phase 2 sections подписываются на route changes. -->
|
||||
@@ -0,0 +1,151 @@
|
||||
# Phase 2: Split admin.html → per-section modules
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
|
||||
Разделить монолит `admin.js` (3500L) на per-section модули в `frontend/js/admin/sections/*.js`. После фазы `admin.js` становится оркестратором (~500-800L): он только подключает router, инициализирует общие виджеты (notif, sidebar) и делегирует загрузку section-данных в соответствующий модуль.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Создать `frontend/js/admin/sections/` директорию
|
||||
- [ ] Определить единый паттерн модуля:
|
||||
```js
|
||||
// js/admin/sections/<name>.js
|
||||
(function () {
|
||||
'use strict';
|
||||
let inited = false;
|
||||
const ctx = { user: null, isAdmin: false }; // прокидываем из admin.js
|
||||
async function load() { /* существующий loadX код */ }
|
||||
window.AdminSections = window.AdminSections || {};
|
||||
window.AdminSections.<name> = {
|
||||
init: async (sharedCtx) => {
|
||||
Object.assign(ctx, sharedCtx);
|
||||
if (inited) return; inited = true; await load();
|
||||
},
|
||||
reload: load,
|
||||
};
|
||||
})();
|
||||
```
|
||||
- [ ] Извлечь 13 секций (в порядке риска — от меньшего к большему):
|
||||
- [ ] `stats.js` — `loadStats` + связанные функции (small, ~50L)
|
||||
- [ ] `sublog.js` — submission log (medium)
|
||||
- [ ] `sims.js`, `games.js`, `tpl.js` — admin-only (small каждая)
|
||||
- [ ] `subjects.js` — настройка доступных тестов
|
||||
- [ ] `permissions.js`
|
||||
- [ ] `shop.js` — items + purchases + award coins
|
||||
- [ ] `gam.js` — gamification stats + award xp
|
||||
- [ ] `assignments.js`
|
||||
- [ ] `tests.js`
|
||||
- [ ] `questions.js` — самая большая, ~800L (включая Q-modal)
|
||||
- [ ] `users.js` — users-table + pagination + user-panel (overlay остаётся!)
|
||||
- [ ] `sessions.js` — sessions-table + session detail
|
||||
- [ ] Модифицировать `admin.js`:
|
||||
- Удалить функции, перенесённые в sections
|
||||
- Заменить inline вызовы (`loadUsers()` → `AdminSections.users.init(ctx)`)
|
||||
- Добавить генератор route→section маппинга:
|
||||
```js
|
||||
const ROUTE_TO_SECTION = {
|
||||
stats: 'stats', users: 'users', sessions: 'sessions',
|
||||
questions: 'questions', tests: 'tests', assignments: 'assignments',
|
||||
subjects: 'subjects', permissions: 'permissions',
|
||||
shop: 'shop', gam: 'gam', tpl: 'tpl', sims: 'sims', games: 'games', sublog: 'sublog',
|
||||
};
|
||||
AdminRouter.on('change', ({ route }) => {
|
||||
const sec = ROUTE_TO_SECTION[route];
|
||||
if (sec && AdminSections[sec]) AdminSections[sec].init(sharedCtx);
|
||||
});
|
||||
```
|
||||
- [ ] Все 13 `<script>` тегов добавить в `admin.html` (после router.js, перед admin.js)
|
||||
- [ ] Глобальные функции, которые используются из onclick HTML, остаются доступными через `window.X`:
|
||||
- `changeRole`, `openUserPanel`, `goAddQuestion`, `confirmDeleteUser`, etc.
|
||||
- Каждый section module экспортирует свои onclick-handler'ы через `window.X = X` или через делегацию из admin.js
|
||||
- [ ] Удалить per-tab `*Inited` флаги из admin.js — они переехали внутрь section modules
|
||||
|
||||
## Files to Modify/Create
|
||||
|
||||
- `frontend/js/admin/sections/stats.js` — новый
|
||||
- `frontend/js/admin/sections/users.js` — новый, ~400-500L
|
||||
- `frontend/js/admin/sections/sessions.js` — новый
|
||||
- `frontend/js/admin/sections/questions.js` — новый, ~800L
|
||||
- `frontend/js/admin/sections/tests.js` — новый
|
||||
- `frontend/js/admin/sections/assignments.js` — новый
|
||||
- `frontend/js/admin/sections/subjects.js` — новый
|
||||
- `frontend/js/admin/sections/permissions.js` — новый
|
||||
- `frontend/js/admin/sections/shop.js` — новый
|
||||
- `frontend/js/admin/sections/gam.js` — новый
|
||||
- `frontend/js/admin/sections/tpl.js` — новый
|
||||
- `frontend/js/admin/sections/sims.js` — новый
|
||||
- `frontend/js/admin/sections/games.js` — новый
|
||||
- `frontend/js/admin/sections/sublog.js` — новый
|
||||
- `frontend/js/admin/admin.js` — сильно ужать (с 3500L до ~500-800L)
|
||||
- `frontend/admin.html` — добавить 13 `<script>` тегов
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- `admin.js` ≤ 800L (от 3500L)
|
||||
- Каждый section-файл ≤ 900L (questions.js самый большой)
|
||||
- Все 13 табов работают идентично текущему поведению (no regressions)
|
||||
- Cross-tab handlers (`goAddQuestion`, `confirmDelete*`) работают
|
||||
- Lazy-load работает: при первом открытии tab делается fetch, при повторном — нет
|
||||
- F5 на любом `#X` корректно ленево-грузит секцию (через router из Phase 1)
|
||||
- Browser back/forward работают
|
||||
- Никаких console errors в Devtools
|
||||
|
||||
## Notes
|
||||
|
||||
### Стратегия извлечения
|
||||
|
||||
Один section за раз, мелкими безопасными шагами:
|
||||
1. Скопировать функции `loadX, openXModal, deleteX, ...` в новый файл sections/<name>.js, обернуть в IIFE
|
||||
2. Экспортировать через `window.AdminSections.X`
|
||||
3. Подключить `<script>` в admin.html
|
||||
4. В admin.js заменить вызовы (`loadX()` → `AdminSections.X.init(ctx)`)
|
||||
5. Удалить дубликаты в admin.js
|
||||
6. Тест: открыть tab — работает?
|
||||
7. Перейти к следующей секции
|
||||
|
||||
### Что НЕ переезжает в sections
|
||||
|
||||
- `LS.initPage()` + auth check — остаётся в admin.js
|
||||
- `switchTab` (helper) — остаётся
|
||||
- `pctClass`, `fmtDate`, `fmtTime` — общие утилиты, остаются (или переезжают в `admin/_shared.js`)
|
||||
- Sidebar / notif init — остаётся
|
||||
- Router setup — остаётся
|
||||
|
||||
### Глобальные функции из onclick
|
||||
|
||||
Сейчас многие функции вызываются из HTML onclick (`onclick="openUserPanel(...)"`). Чтобы не переписывать HTML на этой фазе, в каждом section module экспортируем нужные функции через `window.X = X` внутри IIFE. Phase 5/6 могут заменить onclick на event delegation, но Phase 2 этого не делает (incremental).
|
||||
|
||||
### Тестирование каждой секции
|
||||
|
||||
После каждой выделенной секции:
|
||||
- Открыть `/admin` → переключиться на этот tab → данные загрузились
|
||||
- Все кнопки/модалки секции работают
|
||||
- Cross-tab navigation (если есть) работает
|
||||
- F5 на `#<route>` корректно открывает tab
|
||||
|
||||
Если регрессия — откатить эту итерацию, разобраться, починить.
|
||||
|
||||
### Совет implementer'у
|
||||
|
||||
Если фаза становится огромной — можно сделать несколько коммитов внутри phase branch. Это inscope. Не нужно делать один гигантский коммит на 14 файлов.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- [ ] Все 13 секций имеют одинаковую структуру (init/reload)
|
||||
- [ ] admin.js ≤ 800L, в нём нет дублирования с sections
|
||||
- [ ] Все window.X экспорты есть для onclick handlers
|
||||
- [ ] Lazy-init работает (профилировка: при открытии tab → fetch, при повторе → нет)
|
||||
- [ ] F5 на каждом из 13 routes восстанавливает секцию
|
||||
- [ ] Build passes: server starts, no errors
|
||||
|
||||
## Handoff to Next Phase
|
||||
|
||||
<!-- Implementer должен зафиксировать:
|
||||
- Структуру window.AdminSections.X.init/reload (точное API)
|
||||
- Какие функции стали глобальными через window.X (список)
|
||||
- Как Phase 3 (dashboard) должна добавиться: новый sections/overview.js + new route в ROUTE_TO_SECTION
|
||||
- Где живут shared утилиты (pctClass, fmtDate, esc) — admin.js или вынесены в _shared.js -->
|
||||
@@ -0,0 +1,112 @@
|
||||
# Phase 3: Dashboard #overview
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**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
|
||||
|
||||
- [ ] 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'ов где возможно
|
||||
- [ ] 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
|
||||
|
||||
<!-- Implementer: записать, какой shape у overview-ответа,
|
||||
какие route переходы внедрены, какие deep-links открыты для Phase 6
|
||||
(например, `#users?filter=banned` или `#sessions?status=failed`). -->
|
||||
@@ -0,0 +1,113 @@
|
||||
# Phase 4: Cmd+K command palette
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
**Parallelizable with:** Phase 3, Phase 5
|
||||
|
||||
## Objective
|
||||
|
||||
Глобальный палеттный поиск по Ctrl+K (Cmd+K на Mac) — нахоит entities (users, tests, classes, sessions) + actions ("выдать монеты ученику", "разбанить", "создать класс", deep-link routes). Радикально сокращает количество кликов для частых сценариев.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Backend: новый endpoint `GET /api/admin/search?q=X&limit=8`:
|
||||
- Возвращает смешанный результат:
|
||||
```js
|
||||
{
|
||||
users: [{ id, name, email, role }], // top 5 по name LIKE / email LIKE
|
||||
tests: [{ id, name, subject_slug }], // top 3
|
||||
classes: [{ id, name, code }], // top 3
|
||||
sessions: [] // skip пока, добавим если нужно
|
||||
}
|
||||
```
|
||||
- Контроллер: новая функция `globalSearch` в `adminController.js`
|
||||
- Route: `router.get('/search', requireAdmin, globalSearch)`
|
||||
- Каждая sub-query SELECT отдельно с LIMIT, общий ответ — простой json
|
||||
- Auth: admin only (teachers видят только своих учеников; для упрощения — admin)
|
||||
- [ ] Frontend: `frontend/js/admin/palette.js` — palette модуль:
|
||||
- Не section, а глобальный widget — подключается в admin.js init
|
||||
- Слушает `keydown` на `Ctrl+K` / `Cmd+K` (preventDefault)
|
||||
- Открывает modal через `LS.modal()`:
|
||||
- Header: search input (autofocus)
|
||||
- Body: список результатов с keyboard nav (↑↓ Enter Esc)
|
||||
- Иконка типа справа от каждого результата (User, Test, Class, Action)
|
||||
- Дебаунс поиска ~150ms
|
||||
- Min длина query: 2 символа
|
||||
- При query='' → показать "Recent Actions" hardcoded list
|
||||
- [ ] Actions index (hardcoded в palette.js):
|
||||
```js
|
||||
const ACTIONS = [
|
||||
{ id: 'award_coins', name: 'Выдать монеты', icon: 'coins', handler: () => AdminRouter.navigate('#shop') },
|
||||
{ id: 'award_xp', name: 'Выдать XP', icon: 'zap', handler: () => AdminRouter.navigate('#gam') },
|
||||
{ id: 'new_class', name: 'Создать класс', icon: 'plus-circle', handler: () => window.location.href = '/classes' },
|
||||
{ id: 'new_test', name: 'Создать тест', icon: 'file-plus', handler: () => AdminRouter.navigate('#tests') },
|
||||
{ id: 'view_users', name: 'Все пользователи', icon: 'users', handler: () => AdminRouter.navigate('#users') },
|
||||
{ id: 'view_sessions', name: 'Все сессии', icon: 'history', handler: () => AdminRouter.navigate('#sessions') },
|
||||
{ id: 'view_audit', name: 'Audit log', icon: 'shield', handler: () => AdminRouter.navigate('#sublog') },
|
||||
// …добавлять по мере надобности
|
||||
];
|
||||
```
|
||||
- Fuzzy-match в JS (substring match по name) при query
|
||||
- [ ] Открытие результата:
|
||||
- User → `AdminRouter.navigate('#users/' + id)` (Phase 6 будет рендерить deep page; пока fallback на `#users` + opening user-panel через имеющийся `openUserPanel`)
|
||||
- Test → `AdminRouter.navigate('#tests')` + scroll к row (если поддерживается, иначе просто tab)
|
||||
- Class → `window.location.href = '/classes#' + id`
|
||||
- Action → выполнить handler
|
||||
- [ ] Стили palette: глассморфизм/blur, центрировано, max-width 600px, dark/light theme-friendly. Использовать существующие токены `--surface`, `--border`, `--text-2`.
|
||||
- [ ] Подсказка в UI: footer dialog'а "↑↓ — навигация · ↵ — выбрать · esc — закрыть"
|
||||
|
||||
## Files to Modify/Create
|
||||
|
||||
- `backend/src/controllers/adminController.js` — добавить `globalSearch` (~60L)
|
||||
- `backend/src/routes/admin.js` — добавить `/search` route
|
||||
- `js/api.js` — добавить `LS.adminGlobalSearch(q)` helper (~5L)
|
||||
- `frontend/js/admin/palette.js` — новый, ~300-400L
|
||||
- `frontend/admin.html` — добавить `<script src="/js/admin/palette.js"></script>`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Ctrl+K (Cmd+K) открывает palette из любого таба admin
|
||||
- Esc закрывает
|
||||
- Печать "иван" → top users с именем "Иван..."
|
||||
- Печать "монеты" → action "Выдать монеты"
|
||||
- ↑↓ навигация работает, Enter выполняет
|
||||
- Поиск отрабатывает <100ms для 8 результатов на тестовой БД
|
||||
- Click outside / Esc закрывают
|
||||
- LS.modal используется (не reinventing wheel)
|
||||
- Auth: только admin может открыть (teachers — палетту не открывают)
|
||||
|
||||
## Notes
|
||||
|
||||
### Почему Ctrl+K а не /
|
||||
|
||||
Ctrl+K — индустри-стандарт (GitHub, Linear, Vercel, Slack). `/` конфликтует с input'ами.
|
||||
|
||||
### Дебаунсинг
|
||||
|
||||
Простой setTimeout/clearTimeout. Без библиотек.
|
||||
|
||||
### LS.modal compat
|
||||
|
||||
LS.modal сейчас принимает `{ title, body, footer, onOk, onClose, size }`. Для palette нужен focus management — autofocus input при открытии. Можно использовать через колбэк `onMount` если он есть, либо `setTimeout(() => input.focus(), 0)` после открытия.
|
||||
|
||||
### Что НЕ делать в этой фазе
|
||||
|
||||
- Не делать ML/fuzzy-search в backend (LIKE достаточно)
|
||||
- Не делать historic recents (Cmd+K recents) — это уже после merge
|
||||
- Не делать collaboration ("кто-то ещё печатает")
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- [ ] Ctrl+K не конфликтует с системными shortcut'ами браузера
|
||||
- [ ] Palette не открывается если фокус в textarea / input (если требует ввод)... опционально, можно открывать всегда
|
||||
- [ ] No SQL injection в /admin/search
|
||||
- [ ] Эскейпинг через LS.esc для рендеринга имён пользователей
|
||||
- [ ] No N+1 queries (один SELECT на тип сущности)
|
||||
- [ ] Build passes
|
||||
|
||||
## Handoff to Next Phase
|
||||
|
||||
<!-- Implementer: записать, какой формат ответа /admin/search,
|
||||
как palette вызывает navigate (важно для Phase 6 — deep user page будет ловить #users/N),
|
||||
какие actions zarejestrowano (Phase 6 может добавить ещё). -->
|
||||
@@ -0,0 +1,94 @@
|
||||
# Phase 5: Per-row quick actions
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
**Parallelizable with:** Phase 3, Phase 4
|
||||
|
||||
## Objective
|
||||
|
||||
На hover-строке user / session показывать кнопки частых action прямо в таблице — без открытия overlay-панели. Сокращает 2-3 клика до 1 для типичных задач (бан, выдача монет, удаление сессии).
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] **Users table** (`frontend/js/admin/sections/users.js`):
|
||||
- Добавить в каждый `<tr>` дополнительную ячейку или абсолютно-позиционированный блок с action-кнопками
|
||||
- Visible: только на `:hover` строки (via CSS)
|
||||
- Кнопки:
|
||||
- **🔒 Ban / Unban** — открывает confirm modal, на confirm вызывает существующий `toggleBanUser()` (или его эквивалент с userId)
|
||||
- **🪙 Award coins** — открывает быстрый prompt-modal "Сколько монет?", вызывает существующий `shopAdminAwardCoins` без перехода в shop tab
|
||||
- **📜 Sessions** — навигирует через `AdminRouter.navigate('#sessions?user=' + uid)` (param Phase 6 будет обрабатывать; пока fallback — переход на sessions tab)
|
||||
- **🗑 Delete** — confirm, вызывает существующий `confirmDeleteUser`
|
||||
- **ВАЖНО:** иконки только inline SVG (.ic класс) или Lucide — НИКАКИХ эмоджи
|
||||
- Кнопки `event.stopPropagation()` чтобы не триггерить `openUserPanel`
|
||||
- [ ] **Sessions table** (`frontend/js/admin/sections/sessions.js`):
|
||||
- **👁 View** — открыть session detail (текущий механизм)
|
||||
- **🗑 Delete** — confirm + DELETE /admin/sessions/:id (если такой endpoint есть, иначе добавить)
|
||||
- [ ] **Если delete session endpoint отсутствует** — добавить в backend:
|
||||
- `DELETE /api/admin/sessions/:id` с auth admin only
|
||||
- Контроллер: удалить из `test_sessions` + connected `session_answers`
|
||||
- Audit log entry
|
||||
- [ ] **CSS** (в admin.html style блоке или новый файл):
|
||||
```css
|
||||
.row-actions { opacity: 0; transition: opacity .15s; display: inline-flex; gap: 4px; }
|
||||
tr:hover .row-actions { opacity: 1; }
|
||||
.row-action-btn { width: 28px; height: 28px; border-radius: 6px; ... }
|
||||
```
|
||||
- [ ] Подсказки через `title="..."` атрибут на каждой кнопке
|
||||
- [ ] Confirm-модалки используют `LS.confirm` (не reinventing)
|
||||
|
||||
## Files to Modify/Create
|
||||
|
||||
- `frontend/js/admin/sections/users.js` — модификация renderRow + action handlers (~50-100L добавления)
|
||||
- `frontend/js/admin/sections/sessions.js` — same (~30-50L)
|
||||
- `frontend/admin.html` — стили для `.row-actions` (~30L)
|
||||
- `backend/src/controllers/adminController.js` — `deleteSession` если отсутствует
|
||||
- `backend/src/routes/admin.js` — `DELETE /sessions/:id` если отсутствует
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Hover на user row → видны 4 кнопки справа без раздвигания layout
|
||||
- Hover на session row → видны 2 кнопки
|
||||
- Каждая кнопка работает (ban / coins / sessions / delete)
|
||||
- Click на кнопку НЕ открывает user-panel overlay (stopPropagation)
|
||||
- Tooltip на hover каждой кнопки
|
||||
- Confirm для деструктивных action (delete, ban)
|
||||
- LS.toast после success
|
||||
- Auth check — все action available только admin
|
||||
- Mobile: actions hidden (tap-only context), либо альтернативный UI (long-press → menu) — пока минимум скрыть на ≤768px
|
||||
|
||||
## Notes
|
||||
|
||||
### Существующие helpers использовать
|
||||
|
||||
- `LS.confirm(message, { okText, danger })` для подтверждений
|
||||
- `LS.modal(...)` если нужна форма (например award coins amount)
|
||||
- `LS.toast` для feedback
|
||||
- Существующие admin* функции (toggleBanUser, awardCoins, etc.) — не дублировать
|
||||
|
||||
### Визуальный паттерн
|
||||
|
||||
Inspired by Linear / Vercel admin: actions visible on row hover, positioned right-aligned, ghost-style buttons (transparent bg, border on hover). Иконки только.
|
||||
|
||||
### Что НЕ делать в этой фазе
|
||||
|
||||
- Не делать bulk-actions (select multiple → action) — это после merge
|
||||
- Не делать undo (toast с "отменить" внутри) — Phase 6+
|
||||
- Не менять структуру таблицы radically
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- [ ] Кнопки не сдвигают layout (используют absolute / hidden / opacity)
|
||||
- [ ] Все action эскейпят пользовательский ввод
|
||||
- [ ] No emoji — только SVG
|
||||
- [ ] event.stopPropagation на всех кнопках
|
||||
- [ ] Confirm для destructive actions
|
||||
- [ ] Tooltip присутствует
|
||||
- [ ] Mobile-friendly (hidden или альтернативный UI)
|
||||
- [ ] Build passes
|
||||
|
||||
## Handoff to Next Phase
|
||||
|
||||
<!-- Implementer: записать, какие action-кнопки добавлены,
|
||||
какие param-форматы router использует (`#sessions?user=N`),
|
||||
что Phase 6 deep-page должна включить (например, replace #users overlay на deep page). -->
|
||||
@@ -0,0 +1,117 @@
|
||||
# Phase 6: Deep entity pages
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
|
||||
Заменить выезжающую `.user-panel` overlay на полноценную страницу с URL `#users/123`. Аналогично для session: `#sessions/456` = full detail page. Это самая комплексная фаза — она ломает совместимость с старым overlay UI (удаляет код), потому идёт ПОСЛЕ всех остальных.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] **User detail page** (`frontend/js/admin/sections/user-detail.js`):
|
||||
- Реагирует на route `#users/:id`
|
||||
- Layout:
|
||||
- **Header**: avatar, name, role badge, email, action buttons (ban/edit/perms/delete), back-link to `#users`
|
||||
- **Tabs** (sub-nav в странице):
|
||||
- Overview — статистика (тестов, средний %, регистрация, посл вход)
|
||||
- Sessions — таблица последних 20 сессий с pagination
|
||||
- Classes — список классов где он состоит
|
||||
- Audit — журнал действий (если есть audit log с user_id)
|
||||
- **Graphs** (опционально, можно отдельным таб'ом):
|
||||
- Простой SVG-чарт: успеваемость по неделям
|
||||
- Mini-bar chart: avg % по предметам
|
||||
- [ ] **Session detail page** (`frontend/js/admin/sections/session-detail.js`):
|
||||
- Реагирует на route `#sessions/:id`
|
||||
- Layout: header (user, subject, score, дата) + список вопросов/ответов (правильно/нет, текст), back-link
|
||||
- [ ] **Router updates** (`frontend/js/admin/router.js` если ещё не поддерживает):
|
||||
- `#users/123` → emit { route: 'users', params: ['123'] }
|
||||
- `#sessions/456` → emit { route: 'sessions', params: ['456'] }
|
||||
- [ ] **Admin.js dispatch**:
|
||||
- При route с params → init detail-section вместо list-section
|
||||
- При route без params → init list-section (как раньше)
|
||||
- [ ] **Удалить overlay-код:**
|
||||
- В `frontend/admin.html` удалить `<div class="user-panel" id="user-panel">` блок
|
||||
- В `sections/users.js` удалить `openUserPanel`, `closeUserPanel`, `reloadUserPanel`
|
||||
- В `sections/users.js` поменять onclick: `onclick="openUserPanel(event,${u.id},'${u.role}')"` → `onclick="AdminRouter.navigate('#users/${u.id}')"`
|
||||
- [ ] **Replace** в Phase 5 quick action "Sessions" — теперь `AdminRouter.navigate('#users/${uid}/sessions')`:
|
||||
- Парсить sub-tab из route
|
||||
- Открывать user-detail page с активным Sessions tab
|
||||
- [ ] **Глоссарий routes после фазы:**
|
||||
- `#overview` — dashboard (Phase 3)
|
||||
- `#users` — list
|
||||
- `#users/123` — user detail (overview tab default)
|
||||
- `#users/123/sessions` — user detail with sessions sub-tab
|
||||
- `#sessions` — list
|
||||
- `#sessions/456` — session detail
|
||||
- … остальные без params — как было
|
||||
|
||||
## Files to Modify/Create
|
||||
|
||||
- `frontend/js/admin/sections/user-detail.js` — новый, ~400-600L
|
||||
- `frontend/js/admin/sections/session-detail.js` — новый, ~200-300L
|
||||
- `frontend/admin.html` — удалить `.user-panel` overlay, добавить `<div id="tab-user-detail">` и `<div id="tab-session-detail">`, добавить `<script>` теги
|
||||
- `frontend/js/admin/sections/users.js` — удалить overlay-функции (~100-150L удаления)
|
||||
- `frontend/js/admin/router.js` — улучшения parsing для sub-routes (если нужно)
|
||||
- `frontend/js/admin/admin.js` — dispatch logic для routes с params
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Click на user row → URL становится `#users/123`, открывается deep page
|
||||
- F5 на `#users/123` восстанавливает страницу
|
||||
- Back navigation → возврат на `#users` list
|
||||
- Header содержит все action-кнопки (ban, edit, perms, delete)
|
||||
- Sub-tabs (overview, sessions, classes, audit) переключаются, URL обновляется
|
||||
- Старая `.user-panel` overlay полностью удалена из HTML и JS
|
||||
- Click на session id (в любом контексте) → `#sessions/456` → detail page
|
||||
- Нет console errors
|
||||
- Графики (если делаются) рендерятся корректно
|
||||
|
||||
## Notes
|
||||
|
||||
### Backward compat
|
||||
|
||||
После Phase 6 старые ссылки/onclick типа `openUserPanel(...)` УЖЕ НЕ работают. Это intentional — мы их удалили. Но `onclick="AdminRouter.navigate('#users/N')"` работает везде.
|
||||
|
||||
Если есть external links на админку user-panel — они продолжат работать как `#users/N` через router.
|
||||
|
||||
### Графики
|
||||
|
||||
Можно использовать chart.js (CDN ~50KB), но проще — inline SVG bar/line chart на нескольких десятках строк. У нас уже есть `.perf-bar` для процентов — можно расширить.
|
||||
|
||||
Не обязательно делать графики в этой фазе — можно сделать MVP без них и добавить чартами позже. В acceptance criteria графики помечены опционально.
|
||||
|
||||
### Audit log
|
||||
|
||||
Если в БД есть таблица `audit_log` с `user_id` — sub-tab Audit показывает её. Если нет — sub-tab скрывается или показывает empty state "Audit logging не активирован".
|
||||
|
||||
### Session detail
|
||||
|
||||
Сейчас session detail открывается через `adminGetSessionDetail` → возвращает массив answers. Используем тот же endpoint, рендерим в полноценную страницу вместо modal.
|
||||
|
||||
### Удаление overlay-кода (опасный шаг)
|
||||
|
||||
Делать в КОНЦЕ фазы, после того как deep page работает. Сначала добавить deep page, протестировать, потом удалить overlay. Можно даже сделать отдельным коммитом ("remove overlay").
|
||||
|
||||
### Что НЕ делать
|
||||
|
||||
- Не делать realtime updates (Phase 7+)
|
||||
- Не делать collaborative cursors
|
||||
- Не оптимизировать графики до production-grade (chart.js or similar OK)
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- [ ] Deep pages работают: F5, back/forward
|
||||
- [ ] Sub-tabs URL-обновляемы
|
||||
- [ ] Old overlay code fully removed
|
||||
- [ ] No regressions: ban/edit/delete user работают из deep page
|
||||
- [ ] Mobile-friendly: tabs scrollable, layout не ломается
|
||||
- [ ] Build passes
|
||||
- [ ] **Final smoke test:** пройти полный сценарий — открыть админку, найти пользователя через Cmd+K, перейти на deep page, выдать монеты, посмотреть сессии, забанить, разбанить, вернуться в overview
|
||||
|
||||
## Handoff to Next Phase
|
||||
|
||||
<!-- Это финальная фаза. Implementer записывает: что ещё не сделано,
|
||||
какие follow-up задачи стоит зафиксировать (графики, realtime, мобильная версия).
|
||||
Эти заметки помогут final-reviewer и при подготовке merge-summary. -->
|
||||
Reference in New Issue
Block a user