31 KiB
🎨 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/ # Модули представлений
│ │ ├── users.js # Управление пользователями
│ │ ├── groups.js # Управление группами
│ │ ├── classrooms.js # Управление аудиториями
│ │ ├── subjects.js # Управление дисциплинами
│ │ ├── equipments.js # Управление оборудованием
│ │ ├── edu-forms.js # Формы обучения
│ │ ├── profiles.js # Профили обучения специальностей
│ │ ├── department-workspace.js # Кабинет кафедры в общей панели
│ │ ├── schedule-view.js # Read-only просмотр расписаний в матрицах
│ │ ├── schedule.js # Конструктор правил расписания
│ │ ├── academic-calendar.js # Календарные учебные графики
│ │ ├── auditorium-workload.js # Загруженность аудиторий, преподавателей и кафедр
│ │ ├── database.js # Управление тенантами
│ │ └── departments-data.js # Создание кафедры/специальности
│ ├── views/ # HTML-шаблоны представлений
│ │ ├── users.html
│ │ ├── groups.html
│ │ ├── classrooms.html
│ │ ├── subjects.html
│ │ ├── equipments.html
│ │ ├── edu-forms.html
│ │ ├── profiles.html
│ │ ├── department-workspace.html
│ │ ├── schedule-view.html
│ │ ├── schedule.html
│ │ ├── academic-calendar.html
│ │ ├── auditorium-workload.html
│ │ ├── database.html
│ │ └── departments-data.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:
<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 также фильтрует вкладки по роли:
| Роль | Доступные вкладки |
|---|---|
ADMIN |
Все вкладки |
EDUCATION_OFFICE |
Просмотр расписаний, конструктор расписания, календарный график, загруженность, аудитории, оборудование |
DEPARTMENT |
Кабинет кафедры, просмотр расписаний |
SCHEDULE_VIEWER |
Только просмотр расписаний |
Пути /department/ и /edu-office/ оставлены как входные redirect-страницы в общую панель. Отдельные кабинеты не дублируют UI админ-панели.
Пункты меню, недоступные роли, не отображаются в sidebar: main.js выставляет hidden, а layout.css явно скрывает такие элементы, чтобы базовый display: flex у .nav-item не возвращал их на экран.
Разделы админ-панели
| Tab | Описание | API |
|---|---|---|
users |
CRUD пользователей | /api/users |
groups |
CRUD групп, подгруппы лабораторных и назначения графиков | /api/groups, /api/subgroups |
edu-forms |
Формы обучения | /api/education-forms |
profiles |
Создание, редактирование и удаление профилей обучения специальностей | /api/specialties/{id}/profiles |
equipments |
Оборудование | /api/equipments |
classrooms |
Аудитории | /api/classrooms |
subjects |
Дисциплины | /api/subjects |
department-workspace |
Кабинет кафедры: дисциплины, импорт, комментарии, преподаватели и нагрузка | /api/department/*, /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/calendar/activity-types, /api/education-forms |
auditorium-workload |
Динамическая загруженность аудиторий, преподавателей и кафедр: сводная матрица по дате или совмещённая таблица выбранной сущности по чётной/нечётной неделе | /api/classrooms, /api/users/teachers, /api/departments, /api/admin/time-slots, /api/equipments, /api/groups, /api/schedule, /api/admin/calendar/years |
database |
Тенанты | /api/database |
departments-data |
Создание, редактирование и удаление кафедр/специальностей | /api/departments, /api/specialties |
Особенности админских вкладок
- Вкладка
groupsзагружает кафедры, специальности, профили, учебные годы и календарные графики. Список групп открывается через/api/groups?includeArchived=true, поэтому в таблице видны активные, будущие, завершившие обучение и архивные группы со статусом. Группа создаётся через/api/groupsсspecialtyIdиspecialtyProfileId, блок подгрупп использует/api/subgroupsи/api/groups/{id}/subgroups, а блок назначений использует/api/groups/{id}/calendar-assignments. В селекты подгрупп и назначений попадают только группы сactive=true. - Вкладка
schedule-viewпоказывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; frontend запрашивает двухнедельный диапазон от понедельника выбранной даты и собирает найденные расписания в переключатель результатов. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания. Для режима кафедры и ролиDEPARTMENTрасписание ограничивается кафедрой пользователя; преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. На мобильной ширине вместо широкой недельной матрицы показывается один день активного расписания с переключателем дней. - Вкладка
auditorium-workloadстала общей вкладкойЗагруженность: в поле «Что смотреть» выбираются аудитории, преподаватели или кафедры. Сводная матрица по выбранной дате использует одинаковую структуру: строки — выбранный тип сущности, столбцы — эффективные временные слоты дня из/api/admin/time-slots/effective, занятость собирается из динамического расписания/api/scheduleпо группам. Кафедральная матрица группирует занятия по кафедре преподавателя. Для аудиторий доступны фильтры корпуса, вместимости и оборудования. В поле «Отображение» можно выбрать конкретную аудиторию, преподавателя или кафедру; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной сущности растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из/api/admin/calendar/years. - Вкладка
profilesвыделена под профили обучения: администратор выбирает специальность, создаёт профиль, редактирует описание и удаляет неиспользуемые профили. - Вкладка
scheduleне обращается к старымlessonsAPI. Создание и редактирование расписания выполняется через правила/api/admin/schedule-rules, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пунктВся группа; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. - Вкладка
academic-calendarполностью отделяет календарную систему от расписания занятий: администратор создаёт учебные годы и семестры, заводит календарные графики, выбирает форму обучения из общего справочника/api/education-forms, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через/api/admin/academic-calendars/{id}/grid. - Редактор годового графика во вкладке
academic-calendarпоказывает компактную табличную сетку: курсы раскрываются отдельными секциями со стрелкой, семестры внутри курса на широком экране идут рядом в две колонки одинаковой высоты, столбцы подписаны номерами недель учебного года, строки — днями недели, а ячейки содержат буквенный код активности. Подробная расшифровка и изменение кода открываются в компактном модальном окне по клику на ячейку; при наведении отображается кастомная подсказка с датой, курсом, кодом активности и временной сеткой. Ячейки можно выделять протяжкой мышью и менять код активности через ту же модалку, выбранные ячейки подсвечиваются мягкой заливкой, а кнопка «Применить» закрывает окно. - Вкладка
departments-dataиспользует модальные формы редактирования и маршрутыPUT/DELETE /api/departments/{id}иPUT/DELETE /api/specialties/{id}. - Вкладка
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 перед каждым запросом:
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 очищается и пользователь возвращается на страницу входа.
Аутентификация (Frontend)
Страница входа (/index.html)
- Пользователь вводит логин/пароль
script.jsотправляетPOST /api/auth/login- При успехе сохраняет в
localStorage:token— access JWTrole— роль пользователяdepartmentId— кафедра пользователяuserId— ID пользователя для личного расписания преподавателя
- Refresh-токен сохраняется браузером как
HttpOnlycookie и недоступен JavaScript - Перенаправляет на соответствующий интерфейс:
ADMIN→/admin/EDUCATION_OFFICE→/admin/#schedule-viewDEPARTMENT→/admin/#department-workspaceSCHEDULE_VIEWER→/admin/#schedule-viewTEACHER→/teacher/STUDENT→/student/
Проверка авторизации
На каждой странице проверяется наличие токена и роли:
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-архитектура
Модульный подход
Стили разделены на модульные файлы (порядок подключения важен):
main.css— CSS-переменные (цвета, шрифты, отступы), глобальные стили, тёмная темаlayout.css— Sidebar, topbar, content area, dropdown настроек, responsivecomponents.css— Кнопки, таблицы, карточки, badge, формы, theme-togglemodals.css— Модальные окнаdepartments-data.css— Стили создания кафедры/специальности
Темизация
CSS-переменные позволяют поддерживать светлую/тёмную тему:
:root {
--bg-primary: #ffffff;
--text-primary: #1a1a2e;
--accent: #6366f1;
}
[data-theme="dark"] {
--bg-primary: #0f0f23;
--text-primary: #e2e8f0;
--accent: #818cf8;
}
Переключение — через 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
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 на всех устройствах
- Таблицы получают горизонтальный скролл