diff --git a/frontend/js/admin/sections/access.js b/frontend/js/admin/sections/access.js index f8c8462..07e8444 100644 --- a/frontend/js/admin/sections/access.js +++ b/frontend/js/admin/sections/access.js @@ -27,6 +27,12 @@ let _matrix = null; // { classes:[{id,name}], open:{ [cid]:{textbook:[],exam:[]} } } let _mSearch = ''; + // content-mode left search + let _leftSearch = ''; + const SUBJ_LABEL = { math: 'Математика', physics: 'Физика', phys: 'Физика', chemistry: 'Химия', + chem: 'Химия', biology: 'Биология', bio: 'Биология', informatics: 'Информатика', + russian: 'Русский язык', english: 'Английский', geography: 'География', history: 'История' }; + const esc = (s) => (window.LS && LS.esc ? LS.esc(s) : String(s == null ? '' : s)); const bucket = (type) => (type === 'textbook' ? 'textbooks' : 'exams'); const keyName = (type) => (type === 'textbook' ? 'slug' : 'exam_key'); @@ -83,27 +89,57 @@ color:${has ? 'var(--ok,#16a34a)' : 'var(--muted)'}">${open}/${total}`; } + /* ── список контента в левой колонке (с поиском + подзаголовками по предмету) ── */ + function contentItemBtn(type, it, total) { + const ref = it[keyName(type)]; + const active = _selContent && _selContent.type === type && _selContent.ref === ref; + const open = (_summary[bucket(type)] || {})[ref] || 0; + return ``; + } + function contentLeftList() { + const total = _summary.totalClasses || 0; + const term = _leftSearch.trim().toLowerCase(); + const match = (it) => !term || (it.title || '').toLowerCase().includes(term); + const tbs = (_catalog.textbooks || []).filter(match); + const exs = (_catalog.exams || []).filter(match); + let html = ''; + if (tbs.length) { + html += `
Учебники
`; + let lastSubj = null; + tbs.forEach(it => { + const sj = it.subject || ''; + if (sj !== lastSubj) { + lastSubj = sj; + html += `
${esc(SUBJ_LABEL[sj] || sj || 'Прочее')}
`; + } + html += contentItemBtn('textbook', it, total); + }); + } + if (exs.length) { + html += `
Экзамены
`; + exs.forEach(it => { html += contentItemBtn('exam', it, total); }); + } + return html || empty('Ничего не найдено'); + } + function leftSearch(v) { + _leftSearch = v; + const el = document.getElementById('acc-left-list'); + if (el) el.innerHTML = contentLeftList(); + } + /* ── ЛЕВАЯ колонка ── */ function renderLeft() { const left = document.getElementById('acc-left'); if (_mode === 'content') { - const total = _summary.totalClasses || 0; - const list = (type, items) => items.map(it => { - const ref = it[keyName(type)]; - const active = _selContent && _selContent.type === type && _selContent.ref === ref; - const open = (_summary[bucket(type)] || {})[ref] || 0; - return ``; - }).join(''); left.innerHTML = ` -
Учебники
- ${list('textbook', _catalog.textbooks || []) || empty('Нет учебников')} -
Экзамены
- ${list('exam', _catalog.exams || []) || empty('Нет экзаменов')}`; + +
${contentLeftList()}
`; } else { const classes = _targets.classes || []; left.innerHTML = ` @@ -157,6 +193,17 @@ ${btn('null', 'Наследовать', state === 'inherit')}${btn(1, 'Открыт', state === 'open')}${btn(0, 'Закрыт', state === 'closed')}`; } + /* эффективный доступ ученика: что он реально видит и почему */ + function effBadge(uid, classOpen) { + const v = _rules.studentRules[uid]; + let open, why; + if (v === 1) { open = true; why = 'лично'; } + else if (v === 0) { open = false; why = 'лично'; } + else { open = !!classOpen; why = classOpen ? 'по классу' : 'по умолч.'; } + return `${open ? 'видит' : 'не видит'} · ${why}`; + } + function classRowContent(c) { const openToClass = _rules.classRules[c.id] === 1; const expanded = _open.has(c.id); @@ -165,7 +212,8 @@
${students.length ? students.map(s => `
- ${esc(s.name || s.email)}${studentTri(s.id)} + ${esc(s.name || s.email)} + ${effBadge(s.id, openToClass)}${studentTri(s.id)}
`).join('') : '

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

'}
` : ''; return ` @@ -419,6 +467,7 @@ window.accClassBulk = classBulk; window.accMx = mxToggle; window.accMxSearch = mxSearch; + window.accLeftSearch = leftSearch; window.AdminSections = window.AdminSections || {}; window.AdminSections.access = {