feat(admin): phase 2 — split admin.js into 13 section modules
Replace ~3500L admin.js monolith with thin orchestrator (~700L) + 14 IIFE-wrapped per-section modules under /js/admin/sections/. Section modules expose AdminSections.<name>.init/reload (lazy init via switchTab/router) and re-expose onclick handlers via window.X for backward compat. Shared helpers (MODES/DIFFS, fmtDate, pctClass, renderMath, qTypeBadge, pagination) live in /js/admin/_shared.js exposed on window.AdminCtx. switchTab now dispatches to AdminSections via ROUTE_TO_SECTION map; non-extracted system tabs (topics/audit/errors/health/classroom/avatars) remain inline in admin.js. user-panel overlay markup untouched — Phase 6 will remove it.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
'use strict';
|
||||
/* admin → games (game features + free-student features) section */
|
||||
(function () {
|
||||
'use strict';
|
||||
let inited = false;
|
||||
|
||||
const GAME_FEATURES = [
|
||||
{ key: 'hangman', label: 'Виселица', desc: 'Игра «Угадай слово» — отгадывание терминов по буквам', icon: 'gamepad-2' },
|
||||
{ key: 'crossword', label: 'Кроссворд', desc: 'Кроссворд из терминов — генерируется автоматически по темам', icon: 'grid-3x3' },
|
||||
{ key: 'pet', label: 'Питомец', desc: 'Виртуальный питомец, отражающий активность ученика', icon: 'heart' },
|
||||
{ key: 'red_book', label: 'Красная книга', desc: 'Интерактивная Красная книга РБ: виды, биомы, пищевые сети, квесты', icon: 'leaf' },
|
||||
{ key: 'collection', label: 'Коллекция', desc: 'Коллекция карточек и достижений — игровой прогресс ученика', icon: 'layers' },
|
||||
{ key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для запоминания терминов и понятий методом интервальных повторений', icon: 'square-stack' },
|
||||
{ key: 'knowledge_map', label: 'Карта знаний', desc: 'Визуальная карта тем и связей между биологическими понятиями', icon: 'share-2' },
|
||||
{ key: 'board', label: 'Доска', desc: 'Классная доска с объявлениями, постами и обсуждениями', icon: 'layout-dashboard'},
|
||||
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
||||
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
||||
];
|
||||
|
||||
const FS_FEATURES = [
|
||||
{ key: 'gamification', label: 'Геймификация', desc: 'XP, уровни, достижения, монеты, стрики, магазин', icon: 'trophy' },
|
||||
{ key: 'hangman', label: 'Виселица', desc: 'Игра «Угадай слово» — отгадывание терминов по буквам', icon: 'gamepad-2' },
|
||||
{ key: 'crossword', label: 'Кроссворд', desc: 'Кроссворд из терминов — генерируется автоматически', icon: 'grid-3x3' },
|
||||
{ key: 'pet', label: 'Питомец', desc: 'Виртуальный питомец, отражающий активность ученика', icon: 'heart' },
|
||||
{ key: 'red_book', label: 'Красная книга', desc: 'Интерактивная Красная книга РБ: виды, биомы, квесты', icon: 'leaf' },
|
||||
{ key: 'collection', label: 'Коллекция', desc: 'Коллекция карточек и игровой прогресс ученика', icon: 'layers' },
|
||||
{ key: 'lab', label: 'Лаборатория', desc: 'Виртуальные симуляции и интерактивные опыты', icon: 'flask-conical' },
|
||||
{ key: 'knowledge_map',label: 'Карта знаний', desc: 'Визуальная карта тем и связей между понятиями', icon: 'map' },
|
||||
{ key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для повторения терминов и понятий', icon: 'square-stack' },
|
||||
{ key: 'board', label: 'Доска', desc: 'Классная доска с объявлениями и постами', icon: 'layout-dashboard' },
|
||||
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
||||
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
||||
];
|
||||
|
||||
async function loadGamesAdmin() {
|
||||
const grid = document.getElementById('games-features-grid');
|
||||
try {
|
||||
const features = await LS.api('/api/admin/features');
|
||||
grid.innerHTML = '';
|
||||
for (const f of GAME_FEATURES) {
|
||||
const enabled = features[f.key] !== false;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'perm-card' + (enabled ? ' enabled' : '');
|
||||
card.innerHTML = `
|
||||
<div class="perm-info">
|
||||
<div class="perm-label"><i data-lucide="${f.icon}" style="width:14px;height:14px;vertical-align:-2px;margin-right:6px"></i>${f.label}</div>
|
||||
<div class="perm-desc">${f.desc}</div>
|
||||
</div>
|
||||
<label class="perm-toggle">
|
||||
<input type="checkbox" ${enabled ? 'checked' : ''} onchange="toggleGameFeature('${f.key}', this.checked, this)" />
|
||||
<span class="perm-track"></span>
|
||||
<span class="perm-thumb"></span>
|
||||
</label>`;
|
||||
grid.appendChild(card);
|
||||
}
|
||||
if (window.lucide) lucide.createIcons();
|
||||
} catch(e) {
|
||||
grid.innerHTML = '<div class="error">Ошибка загрузки</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleGameFeature(key, enabled, checkbox) {
|
||||
try {
|
||||
await LS.api('/api/admin/features', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ [key]: enabled }),
|
||||
});
|
||||
const card = checkbox.closest('.perm-card');
|
||||
if (card) card.classList.toggle('enabled', enabled);
|
||||
LS.toast(enabled ? 'Функция включена' : 'Функция отключена', 'success');
|
||||
} catch(e) {
|
||||
checkbox.checked = !enabled;
|
||||
LS.toast('Ошибка: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFsFeatures() {
|
||||
const grid = document.getElementById('fs-features-grid');
|
||||
try {
|
||||
const features = await LS.api('/api/admin/free-student-features');
|
||||
grid.innerHTML = '';
|
||||
for (const f of FS_FEATURES) {
|
||||
const enabled = features[f.key] !== false;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'perm-card' + (enabled ? ' enabled' : '');
|
||||
card.innerHTML = `
|
||||
<div class="perm-info">
|
||||
<div class="perm-label"><i data-lucide="${f.icon}" style="width:14px;height:14px;vertical-align:-2px;margin-right:6px"></i>${f.label}</div>
|
||||
<div class="perm-desc">${f.desc}</div>
|
||||
</div>
|
||||
<label class="perm-toggle">
|
||||
<input type="checkbox" ${enabled ? 'checked' : ''} onchange="toggleFsFeature('${f.key}', this.checked, this)" />
|
||||
<span class="perm-track"></span>
|
||||
<span class="perm-thumb"></span>
|
||||
</label>`;
|
||||
grid.appendChild(card);
|
||||
}
|
||||
if (window.lucide) lucide.createIcons();
|
||||
} catch(e) {
|
||||
grid.innerHTML = '<div class="error">Ошибка загрузки</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleFsFeature(key, enabled, checkbox) {
|
||||
try {
|
||||
await LS.api('/api/admin/free-student-features', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ [key]: enabled }),
|
||||
});
|
||||
const card = checkbox.closest('.perm-card');
|
||||
if (card) card.classList.toggle('enabled', enabled);
|
||||
LS.toast(enabled ? 'Модуль включён' : 'Модуль отключён', 'success');
|
||||
} catch(e) {
|
||||
checkbox.checked = !enabled;
|
||||
LS.toast('Ошибка: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
await loadGamesAdmin();
|
||||
await loadFsFeatures();
|
||||
}
|
||||
|
||||
window.toggleGameFeature = toggleGameFeature;
|
||||
window.toggleFsFeature = toggleFsFeature;
|
||||
|
||||
window.AdminSections = window.AdminSections || {};
|
||||
window.AdminSections.games = {
|
||||
init: async () => { if (inited) return; inited = true; await load(); },
|
||||
reload: load,
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user