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 bucket = (type) => BUCKET[type] || (type + 's');
|
||||||
const keyName = (type) => KEYNAME[type] || 'id';
|
const keyName = (type) => KEYNAME[type] || 'id';
|
||||||
const itemsOf = (type) => (_catalog && _catalog[bucket(type)]) || [];
|
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) {
|
function contentTitle(type, ref) {
|
||||||
const it = itemsOf(type).find(x => x[keyName(type)] === ref);
|
const it = itemsOf(type).find(x => x[keyName(type)] === ref);
|
||||||
return it ? it.title : ref;
|
return it ? it.title : ref;
|
||||||
@@ -346,10 +348,17 @@
|
|||||||
function renderClassDetail(right) {
|
function renderClassDetail(right) {
|
||||||
let html = `
|
let html = `
|
||||||
<div style="font-size:16px;font-weight:700;color:var(--text-1);margin-bottom:14px">Класс «${esc(_selClass.name)}»</div>
|
<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" onclick="accClassBulk(1)">Открыть весь контент</button>
|
||||||
<button class="adm-btn adm-btn-small" style="background:var(--border-h);color:var(--text-3)" onclick="accClassBulk(0)">Закрыть весь</button>
|
<button class="adm-btn adm-btn-small" style="background:var(--border-h);color:var(--text-3)" onclick="accClassBulk(0)">Закрыть весь</button>
|
||||||
</div>`;
|
</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 => {
|
CONTENT_TYPES.forEach(type => {
|
||||||
const items = itemsOf(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>`;
|
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); }
|
} 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) {
|
async function classBulk(allow) {
|
||||||
if (!allow && !confirm(`Закрыть весь контент у класса «${_selClass.name}»?`)) return;
|
if (!allow && !confirm(`Закрыть весь контент у класса «${_selClass.name}»?`)) return;
|
||||||
const all = CONTENT_TYPES.flatMap(t => itemsOf(t).map(it => [t, it[keyName(t)]]));
|
const all = CONTENT_TYPES.flatMap(t => itemsOf(t).map(it => [t, it[keyName(t)]]));
|
||||||
@@ -510,6 +531,7 @@
|
|||||||
window.accSelClass = selClass;
|
window.accSelClass = selClass;
|
||||||
window.accClassToggle = classToggle;
|
window.accClassToggle = classToggle;
|
||||||
window.accClassBulk = classBulk;
|
window.accClassBulk = classBulk;
|
||||||
|
window.accClassSubj = classSubjectBulk;
|
||||||
window.accMx = mxToggle;
|
window.accMx = mxToggle;
|
||||||
window.accMxSearch = mxSearch;
|
window.accMxSearch = mxSearch;
|
||||||
window.accMxRowBulk = mxRowBulk;
|
window.accMxRowBulk = mxRowBulk;
|
||||||
|
|||||||
Reference in New Issue
Block a user