docs(admin-redesign): mark phase 2 done + handoff notes for phase 3+

This commit is contained in:
Maxim Dolgolyov
2026-05-16 22:52:24 +03:00
parent 92030b462c
commit 8a815ca3eb
3 changed files with 133 additions and 45 deletions
+18 -4
View File
@@ -5,7 +5,7 @@
(будет обновляться после каждой фазы)
- ✅ Phase 1 implemented — `window.AdminRouter` обёртывает старый `switchTab` (hash ↔ tab двусторонне). `switchTab` принимает 2-й аргумент `{ fromRouter: true }` для предотвращения рекурсии. Default = `#stats`. Файлы: `frontend/js/admin/router.js` (новый), `frontend/admin.html` (+1 строка), `frontend/js/admin/admin.js` (модификация `switchTab` + IIFE `initAdminRouter`).
- Phase 2 not started — все 13 секций в admin.js монолите
- Phase 2 implemented (commit 92030b4) — admin.js ужат с ~3591L до 701L. Все 13 plan-tabs живут в `frontend/js/admin/sections/*.js` (IIFE pattern) + `frontend/js/admin/_shared.js` (window.AdminCtx). switchTab() диспетчит в `AdminSections[ROUTE_TO_SECTION[name]].init()`. Lazy-load работает (inited флаг внутри каждой IIFE). System tabs (topics/audit/errors/health/classroom/avatars) остались inline в admin.js — Phase 2 их не extract'ил.
- ⬜ Phase 3-6 not started
## Temporary Workarounds
@@ -49,23 +49,37 @@ AdminRouter.navigate('#users/123', { replace: true, silent: true });
- `goAddQuestion(slug)` и подобные cross-tab onclick handlers должны работать
- Старые ссылки `<a href="#stats">` (если есть) тоже
### Конвенции вновь создаваемых модулей
### Конвенции вновь создаваемых модулей (Phase 2 закреплено)
Каждая section (фаза 2):
Каждая section:
```js
// js/admin/sections/<name>.js
(function () {
'use strict';
let inited = false;
async function load() { /* ... */ }
async function load() { /* fetch + render */ }
// Optional onclick handlers used by HTML / dynamic templates:
window.handlerX = handlerX;
window.AdminSections = window.AdminSections || {};
window.AdminSections.<name> = {
init: async () => { if (inited) return; inited = true; await load(); },
reload: load,
// Optional extras for cross-section calls (e.g. questions.openModal):
// openModal: (...) => { ... },
};
})();
```
Shared utilities — на `window.AdminCtx` (см. `_shared.js`):
- `user`, `isTeacher`, `isAdmin` (filled by admin.js)
- `MODES`, `DIFFS`, `DIFF_LABELS`, `TYPE_LABELS`
- `pctClass`, `fmtDate`, `fmtTime`, `fmtDuration`
- `renderMath`, `qTypeBadge`, `qOptsPreview`
- `renderPgnControls`, `ensurePgnStyles`
ROUTE_TO_SECTION map в admin.js — добавлять новые ключи при добавлении секций
(Phase 3 = `overview`, Phase 6 = `user`/`session` deep pages).
Router (фаза 1):
```js
// js/admin/router.js
+3 -3
View File
@@ -36,7 +36,7 @@
## Phases
- [x] 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)
- [x] 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)
@@ -48,8 +48,8 @@
| Phase | Domain | Status | Review | Build | Committed |
|-------|--------|--------|--------|-------|-----------|
| Phase 1: Hash-router | frontend | ✅ Implemented | ⬜ | ✅ | ⬜ |
| Phase 2: Split sections | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 1: Hash-router | frontend | ✅ Done | ✅ PASS w/ notes | ✅ | ✅ 8a7bed4 |
| Phase 2: Split sections | frontend | ✅ Done | ⬜ pending | ✅ node --check | ✅ 92030b4 |
| Phase 3: Dashboard | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 4: Palette | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 5: Quick actions | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
+112 -38
View File
@@ -1,8 +1,9 @@
# Phase 2: Split admin.html → per-section modules
**Status:** ⬜ Not Started
**Status:** ✅ Done
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
**Commit:** 92030b4
## Objective
@@ -10,8 +11,8 @@
## Tasks
- [ ] Создать `frontend/js/admin/sections/` директорию
- [ ] Определить единый паттерн модуля:
- [x] Создать `frontend/js/admin/sections/` директорию
- [x] Определить единый паттерн модуля:
```js
// js/admin/sections/<name>.js
(function () {
@@ -29,23 +30,23 @@
};
})();
```
- [ ] Извлечь 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`:
- [x] Извлечь 13 секций (в порядке риска — от меньшего к большему):
- [x] `stats.js` — `loadStats` + связанные функции (50L)
- [x] `sublog.js` — submission log (104L)
- [x] `sims.js` (118L), `games.js` (132L), `tpl.js` (73L) — admin-only
- [x] `subjects.js` — настройка доступных тестов (338L)
- [x] `permissions.js` (68L)
- [x] `shop.js` — items + purchases + award coins (207L)
- [x] `gam.js` — gamification stats + award xp (183L)
- [x] `assignments.js` (477L)
- [x] `tests.js` (283L)
- [x] `questions.js` — самая большая, 535L (включая Q-modal)
- [x] `users.js` — users-table + pagination + user-panel (343L, overlay остался)
- [x] `sessions.js` — sessions-table + session detail (159L)
- [x] Модифицировать `admin.js`:
- Удалить функции, перенесённые в sections
- Заменить inline вызовы (`loadUsers()` → `AdminSections.users.init(ctx)`)
- Добавить генератор route→section маппинга:
- Заменить inline вызовы (`loadUsers()` → `AdminSections.users.init()`)
- Добавить ROUTE_TO_SECTION mapping (см. ниже)
```js
const ROUTE_TO_SECTION = {
stats: 'stats', users: 'users', sessions: 'sessions',
@@ -53,16 +54,16 @@
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`:
Маппинг применяется внутри `switchTab` (не отдельный router-listener) —
`switchTab` уже вызывается router'ом на change через `activate(route)`,
поэтому достаточно один раз dispatch'ить в `switchTab`.
- [x] Все 14 `<script>` тегов добавлены в `admin.html` (_shared.js + 13 sections, после router.js, перед admin.js)
- [x] Глобальные функции, которые используются из 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
- Каждый section module экспортирует свои onclick-handler'ы через `window.X = X` в конце IIFE
- Cross-section orchestrator `goAddQuestion` живёт в admin.js (вызывает `AdminSections.questions.openModal`)
- [x] Удалены per-tab `*Inited` флаги из admin.js — они переехали внутрь section modules
## Files to Modify/Create
@@ -135,17 +136,90 @@
## 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
- [x] Все 13 секций имеют одинаковую структуру (init/reload)
- [x] admin.js = 701L (≤ 800L), нет дублирования с sections
- [x] Все window.X экспорты есть для onclick handlers (см. handoff ниже)
- [x] Lazy-init работает: `inited` флаг внутри каждой section IIFE
- [x] F5 на каждом из 13 routes восстанавливает секцию (через router.activate → switchTab → AdminSections.X.init)
- [x] Sanity: все 14 .js файлов проходят `node --check`
## 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 -->
### Section module API (Phase 3+ должна следовать)
```js
window.AdminSections.<name> = {
init: async () => { /* lazy: первый вызов делает fetch, повторные — no-op */ },
reload: async () => { /* всегда выполняет fetch (для refresh-кнопок) */ },
// опционально: extra-методы для cross-section orchestration:
// openModal(...), loadModalTopics() — см. questions.js
};
```
### Где живут shared утилиты
`frontend/js/admin/_shared.js` — экспортирует на `window.AdminCtx`:
- `user`, `isTeacher`, `isAdmin` (filled by admin.js после LS.initPage())
- `MODES`, `DIFFS`, `DIFF_LABELS`, `TYPE_LABELS` — константы
- `pctClass(p)`, `fmtDate(d)`, `fmtTime(s)`, `fmtDuration(s)` — форматтеры
- `renderMath(el)` — KaTeX
- `qTypeBadge(type)`, `qOptsPreview(q)` — used by tests + subjects
- `renderPgnControls(elId, page, total, perPage, gotoFn)` + `ensurePgnStyles()` — пагинация
### ROUTE_TO_SECTION map (admin.js)
13 ключей, расширение для Phase 3 (`overview`) и Phase 6 (`user`, `session` deep pages):
```js
const ROUTE_TO_SECTION = {
stats:'stats', questions:'questions', tests:'tests',
assignments:'assignments', subjects:'subjects', users:'users',
sessions:'sessions', permissions:'permissions', shop:'shop',
gam:'gam', tpl:'tpl', sims:'sims', games:'games', sublog:'sublog',
};
```
**Phase 3:** добавить `overview: 'overview'` + sections/overview.js, и поменять
дефолтный hash в admin.js с `#stats` на `#overview`.
**Phase 6:** добавить `user: 'userDetail'`, `session: 'sessionDetail'` —
sections/user-detail.js и sections/session-detail.js будут читать
`AdminRouter.current().params[0]` для id.
### Window-exposed globals (для HTML onclicks)
**Из admin.js (orchestrator):**
- `switchTab`, `toggleAdminGroup`, `goAddQuestion`
- Topics: `showAddTopic`, `createTopic`, `renameTopic`, `deleteTopic`
- Logs/health: `sendBroadcast`, `clearAuditLog`, `clearErrorLog`
- Classroom: `crMasterToggle`, `crHistDebounce`, `loadCrHistory`,
`toggleCrDetail`, `adminEndSession`, `adminExportChat`, `adminDeleteSession`
- Avatars: `avatarApprove`, `avatarRejectPrompt`, `avatarReject`
- Function declarations at script-top-level (`loadTopics`, `loadCrActiveSessions`,
`loadAvatarRequests`, `loadHealth`, `loadAuditLog`, `loadErrorLog`, `loadCrModuleState`,
`loadCrSessionDetail`, `loadTopicSubjects`, `renderCrPagination`, `fmtLiveDuration`,
`avatarReject`) — автоматически на `window` в non-module script.
**Из section modules (явный `window.X = X` в IIFE):**
- questions: ~25 handlers (openQModal, saveQuestion, setQType, addOpt, removeOpt, etc.)
- users: ~17 handlers (loadUsers, openUserPanel, changeRole, etc.)
- tests: ~12 handlers
- assignments: ~18 handlers
- subjects: ~9 handlers (toggleScCard, applyPreset, scAddQ/scRemoveQ, etc.)
- shop, gam, sims, games, tpl, permissions, sessions, sublog: 1-9 each
### Cross-section dependencies
- `goAddQuestion(slug)` → admin.js orchestrator → switches tab + calls
`AdminSections.questions.openModal()` + `loadModalTopics()`. The section
exposes these as extra methods on `AdminSections.questions`.
- Subjects-section's `goAddQuestion(slug)` onclick uses the admin.js
orchestrator (same window-global).
- `_matchPairs` (matching-question editor state): exposed on `window` because
inline `oninput="window._matchPairs[i].left=this.value"` references it.
### Что НЕ переехало (по плану)
- Tabs *topics, audit, errors, health, classroom, avatars* остались inline
в admin.js — Phase 2 их не extract'ил (не входило в 13 секций плана).
- `.user-panel` overlay markup в admin.html не тронут (Phase 6 удалит).