374 lines
40 KiB
Markdown
374 lines
40 KiB
Markdown
# 🎨 Frontend
|
||
|
||
## Общая информация
|
||
|
||
| Параметр | Значение |
|
||
|----------|----------|
|
||
| **Фреймворк** | Нет (Vanilla JavaScript) |
|
||
| **Модульная система** | ES6 Modules (`import`/`export`) |
|
||
| **Стили** | CSS (модульный подход) |
|
||
| **Шрифт** | Inter на странице входа, системный шрифт в кабинетах расписания |
|
||
| **Веб-сервер** | Apache httpd:alpine |
|
||
|
||
---
|
||
|
||
## Структура файлов
|
||
|
||
```
|
||
frontend/
|
||
├── index.html # 🔐 Страница авторизации (общая)
|
||
├── script.js # Логика авторизации
|
||
├── style.css # Стили страницы авторизации
|
||
├── theme-toggle.js # Переключение светлой/тёмной темы
|
||
├── Dockerfile # httpd:alpine
|
||
│
|
||
├── admin/ # 👨💼 Интерфейс администратора
|
||
│ ├── index.html # SPA-оболочка с sidebar
|
||
│ ├── css/
|
||
│ │ ├── main.css # CSS-переменные, цвета, типографика
|
||
│ │ ├── layout.css # Раскладка (sidebar, topbar, content)
|
||
│ │ ├── components.css # Кнопки, таблицы, карточки, формы
|
||
│ │ ├── modals.css # Модальные окна
|
||
│ │ ├── auditorium-workload.css # Таблицы расписаний и загруженности
|
||
│ │ └── departments-data.css # Стили создания кафедры/специальности
|
||
│ ├── js/
|
||
│ │ ├── main.js # Инициализация, маршрутизация, навигация
|
||
│ │ ├── api.js # HTTP-обёртка (fetch + Authorization)
|
||
│ │ ├── utils.js # Утилиты
|
||
│ │ ├── otel.js # OpenTelemetry (клиентская телеметрия, только прод)
|
||
│ │ └── views/ # Модули представлений
|
||
│ │ ├── dashboard.js # Дашборд
|
||
│ │ ├── users.js # Управление пользователями
|
||
│ │ ├── teacher-requests.js # Заявки кафедр на преподавателей
|
||
│ │ ├── groups.js # Управление группами
|
||
│ │ ├── classrooms.js # Управление аудиториями
|
||
│ │ ├── subjects.js # Управление дисциплинами
|
||
│ │ ├── university-structure.js # Кафедры, специальности и профили обучения
|
||
│ │ ├── department-workspace.js # Кабинет кафедры в общей панели
|
||
│ │ ├── schedule-view.js # Read-only просмотр расписаний в матрицах
|
||
│ │ ├── schedule.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
|
||
│ │ ├── 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 # Недельный просмотр динамического расписания преподавателя
|
||
├── department/ # 🏛 Кабинет кафедры
|
||
│ └── index.html # Redirect в `/admin/#department-workspace`
|
||
├── edu-office/ # 🗓 Кабинет учебного отдела
|
||
│ └── index.html # Redirect в `/admin/#schedule-view`
|
||
│
|
||
└── student/ # 🎓 Интерфейс студента
|
||
└── index.html # Недельный просмотр динамического расписания группы
|
||
```
|
||
|
||
---
|
||
|
||
## Система маршрутизации (Admin SPA)
|
||
|
||
Админ-панель работает как **Single Page Application** без фреймворка.
|
||
|
||
Навигация реализована через `data-tab` атрибуты на элементах sidebar:
|
||
|
||
```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>
|
||
```
|
||
|
||
При клике на пункт меню `main.js`:
|
||
1. Загружает HTML-шаблон из `views/{tab}.html` через `fetch()`
|
||
2. Вставляет его в `#app-content`
|
||
3. Подключает соответствующий JS-модуль из `js/views/{tab}.js`
|
||
4. Обновляет заголовок страницы (`#page-title`)
|
||
|
||
`main.js` также фильтрует вкладки по роли:
|
||
|
||
| Роль | Доступные вкладки |
|
||
|------|-------------------|
|
||
| `ADMIN` | Все вкладки |
|
||
| `EDUCATION_OFFICE` | Просмотр расписаний, конструктор расписания, календарный график, загруженность, аудитории, оборудование |
|
||
| `DEPARTMENT` | Кабинет кафедры, просмотр расписаний |
|
||
| `SCHEDULE_VIEWER` | Только просмотр расписаний |
|
||
|
||
Пути `/department/` и `/edu-office/` оставлены как входные redirect-страницы в общую панель. Отдельные кабинеты не дублируют UI админ-панели.
|
||
|
||
Пункты меню, недоступные роли, не отображаются в sidebar: `main.js` выставляет `hidden`, а `layout.css` явно скрывает такие элементы, чтобы базовый `display: flex` у `.nav-item` не возвращал их на экран.
|
||
|
||
### Разделы админ-панели
|
||
|
||
| Tab | Описание | API |
|
||
|-----|----------|-----|
|
||
| `teacher-requests` | Очередь заявок кафедр на создание преподавателей с редактированием перед одобрением | `/api/teacher-requests`, `/api/departments` |
|
||
| `users` | CRUD пользователей | `/api/users` |
|
||
| `groups` | CRUD групп, мультифильтр списка по формам обучения, настройка 0/2/3 подгрупп для лабораторных и назначения графиков | `/api/groups`, `/api/subgroups` |
|
||
| `classrooms` | Аудитории | `/api/classrooms` |
|
||
| `subjects` | Дисциплины | `/api/subjects` |
|
||
| `university-structure` | Кафедры, специальности и профили обучения; профили доступны отдельной внутренней вкладкой и через кнопку специальности | `/api/departments`, `/api/specialties`, `/api/specialties/{id}/profiles` |
|
||
| `department-workspace` | Кабинет кафедры: дисциплины, импорт, комментарии, преподаватели, привязка преподавателей, заявки на новых преподавателей и нагрузка | `/api/department/*`, `/api/department/teacher-requests`, `/api/workload/teachers` |
|
||
| `schedule-view` | Read-only просмотр расписаний: по одной выбранной дате строится двухнедельный диапазон, найденные расписания выбираются в переключателе, а на экране отображается одна активная совмещённая таблица чётной/нечётной недели | `/api/schedule/search` |
|
||
| `schedule` | Конструктор правил динамического расписания с выезжающей визуальной матрицей групп по дням и времени | `/api/admin/schedule-rules`, `/api/admin/time-slots`, `/api/admin/calendar/years`, `/api/lesson-types`, `/api/subgroups` |
|
||
| `academic-calendar` | Учебные годы, семестры, создание календарных графиков, Excel-подобный редактор дневной сетки и привязка дисциплин к семестрам графика | `/api/admin/calendar`, `/api/admin/academic-calendars`, `/api/admin/academic-calendars/{id}/subjects`, `/api/admin/calendar/activity-types`, `/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` |
|
||
|
||
### Особенности админских вкладок
|
||
|
||
- Вкладка `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 символов, затем одобрить заявку через `/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` и видеть статусы собственных заявок в таблице.
|
||
- Компоновка `department-workspace` использует собственные CSS-сетки `department-workspace-filter-grid` и `department-workspace-actions-grid`: фильтры периода отделены от сеток расписания, загрузка дисциплин занимает широкую колонку, формы преподавателей выравниваются справа, а списки и таблицы идут полноширинными блоками ниже.
|
||
- Вкладка `schedule-view` показывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; frontend запрашивает двухнедельный диапазон от понедельника выбранной даты и собирает найденные расписания в переключатель результатов. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания; чипы результатов переносятся и отделены от счётчика стабильным отступом. Для режима кафедры и роли `DEPARTMENT` расписание ограничивается кафедрой пользователя; преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. Бейдж диапазона недель скрывается для занятий на весь семестр, а для занятий до конца семестра показывает только неделю начала в формате `(с 5 нед.)`. На мобильной ширине вместо широкой недельной матрицы показывается один день активного расписания с переключателем дней.
|
||
- Вкладка `auditorium-workload` стала общей вкладкой `Загруженность`: в поле «Что смотреть» выбираются аудитории, преподаватели или кафедры. Сводная матрица по выбранной дате использует одинаковую структуру: строки — выбранный тип сущности, столбцы — эффективные временные слоты дня из `/api/admin/time-slots/effective`, занятость собирается из динамического расписания `/api/schedule` по группам. Кафедральная матрица группирует занятия по кафедре преподавателя. Для аудиторий доступны фильтры корпуса, вместимости и оборудования. В поле «Отображение» можно выбрать конкретную аудиторию, преподавателя или кафедру; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной сущности растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из `/api/admin/calendar/years`.
|
||
- Вкладка `university-structure` содержит внутренние разделы `Кафедры`, `Специальности` и `Профили`, оформленные тем же визуальным паттерном вкладок, что и `academic-calendar`: администратор создаёт профили как из общего списка, так и через кнопку `Профили` у конкретной специальности.
|
||
- Вкладка `schedule` не обращается к старым `lessons` API. Создание и редактирование расписания выполняется через правила `/api/admin/schedule-rules`, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пункт `Вся группа`; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Типы занятий в слоте сортируются в порядке: лекция, лабораторная работа, практика. Список слотов отображается без внутреннего вертикального скролла: при добавлении строк форма расширяется вниз, а кнопка сохранения остаётся отдельным блоком под слотами. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: пользователь выбирает учебный год, семестр и группы, после чего матрица строится только по правилам выбранного семестра. Столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Период недель не показывается для занятия на весь семестр; если занятие идёт до конца семестра не с первой недели, выводится только неделя начала в формате `(с 5 нед.)`, а ограниченный диапазон — как `(с 1 по 3 нед.)`. Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. Кнопка с тремя точками в правой части карточки пары открывает контекстное меню: можно открыть полное правило в форме, изменить только день и базовую пару выбранного слота через компактную модалку или удалить правило целиком. В списке правил действия отображаются едиными кнопками одинакового размера с отступами между ними.
|
||
- Вкладка `academic-calendar` полностью отделяет календарную систему от расписания занятий и внутри себя разделена на три вкладки: `Графики` для учебных годов, семестров и карточек календарных графиков, `Сетки` для редактора дневной сетки, `Дисциплины` для ручной привязки дисциплин из `/api/subjects` к номерам учебных семестров графика. Администратор выбирает форму обучения из общего справочника `/api/education-forms`, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через `/api/admin/academic-calendars/{id}/grid`.
|
||
- При сохранении правила во вкладке `schedule` frontend различает `409 Conflict` от остальных ошибок API. Если backend возвращает `conflictRule`, открывается широкое модальное окно разрешения конфликта: пользователь видит новое и ранее созданное правило, чипы причины конфликта из `conflictReasons` и подсветку соответствующих полей (`teacher`, `classroom`, `group`). Пользователь меняет у конфликтующего правила день, чётность, пару, преподавателя или аудиторию, после чего frontend сохраняет конфликтующее правило через `PUT /api/admin/schedule-rules/{id}` и автоматически повторяет исходный `POST` или `PUT`. Если после переноса появляется следующий конфликт, показывается следующее конфликтующее правило без потери исходного черновика.
|
||
- Редактор годового графика во вкладке `academic-calendar` показывает компактную табличную сетку: курсы раскрываются отдельными секциями со стрелкой, семестры внутри курса на широком экране идут рядом в две равные колонки одинаковой высоты, столбцы подписаны номерами недель учебного года, строки — днями недели, а ячейки содержат буквенный код активности. В рамках одного курса семестровые таблицы получают одинаковое число недельных колонок: недостающие колонки заполняются пустыми ячейками, поэтому левая и правая части занимают всю ширину секции курса без внешней пустоты. Подробная расшифровка и изменение кода открываются в компактном модальном окне по клику на ячейку; при наведении отображается кастомная подсказка с датой, курсом, кодом активности и временной сеткой, а код активности в подсказке центрируется внутри квадратного индикатора. Ячейки можно выделять протяжкой мышью и менять код активности через ту же модалку, выбранные ячейки подсвечиваются мягкой заливкой, а кнопка «Применить» закрывает окно.
|
||
- Вкладка `classrooms` теперь показывает архивные аудитории через `includeArchived=true`. Кнопка удаления заменена на архивирование: аудитория выводится из эксплуатации, но остаётся в историческом расписании. Для архивных аудиторий доступно восстановление.
|
||
- Вкладка `users` поддерживает роли `EDUCATION_OFFICE`, `DEPARTMENT` и `SCHEDULE_VIEWER`; удаление пользователя работает как архивирование.
|
||
|
||
### Страница настроек (`/admin/settings/`)
|
||
|
||
Настройки — это **отдельный SPA** со своей боковой панелью и вкладками, не связанными с основной админ-панелью.
|
||
|
||
- Доступ: через dropdown «Настройки» в footer боковой панели админки для `ADMIN` и `EDUCATION_OFFICE`
|
||
- Кнопка «Назад в панель» для возврата в `/admin/`
|
||
- Текущие вкладки:
|
||
- **Общие настройки** — заглушка (только `ADMIN`)
|
||
- **Временные слоты** — выбор сетки через выпадающий список, CRUD слотов выбранной сетки, добавление ручных сеток через модальное окно и удаление выбранной ручной сетки. Базовая сетка применяется по умолчанию, субботняя — автоматически по субботам; ручное применение выполняется в сетке календарного графика.
|
||
|
||
---
|
||
|
||
## API-клиент (`api.js`)
|
||
|
||
Все HTTP-запросы проходят через обёртку `apiFetch()`. Access JWT читается из `localStorage` перед каждым запросом:
|
||
|
||
```javascript
|
||
export async function apiFetch(endpoint, method = 'GET', body = null, retryOnUnauthorized = true) {
|
||
const response = await fetch(endpoint, {
|
||
method,
|
||
headers: getHeaders(body ? 'application/json' : null),
|
||
credentials: 'same-origin',
|
||
body: body ? JSON.stringify(body) : undefined
|
||
});
|
||
|
||
if (response.status === 401 && retryOnUnauthorized) {
|
||
const refreshed = await refreshAccessToken();
|
||
if (refreshed) return apiFetch(endpoint, method, body, false);
|
||
clearAuthState();
|
||
window.location.href = '/';
|
||
}
|
||
|
||
if (!response.ok) {
|
||
throw new Error(data?.message || `Ошибка HTTP: ${response.status}`);
|
||
}
|
||
|
||
return await response.json();
|
||
}
|
||
|
||
// Shortcut-методы
|
||
export const api = {
|
||
get: (url) => apiFetch(url, 'GET'),
|
||
post: (url, body) => apiFetch(url, 'POST', body),
|
||
put: (url, body) => apiFetch(url, 'PUT', body),
|
||
delete: (url, body) => apiFetch(url, 'DELETE', body)
|
||
};
|
||
```
|
||
|
||
При `401` клиент один раз вызывает `POST /api/auth/refresh`, обновляет `localStorage.token` и повторяет исходный запрос. Если refresh неуспешен, auth state очищается и пользователь возвращается на страницу входа.
|
||
|
||
После мутаций `api.js` инвалидирует кэш не только по точному URL, но и по связанным префиксам. Для заявок и кафедральных привязок очищаются `/api/users`, `/api/users/teachers`, `/api/teacher-requests`, `/api/department/teacher-requests` и `/api/department/teachers`, чтобы таблицы заявок и списки преподавателей обновлялись без ручного сброса страницы.
|
||
|
||
---
|
||
|
||
## Аутентификация (Frontend)
|
||
|
||
### Страница входа (`/index.html`)
|
||
|
||
1. Пользователь вводит логин/пароль
|
||
2. `script.js` отправляет `POST /api/auth/login`
|
||
3. При успехе сохраняет в `localStorage`:
|
||
- `token` — access JWT
|
||
- `role` — роль пользователя
|
||
- `departmentId` — кафедра пользователя
|
||
- `userId` — ID пользователя для личного расписания преподавателя
|
||
4. Refresh-токен сохраняется браузером как `HttpOnly` cookie и недоступен JavaScript
|
||
5. Перенаправляет на соответствующий интерфейс:
|
||
- `ADMIN` → `/admin/`
|
||
- `EDUCATION_OFFICE` → `/admin/#schedule-view`
|
||
- `DEPARTMENT` → `/admin/#department-workspace`
|
||
- `SCHEDULE_VIEWER` → `/admin/#schedule-view`
|
||
- `TEACHER` → `/teacher/`
|
||
- `STUDENT` → `/student/`
|
||
|
||
### Проверка авторизации
|
||
|
||
На каждой странице проверяется наличие токена и роли:
|
||
|
||
```javascript
|
||
export function isAuthenticatedAsAdmin() {
|
||
const role = localStorage.getItem('role');
|
||
return getToken() && role === 'ADMIN';
|
||
}
|
||
```
|
||
|
||
### Выход
|
||
|
||
Кнопка «Выйти» вызывает `POST /api/auth/logout`, затем очищает `localStorage` и перенаправляет на `/`.
|
||
|
||
---
|
||
|
||
## Кабинеты расписания
|
||
|
||
### Учебный отдел (`/admin/#schedule-view`)
|
||
|
||
Учебный отдел работает в общей админ-панели с ограниченным набором вкладок:
|
||
|
||
- расширенный поиск `/api/schedule/search` по дате в периоде, преподавателю, аудитории и кафедре;
|
||
- конструктор расписания через `/api/admin/schedule-rules`;
|
||
- создание точечного изменения пары через `/api/edu-office/schedule/overrides` на уровне API;
|
||
- матричный просмотр загруженности аудиторий, преподавателей и кафедр через общую вкладку `Загруженность`;
|
||
- аудитории, оборудование, календарный график и загруженность.
|
||
|
||
### Кафедра (`/admin/#department-workspace`)
|
||
|
||
Кабинет кафедры встроен в общую панель и работает в контексте `departmentId` текущего пользователя:
|
||
|
||
- список дисциплин кафедры через `/api/department/subjects`;
|
||
- загрузка дисциплин из текстового списка в формате `код; название`;
|
||
- комментарии к дисциплинам;
|
||
- просмотр расписания кафедры через вкладку `schedule-view`;
|
||
- загруженность преподавателей кафедры.
|
||
|
||
### Просмотр расписаний (`/admin/#schedule-view`)
|
||
|
||
Роль `SCHEDULE_VIEWER` видит только read-only вкладку просмотра расписаний. Доступны фильтры по дате в периоде, группе, преподавателю, аудитории, кафедре, дисциплине, типу занятия, чётности и разрезу таблиц. Найденные занятия отображаются отдельными совмещёнными таблицами чётной/нечётной недели с парами по строкам и днями недели по столбцам.
|
||
|
||
### Преподаватель (`/teacher/`)
|
||
|
||
Страница показывает недельную сетку занятий преподавателя. ID преподавателя берётся из `localStorage.userId`, который сохраняется после `POST /api/auth/login`.
|
||
|
||
Основные элементы:
|
||
- навигация по неделям: предыдущая, текущая, следующая;
|
||
- выбор даты через `input[type="date"]`;
|
||
- запрос `GET /api/schedule?teacherId={userId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}`;
|
||
- отображение дисциплины, времени, типа занятия, лабораторных подгрупп, аудитории и всех групп правила.
|
||
|
||
Если пользователь вошёл до появления поля `userId`, страница попросит выполнить вход заново.
|
||
|
||
### Студент (`/student/`)
|
||
|
||
В текущей модели студент не связан с конкретной группой, поэтому страница использует выбор группы из `/api/groups`.
|
||
|
||
Основные элементы:
|
||
- селект группы с сохранением выбора в `localStorage.studentGroupId`;
|
||
- недельная сетка по дням;
|
||
- запрос `GET /api/schedule?groupId={groupId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}`;
|
||
- отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторных подгрупп.
|
||
|
||
---
|
||
|
||
## CSS-архитектура
|
||
|
||
### Модульный подход
|
||
|
||
Стили разделены на модульные файлы (порядок подключения важен):
|
||
|
||
1. **`main.css`** — CSS-переменные (цвета, шрифты, отступы), глобальные стили, тёмная тема
|
||
2. **`layout.css`** — Sidebar, topbar, content area, dropdown настроек, responsive
|
||
3. **`components.css`** — Кнопки, таблицы, карточки, badge, формы, theme-toggle
|
||
4. **`modals.css`** — Модальные окна
|
||
5. **`departments-data.css`** — Стили создания кафедры/специальности
|
||
|
||
### Темизация
|
||
|
||
CSS-переменные позволяют поддерживать светлую/тёмную тему:
|
||
|
||
```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` для архивирования, выхода и других рискованных действий без немедленного удаления.
|
||
- Единый радиус кнопок — `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 (`otel.js`)
|
||
|
||
Клиентская телеметрия (document-load, fetch, XHR) отправляется через `BatchSpanProcessor` на `/otel/v1/traces`.
|
||
|
||
- **На production** — загружается автоматически через динамический `import()`
|
||
- **На localhost** — пропускается, чтобы избежать таймаутов CDN `esm.sh`
|
||
|
||
```javascript
|
||
if (!['localhost', '127.0.0.1'].includes(window.location.hostname)) {
|
||
import('./otel.js').catch(e => console.warn('OTel init skipped:', e.message));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Адаптивность
|
||
|
||
Интерфейс адаптирован под мобильные устройства:
|
||
- Sidebar скрывается на экранах < 768px, выезжает как overlay
|
||
- Появляется кнопка-гамбургер (`#menu-toggle`)
|
||
- Кнопка-крестик закрывает sidebar на всех устройствах
|
||
- Таблицы получают горизонтальный скролл
|