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 = {