# 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 `