'use strict'; /* admin → access section — открыть/закрыть доступ к учебникам и экзаменам * для классов и отдельных учеников. Модель allowlist: по умолчанию закрыто, * правило ученика важнее правила класса. */ (function () { 'use strict'; let inited = false; let _catalog = null; // { textbooks:[], exams:[] } let _targets = null; // { classes:[{id,name,students:[]}], looseStudents:[] } let _sel = null; // { type:'textbook'|'exam', ref, title } let _rules = { classRules: {}, studentRules: {} }; const _open = new Set(); // class ids развёрнутых строк const esc = (s) => (window.LS && LS.esc ? LS.esc(s) : String(s == null ? '' : s)); async function load() { try { [_catalog, _targets] = await Promise.all([LS.accessCatalog(), LS.accessTargets()]); renderList(); } catch (e) { document.getElementById('acc-textbooks').innerHTML = `

Ошибка загрузки: ${esc(e.message)}

`; } } function itemBtn(type, ref, title, sub) { const active = _sel && _sel.type === type && _sel.ref === ref; return ``; } function renderList() { const tb = document.getElementById('acc-textbooks'); const ex = document.getElementById('acc-exams'); tb.innerHTML = (_catalog.textbooks || []) .map(t => itemBtn('textbook', t.slug, t.title, t.grade ? t.grade + ' кл.' : '')).join('') || '

Нет учебников

'; ex.innerHTML = (_catalog.exams || []) .map(e => itemBtn('exam', e.exam_key, e.title, e.grade ? e.grade + ' кл.' : '')).join('') || '

Нет экзаменов

'; } async function select(type, ref) { const src = type === 'textbook' ? _catalog.textbooks : _catalog.exams; const keyName = type === 'textbook' ? 'slug' : 'exam_key'; const item = (src || []).find(x => x[keyName] === ref); _sel = { type, ref, title: item ? item.title : ref }; renderList(); document.getElementById('acc-detail-empty').style.display = 'none'; const det = document.getElementById('acc-detail'); det.style.display = ''; det.innerHTML = '

Загрузка…

'; try { _rules = await LS.accessRules(type, ref); renderDetail(); } catch (e) { det.innerHTML = `

Ошибка: ${esc(e.message)}

`; } } /* tri-state кнопки для ученика внутри класса */ function studentTri(uid) { const v = _rules.studentRules[uid]; // 1 | 0 | undefined const state = v === 1 ? 'open' : v === 0 ? 'closed' : 'inherit'; const btn = (val, label, on) => ``; return ` ${btn('null', 'Наследовать', state === 'inherit')} ${btn(1, 'Открыт', state === 'open')} ${btn(0, 'Закрыт', state === 'closed')} `; } function classRow(c) { const openToClass = _rules.classRules[c.id] === 1; const expanded = _open.has(c.id); const students = c.students || []; const studentsHtml = expanded ? `
${students.length ? students.map(s => `
${esc(s.name || s.email)} ${studentTri(s.id)}
`).join('') : '

В классе нет учеников

'}
` : ''; return `
${studentsHtml}
`; } function looseRow(s) { const open = _rules.studentRules[s.id] === 1; return `
${esc(s.name || s.email)} ${esc(s.email)}
`; } function renderDetail() { const det = document.getElementById('acc-detail'); const classes = _targets.classes || []; const loose = _targets.looseStudents || []; det.innerHTML = `
${esc(_sel.title)}
${_sel.type === 'exam' ? 'Экзамен' : 'Учебник'}
${classes.length ? classes.map(classRow).join('') : '

Нет классов.

'}
${loose.length ? `
Отдельные ученики (без класса)
${loose.map(looseRow).join('')}
` : ''} `; } /* ── handlers (optimistic update) ── */ async function setClass(classId, checked) { const allow = checked ? 1 : null; try { await LS.accessSetRule(_sel.type, _sel.ref, 'class', classId, allow); if (allow === 1) _rules.classRules[classId] = 1; else delete _rules.classRules[classId]; renderDetail(); LS.toast(checked ? 'Открыт классу' : 'Закрыт для класса', 'success'); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); renderDetail(); } } async function setStudent(uid, allow) { // allow: 1 | 0 | null (строка 'null' приходит из tri-кнопок) if (allow === 'null') allow = null; try { await LS.accessSetRule(_sel.type, _sel.ref, 'student', uid, allow); if (allow === 1) _rules.studentRules[uid] = 1; else if (allow === 0) _rules.studentRules[uid] = 0; else delete _rules.studentRules[uid]; renderDetail(); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); renderDetail(); } } function toggleExpand(classId) { if (_open.has(classId)) _open.delete(classId); else _open.add(classId); renderDetail(); } window.accSelect = select; window.accSetClass = setClass; window.accSetStudent = setStudent; window.accToggleExpand = toggleExpand; window.AdminSections = window.AdminSections || {}; window.AdminSections.access = { init: async () => { if (inited) return; inited = true; await load(); }, reload: load, }; })();