feat(access): Фаза 2c — «Открыть весь предмет классу» в режиме «По классу»
Панель кнопок по предметам: один клик открывает выбранному классу весь контент этого предмета (учебники/экзамены/симуляции/курсы вместе). Нормализация поля предмета (subject|subject_slug), метки через SUBJ_LABEL. Чистый фронтенд на существующем accSetRule. Закрывает находку ревью «нет операции открыть весь предмет». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,8 @@
|
||||
const bucket = (type) => BUCKET[type] || (type + 's');
|
||||
const keyName = (type) => KEYNAME[type] || 'id';
|
||||
const itemsOf = (type) => (_catalog && _catalog[bucket(type)]) || [];
|
||||
const subjOf = (it) => it.subject || it.subject_slug || ''; // нормализация поля предмета
|
||||
const subjLabel = (s) => SUBJ_LABEL[s] || s || 'Прочее';
|
||||
function contentTitle(type, ref) {
|
||||
const it = itemsOf(type).find(x => x[keyName(type)] === ref);
|
||||
return it ? it.title : ref;
|
||||
@@ -346,10 +348,17 @@
|
||||
function renderClassDetail(right) {
|
||||
let html = `
|
||||
<div style="font-size:16px;font-weight:700;color:var(--text-1);margin-bottom:14px">Класс «${esc(_selClass.name)}»</div>
|
||||
<div style="display:flex;gap:8px;margin-bottom:14px">
|
||||
<div style="display:flex;gap:8px;margin-bottom:10px">
|
||||
<button class="adm-btn adm-btn-small" onclick="accClassBulk(1)">Открыть весь контент</button>
|
||||
<button class="adm-btn adm-btn-small" style="background:var(--border-h);color:var(--text-3)" onclick="accClassBulk(0)">Закрыть весь</button>
|
||||
</div>`;
|
||||
const subjects = [...new Set(CONTENT_TYPES.flatMap(t => itemsOf(t).map(subjOf)).filter(Boolean))].sort();
|
||||
if (subjects.length) {
|
||||
html += `<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:14px">
|
||||
<span style="font-size:12px;color:var(--muted)">Открыть по предмету:</span>
|
||||
${subjects.map(s => `<button class="adm-btn adm-btn-small" style="background:var(--accent-soft,#eef2ff);color:var(--accent,#4f46e5)" onclick="accClassSubj('${esc(s)}')">+ ${esc(subjLabel(s))}</button>`).join('')}
|
||||
</div>`;
|
||||
}
|
||||
CONTENT_TYPES.forEach(type => {
|
||||
const items = itemsOf(type);
|
||||
html += `<div style="font-weight:600;font-size:13px;color:var(--text-3);margin:14px 0 8px">${TYPE_LABEL[type]}</div>`;
|
||||
@@ -376,6 +385,18 @@
|
||||
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); selClass(_selClass.id); }
|
||||
}
|
||||
|
||||
/* открыть классу весь контент одного предмета (любого типа) */
|
||||
async function classSubjectBulk(subj) {
|
||||
const items = CONTENT_TYPES.flatMap(t => itemsOf(t).filter(it => subjOf(it) === subj).map(it => [t, it[keyName(t)]]));
|
||||
if (!items.length) return;
|
||||
try {
|
||||
await Promise.all(items.map(([t, ref]) => LS.accessSetRule(t, ref, 'class', _selClass.id, 1)));
|
||||
items.forEach(([t, ref]) => { const set = _classOpen[bucket(t)]; if (set && !set.has(ref)) { set.add(ref); bumpSummary(t, ref, +1); } });
|
||||
renderRight();
|
||||
LS.toast(`Открыт весь контент по предмету «${subjLabel(subj)}»`, 'success');
|
||||
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); selClass(_selClass.id); }
|
||||
}
|
||||
|
||||
async function classBulk(allow) {
|
||||
if (!allow && !confirm(`Закрыть весь контент у класса «${_selClass.name}»?`)) return;
|
||||
const all = CONTENT_TYPES.flatMap(t => itemsOf(t).map(it => [t, it[keyName(t)]]));
|
||||
@@ -510,6 +531,7 @@
|
||||
window.accSelClass = selClass;
|
||||
window.accClassToggle = classToggle;
|
||||
window.accClassBulk = classBulk;
|
||||
window.accClassSubj = classSubjectBulk;
|
||||
window.accMx = mxToggle;
|
||||
window.accMxSearch = mxSearch;
|
||||
window.accMxRowBulk = mxRowBulk;
|
||||
|
||||
Reference in New Issue
Block a user