40 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/ # Модули представлений
│ │ ├── 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:
<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 |
|---|---|---|
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содержит внутренние разделыКафедры,СпециальностииПрофили: администратор создаёт профили как из общего списка, так и через кнопкуПрофилиу конкретной специальности. - Вкладка
scheduleне обращается к старымlessonsAPI. Создание и редактирование расписания выполняется через правила/api/admin/schedule-rules, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пунктВся группа; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Типы занятий в слоте сортируются в порядке: лекция, лабораторная работа, практика. Список слотов отображается без внутреннего вертикального скролла: при добавлении строк форма расширяется вниз, а кнопка сохранения остаётся отдельным блоком под слотами. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: пользователь выбирает учебный год, семестр и группы, после чего матрица строится только по правилам выбранного семестра. Столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Период недель не показывается для занятия на весь семестр; если занятие идёт до конца семестра не с первой недели, выводится только неделя начала в формате(с 5 нед.), а ограниченный диапазон — как(с 1 по 3 нед.). Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. Кнопка с тремя точками в правой части карточки пары открывает контекстное меню: можно открыть полное правило в форме, изменить только день и базовую пару выбранного слота через компактную модалку или удалить правило целиком. В списке правил действия отображаются едиными кнопками одинакового размера с отступами между ними. - Вкладка
academic-calendarполностью отделяет календарную систему от расписания занятий и внутри себя разделена на три вкладки:Графикидля учебных годов, семестров и карточек календарных графиков,Сеткидля редактора дневной сетки,Дисциплиныдля ручной привязки дисциплин из/api/subjectsк номерам учебных семестров графика. Администратор выбирает форму обучения из общего справочника/api/education-forms, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через/api/admin/academic-calendars/{id}/grid. - При сохранении правила во вкладке
schedulefrontend различает409 Conflictот остальных ошибок API. Если backend возвращаетconflictRule, открывается широкое модальное окно разрешения конфликта: пользователь видит новое и ранее созданное правило, меняет у конфликтующего правила день, чётность, пару, преподавателя или аудиторию, после чего 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 перед каждым запросом:
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)
- Пользователь вводит логин/пароль
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: #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
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 на всех устройствах
- Таблицы получают горизонтальный скролл