feat: завершить миграцию фронтенда на React (этапы 1-5)

Админка, настройки, логин, кабинеты преподавателя и студента
полностью переведены на React 19 (модель strangler, docs/REACT_MIGRATION.md).

- 15 вкладок админки: lazy-загрузка, общий error boundary, сервисный слой
- Удалены legacy-views и переходный слой LegacyTabView (45 файлов)
- Сборка esbuild (frontend/scripts/build-react.mjs) с бюджетами gzip на чанки
- CSP-хэши, кэширование: chunk'и immutable, CSS/HTML no-cache (react-cache.conf)
- Фиксы: lazy-импорт именованных экспортов (краш после авторизации),
  растяжение #admin-root/#settings-root на всю ширину,
  возврат subjects/lessonTypes из справочников (краш просмотра расписаний),
  отказ от заведомо широкого поиска расписания без выбранной цели,
  центрирование карточки логина
- Тесты: 237/237 (npm run check), регрессионные на lazy-вкладки и ширину
- Документация: REACT_MIGRATION.md, REACT_MIGRATION_RESULT.md, FRONTEND.md
This commit is contained in:
dipatrik10
2026-09-28 23:15:04 +03:00
parent c6083b35e0
commit 97bf3cc50a
167 changed files with 31283 additions and 19033 deletions

View File

@@ -4,12 +4,16 @@
| Параметр | Значение |
|----------|----------|
| **Фреймворк** | Нет (Vanilla JavaScript) |
| **Фреймворк** | Переходный режим: React 19 для страницы входа, кабинетов студента и преподавателя, страницы настроек и общего слоя `react/shared`, Vanilla JavaScript для остальных разделов |
| **Модульная система** | ES6 Modules (`import`/`export`) |
| **Стили** | CSS (модульный подход) |
| **Шрифт** | Системный стек без внешних font-CDN |
| **Веб-сервер** | Apache httpd на Alpine со строгим CSP |
Пошаговый план и актуальная точка продолжения находятся в
[`REACT_MIGRATION.md`](REACT_MIGRATION.md). Миграция выполняется по целым страницам и
разделам без изменения действующих URL и REST-контрактов.
---
## Структура файлов
@@ -23,10 +27,90 @@ frontend/
├── schedule-overview.js # Общий расчёт и рендер «Сегодня / Следующая пара»
├── schedule-overview.css # Общие карточки ближайших занятий и поискового выбора группы
├── security.conf # CSP и защитные HTTP-заголовки Apache
├── react-cache.conf # Cache-Control: immutable для chunk'ов, no-cache для entrypoint, CSS и HTML
├── telemetry/
│ └── otel-entry.js # Исходная точка сборки OpenTelemetry
├── scripts/
│ ├── build-react.mjs # Production-сборка React entrypoint и проверка gzip-бюджета
│ ├── build-react-tests.mjs # Сборка компонентных проверок для node:test
│ └── build-vendor.mjs # Сборка `/vendor/otel.js` через esbuild
├── react/
│ ├── login/
│ │ ├── LoginApp.jsx # React-компонент страницы входа
│ │ ├── login-service.js # Валидация и контракт POST /api/auth/login
│ │ └── main.jsx # React entrypoint для `/`
│ ├── student/ # Кабинет студента на React
│ │ ├── StudentApp.jsx # Сессия, выбор группы, неделя и состояния
│ │ ├── WeekGrid.jsx # Недельная сетка дней и занятий
│ │ ├── student-service.js # Чипы, статусы и контракты API кабинета студента
│ │ ├── main.jsx # React entrypoint для `/student/`
│ │ └── testing/ # render-student.jsx для DOM-проверок
│ ├── teacher/ # Кабинет преподавателя на React
│ │ ├── TeacherApp.jsx # Сессия, вкладки, неделя и состояния
│ │ ├── TeacherWeekGrid.jsx # Матрица слотов и мобильный список дней
│ │ ├── AbsencePanel.jsx # Заявки об отсутствии
│ │ ├── PreferencesTab.jsx # Семестровый календарь доступности
│ │ ├── RequestsTab.jsx # Журнал заявок на изменение пар
│ │ ├── RequestDialog.jsx # Диалог переноса/аудитории/отмены
│ │ ├── teacher-service.js # Слоты, матрица недели, метки и контракты API
│ │ ├── main.jsx # React entrypoint для `/teacher/`
│ │ └── testing/ # render-teacher.jsx для DOM-проверок
│ ├── settings/ # Страница настроек на React
│ │ ├── SettingsApp.jsx # Сессия, боковая панель, вкладки и тема
│ │ ├── GeneralTab.jsx # Заглушка общих настроек
│ │ ├── TimeSlotsTab.jsx # Сетки времени и CRUD временных слотов
│ │ ├── EduFormsTab.jsx # Формы обучения
│ │ ├── DatabaseTab.jsx # Статус подключения и тенанты
│ │ ├── settings-service.js # Метки сеток, валидация и контракты API
│ │ ├── main.jsx # React entrypoint для `/admin/settings/`
│ │ └── testing/ # render-settings.jsx для DOM-проверок
│ ├── admin/ # Оболочка основной админ-панели на React
│ │ ├── AdminApp.jsx # Сессия, sidebar с секциями, topbar, hash-router
│ │ ├── react-tabs.js # Реестр всех вкладок админ-панели (lazy + Suspense)
│ │ ├── admin-service.js # Маршруты, секции навигации, склонения, контракты API
│ │ ├── classrooms-service.js # Статусы, оборудование и контракты API аудиторий
│ │ ├── subjects-service.js # Состояние реестра, URL-параметры и контракты API дисциплин
│ │ ├── dashboard-service.js # Метрики, слоты, свободные аудитории и конфликты Red Zone
│ │ ├── users-service.js # Роли, состояние реестра и контракты API пользователей
│ │ ├── groups-service.js # Подгруппы, календарные графики и контракты API групп
│ │ ├── university-structure-service.js # Фильтрация списков и контракты API кафедр/специальностей/профилей
│ │ ├── teacher-requests-service.js # Состояние реестра, валидация одобрения и контракты API заявок
│ │ ├── department-workspace-service.js # Нагрузка, payload-валидация и контракты API кабинета кафедры
│ │ ├── auditorium-workload-service.js # Дедупликация занятий, чётность недель и контракты API загруженности
│ │ ├── academic-calendar-service.js # Учебные годы, сетка графика, диалог ячейки и контракты API календаря
│ │ ├── schedule-view-service.js # Разделы расписания, семестровая матрица и контракты API просмотра
│ │ ├── schedule-service.js # Слоты правил, payload-валидация, визуальная матрица и контракты API конструктора
│ │ ├── schedule-override-service.js # Источник правки, календарь семестра и контракты API разовых правок
│ │ ├── schedule-versions-service.js # Группировка версий, подписи журнала и контракты API контура публикаций
│ │ ├── schedule-quality-service.js # Диапазоны оценки, фильтры проблем и контракт API анализа качества
│ │ ├── teacher-absences-service.js # Решения мастера замены, подписи статусов и контракты API отсутствий
│ │ ├── tabs/ # Вкладки админ-панели (волны 1–6, все вкладки на React)
│ │ │ ├── DashboardTab.jsx # Метрики, мониторинг пар, Red Zone
│ │ │ ├── ClassroomsTab.jsx # Аудиторный фонд с мультиселектом оборудования
│ │ │ ├── SubjectsTab.jsx # Реестр дисциплин с пагинацией и преподавателями
│ │ │ ├── UsersTab.jsx # Реестр пользователей с фильтром роли и архивированием
│ │ │ ├── GroupsTab.jsx # Реестр групп, подгруппы и календарные графики
│ │ │ ├── UniversityStructureTab.jsx # Кафедры, специальности и профили обучения
│ │ │ ├── TeacherRequestsTab.jsx # Реестр заявок с редактируемыми строками и отклонением через PromptDialog
│ │ │ ├── DepartmentWorkspaceTab.jsx # Кабинет кафедры: дисциплины, преподаватели, нагрузка, заявки
│ │ │ ├── AuditoriumWorkloadTab.jsx # Загруженность: сводная матрица и двухнедельная сводка сущности
│ │ │ ├── AcademicCalendarTab.jsx # Календарные графики: годы, сетки, дисциплины (MaskedDateInput)
│ │ │ ├── ScheduleViewTab.jsx # Просмотр расписаний: фильтры, разделы, матрица и панель правок
│ │ │ ├── ScheduleTab.jsx # Конструктор правил: каскад версий, форма правила и визуальная матрица
│ │ │ ├── ScheduleVersionsTab.jsx # Контур публикаций: черновики, diff, архив, журнал и модальные операции
│ │ │ ├── ScheduleQualityTab.jsx # Анализ качества: скорборд, метрики, карта проблем и панель правила
│ │ │ └── TeacherAbsencesTab.jsx # Отсутствия: реестр, мастер замены, пожелания и заявки на изменение
│ │ ├── main.jsx # React entrypoint для `/admin/`
│ │ └── testing/ # render-admin.jsx для DOM-проверок
│ └── shared/ # Общий React-слой для следующих переносимых страниц
│ ├── api-client.js # requestJson/apiClient поверх auth-транспорта
│ ├── auth-client.js # Восстановление сессии и logout с redirect
│ ├── dates.js # Дата-математика (недели, ISO, форматирование)
│ ├── session-policy.js # Фазы loading/ready/unauthorized/error и русские тексты
│ ├── use-authorized-session.js # Хук проверки роли при монтировании страницы
│ ├── runtime-entry.jsx # Единая точка экспорта общего runtime-chunk
│ ├── ui/ # AppErrorBoundary, AsyncState, FormAlert, AsyncCombobox, ScheduleOverview, ConfirmDialog, Pagination
│ └── testing/ # render-shared.jsx для DOM-проверок через renderToStaticMarkup
├── tests/
│ ├── academic-calendar-grid.test.mjs # Неполная первая неделя календарного графика
│ ├── academic-calendar-title.test.mjs # Составное название календарного графика
@@ -34,6 +118,13 @@ frontend/
│ ├── schedule-overview.test.mjs # Текущее занятие, список на сегодня и следующая пара
│ ├── admin-ui-regressions.test.mjs # Заголовки, меню, действия слотов и компоновка вкладок
│ ├── auth-session.test.mjs # Login/refresh/reload/logout и single-flight refresh
│ ├── react-login.test.mjs # Валидация и API-контракт React-формы входа
│ ├── react-shared.test.mjs # Общая политика сессии, API-клиент и DOM-проверки состояний
│ ├── react-student.test.mjs # Дата-математика, чипы, контракты API и DOM-проверки кабинета студента
│ ├── react-teacher.test.mjs # Слоты, матрица недели, метки, payload заявок и DOM-проверки кабинета преподавателя
│ ├── react-settings.test.mjs # Вкладки по ролям, метки сеток, валидация и контракты API страницы настроек
│ ├── react-admin.test.mjs # Навигация по ролям, hash-маршруты, склонения и DOM-проверки оболочки админки
│ ├── react-admin-tabs.test.mjs # Сервисы и DOM-проверки вкладок волн 1–5 (дашборд, аудитории, дисциплины, пользователи, группы, структура вуза, заявки, кабинет кафедры, загруженность, календарные графики, вкладки расписания)
│ ├── dashboard-conflicts.test.mjs # Регрессии дат и состояний проверки конфликтов
│ ├── schedule-overrides.test.mjs # Действия, роли, недельный выбор и подбор времени разовой правки
│ ├── schedule-quality.test.mjs # Метрики качества, фильтры проблем и доступность вкладки
@@ -43,13 +134,12 @@ frontend/
│ ├── 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
│ ├── index.html # Монтирует React-оболочку `/react/admin.js`
│ ├── css/
│ │ ├── main.css # CSS-переменные, цвета, типографика
│ │ ├── layout.css # Раскладка (sidebar, topbar, content)
@@ -61,81 +151,37 @@ frontend/
│ │ ├── 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
│ │ └── views/ # Общие чистые хелперы (используются React-сервисами)
│ │ ├── academic-calendar-grid.js # Расчёт ISO-недели дневной сетки (общий с React-сервисом)
│ │ └── academic-calendar-title.js # Название из кода, профиля, формы и года (общий с React-сервисом)
│ │
│ └── 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 # Базовая, субботняя и ручные сетки времени
│ └── settings/ # ⚙️ Страница настроек (отдельный SPA на React)
│ ├── index.html # Оболочка с корневым элементом settings-root
│ └── css/
│ ├── main.css # CSS-переменные, базовые стили
│ └── layout.css # Sidebar, topbar, content
│
├── teacher/ # 👩‍🏫 Интерфейс преподавателя
│ ├── index.html # Расписание, календарь доступности и журнал заявок
│ ├── app.js # Пожелания, переносы и недельный просмотр через общий auth-session
├── teacher/ # 👩‍🏫 Интерфейс преподавателя (React)
│ ├── index.html # Оболочка с корневым элементом teacher-root
│ └── 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
└── student/ # 🎓 Интерфейс студента (React)
├── index.html # Оболочка с корневым элементом student-root
└── style.css # Стили кабинета без inline-блока
```
@@ -143,24 +189,36 @@ frontend/
## Система маршрутизации (Admin SPA)
Админ-панель работает как **Single Page Application** без фреймворка.
Оболочка админ-панели работает на **React** (`react/admin/AdminApp.jsx`, entrypoint
`/react/admin.js`): sidebar с секциями, topbar с заголовком вкладки и счётчиком заявок,
тема, сворачивание панели, настройки с выходом и hash-router.
Навигация реализована через `data-tab` атрибуты на элементах sidebar:
Навигация реализована через hash-адреса вкладок (`#users`, `#schedule` и т. д.):
```html
<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>
```
- при старте активная вкладка читается из `location.hash` (неразрешённые роли
значения заменяются вкладкой по умолчанию);
- слушатель `hashchange` синхронизирует вкладку с адресной строкой (работают
кнопки «назад/вперёд» и ссылки из других кабинетов);
- переключение идёт через `requestTabSwitch` с guard'ом несохранённых изменений
из общего модуля `admin/js/dirty-state.js`.
При клике на пункт меню `main.js`:
1. Загружает HTML-шаблон из `views/{tab}.html` через `fetch()`
2. Вставляет его в `#app-content`
3. Подключает соответствующий JS-модуль из `js/views/{tab}.js`
4. Обновляет заголовок страницы (`#page-title`)
Содержимое вкладки выбирается по реестру `react/admin/react-tabs.js`:
`main.js` и отдельный settings SPA получают разрешённые вкладки из единого
- **все 15 вкладок админ-панели** (волна 1: `dashboard`, `classrooms`, `subjects`;
волна 2: `users`, `groups`, `university-structure`; волна 3:
`teacher-requests`, `department-workspace`; волна 4: `auditorium-workload`,
`academic-calendar`; волна 5: `schedule-view`, `schedule-versions`,
`schedule-quality`, `teacher-absences`; волна 6: `schedule`)
рендерятся React-компонентами из `react/admin/tabs/`, каждый загружается
отдельным lazy-chunk'ом (`React.lazy` + `Suspense`, fallback — индикатор
`tab-loading`);
- переходный слой `LegacyTabView` + `legacy-views.js` и каталоги
`admin/views/*.html` / `admin/js/views/{tab}.js` удалены после волны 6 —
HTML-фрагменты больше не загружаются через `fetch`, в `admin/js/views/`
остались только общие чистые хелперы календарных графиков.
React-оболочка админки (`react/admin/admin-service.js`) и React-оболочка настроек
(`react/settings/SettingsApp.jsx`) получают разрешённые вкладки из единого
`admin/js/role-capabilities.js`; локальные дубли `ROLE_NAVIGATION`/`ROLE_TABS` удалены.
| Роль | Доступные вкладки |
@@ -172,10 +230,11 @@ frontend/
Пути `/department/` и `/edu-office/` оставлены как входные redirect-страницы в общую панель. Отдельные кабинеты не дублируют UI админ-панели.
Пункты меню, недоступные роли, не отображаются в sidebar: `main.js` выставляет `hidden`, а `layout.css` явно скрывает такие элементы, чтобы базовый `display: flex` у `.nav-item` не возвращал их на экран.
Верхние панели основной админки и settings SPA не содержат неработающей кнопки
бургер-меню. В светлой теме пункты и заголовки sidebar получают контрастные тёмные цвета
для всех ролей, включая `DEPARTMENT`.
Пункты меню, недоступные роли, не попадают в sidebar: React-оболочка фильтрует
секции навигации по матрице ролей до рендера. Верхние панели основной админки и
settings SPA не содержат неработающей кнопки бургер-меню. В светлой теме пункты и
заголовки sidebar получают контрастные тёмные цвета для всех ролей, включая
`DEPARTMENT`.
### Разделы админ-панели
@@ -267,9 +326,9 @@ frontend/
восстанавливают поиск, фильтры, сортировку, номер и размер страницы после reload или
возврата по ссылке. Просмотр расписания сохраняет вид сущности, выбранную сущность, дату,
учебный год, семестр и дополнительные фильтры; конечные кабинеты сохраняют неделю, вкладку и группу.
- `pagination.js` работает с общим ответом `PageResponse`; размер страницы — 25, 50 или
100. `view-state.js` унифицирует загрузку со spinner, пустой результат и ошибку с кнопкой
повторного запроса.
- Общий React-компонент `react/shared/ui/Pagination.jsx` работает с общим ответом
`PageResponse`; размер страницы — 25, 50 или 100. `view-state.js` унифицирует
загрузку со spinner, пустой результат и ошибку с кнопкой повторного запроса.
- `AsyncCombobox` загружает варианты с сервера, сохраняет выбранный ID, поддерживает
клавиатуру и роли `combobox/listbox`. Он используется для групп, преподавателей,
аудиторий и совместимых календарных графиков; предварительная загрузка вариантов не
@@ -288,13 +347,26 @@ frontend/
### Страница настроек (`/admin/settings/`)
Настройки — это **отдельный SPA** со своей боковой панелью и вкладками, не связанными с основной админ-панелью.
Страница переведена на React (`react/settings/`, entrypoint `dist/react/settings.js`) и использует
общий слой `react/shared`: `useAuthorizedSession(SETTINGS_ROLES)`, `apiClient`, `AsyncState`,
`AppErrorBoundary` и общий `ConfirmDialog`/`PromptDialog` (классы `.project-dialog*`,
закрытие по Escape и клику по фону; `PromptDialog` — реплика legacy `promptAction`
с обязательным текстовым полем и ошибкой внутри диалога). Настройки — **отдельный SPA** со своей боковой панелью и вкладками, не связанными с
основной админ-панелью; как и в legacy-версии, hash-навигации нет — активная вкладка живёт
в состоянии React.
- Доступ: через dropdown «Настройки» в footer боковой панели админки для `ADMIN` и `EDUCATION_OFFICE`
- Кнопка «Назад в панель» для возврата в `/admin/`
- Доступ: через dropdown «Настройки» в footer боковой панели админки для `ADMIN` и `EDUCATION_OFFICE`;
список и вкладка по умолчанию фильтруются единой матрицей `admin/js/role-capabilities.js`
- Кнопка «Назад в панель» для возврата в `/admin/`, кнопка выхода через общий `logoutAndRedirect`
- Сворачивание боковой панели сохраняется в `localStorage.sidebar-collapsed`; тема управляется
в React (кнопка в topbar), `theme-toggle.js` на странице больше не подключается
- Текущие вкладки:
- **Общие настройки** — заглушка (только `ADMIN`)
- **Временные слоты** — выбор сетки через выпадающий список, CRUD слотов выбранной сетки, добавление ручных сеток через модальное окно и удаление выбранной ручной сетки. Поле длительности доступно только для чтения и пересчитывается при изменении времени начала или окончания; в API отправляются только границы, а окончательное значение вычисляет backend. Базовая сетка применяется по умолчанию, субботняя — автоматически по субботам; ручное применение выполняется в сетке календарного графика.
- **Формы обучения** — создание и удаление форм обучения
- **База данных** — статус текущего подключения, реестр тенантов, проверка и добавление нового
подключения (пароль помечен `autocomplete="new-password"`); inline-стили legacy заменены
классами `.db-status-grid`/`.form-group-wide`/`.form-row-actions`
---
@@ -339,11 +411,15 @@ SHA-256 style-хэшей с CSP. Там же закреплены безопас
Команды выполняются из каталога `frontend/`:
```bash
npm run build # собрать локальный dist/vendor/otel.js
npm run build # собрать dist/vendor/otel.js и dist/react/login.js
npm test # frontend unit/static tests
npm run check # синтаксис auth/UI-модулей + все frontend-тесты
npm run check # React-сборка, синтаксис auth/UI-модулей + все frontend-тесты
```
React-сборка выполняется существующим esbuild без отдельного dev-сервера. Для страницы
входа установлен лимит 75 000 байт gzip; превышение завершает сборку ошибкой. При появлении
нескольких React entrypoint общий runtime будет вынесен в отдельный кэшируемый chunk.
---
## Аутентификация (Frontend)
@@ -351,7 +427,7 @@ npm run check # синтаксис auth/UI-модулей + все frontend-т
### Страница входа (`/index.html`)
1. Пользователь вводит логин/пароль
2. `script.js` отправляет `POST /api/auth/login`
2. `react/login/LoginApp.jsx` через `login-service.js` отправляет `POST /api/auth/login`
3. При успехе кладёт access JWT и профиль пользователя только в память текущей страницы.
4. Refresh-токен сохраняется браузером как `HttpOnly` cookie и недоступен JavaScript.
5. После перехода новый документ восстанавливает память через `POST /api/auth/refresh`.
@@ -409,25 +485,31 @@ if (!session || !AUTHORIZED_ROLES.includes(session.role)) {
### Преподаватель (`/teacher/`)
Страница объединяет недельную сетку занятий, семестровый календарь доступности и журнал
заявок на изменение пар. ID преподавателя берётся из восстановленного в памяти профиля
сессии.
Страница переведена на React (`react/teacher/`, entrypoint `dist/react/teacher.js`) и использует
общий слой `react/shared`: `useAuthorizedSession(['TEACHER'])`, `apiClient`, `AsyncState`,
`AppErrorBoundary`, дата-математику `dates.js` и общий `ScheduleOverview` с ролью `teacher`.
ID преподавателя берётся из восстановленного в памяти профиля сессии.
Кабинет объединяет недельную сетку занятий, семестровый календарь доступности и журнал
заявок на изменение пар в трёх вкладках; активная вкладка и дата недели сохраняются
в query-параметрах `teacherTab`/`teacherDate` через общий `url-state.js`.
Основные элементы:
- общий блок «Сегодня» и карточка «Следующая пара»; отдельный запрос покрывает ближайшие
14 дней, поэтому следующая пара находится и за границей текущей недели;
- навигация по неделям: предыдущая, текущая, следующая;
- выбор даты через `input[type="date"]`;
- навигация по неделям: предыдущая, текущая, следующая; выбор даты через `input[type="date"]`;
- запрос `GET /api/schedule?teacherId={userId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}`;
- матрица «слоты × дни недели» на десктопе и дневной список на мобильных (переключение
через `matchMedia` с живым откликом на смену ширины);
- отображение дисциплины, времени, типа занятия, лабораторных подгрупп, аудитории и всех групп правила;
- вкладка «Мои пожелания» с выбором семестра, режимом отметки строгих, предпочтительных и
нежелательных интервалов, полностью недоступных дат, пар подряд и расписания без окон;
- цветовые статусы `PENDING`, `APPROVED`, `REJECTED`, `CANCELLED`; ожидающее пожелание можно
отозвать кликом по отмеченной ячейке или кнопкой в списке дат;
- кнопка «Заявка на изменение» в карточке фактического занятия и модальное окно переноса,
- кнопка «Запросить изменение» в карточке занятия из правила и модальное окно переноса,
смены аудитории или отмены. Недоступные кандидаты отфильтрованы после проверки backend;
- вкладка журнала заявок с причиной, решением, ссылкой на применённый override и историей
статусов; ожидающую заявку можно отозвать;
- вкладка журнала заявок с причиной, решением и историей статусов; ожидающую заявку можно
отозвать; счётчик ожидающих заявок выводится прямо на вкладке навигации;
- форма собственной заявки на отсутствие через `POST /api/teacher-absences`;
- список статусов заявок и отмена ещё не подтверждённой записи через
`DELETE /api/teacher-absences/{id}`.
@@ -436,6 +518,11 @@ if (!session || !AUTHORIZED_ROLES.includes(session.role)) {
### Студент (`/student/`)
Страница переведена на React (`react/student/`, entrypoint `dist/react/student.js`) и использует
общий слой `react/shared`: `useAuthorizedSession(['STUDENT'])`, `apiClient`, `AsyncState`,
`AppErrorBoundary` и React-версия поискового списка `AsyncCombobox`. Модель обзора «Сегодня /
Следующая пара» переиспользует чистые функции `schedule-overview.js`.
В текущей модели студент не связан с конкретной группой, поэтому страница использует
асинхронный поиск группы через `/api/groups/options`.
@@ -443,9 +530,10 @@ if (!session || !AUTHORIZED_ROLES.includes(session.role)) {
- поисковый combobox группы с подсказками по специальности, профилю и курсу; выбор хранится
в `localStorage.studentGroupId` и `studentGroup` query string;
- общий блок «Сегодня» и карточка «Следующая пара», рассчитанные по ближайшим 14 дням;
- недельная сетка по дням;
- недельная сетка по дням с навигацией «Предыдущая/Следующая неделя», «Сегодня» и выбором даты;
- запрос `GET /api/schedule?groupId={groupId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}`;
- отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторных подгрупп.
- отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторных подгрупп;
- URL-параметры `studentGroup` и `studentDate` сохраняются через общий `url-state.js`.
---