docs(admin-redesign): mark phase 2 done + handoff notes for phase 3+
This commit is contained in:
@@ -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 удалит).
|
||||
|
||||
Reference in New Issue
Block a user