From f1fbebe4da6fb42dd2c3e2307a765f976fb2f29d Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 16 May 2026 17:49:00 +0300 Subject: [PATCH] =?UTF-8?q?ux:=20sidebar=20=E2=80=94=20=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=BF=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BF=D0=BE?= =?UTF-8?q?=204=20=D1=81=D0=B5=D0=BA=D1=86=D0=B8=D1=8F=D0=BC,=20=D1=81?= =?UTF-8?q?=D0=B2=D0=BE=D1=80=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=D0=BE=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Раньше: плоский список из 25 пунктов в один столбец, для учителя видно ~20+, новички терялись. Теперь: 4 группы со свёртывающимися заголовками + always-visible top-блок и admin-нижний блок. Группы: • (top, без заголовка) Поиск · Дашборд · Путеводитель · Руководство • «УЧЕБНЫЙ ПРОЦЕСС» (6): Классы, Мои ученики, Онлайн-урок, Архив уроков, Live-квиз, Доска • «КОНТЕНТ» (6): Учебники, Библиотека, Теория, Карта знаний, Банк вопросов, Экзамен 9 класс • «ПРАКТИКА И ИГРЫ» (7): Лаборатория, Биохимия, Красная книга, Кроссворд, Виселица, Питомец, Коллекция • «ОТЧЁТЫ И УПРАВЛЕНИЕ» (3, teacher+): Аналитика, Журнал, Управление Технически: - Helper G(slug, label, body) — создаёт группу с уникальным slug - localStorage 'ls_sb_g_' хранит свёрнутое состояние - Click на заголовок группы → toggle .collapsed - Sidebar-collapsed (icon-only) режим: заголовки групп скрыты, все пункты остаются видны (компактный режим работает как раньше) - Стили инжектятся inline через id=sb-group-styles (защита от повторной инжекции при HMR) Совместимость: все ссылки и их id/class сохранены, нет правок в других файлах. Существующие onClick'и через id (btn-board, btn-classes, btn-admin, btn-join) работают. --- js/sidebar.js | 113 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 23 deletions(-) diff --git a/js/sidebar.js b/js/sidebar.js index 95535a2..4fef137 100644 --- a/js/sidebar.js +++ b/js/sidebar.js @@ -37,6 +37,19 @@ return `${label}`; } + // Collapsible group: section header + body. State persisted in localStorage. + function G(slug, label, body) { + const stored = localStorage.getItem('ls_sb_g_' + slug); + const collapsed = stored === '1'; + return `
+ +
${body}
+
`; + } + const initials = (user?.name || 'LS').split(' ').slice(0, 2).map(w => (w[0] || '').toUpperCase()).join('') || 'LS'; const displayName = esc(user?.name || user?.email || '—'); @@ -50,29 +63,40 @@ ${L('/dashboard', 'home', 'Дашборд')} ${L('/sitemap', 'map', 'Путеводитель')} ${L('/teacher-guide', 'book-marked', 'Руководство', { cls: 'sb-teacher-only', hidden: !isTch })} - ${L('/board', 'layout-dashboard', 'Доска', { id: 'btn-board', hidden: true })} - ${L('/classes', 'graduation-cap', 'Классы', { id: 'btn-classes', hidden: !isTch })} - ${L('/my-students', 'user-plus', 'Мои ученики', { cls: 'sb-teacher-only', hidden: !isTch })} - ${L('/library', 'book-open', 'Библиотека')} - ${L('/theory', 'brain', 'Теория')} - ${L('/lab', 'atom', 'Лаборатория')} - ${L('/biochem', 'flask-conical', 'Биохимия')} - ${L('/hangman', 'gamepad-2', 'Виселица')} - ${L('/crossword', 'grid-3x3', 'Кроссворд')} - ${L('/pet', 'heart', 'Питомец')} - ${L('/collection', 'layers', 'Коллекция')} - ${L('/knowledge-map', 'share-2', 'Карта знаний')} - ${L('/red-book', 'leaf', 'Красная книга')} - ${L('/exam9', 'clipboard-check', 'Экзамен 9 класс')} - ${L('/textbooks', 'book-open-text', 'Учебники')} - ${L('/classroom', 'presentation', 'Онлайн-урок')} - ${L('/lesson-history','archive', 'Архив уроков')} -
- ${L('/analytics', 'bar-chart-2', 'Аналитика', { cls: 'sb-teacher-only', hidden: !isTch })} - ${L('/question-bank', 'database', 'Банк вопросов', { cls: 'sb-teacher-only', hidden: !isTch })} - ${L('/live-quiz', 'radio', 'Live-квиз', { cls: 'sb-teacher-only', hidden: !isTch })} - ${L('/gradebook', 'table', 'Журнал', { cls: 'sb-teacher-only', hidden: !isTch })} - ${L('/admin', 'settings', 'Управление', { id: 'btn-admin', hidden: !isTch })} + + ${G('learning', 'Учебный процесс', ` + ${L('/classes', 'graduation-cap', 'Классы', { id: 'btn-classes', hidden: !isTch })} + ${L('/my-students', 'user-plus', 'Мои ученики', { cls: 'sb-teacher-only', hidden: !isTch })} + ${L('/classroom', 'presentation', 'Онлайн-урок')} + ${L('/lesson-history','archive', 'Архив уроков')} + ${L('/live-quiz', 'radio', 'Live-квиз', { cls: 'sb-teacher-only', hidden: !isTch })} + ${L('/board', 'layout-dashboard', 'Доска', { id: 'btn-board', hidden: true })} + `)} + + ${G('content', 'Контент', ` + ${L('/textbooks', 'book-open-text', 'Учебники')} + ${L('/library', 'book-open', 'Библиотека')} + ${L('/theory', 'brain', 'Теория')} + ${L('/knowledge-map', 'share-2', 'Карта знаний')} + ${L('/question-bank', 'database', 'Банк вопросов', { cls: 'sb-teacher-only', hidden: !isTch })} + ${L('/exam9', 'clipboard-check', 'Экзамен 9 класс')} + `)} + + ${G('practice', 'Практика и игры', ` + ${L('/lab', 'atom', 'Лаборатория')} + ${L('/biochem', 'flask-conical', 'Биохимия')} + ${L('/red-book', 'leaf', 'Красная книга')} + ${L('/crossword', 'grid-3x3', 'Кроссворд')} + ${L('/hangman', 'gamepad-2', 'Виселица')} + ${L('/pet', 'heart', 'Питомец')} + ${L('/collection', 'layers', 'Коллекция')} + `)} + + ${isTch ? G('management', 'Отчёты и управление', ` + ${L('/analytics', 'bar-chart-2', 'Аналитика', { cls: 'sb-teacher-only', hidden: !isTch })} + ${L('/gradebook', 'table', 'Журнал', { cls: 'sb-teacher-only', hidden: !isTch })} + ${L('/admin', 'settings', 'Управление', { id: 'btn-admin', hidden: !isTch })} + `) : ''}
${isStu ? '' : ''} @@ -93,6 +117,49 @@
`; + // Inject group styles once + if (!document.getElementById('sb-group-styles')) { + const style = document.createElement('style'); + style.id = 'sb-group-styles'; + style.textContent = ` + .sb-group { margin: 8px 0 2px; } + .sb-group-hdr { + width: 100%; padding: 4px 12px; border: none; background: none; + display: flex; align-items: center; justify-content: space-between; + font-family: 'Manrope', system-ui, sans-serif; + font-size: 0.66rem; font-weight: 800; letter-spacing: 0.08em; + color: rgba(255, 255, 255, 0.45); text-transform: uppercase; + cursor: pointer; transition: color .12s; + } + .sb-group-hdr:hover { color: rgba(255, 255, 255, 0.75); } + .sb-group-chev { + width: 12px; height: 12px; transition: transform .18s; + } + .sb-group.collapsed .sb-group-chev { transform: rotate(-90deg); } + .sb-group-body { + display: flex; flex-direction: column; + max-height: 800px; overflow: hidden; + transition: max-height .25s ease, opacity .18s; + opacity: 1; + } + .sb-group.collapsed .sb-group-body { + max-height: 0; opacity: 0; pointer-events: none; + } + /* When sidebar collapsed (icon-only mode) — hide group headers */ + .app-layout.sb-collapsed .sb-group-hdr { display: none; } + .app-layout.sb-collapsed .sb-group-body { max-height: none !important; opacity: 1 !important; pointer-events: auto !important; } + `; + document.head.appendChild(style); + } + + // Toggle handler (global so onclick works) + window.__lsSbToggle = function (slug) { + const g = el.querySelector(`[data-sb-group="${slug}"]`); + if (!g) return; + const collapsed = g.classList.toggle('collapsed'); + try { localStorage.setItem('ls_sb_g_' + slug, collapsed ? '1' : '0'); } catch {} + }; + // Insert notif-drop sibling after