Files
magistr/docs/FRONTEND.md

30 KiB
Raw Blame History

🎨 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:

  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
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 с specialtyId и specialtyProfileId, блок подгрупп использует /api/subgroups и /api/groups/{id}/subgroups, а блок назначений использует /api/groups/{id}/calendar-assignments.
  • Вкладка schedule-view показывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; frontend запрашивает двухнедельный диапазон от понедельника выбранной даты и собирает найденные расписания в переключатель результатов. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания. Для режима кафедры и роли DEPARTMENT расписание ограничивается кафедрой пользователя; преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. На мобильной ширине вместо широкой недельной матрицы показывается один день активного расписания с переключателем дней.
  • Вкладка auditorium-workload стала общей вкладкой Загруженность: в поле «Что смотреть» выбираются аудитории, преподаватели или кафедры. Сводная матрица по выбранной дате использует одинаковую структуру: строки — выбранный тип сущности, столбцы — эффективные временные слоты дня из /api/admin/time-slots/effective, занятость собирается из динамического расписания /api/schedule по группам. Кафедральная матрица группирует занятия по кафедре преподавателя. Для аудиторий доступны фильтры корпуса, вместимости и оборудования. В поле «Отображение» можно выбрать конкретную аудиторию, преподавателя или кафедру; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной сущности растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из /api/admin/calendar/years.
  • Вкладка profiles выделена под профили обучения: администратор выбирает специальность, создаёт профиль, редактирует описание и удаляет неиспользуемые профили.
  • Вкладка schedule не обращается к старым lessons API. Создание и редактирование расписания выполняется через правила /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():

export async function apiFetch(endpoint, method = 'GET', body = null) {
    const response = await fetch(endpoint, {
        method,
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: body ? JSON.stringify(body) : null
    });

    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)
};

Токен берётся из localStorage.getItem('token').


Аутентификация (Frontend)

Страница входа (/index.html)

  1. Пользователь вводит логин/пароль
  2. script.js отправляет POST /api/auth/login
  3. При успехе сохраняет в localStorage:
    • token — UUID-токен
    • role — роль пользователя
    • departmentId — кафедра пользователя
    • userId — ID пользователя для личного расписания преподавателя
  4. Перенаправляет на соответствующий интерфейс:
    • ADMIN/admin/
    • EDUCATION_OFFICE/admin/#schedule-view
    • DEPARTMENT/admin/#department-workspace
    • SCHEDULE_VIEWER/admin/#schedule-view
    • TEACHER/teacher/
    • STUDENT/student/

Проверка авторизации

На каждой странице проверяется наличие токена и роли:

export function isAuthenticatedAsAdmin() {
    const role = localStorage.getItem('role');
    return token && role === 'ADMIN';
}

Выход

Кнопка «Выйти» находится в dropdown-меню «Настройки» в footer боковой панели. Очищает 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-переменные позволяют поддерживать светлую/тёмную тему:

: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 на всех устройствах
  • Таблицы получают горизонтальный скролл