Files
Learn_System/plans/admin-redesign/phase-2-split-sections.md

226 lines
12 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 2: Split admin.html → per-section modules
**Status:** ✅ Done
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
**Commit:** 92030b4
## Objective
Разделить монолит `admin.js` (3500L) на per-section модули в `frontend/js/admin/sections/*.js`. После фазы `admin.js` становится оркестратором (~500-800L): он только подключает router, инициализирует общие виджеты (notif, sidebar) и делегирует загрузку section-данных в соответствующий модуль.
## Tasks
- [x] Создать `frontend/js/admin/sections/` директорию
- [x] Определить единый паттерн модуля:
```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,
};
})();
```
- [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()`)
- Добавить ROUTE_TO_SECTION mapping (см. ниже)
```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',
};
```
Маппинг применяется внутри `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` в конце IIFE
- Cross-section orchestrator `goAddQuestion` живёт в admin.js (вызывает `AdminSections.questions.openModal`)
- [x] Удалены 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
- [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
### 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 удалит).