From d1f24736c3e451957d39b2c2dec97dd530b23722 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 3 Jun 2026 13:36:34 +0300 Subject: [PATCH] =?UTF-8?q?feat(access):=20=D0=A4=D0=B0=D0=B7=D0=B0=202c?= =?UTF-8?q?=20(=D1=87=D0=B0=D1=81=D1=82=D1=8C)=20=E2=80=94=20=D0=BC=D0=B0?= =?UTF-8?q?=D1=81=D1=81=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BE=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2=20=D0=BC=D0=B0=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D1=86=D0=B5=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Клик по названию контента в матрице открывает/закрывает его сразу ВСЕМ классам; клик по имени класса (заголовок столбца) — открывает/закрывает ВЕСЬ контент этому классу. Массовое закрытие спрашивает подтверждение; перерисовывается только tbody. Использует существующий accSetRule (без новых эндпоинтов). Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/admin/sections/access.js | 46 ++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/frontend/js/admin/sections/access.js b/frontend/js/admin/sections/access.js index aa26b92..2e1c955 100644 --- a/frontend/js/admin/sections/access.js +++ b/frontend/js/admin/sections/access.js @@ -396,7 +396,10 @@ /* ════════ режим «Матрица» (класс × контент одним экраном) ════════ */ function matrixHeadCells(classes) { return classes.map(c => - `${esc(c.name)}`).join(''); + ` + + `).join(''); } function matrixBody() { const classes = _matrix.classes || []; @@ -410,7 +413,10 @@ return ` `; }).join(''); - return `${esc(it.title)}${cells}`; + return ` + + ${cells}`; }).join(''); if (!rows) return ''; return `${TYPE_LABEL[type] || type}${rows}`; @@ -453,6 +459,40 @@ } function mxSearch(v) { _mSearch = v; const b = document.getElementById('acc-mx-body'); if (b) b.innerHTML = matrixBody(); } + function mxRepaint() { const b = document.getElementById('acc-mx-body'); if (b) b.innerHTML = matrixBody(); } + function mxApply(o, type, ref, open) { + const arr = o[type] || (o[type] = []); + const i = arr.indexOf(ref); + if (open && i < 0) arr.push(ref); + if (!open && i >= 0) arr.splice(i, 1); + } + /* строка матрицы: открыть/закрыть один контент всем классам */ + async function mxRowBulk(type, ref) { + const classes = _matrix.classes || []; + const allOpen = classes.length && classes.every(c => ((_matrix.open[c.id] || {})[type] || []).includes(ref)); + const open = !allOpen; + if (!open && !confirm(`Закрыть «${contentTitle(type, ref)}» у всех классов?`)) return; + try { + await Promise.all(classes.map(c => LS.accessSetRule(type, ref, 'class', c.id, open ? 1 : null))); + classes.forEach(c => mxApply(_matrix.open[c.id] || (_matrix.open[c.id] = {}), type, ref, open)); + mxRepaint(); + } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); _matrix = null; renderMatrix(); } + } + /* столбец матрицы: открыть/закрыть весь контент одному классу */ + async function mxColBulk(classId) { + const items = CONTENT_TYPES.flatMap(t => itemsOf(t).map(it => [t, it[keyName(t)]])); + const o = _matrix.open[classId] || (_matrix.open[classId] = {}); + const allOpen = items.length && items.every(([t, ref]) => (o[t] || []).includes(ref)); + const open = !allOpen; + const cls = (_matrix.classes.find(c => c.id === classId) || {}).name || ('#' + classId); + if (!open && !confirm(`Закрыть весь контент у класса «${cls}»?`)) return; + try { + await Promise.all(items.map(([t, ref]) => LS.accessSetRule(t, ref, 'class', classId, open ? 1 : null))); + items.forEach(([t, ref]) => mxApply(o, t, ref, open)); + mxRepaint(); + } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); _matrix = null; renderMatrix(); } + } + /* ── режим ── */ function setMode(m) { if (m === _mode) return; @@ -472,6 +512,8 @@ window.accClassBulk = classBulk; window.accMx = mxToggle; window.accMxSearch = mxSearch; + window.accMxRowBulk = mxRowBulk; + window.accMxColBulk = mxColBulk; window.accLeftSearch = leftSearch; window.AdminSections = window.AdminSections || {};