# 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/.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. = { 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 `