77 KiB
🎨 Frontend
Общая информация
| Параметр | Значение |
|---|---|
| Фреймворк | Нет (Vanilla JavaScript) |
| Модульная система | ES6 Modules (import/export) |
| Стили | CSS (модульный подход) |
| Шрифт | Системный стек без внешних font-CDN |
| Веб-сервер | Apache httpd на Alpine со строгим CSP |
Структура файлов
frontend/
├── package.json # Exact build-зависимости и команды проверки
├── package-lock.json # Зафиксированное npm-дерево
├── auth-session.js # Access JWT и профиль сессии только в памяти вкладки
├── telemetry.js # Same-origin загрузчик собранного OTel bundle
├── schedule-overview.js # Общий расчёт и рендер «Сегодня / Следующая пара»
├── schedule-overview.css # Общие карточки ближайших занятий и поискового выбора группы
├── security.conf # CSP и защитные HTTP-заголовки Apache
├── telemetry/
│ └── otel-entry.js # Исходная точка сборки OpenTelemetry
├── scripts/
│ └── build-vendor.mjs # Сборка `/vendor/otel.js` через esbuild
├── tests/
│ ├── academic-calendar-grid.test.mjs # Неполная первая неделя календарного графика
│ ├── academic-calendar-title.test.mjs # Составное название календарного графика
│ ├── academic-calendar-tooltip.test.mjs # Позиционирование подсказки возле курсора и границ viewport
│ ├── schedule-overview.test.mjs # Текущее занятие, список на сегодня и следующая пара
│ ├── admin-ui-regressions.test.mjs # Заголовки, меню, действия слотов и компоновка вкладок
│ ├── auth-session.test.mjs # Login/refresh/reload/logout и single-flight refresh
│ ├── dashboard-conflicts.test.mjs # Регрессии дат и состояний проверки конфликтов
│ ├── schedule-overrides.test.mjs # Действия, роли, недельный выбор и подбор времени разовой правки
│ ├── schedule-quality.test.mjs # Метрики качества, фильтры проблем и доступность вкладки
│ ├── schedule-versions.test.mjs # Статусы версий, журнал и навигация в черновик/качество
│ ├── schedule-view-semesters.test.mjs # Выбор семестра и расчёт двухнедельного диапазона просмотра
│ ├── teacher-absences.test.mjs # Payload мастера замены и доступность вкладки по ролям
│ ├── teacher-preferences.test.mjs # Календарь пожеланий, заявки и подсказки конструктора
│ └── security-policy.test.mjs # Web Storage, XSS, пароли, язык, CSP и Dockerfile
├── index.html # 🔐 Страница авторизации (общая)
├── script.js # Логика авторизации
├── style.css # Стили страницы авторизации
├── theme-toggle.js # Переключение светлой/тёмной темы
├── Dockerfile # Multi-stage: npm bundle → Apache httpd
│
├── admin/ # 👨💼 Интерфейс администратора
│ ├── index.html # SPA-оболочка с sidebar
│ ├── css/
│ │ ├── main.css # CSS-переменные, цвета, типографика
│ │ ├── layout.css # Раскладка (sidebar, topbar, content)
│ │ ├── components.css # Кнопки, таблицы, карточки, формы
│ │ ├── modals.css # Модальные окна
│ │ ├── auditorium-workload.css # Таблицы расписаний и загруженности
│ │ ├── departments-data.css # Стили создания кафедры/специальности
│ │ ├── teacher-absences.css # Отсутствия, пожелания и очередь заявок преподавателей
│ │ ├── schedule-quality.css # Диагностический экран качества расписания
│ │ └── schedule-versions.css # Контур публикации, diff и журнал версий
│ ├── js/
│ │ ├── main.js # Инициализация, маршрутизация, навигация
│ │ ├── role-capabilities.js # Единая матрица вкладок admin/settings по ролям
│ │ ├── api.js # HTTP-обёртка (fetch + Authorization)
│ │ ├── dashboard-conflicts.js # Чистые функции дат, загрузки и состояний Red Zone
│ │ ├── date-input.js # Маска ДД.ММ.ГГГГ и преобразование дат в ISO
│ │ ├── async-combobox.js # Асинхронный поиск больших справочников
│ │ ├── dialog.js # Доступные подтверждения, сообщения и ввод причины
│ │ ├── dirty-state.js # Защита несохранённых изменений
│ │ ├── pagination.js # Общая панель серверной пагинации
│ │ ├── url-state.js # Чтение и запись фильтров в query string
│ │ ├── view-state.js # Единые loading/empty/error-состояния таблиц
│ │ ├── academic-period.js # Каскад учебного года и семестра в профильных вкладках
│ │ ├── utils.js # Утилиты
│ │ ├── schedule-period.js # Выбор ближайшего учебного периода для расписаний и нагрузки
│ │ └── views/ # Модули представлений
│ │ ├── dashboard.js # Дашборд
│ │ ├── users.js # Управление пользователями
│ │ ├── teacher-requests.js # Заявки кафедр на преподавателей
│ │ ├── groups.js # Управление группами
│ │ ├── classrooms.js # Управление аудиториями
│ │ ├── subjects.js # Управление дисциплинами
│ │ ├── university-structure.js # Кафедры, специальности и профили обучения
│ │ ├── department-workspace.js # Кабинет кафедры в общей панели
│ │ ├── schedule-view.js # Просмотр расписаний и запуск разовой правки из карточки
│ │ ├── schedule-override-panel.js # Боковая панель и реестр разовых изменений
│ │ ├── teacher-absences.js # Отсутствия, согласование пожеланий и заявок на изменение
│ │ ├── schedule-quality.js # Оценка, фильтры и переход к редактированию правил
│ │ ├── schedule-versions.js # Черновики, публикация, восстановление, diff и аудит
│ │ ├── schedule.js # Конструктор правил и подсказки пожеланий преподавателей
│ │ ├── academic-calendar-grid.js # Расчёт ISO-недели дневной сетки
│ │ ├── academic-calendar-title.js # Название из кода, профиля, формы и года
│ │ ├── academic-calendar.js # Календарные учебные графики
│ │ └── auditorium-workload.js # Загруженность аудиторий, преподавателей и кафедр
│ ├── views/ # HTML-шаблоны представлений
│ │ ├── dashboard.html
│ │ ├── users.html
│ │ ├── teacher-requests.html
│ │ ├── groups.html
│ │ ├── classrooms.html
│ │ ├── subjects.html
│ │ ├── university-structure.html
│ │ ├── department-workspace.html
│ │ ├── schedule-view.html
│ │ ├── teacher-absences.html
│ │ ├── schedule-quality.html
│ │ ├── schedule-versions.html
│ │ ├── schedule.html
│ │ ├── academic-calendar.html
│ │ └── auditorium-workload.html
│ │
│ └── settings/ # ⚙️ Страница настроек (отдельный SPA)
│ ├── index.html # Оболочка с собственной sidebar
│ ├── css/
│ │ ├── main.css # CSS-переменные, базовые стили
│ │ └── layout.css # Sidebar, topbar, content
│ ├── js/
│ │ ├── main.js # Навигация по вкладкам настроек
│ │ └── views/
│ │ └── time-slots.js # Настройка временных слотов
│ └── views/
│ ├── general.html # Общие настройки (заглушка)
│ └── time-slots.html # Базовая, субботняя и ручные сетки времени
│
├── teacher/ # 👩🏫 Интерфейс преподавателя
│ ├── index.html # Расписание, календарь доступности и журнал заявок
│ ├── app.js # Пожелания, переносы и недельный просмотр через общий auth-session
│ └── style.css # Адаптивный кабинет в стиле диспетчерского журнала
├── department/ # 🏛 Кабинет кафедры
│ └── index.html # Redirect в `/admin/#department-workspace`
├── edu-office/ # 🗓 Кабинет учебного отдела
│ └── index.html # Redirect в `/admin/#schedule-view`
│
└── student/ # 🎓 Интерфейс студента
├── index.html # CSP-совместимая HTML-оболочка
├── app.js # Недельный просмотр и общий auth-session
└── style.css # Стили кабинета без inline-блока
Система маршрутизации (Admin SPA)
Админ-панель работает как Single Page Application без фреймворка.
Навигация реализована через data-tab атрибуты на элементах sidebar:
<a href="#" class="nav-item" data-tab="users">Пользователи</a>
<a href="#" class="nav-item" data-tab="groups">Группы</a>
<a href="#" class="nav-item" data-tab="schedule">Расписание занятий</a>
<a href="#" class="nav-item" data-tab="academic-calendar">Календарный график</a>
При клике на пункт меню main.js:
- Загружает HTML-шаблон из
views/{tab}.htmlчерезfetch() - Вставляет его в
#app-content - Подключает соответствующий JS-модуль из
js/views/{tab}.js - Обновляет заголовок страницы (
#page-title)
main.js и отдельный settings SPA получают разрешённые вкладки из единого
admin/js/role-capabilities.js; локальные дубли ROLE_NAVIGATION/ROLE_TABS удалены.
| Роль | Доступные вкладки |
|---|---|
ADMIN |
Все вкладки |
EDUCATION_OFFICE |
Просмотр, конструктор и анализ качества расписаний, отсутствия и замены, календарный график, загруженность и аудитории; в settings — временные слоты и формы обучения |
DEPARTMENT |
Кабинет кафедры, просмотр расписаний, отсутствия и подтверждение заявок своих преподавателей |
SCHEDULE_VIEWER |
Только просмотр расписаний |
Пути /department/ и /edu-office/ оставлены как входные redirect-страницы в общую панель. Отдельные кабинеты не дублируют UI админ-панели.
Пункты меню, недоступные роли, не отображаются в sidebar: main.js выставляет hidden, а layout.css явно скрывает такие элементы, чтобы базовый display: flex у .nav-item не возвращал их на экран.
Верхние панели основной админки и settings SPA не содержат неработающей кнопки
бургер-меню. В светлой теме пункты и заголовки sidebar получают контрастные тёмные цвета
для всех ролей, включая DEPARTMENT.
Разделы админ-панели
| Tab | Описание | API |
|---|---|---|
dashboard |
Сводные метрики и проверка конфликтов текущей недели с явным статусом полноты данных | /api/departments, /api/classrooms, /api/groups, /api/users/teachers, /api/schedule/search, /api/admin/time-slots |
teacher-requests |
Очередь заявок кафедр на создание преподавателей с поиском, статусом, пагинацией и редактированием перед одобрением | /api/teacher-requests/page, /api/departments |
users |
CRUD пользователей с серверными поиском, ролевым фильтром, сортировкой и пагинацией | /api/users/page |
groups |
CRUD групп, серверный поиск и пагинация, мультифильтр по формам обучения, настройка 0/2/3 подгрупп и поиск совместимого графика | /api/groups/page, /api/groups/options, /api/admin/academic-calendars/options, /api/groups/{id}/subgroups |
classrooms |
Аудитории | /api/classrooms |
subjects |
Дисциплины с серверным реестром, привязками только для текущей страницы и асинхронным выбором преподавателя | /api/subjects/page, /api/teacher-subjects?subjectId=…, /api/users/teachers/options |
university-structure |
Кафедры, специальности и профили обучения; профили доступны отдельной внутренней вкладкой и через кнопку специальности | /api/departments, /api/specialties, /api/specialties/{id}/profiles |
department-workspace |
Кабинет кафедры: дисциплины, импорт, комментарии, преподаватели, привязка преподавателей, заявки на новых преподавателей и нагрузка | /api/department/*, /api/department/teacher-requests, /api/workload/teachers |
schedule-view |
Просмотр опубликованного расписания: учебный год и семестр выбираются каскадом в дополнительных фильтрах, поисковые списки сущностей при открытии остаются свёрнутыми и используют размытый непрозрачный слой меню, найденные расписания переключаются без общего контекста страницы, а ADMIN и EDUCATION_OFFICE редактируют конкретное занятие в боковой панели без изменения правила |
/api/schedule/semesters, /api/schedule/search, /api/edu-office/schedule/overrides, /api/admin/time-slots/effective |
teacher-absences |
Запросы преподавателей: отсутствия и мастер замены, согласование семестровых пожеланий, заявки на перенос, аудиторию или отмену; преподаватель ищется асинхронно по всем кафедрам для ADMIN/EDUCATION_OFFICE и только по своей кафедре для DEPARTMENT; поисковое меню размывает фон, а фильтры согласования оформлены для светлой и тёмной тем |
/api/teacher-absences, /api/teacher-preferences, /api/teacher-change-requests, /api/users/teachers/options |
schedule |
Конструктор правил: локальный каскад Учебный год → Семестр → Версия открывает опубликованное расписание или черновик; первичная загрузка скрывает нестабильную компоновку до подготовки полей, а таблица правил использует фиксированные колонки и серверную пагинацию; группы, преподаватели и аудитории ищутся на сервере, подгруппы загружаются только для выбранных групп; предупреждение о несохранённых изменениях появляется только после действия пользователя |
/api/admin/schedule-rules, /api/edu-office/schedule/versions, /api/teacher-preferences, /api/admin/time-slots, /api/admin/calendar/years, /api/lesson-types, /api/groups/options, /api/subgroups?groupId=…, /api/users/teachers/options, /api/classrooms/options |
schedule-versions |
Контур публикации с локальным выбором учебного года и семестра: текущая версия, черновики, diff, архив, восстановление и журнал | /api/edu-office/schedule/versions |
schedule-quality |
Диагностика выбранной версии через каскад Учебный год → Семестр → Версия: оценка, метрики и проблемные правила с переходом к их редактированию |
/api/edu-office/schedule/quality, /api/admin/schedule-rules |
academic-calendar |
Учебные годы, семестры, создание календарных графиков, Excel-подобный редактор дневной сетки и привязка дисциплин к семестрам графика; редко используемая форма создания учебного года расположена внизу вкладки Графики |
/api/admin/calendar, /api/admin/academic-calendars, /api/admin/academic-calendars/{id}/subjects, /api/admin/calendar/activity-types, /api/specialties, /api/specialties/{id}/profiles, /api/education-forms, /api/subjects |
auditorium-workload |
Динамическая загруженность аудиторий, преподавателей и кафедр: сводная матрица по дате или совмещённая таблица выбранной сущности по чётной/нечётной неделе | /api/classrooms, /api/users/teachers, /api/departments, /api/admin/time-slots, /api/equipments, /api/groups, /api/schedule, /api/admin/calendar/years |
Особенности админских вкладок
- Общий
formatLocalDate()формируетYYYY-MM-DDиз локальных компонентовDate, не используяtoISOString(). Кабинет кафедры и редактор академического календаря также выполняют календарную арифметику локальными компонентами, поэтому даты первых часов суток и границы месяца не сдвигаются из-за преобразования в UTC. - Вкладка
dashboardформирует date-only значения из локальных компонентов даты, а текущую неделю — от отдельного объекта понедельника допонедельник + 6 дней. Расписания кафедр загружаются независимо черезPromise.allSettled:COMPLETEозначает ответы всех кафедр,PARTIAL— только части,NOT_RUN— отсутствие пригодных ответов или кафедр. Зелёная карточка «Конфликты расписания не обнаружены» разрешена только дляCOMPLETEбез найденных конфликтов; частичный результат всегда остаётся предупреждением, а полный отказ показывается как «Проверка не выполнена». Технические причины отказов в DOM не выводятся. - Вкладка
groupsзагружает кафедры, специальности, профили, учебные годы и календарные графики. Список групп открывается через/api/groups?includeArchived=true, поэтому в таблице видны активные, будущие, завершившие обучение и архивные группы со статусом. Группа создаётся через/api/groupsсspecialtyIdиspecialtyProfileId, а модалка редактирования использует широкую сетку полей без внутреннего пустого скролла. Блок подгрупп использует/api/subgroupsи/api/groups/{id}/subgroups, а блок назначений использует/api/groups/{id}/calendar-assignments. После назначения графика в таблице назначений сразу выводятся дисциплины графика, сгруппированные по номерам семестров. В селекты подгрупп и назначений попадают только группы сactive=true. - Вкладка
teacher-requestsпоказывает pending-заявки кафедр на создание преподавателей. Администратор может скорректировать кафедру, логин, ФИО и должность, задать пароль минимум 8 символов в скрытом поле сautocomplete="new-password", затем одобрить заявку через/api/teacher-requests/{id}/approveили отклонить её через/api/teacher-requests/{id}/reject. Для ролиADMINсчётчик pending-заявок выводится в пункте меню «Заявки» и рядом с заголовком страницы, чтобы очередь была видна без открытия вкладки. - Вкладка
department-workspaceв блоке преподавателей объединяет данные/api/department/teachersи/api/workload/teachers: каждый преподаватель показывается одной карточкой с должностью и нагрузкой за выбранный период, преподаватели без занятий получают нулевую нагрузку, а преподаватели из расписания добавляются без дублей. Между семестрами период автоматически переносится на первые две недели ближайшего будущего семестра, чтобы будущие правила не выглядели как нулевая нагрузка; при недоступности справочника остаётся диапазон от текущей даты. Если дата начала периода выбрана позже даты окончания, поле окончания очищается, а расчёт нагрузки ждёт корректный период. - Вкладка
department-workspaceпозволяет кафедре добавить существующего активного преподавателя на свою кафедру через/api/department/teachers/{teacherId}/assignments, отправить заявку на нового преподавателя через/api/department/teacher-requestsи видеть статусы собственных заявок в таблице. - Вкладка
teacher-absencesдоступна администратору, учебному отделу и кафедре. Верхний командный блок показывает очередь инцидентов, форма регистрирует преподавателя, период и причину, а реестр разделяет статусы согласования. Полноэкранный мастер выводит каждое затронутое занятие отдельной строкой и предлагает только кандидатов, уже проверенных backend. Пустая строка не отправляется; выбранные решения применяются одним пакетом. Ниже расположены реестр пожеланий на семестр и очередь заявок на изменение опубликованных пар. Пожелание можно принять или отклонить с комментарием. Заявку применяют толькоADMINиEDUCATION_OFFICE; кафедра видит записи своих преподавателей без управляющих действий. Фильтры обоих реестров используют тематические кастомные списки с отдельными поверхностями для светлой и тёмной тем, а поиск преподавателя — общее размытое менюAsyncCombobox. Каждая карточка показывает исходное и запрошенное состояние, результат предварительной проверки и хронологию решения. - Вкладка
schedule-qualityдоступна администратору и учебному отделу. После каскадного выбора учебного года, семестра и версии она выводит круговую оценку от 0 до 100, карточки метрик и реестр правил с фильтрами по серьёзности и типу. Все проблемные занятия одного правила показаны одной карточкой; в ней выводятся число затронутых занятий, категории и суммарный штраф. КнопкаИзменить правилооткрывает выбранную версию в конструкторе и сразу заполняет форму этим правилом. Сценарий одинаков дляPUBLISHEDиDRAFT;ARCHIVEDанализируется только для чтения. Переход между вкладками передаёт ID версии и правила через одноразовые ключиmagistr.schedule.openVersionIdиmagistr.schedule.openRuleIdвlocalStorage. - Вкладка
schedule-versionsдоступна администратору и учебному отделу и оформлена как отдельный контур публикации. В верхней карточке учебный год сначала ограничивает список семестров, поэтому архив периодов не превращается в один длинный селект. Карточка показывает версию, которую видят конечные пользователи; ниже расположены черновики, сравнение правил и занятий, архив и журнал. В конструкторе локальный каскадУчебный год → Семестр → Версияпо умолчанию выбирает опубликованную версию текущего семестра и выделяет её зелёным контуром; её изменения применяются сразу. Селект позволяет перейти в медный режим черновика. КнопкаСоздать черновиккопирует выбранное опубликованное расписание или черновик и сразу открывает новую рабочую копию. Перед публикацией интерфейс одновременно запрашивает полную валидацию и diff, требует причину и блокирует действие при ошибках. Кнопки версий передают их ID одноразово черезlocalStorageи открывают конструктор или анализ качества в нужном контексте; архивную публикацию можно восстановить с причиной. - Компоновка
department-workspaceиспользует собственные CSS-сеткиdepartment-workspace-filter-gridиdepartment-workspace-actions-grid: фильтры периода отделены от сеток расписания, загрузка дисциплин занимает широкую колонку, формы преподавателей выравниваются справа, а списки и таблицы идут полноширинными блоками ниже. - Вкладка
schedule-viewпоказывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; поисковые меню группы, преподавателя и аудитории используют плотный фон сbackdrop-filter, чтобы строки не смешивались с карточками под списком. Основная кнопкаПоказатьрасположена в заголовке блока параметров, а пустое состояние таблицы с подсказкой об обновлении содержит дополнительную кнопкуПоказать расписание. В дополнительных фильтрах учебный год каскадно ограничивает список семестров из/api/schedule/semesters; оба значения сохраняются в URL. При загрузке автоматически выбирается текущий семестр, а между учебными периодами — ближайший будущий; если будущего нет, используется последний завершённый. Экран всегда показывает опубликованную версию: выбор черновика остаётся вschedule-qualityиschedule. Для выбранного семестра frontend запрашивает весь период от его первой до последней даты и собирает повторяющиеся занятия в одну семестровую матрицу. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания; чипы результатов переносятся и отделены от счётчика стабильным отступом. Для режима кафедры и ролиDEPARTMENTрасписание ограничивается действующей кафедральной связью преподавателя на дату занятия; группы другой кафедры не отбрасываются, если занятие ведёт преподаватель текущей кафедры. Преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. Каждая карточка занятия явно показывает фактические границы недель:(с 1 по 18 нед.)либо(7 нед.). На мобильной ширине вместо широкой матрицы показывается один день активного расписания с переключателем дней. - Для
ADMINиEDUCATION_OFFICEкарточка занятия содержит кнопкуИзменить, а уже изменённая пара — индикатор разовой правки. Справа открывается полупрозрачная боковая панель с размытием содержимого под ней; внешний затемнённый слой также размывает страницу, а на мобильном устройстве панель занимает весь экран. РежимРедактированиесравниваетБыло по правилу / Станет, позволяет изменить дату, эффективный временной слот, преподавателя, аудиторию, формат и комментарий, отменить занятие или удалить override черезВернуть по правилу. Преподаватель и аудитория ищутся асинхронно через/api/users/teachers/optionsи/api/classrooms/options; в подписи аудитории показывается её название. По умолчанию выводятся семь дней исходной недели; кнопкаВыбрать другую датураскрывает календарь всего семестра, где неучебные даты отключены. После смены даты загружается эффективная сетка дня: сначала выбирается тот же ID слота, затем совпадающий интервал, иначе требуется ручной выбор. Дата или время формируютMOVE, а только преподаватель, аудитория или формат —REPLACE. - Режим
Изменения за периодзагружает overrides, у которых в двухнедельный диапазон попала исходная или целевая дата. Поэтому в реестре остаются отменённые занятия и входящие/исходящие переносы, которых нет на исходном месте в таблице. После сохранения, отмены или возврата расписание и реестр обновляются без смены выбранного периода. ДляDEPARTMENTиSCHEDULE_VIEWERвкладка остаётся полностью read-only: кнопка, панель и запросы управляющего API не создаются. - Вкладка
auditorium-workloadстала общей вкладкойЗагруженность: в поле «Что смотреть» выбираются аудитории, преподаватели или кафедры. Сводная матрица по выбранной дате использует одинаковую структуру: строки — выбранный тип сущности, столбцы — эффективные временные слоты дня из/api/admin/time-slots/effective, занятость собирается из динамического расписания/api/scheduleпо группам. Кафедральная матрица группирует занятия по кафедре преподавателя. Для аудиторий доступны фильтры корпуса, вместимости и оборудования. В поле «Отображение» можно выбрать конкретную аудиторию, преподавателя или кафедру; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной сущности растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из/api/admin/calendar/years. - Вкладка
university-structureсодержит внутренние разделыКафедры,СпециальностииПрофили, оформленные тем же визуальным паттерном вкладок, что иacademic-calendar: администратор создаёт профили как из общего списка, так и через кнопкуПрофилиу конкретной специальности. - Таблицы правил конструктора и календарных графиков загружают страницы с сервера: по умолчанию 25 записей, доступны 50 и 100. Поиск правил работает по дисциплине, группе и ID; поиск графиков — по названию, направлению, профилю, форме обучения и году, дополнительно доступен фильтр учебного года. Поиск сбрасывает страницу, устаревшие ответы не заменяют актуальную выдачу. Скрытая визуальная матрица конструктора загружается и строится только при открытии; переход из анализа качества получает нужное правило по ID независимо от страницы таблицы. Программное обновление селектов периода обновляет обёртки без повторного вызова обработчиков смены года и семестра.
- В разделе календарного графика списки графиков в «Сетках» и «Дисциплинах» используют
AsyncComboboxи/api/admin/academic-calendars/options: поиск на сервере с задержкой 320 мс, до 20 результатов плюс выбранная запись. Полный справочник графиков не загружается в DOM; выбор редактора не зависит от страницы таблицы. Отмена перехода с несохранёнными дисциплинами восстанавливает выбранный график, устаревшие ответы загрузки игнорируются, а сохранение сетки требует предварительной загрузки именно выбранного графика. - В «Структуре вуза» кафедры, специальности и профили имеют отдельный поиск и клиентскую пагинацию по 25/50/100 строк. Поиск учитывает названия, коды и ID, а у профилей также специальность и описание.
- В «Качестве расписания» правила после фильтрации упорядочиваются по суммарному штрафу по убыванию; при равном штрафе сохраняется исходный порядок.
- Вкладка
scheduleимеет заголовокКонструктор правили не обращается к старымlessonsAPI. До завершения первичной загрузки справочников и правил вкладка показывает единое состояние загрузки, после чего открывает уже подготовленную форму; таблица правил используетtable-layout: fixed, явныйcolgroupи отключённую анимацию строк, поэтому заголовки колонок не смещаются при замене состояния загрузки данными. Создание и редактирование расписания выполняется через правила/api/admin/schedule-rules, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Для выбранных преподавателя и семестра frontend загружает согласованные/api/teacher-preferencesи выводит под строкой слота компактные маркеры строгой недоступности, предпочтительного или нежелательного времени и пожеланий «пары подряд»/«без окон»; количество недоступных дат показывается отдельным маркером. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пунктВся группа; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Типы занятий в слоте сортируются в порядке: лекция, лабораторная работа, практика. Единственный слот имеет действиеОчистить, которое сбрасывает его поля; при наличии нескольких слотов у каждой строки показывается действиеУдалить. Оба действия используют размер формы, минимальную ширину125pxи высоту44px, поэтому совпадают по масштабу с соседними селектами. Список слотов отображается без внутреннего вертикального скролла: при добавлении строк форма расширяется вниз, а кнопка сохранения остаётся отдельным блоком под слотами. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: пользователь выбирает учебный год, семестр и группы, после чего матрица строится только по правилам выбранного семестра. Столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Период недель не показывается для занятия на весь семестр; если занятие идёт до конца семестра не с первой недели, выводится только неделя начала в формате(с 5 нед.), а ограниченный диапазон — как(с 1 по 3 нед.). Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. Кнопка с тремя точками в правой части карточки пары открывает контекстное меню возле нажатой кнопки, с автоматическим разворотом от границ viewport: можно открыть полное правило в форме, изменить только день и базовую пару выбранного слота через компактную модалку или удалить правило целиком. В списке правил действия отображаются едиными кнопками одинакового размера с отступами между ними. - Вкладка
academic-calendarполностью отделяет календарную систему от расписания занятий и внутри себя разделена на три вкладки:Графикидля учебных годов, семестров и карточек календарных графиков,Сеткидля редактора дневной сетки,Дисциплиныдля ручной привязки дисциплин из/api/subjectsк номерам учебных семестров графика. Форма редкого ежегодного действия — создания учебного года вместе с семестрами — расположена после реестров учебных годов и календарных графиков, в нижней части вкладкиГрафики. Пользователь вводит только четырёхзначный год начала, например2026; интерфейс подставляет год окончания2027, формирует название2026-2027и внутренний период01.09.2026–30.06.2027. Границы семестров можно скорректировать до сохранения. У календарного графика нет поля ввода и отдельного столбца названия; отображаемая подпись и сохраняемое backend название автоматически собираются каккод специальности — профиль обучения — форма обучения — учебный год. Frontend не отправляетtitleкалендарного графика в payload. Администратор выбирает форму обучения из общего справочника/api/education-forms, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через/api/admin/academic-calendars/{id}/grid. Каждый код активности окрашивает ячейку, подсказку, диалог и итоговый чип своимcolorCode. REST-контракт сетки остаётся дневным: backend объединяет соседние даты с одинаковой активностью в периоды при записи и разворачивает их при чтении. Шесть полей дат принимают до восьми цифр, автоматически показывают их в форматеДД.ММ.ГГГГ, ограничивают год четырьмя цифрами и перед отправкой преобразуют значение в ISOГГГГ-ММ-ДД; два поля учебного года принимают и показывают только четыре цифры. - При сохранении правила во вкладке
schedulefrontend различает409 Conflictот остальных ошибок API. Если backend возвращаетconflictRule, открывается широкое модальное окно разрешения конфликта: пользователь видит новое и ранее созданное правило, чипы причины конфликта изconflictReasonsи подсветку соответствующих полей (teacher,classroom,group). Пользователь меняет у конфликтующего правила день, чётность, пару, преподавателя или аудиторию, после чего frontend сохраняет конфликтующее правило черезPUT /api/admin/schedule-rules/{id}и автоматически повторяет исходныйPOSTилиPUT. Если после переноса появляется следующий конфликт, показывается следующее конфликтующее правило без потери исходного черновика. - Редактор годового графика во вкладке
academic-calendarпоказывает компактную табличную сетку: курсы раскрываются отдельными секциями со стрелкой, семестры внутри курса на широком экране идут рядом в две равные колонки одинаковой высоты, столбцы подписаны номерами недель учебного года, строки — днями недели, а ячейки содержат буквенный код активности. Недели выровнены по интервалампонедельник–воскресенье: если учебный год начинается не в понедельник, предшествующие позиции первой недели остаются пустыми, а следующий понедельник открывает неделю 2. В рамках одного курса семестровые таблицы получают одинаковое число недельных колонок: недостающие колонки заполняются пустыми ячейками, поэтому левая и правая части занимают всю ширину секции курса без внешней пустоты. Подробная расшифровка и изменение кода открываются в компактном модальном окне по клику на ячейку; при наведении возле курсора отображается кастомная подсказка с датой, курсом, кодом активности и временной сеткой. У границ viewport подсказка автоматически раскрывается в противоположную сторону и остаётся полностью видимой, а код активности в ней центрируется внутри квадратного индикатора. Ячейки можно выделять протяжкой мышью и менять код активности через ту же модалку, выбранные ячейки подсвечиваются мягкой заливкой, а кнопка «Применить» закрывает окно. - Вкладка
classroomsтеперь показывает архивные аудитории черезincludeArchived=true. Кнопка удаления заменена на архивирование: аудитория выводится из эксплуатации, но остаётся в историческом расписании. Для архивных аудиторий доступно восстановление. - Вкладка
usersподдерживает ролиEDUCATION_OFFICE,DEPARTMENTиSCHEDULE_VIEWER; удаление пользователя работает как архивирование.
Общие UX-компоненты первого пакета
- Общей панели учебного контекста под topbar нет. Каскадные селекты периода размещены только
в профильных вкладках:
schedule,schedule-versions,schedule-qualityиschedule-view. Календарный график и назначение графика группе сохраняют собственные локальные селекты. - Вкладка и фильтры сохраняются в URL. Реестры групп, пользователей, дисциплин и заявок восстанавливают поиск, фильтры, сортировку, номер и размер страницы после reload или возврата по ссылке. Просмотр расписания сохраняет вид сущности, выбранную сущность, дату, учебный год, семестр и дополнительные фильтры; конечные кабинеты сохраняют неделю, вкладку и группу.
pagination.jsработает с общим ответомPageResponse; размер страницы — 25, 50 или 100.view-state.jsунифицирует загрузку со spinner, пустой результат и ошибку с кнопкой повторного запроса.AsyncComboboxзагружает варианты с сервера, сохраняет выбранный ID, поддерживает клавиатуру и ролиcombobox/listbox. Он используется для групп, преподавателей, аудиторий и совместимых календарных графиков; предварительная загрузка вариантов не раскрывает меню. Студент больше не загружает полный список групп.dialog.jsзаменяет нативныеconfirm,promptиalert: возвращает фокус, удерживает его внутри окна, закрывается по Escape и может требовать причину. Нативных диалогов в admin/settings-модулях нет.dirty-state.jsпредупреждает при смене вкладки, версии, графика или закрытии страницы. Календарная сетка и привязки дисциплин показывают маркер•на кнопке сохранения; конструктор правил отслеживает изменения полей и состава слотов, но игнорирует программную синхронизацию селектов при загрузке и сбросе формы.trackUxEvent()создаёт базовые события смены раздела, загрузки реестров и расписания, сохранения календаря/правила и просмотра ближайших занятий. В production они преобразуются в OpenTelemetry spans, а до готовности bundle хранятся в ограниченной очереди.
Страница настроек (/admin/settings/)
Настройки — это отдельный SPA со своей боковой панелью и вкладками, не связанными с основной админ-панелью.
- Доступ: через dropdown «Настройки» в footer боковой панели админки для
ADMINиEDUCATION_OFFICE - Кнопка «Назад в панель» для возврата в
/admin/ - Текущие вкладки:
- Общие настройки — заглушка (только
ADMIN) - Временные слоты — выбор сетки через выпадающий список, CRUD слотов выбранной сетки, добавление ручных сеток через модальное окно и удаление выбранной ручной сетки. Поле длительности доступно только для чтения и пересчитывается при изменении времени начала или окончания; в API отправляются только границы, а окончательное значение вычисляет backend. Базовая сетка применяется по умолчанию, субботняя — автоматически по субботам; ручное применение выполняется в сетке календарного графика.
- Общие настройки — заглушка (только
API-клиент (api.js)
Все защищённые HTTP-запросы проходят через fetchWithAuth() из auth-session.js.
Access JWT и role/departmentId/userId существуют только в памяти JavaScript-модуля:
export async function fetchWithAuth(endpoint, options = {}, retry = true) {
const headers = new Headers(options.headers || {});
if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
const response = await fetch(endpoint, { ...options, headers, credentials: 'same-origin' });
if (response.status === 401 && retry && await refreshAccessToken()) {
return fetchWithAuth(endpoint, options, false);
}
return response;
}
При загрузке защищённой страницы память восстанавливается через POST /api/auth/refresh и
HttpOnly cookie. При 401 клиент выполняет не более одной общей refresh-ротации для всех
параллельных запросов и один раз повторяет исходный запрос. Неуспешный refresh очищает память
и возвращает пользователя на страницу входа. Legacy auth-ключи только удаляются из
localStorage/sessionStorage; новые данные авторизации туда не записываются.
После мутаций api.js инвалидирует кэш не только по точному URL, но и по связанным префиксам. Для заявок и кафедральных привязок очищаются /api/users, /api/users/teachers, /api/teacher-requests, /api/department/teacher-requests и /api/department/teachers, чтобы таблицы заявок и списки преподавателей обновлялись без ручного сброса страницы.
Frontend-тесты
Регрессионные проверки используют встроенный node:test (Node.js 18+). Помимо тестов
дашборда, auth-session.test.mjs покрывает login-state, reload через refresh-cookie,
single-flight ротацию, повтор после 401 и logout. security-policy.test.mjs запрещает
auth-данные в Web Storage, удалённые/inline-скрипты, динамические inline-стили и рассинхрон
SHA-256 style-хэшей с CSP. Там же закреплены безопасный DOM-рендеринг ошибок без
интерполяции exception.message в innerHTML, скрытые парольные поля с корректным
autocomplete, русские метки tenant-контура и отсутствие англоязычных/raw exception
сообщений в собственных frontend-логах.
Команды выполняются из каталога frontend/:
npm run build # собрать локальный dist/vendor/otel.js
npm test # frontend unit/static tests
npm run check # синтаксис auth/UI-модулей + все frontend-тесты
Аутентификация (Frontend)
Страница входа (/index.html)
- Пользователь вводит логин/пароль
script.jsотправляетPOST /api/auth/login- При успехе кладёт access JWT и профиль пользователя только в память текущей страницы.
- Refresh-токен сохраняется браузером как
HttpOnlycookie и недоступен JavaScript. - После перехода новый документ восстанавливает память через
POST /api/auth/refresh. - Пользователь перенаправляется на соответствующий интерфейс:
ADMIN→/admin/EDUCATION_OFFICE→/admin/#schedule-viewDEPARTMENT→/admin/#department-workspaceSCHEDULE_VIEWER→/admin/#schedule-viewTEACHER→/teacher/STUDENT→/student/
Проверка авторизации
Каждая защищённая страница сначала восстанавливает сессию и проверяет роль из памяти:
const session = await restoreSession();
if (!session || !AUTHORIZED_ROLES.includes(session.role)) {
window.location.replace('/');
}
Выход
Кнопка «Выйти» вызывает POST /api/auth/logout, очищает access JWT и профиль из памяти и
перенаправляет на /. Refresh-cookie очищает backend.
Кабинеты расписания
Учебный отдел (/admin/#schedule-view)
Учебный отдел работает в общей админ-панели с ограниченным набором вкладок:
- расширенный поиск
/api/schedule/searchпо дате в периоде, преподавателю, аудитории и кафедре; - конструктор расписания через
/api/admin/schedule-rules; - разовая правка конкретного занятия через боковую панель: перенос даты/времени, замена ресурсов, отмена и возврат по правилу;
- матричный просмотр загруженности аудиторий, преподавателей и кафедр через общую вкладку
Загруженность; - аудитории, оборудование, календарный график и загруженность.
Кафедра (/admin/#department-workspace)
Кабинет кафедры встроен в общую панель и работает в контексте departmentId текущего пользователя:
- список дисциплин кафедры через
/api/department/subjects; - загрузка дисциплин из текстового списка в формате
код; название; - комментарии к дисциплинам;
- просмотр расписания кафедры через вкладку
schedule-view; - загруженность преподавателей кафедры.
Просмотр расписаний (/admin/#schedule-view)
Роль SCHEDULE_VIEWER видит только доступную для чтения вкладку просмотра опубликованного расписания. Доступны каскад учебного года и семестра, а также фильтры по группе, преподавателю, аудитории, кафедре, дисциплине, типу занятия, чётности и разрезу таблиц. При выборе семестра найденные занятия отображаются одной матрицей на весь период с явными границами недель для каждой пары.
Преподаватель (/teacher/)
Страница объединяет недельную сетку занятий, семестровый календарь доступности и журнал заявок на изменение пар. ID преподавателя берётся из восстановленного в памяти профиля сессии.
Основные элементы:
- общий блок «Сегодня» и карточка «Следующая пара»; отдельный запрос покрывает ближайшие 14 дней, поэтому следующая пара находится и за границей текущей недели;
- навигация по неделям: предыдущая, текущая, следующая;
- выбор даты через
input[type="date"]; - запрос
GET /api/schedule?teacherId={userId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}; - отображение дисциплины, времени, типа занятия, лабораторных подгрупп, аудитории и всех групп правила;
- вкладка «Мои пожелания» с выбором семестра, режимом отметки строгих, предпочтительных и нежелательных интервалов, полностью недоступных дат, пар подряд и расписания без окон;
- цветовые статусы
PENDING,APPROVED,REJECTED,CANCELLED; ожидающее пожелание можно отозвать кликом по отмеченной ячейке или кнопкой в списке дат; - кнопка «Заявка на изменение» в карточке фактического занятия и модальное окно переноса, смены аудитории или отмены. Недоступные кандидаты отфильтрованы после проверки backend;
- вкладка журнала заявок с причиной, решением, ссылкой на применённый override и историей статусов; ожидающую заявку можно отозвать;
- форма собственной заявки на отсутствие через
POST /api/teacher-absences; - список статусов заявок и отмена ещё не подтверждённой записи через
DELETE /api/teacher-absences/{id}.
Если refresh-cookie недействительна или роль не TEACHER, страница возвращает пользователя на вход.
Студент (/student/)
В текущей модели студент не связан с конкретной группой, поэтому страница использует
асинхронный поиск группы через /api/groups/options.
Основные элементы:
- поисковый combobox группы с подсказками по специальности, профилю и курсу; выбор хранится
в
localStorage.studentGroupIdиstudentGroupquery string; - общий блок «Сегодня» и карточка «Следующая пара», рассчитанные по ближайшим 14 дням;
- недельная сетка по дням;
- запрос
GET /api/schedule?groupId={groupId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}; - отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторных подгрупп.
CSS-архитектура
Модульный подход
Стили разделены на модульные файлы (порядок подключения важен):
main.css— CSS-переменные (цвета, шрифты, отступы), глобальные стили, тёмная темаlayout.css— Sidebar, topbar, content area, dropdown настроек, responsivecomponents.css— Кнопки, таблицы, карточки, badge, формы, theme-togglemodals.css— Модальные окнаdepartments-data.css— Стили создания кафедры/специальности
Темизация
Общий ui-foundation.css подключается последним во всех пяти оболочках.
Текст, заголовки и элементы управления используют прежний стек Inter,
-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif без внешнего CDN.
Если Inter не установлен, браузер выбирает системный шрифт без засечек.
Базовые стили содержат запасной стек на случай недоступности общего файла.
Список свободных аудиторий на дашборде имеет высоту 180 px и внутреннюю вертикальную прокрутку, доступную с клавиатуры. В запросах преподавателей статус, комментарий и кнопки размещаются под содержимым заявки; кнопки переносятся целиком при нехватке ширины, статусы не растягиваются по высоте.
CSS-переменные позволяют поддерживать светлую/тёмную тему:
:root {
--bg-primary: #ffffff;
--text-primary: #1a1a2e;
--accent: #4F46E5;
}
[data-theme="dark"] {
--bg-primary: #0f0f23;
--text-primary: #e2e8f0;
--accent: #8B5CF6;
}
Система кнопок
Кнопки админ-панели, отдельной SPA настроек, страницы входа и личных кабинетов используют единую семантическую модель:
btn— обязательная база для всех кнопок и ссылок-кнопок.- Размеры:
btn-sm— 32px для табличных строк,btn-md— 40px для форм и панелей,btn-lg— 48px для входа и главных широких действий. - Иконки:
btn-icon-sm— 32x32,btn-icon-md— 40x40. Все icon-only кнопки должны иметьaria-labelна русском языке. - Варианты:
btn-primaryдля основного действия блока,btn-secondaryдля редактирования/обновления/показа,btn-ghostдля очистки/отмены/сворачивания,btn-dangerдля удаления,btn-danger-subtleдля архивирования, выхода и других рискованных действий без немедленного удаления. - Каждый вариант
btn, включаяprimary,ghost,dangerи disabled-состояние, сохраняет видимую обводку в светлой и тёмной теме. - Единый радиус кнопок —
8px. Локальные переопределения.btn-primary,.btn-secondary,.btn-deleteв feature CSS запрещены: новые экраны должны наследовать токены изfrontend/admin/css/components.css. - Primary-акцент общий для продукта:
#4F46E5в светлой теме и#8B5CF6в тёмной. Ролевые цвета кнопок в кабинетах преподавателя и студента не используются.
Переключение — через theme-toggle.js.
Боковая панель (Sidebar)
- Скрытие/раскрытие — кнопка-крестик в правом верхнем углу sidebar
- Десктоп (
>768px): sidebar складывается влево, контент расширяется; состояние сохраняется вlocalStorage(sidebar-collapsed) - Мобильные (
≤768px): sidebar скрывается за кнопкой-гамбургер, выезжает как overlay с затемнением - Dropdown «Настройки» в footer sidebar — содержит ссылку на страницу настроек и кнопку выхода
OpenTelemetry (локальный bundle)
Клиентская телеметрия (document-load, fetch, XHR) отправляется через BatchSpanProcessor на
same-origin путь /otel/v1/traces.
- npm-зависимости зафиксированы exact-версиями в
package-lock.json; scripts/build-vendor.mjsсобирает их через esbuild в/vendor/otel.js;- браузер импортирует только same-origin bundle, CDN-код в runtime отсутствует;
- на
localhostтелеметрия не включается. - прикладные
magistr:ux-eventв production записываются отдельными spans с безопасными скалярными атрибутами; очередь ограничена 100 событиями.
telemetryPromise = import('/vendor/otel.js');
Адаптивность
Интерфейс адаптирован под мобильные устройства:
- Sidebar скрывается на экранах < 768px, выезжает как overlay
- Появляется кнопка-гамбургер (
#menu-toggle) - Кнопка-крестик закрывает sidebar на всех устройствах
- Таблицы получают горизонтальный скролл