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

12 KiB
Raw Permalink Blame History

Phase 2: Split admin.html → per-section modules

Status: Done Parent plan: 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

  • Создать frontend/js/admin/sections/ директорию
  • Определить единый паттерн модуля:
    // 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,
      };
    })();
    
  • Извлечь 13 секций (в порядке риска — от меньшего к большему):
    • stats.jsloadStats + связанные функции (50L)
    • sublog.js — submission log (104L)
    • sims.js (118L), games.js (132L), tpl.js (73L) — admin-only
    • subjects.js — настройка доступных тестов (338L)
    • permissions.js (68L)
    • shop.js — items + purchases + award coins (207L)
    • gam.js — gamification stats + award xp (183L)
    • assignments.js (477L)
    • tests.js (283L)
    • questions.js — самая большая, 535L (включая Q-modal)
    • users.js — users-table + pagination + user-panel (343L, overlay остался)
    • sessions.js — sessions-table + session detail (159L)
  • Модифицировать admin.js:
    • Удалить функции, перенесённые в sections
    • Заменить inline вызовы (loadUsers()AdminSections.users.init())
    • Добавить ROUTE_TO_SECTION mapping (см. ниже)
      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.
  • Все 14 <script> тегов добавлены в admin.html (_shared.js + 13 sections, после router.js, перед admin.js)
  • Глобальные функции, которые используются из 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)
  • Удалены 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/.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

  • Все 13 секций имеют одинаковую структуру (init/reload)
  • admin.js = 701L (≤ 800L), нет дублирования с sections
  • Все window.X экспорты есть для onclick handlers (см. handoff ниже)
  • Lazy-init работает: inited флаг внутри каждой section IIFE
  • F5 на каждом из 13 routes восстанавливает секцию (через router.activate → switchTab → AdminSections.X.init)
  • Sanity: все 14 .js файлов проходят node --check

Handoff to Next Phase

Section module API (Phase 3+ должна следовать)

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):

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 удалит).