редизайн кнопок 1st version
BIN
audit-buttons-01-login.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
audit-buttons-02-dashboard.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
audit-buttons-03-users-form.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
audit-buttons-04-settings-menu.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
audit-buttons-05-settings-general.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
audit-buttons-06-teacher.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
audit-buttons-07-student.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
audit-buttons-08-modal.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
audit-buttons-calendar.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
audit-buttons-classrooms.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
audit-buttons-groups.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
audit-buttons-rules.png
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
audit-buttons-schedule-view.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
audit-buttons-settings-database.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
audit-buttons-settings-study-forms.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
audit-buttons-settings-time-slots.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
audit-buttons-structure.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
audit-buttons-subjects.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
audit-buttons-users.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
audit-buttons-workload.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
420
docs/BUTTON_STYLING_PLAN.md
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
# План стилизации кнопок
|
||||||
|
|
||||||
|
Дата аудита: 01.06.2026
|
||||||
|
Среда аудита: `https://localhost`
|
||||||
|
Метод: визуальный обход через Playwright, фиксация скриншотов и computed styles.
|
||||||
|
|
||||||
|
## 1. Краткое резюме
|
||||||
|
|
||||||
|
Интерфейс уже имеет узнаваемую базу: тёмная админ-панель, фиолетовый акцент, компактные таблицы и отдельные публичные кабинеты преподавателя/студента. Главная проблема не в отдельных кнопках, а в отсутствии единого контракта между смыслом действия и его визуальным видом.
|
||||||
|
|
||||||
|
Сейчас одинаковые классы могут выглядеть по-разному в разных SPA, а одинаковые действия могут получать разные цвета, размеры и плотность. Из-за этого пользователь каждый раз заново считывает интерфейс: где главное действие, где безопасное второстепенное, а где рискованное.
|
||||||
|
|
||||||
|
Цель новой структуры: закрепить единую кнопку как системный компонент с понятными ролями, размерами и правилами использования.
|
||||||
|
|
||||||
|
## 2. Что было проверено
|
||||||
|
|
||||||
|
| Раздел | Проверенные сценарии | Скриншот |
|
||||||
|
|---|---|---|
|
||||||
|
| Авторизация | Вход, показ пароля, переключение темы | [`audit-buttons-01-login.png`](../audit-buttons-01-login.png) |
|
||||||
|
| Дашборд админки | Быстрые переходы, recheck, сайдбар | [`audit-buttons-02-dashboard.png`](../audit-buttons-02-dashboard.png) |
|
||||||
|
| Пользователи | Создание пользователя, архивирование | [`audit-buttons-03-users-form.png`](../audit-buttons-03-users-form.png) |
|
||||||
|
| Меню настроек | Выпадающее меню, выход, переход в настройки | [`audit-buttons-04-settings-menu.png`](../audit-buttons-04-settings-menu.png) |
|
||||||
|
| Настройки | Навигация отдельной SPA | [`audit-buttons-05-settings-general.png`](../audit-buttons-05-settings-general.png) |
|
||||||
|
| Кабинет преподавателя | Недели, сегодня, выход, тема | [`audit-buttons-06-teacher.png`](../audit-buttons-06-teacher.png) |
|
||||||
|
| Кабинет студента | Недели, сегодня, выход, тема | [`audit-buttons-07-student.png`](../audit-buttons-07-student.png) |
|
||||||
|
| Модальное окно | Создание ручной сетки, отмена, закрытие | [`audit-buttons-08-modal.png`](../audit-buttons-08-modal.png) |
|
||||||
|
|
||||||
|
Дополнительно были сохранены скриншоты отдельных разделов: календарный график, аудитории, группы, дисциплины, расписание, формы обучения, база данных, временные слоты.
|
||||||
|
|
||||||
|
## 3. Текущее состояние
|
||||||
|
|
||||||
|
### 3.1. Авторизация
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Найдены кнопки:
|
||||||
|
|
||||||
|
| Кнопка | Текущая роль | Комментарий |
|
||||||
|
|---|---|---|
|
||||||
|
| `Войти` | Primary | Корректно считывается как главное действие экрана. |
|
||||||
|
| `Показать пароль` | Icon utility | Нужен единый icon-only стиль и обязательный `aria-label`. |
|
||||||
|
| `Переключить тему` | Icon utility | Отличается от такой же кнопки в кабинетах преподавателя и студента. |
|
||||||
|
|
||||||
|
Вывод: экран логина близок к правильной иерархии, но icon-only controls нужно привести к общей форме и размеру.
|
||||||
|
|
||||||
|
### 3.2. Дашборд админки
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Найдены кнопки и ссылки-кнопки:
|
||||||
|
|
||||||
|
| Кнопка | Текущий вид | Проблема |
|
||||||
|
|---|---|---|
|
||||||
|
| `Конструктор расписания` | Primary link | Хороший кандидат на основной CTA блока. |
|
||||||
|
| `Точечные изменения / Просмотр` | Secondary link | Выглядит как второстепенное действие, это корректно. |
|
||||||
|
| `Перепроверить` | `btn-primary`, маленькая высота | Primary используется для сервисного действия, хотя это не главное действие страницы. |
|
||||||
|
| `Настройки` | Sidebar button | Визуально близко к навигации, но реализовано как кнопка. |
|
||||||
|
| `Переключить тему` | Круглая icon button | Отличается от публичных кабинетов. |
|
||||||
|
|
||||||
|
Основной вывод: `btn-primary` сейчас означает и главный CTA, и компактную служебную кнопку. Это размывает иерархию.
|
||||||
|
|
||||||
|
### 3.3. Формы и таблицы админки
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Типичные кнопки:
|
||||||
|
|
||||||
|
| Кнопка | Смысл | Текущее впечатление |
|
||||||
|
|---|---|---|
|
||||||
|
| `Создать` | Подтверждение формы | Главная кнопка, но размер и стиль отличаются между основной админкой и настройками. |
|
||||||
|
| `Архивировать` | Рискованное, но часто обратимое действие | Визуально слишком близко к удалению. |
|
||||||
|
| `Изменить` | Второстепенное действие строки | В разных разделах бывает фиолетовой или нейтральной. |
|
||||||
|
| `Удалить` | Деструктивное действие | Встречается в разных размерах и оттенках красного. |
|
||||||
|
|
||||||
|
Проблема: таблицы используют кнопки как плотный action bar, но размеры скачут от `24x28` до `113x28` и `96x40`. Это создаёт визуальный шум в строках.
|
||||||
|
|
||||||
|
### 3.4. Меню настроек и выход
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Кнопка `Выйти` находится рядом с навигационными пунктами и выглядит почти как обычный пункт меню. Семантически это сессионное действие с риском потерять текущий контекст, поэтому ему нужен отдельный quiet-danger стиль.
|
||||||
|
|
||||||
|
Рекомендация: `Выйти` не должен выглядеть как обычная навигация. Достаточно `Danger subtle`: красный текст на спокойной поверхности без агрессивной заливки.
|
||||||
|
|
||||||
|
### 3.5. Отдельная SPA настроек
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
В настройках виден тот же сайдбарный паттерн, но кнопки внутри разделов получают другую визуальную систему.
|
||||||
|
|
||||||
|
Пример различий:
|
||||||
|
|
||||||
|
| Класс | Основная админка | Настройки |
|
||||||
|
|---|---|---|
|
||||||
|
| `btn-primary` | Часто прозрачный фон, белый текст, визуальная заливка может приходить из другого слоя | Фиолетовая заливка `rgb(139, 92, 246)`, высота `40px`, жирность `700` |
|
||||||
|
| `btn-delete` | Красный tint, высота часто `28px`, font-weight `400` | Красный tint, высота `40px`, font-weight `700` |
|
||||||
|
| `btn-secondary` | Фиолетовый tint или светлая surface-кнопка | Нейтральная surface-кнопка |
|
||||||
|
|
||||||
|
Вывод: одинаковое имя класса не является дизайн-контрактом. Компонент кнопки нужно нормализовать на уровне токенов и модификаторов.
|
||||||
|
|
||||||
|
### 3.6. Кабинеты преподавателя и студента
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
В публичных кабинетах используется отдельная светлая система:
|
||||||
|
|
||||||
|
| Элемент | Преподаватель | Студент |
|
||||||
|
|---|---|---|
|
||||||
|
| `Сегодня` | Primary blue `rgb(29, 78, 216)` | Primary teal `rgb(15, 118, 110)` |
|
||||||
|
| `Выйти` | Outline, белый фон, radius `8px` | Outline, белый фон, radius `8px` |
|
||||||
|
| `‹` / `›` | Outline icon-like buttons | Outline icon-like buttons |
|
||||||
|
| `Переключить тему` | Текстовый символ `◐` | Текстовый символ `◐` |
|
||||||
|
|
||||||
|
Проблема: одно действие `Сегодня` меняет цвет в зависимости от кабинета. Это допустимо для брендинга ролей, но плохо для системных кнопок. Ролевые цвета лучше использовать в расписании, бейджах и карточках, а не в базовой кнопочной иерархии.
|
||||||
|
|
||||||
|
### 3.7. Модальные окна
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Найдены:
|
||||||
|
|
||||||
|
| Кнопка | Роль | Рекомендация |
|
||||||
|
|---|---|---|
|
||||||
|
| `Создать сетку` | Primary | Оставить primary. |
|
||||||
|
| `Отмена` | Secondary/Ghost | Сделать ghost или secondary без акцента. |
|
||||||
|
| `Закрыть` (`×`) | Icon close | Привести к icon-only button `40x40` или `36x36`, с `aria-label="Закрыть"`. |
|
||||||
|
|
||||||
|
Модальная структура в целом правильная, но её нужно подчинить тем же токенам, что и остальные кнопки.
|
||||||
|
|
||||||
|
## 4. Семантическая группировка найденных кнопок
|
||||||
|
|
||||||
|
### Primary: главное действие
|
||||||
|
|
||||||
|
Примеры: `Войти`, `Создать`, `Добавить`, `Сохранить правило`, `Сохранить год`, `Сохранить семестр`, `Сохранить график`, `Сохранить сетку`, `Сохранить слот`, `Создать сетку`.
|
||||||
|
|
||||||
|
Правило: одна primary-кнопка на форму, модальное окно или смысловой блок. Если на экране несколько форм, primary может быть в каждой форме, но не должна превращаться в оформление всех кнопок подряд.
|
||||||
|
|
||||||
|
### Secondary: обычное второстепенное действие
|
||||||
|
|
||||||
|
Примеры: `Изменить`, `Обновить`, `Показать`, `Тест`, `Сменить статус`, `Преподаватели`, `Загрузить сетку`, `Заполнить диапазон`.
|
||||||
|
|
||||||
|
Правило: использовать для действий, которые помогают выполнить задачу, но не являются главным подтверждением.
|
||||||
|
|
||||||
|
### Tertiary / Ghost: тихое действие
|
||||||
|
|
||||||
|
Примеры: `Очистить`, `Очистить форму`, `Отмена`, `Назад в панель`, `Свернуть`, `Из формы`, `Все`, `Точечные изменения / Просмотр`.
|
||||||
|
|
||||||
|
Правило: использовать там, где действие должно быть доступно, но не конкурировать с сохранением или созданием.
|
||||||
|
|
||||||
|
### Danger: деструктивное действие
|
||||||
|
|
||||||
|
Примеры: `Удалить`.
|
||||||
|
|
||||||
|
Правило: только для необратимых или почти необратимых операций. Красный цвет должен быть зарезервирован именно под риск.
|
||||||
|
|
||||||
|
### Danger subtle: рискованное, но не удаление
|
||||||
|
|
||||||
|
Примеры: `Архив`, `Архивировать`, `Удалить сетку`, `Выйти`.
|
||||||
|
|
||||||
|
Правило: использовать для действий с негативным или сессионным эффектом, но без визуальной паники solid-red кнопки.
|
||||||
|
|
||||||
|
### Icon button: компактное служебное действие
|
||||||
|
|
||||||
|
Примеры: `Переключить тему`, `Показать пароль`, `Скрыть панель`, `Закрыть`, `‹`, `›`.
|
||||||
|
|
||||||
|
Правило: фиксированный квадратный размер, единое состояние hover/focus, обязательный доступный текст через `aria-label`.
|
||||||
|
|
||||||
|
### Navigation item: навигация, а не кнопка действия
|
||||||
|
|
||||||
|
Примеры: пункты сайдбара `Дашборд`, `Конструктор правил`, `Календарный график`, `Пользователи`, `Общие настройки`.
|
||||||
|
|
||||||
|
Правило: не смешивать навигацию с action button. У неё отдельные active, hover и focus-состояния.
|
||||||
|
|
||||||
|
## 5. Предлагаемая дизайн-система кнопок
|
||||||
|
|
||||||
|
### 5.1. Токены
|
||||||
|
|
||||||
|
| Токен | Светлая тема | Тёмная тема | Назначение |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--button-primary-bg` | `#4F46E5` | `#8B5CF6` | Основное действие |
|
||||||
|
| `--button-primary-bg-hover` | `#4338CA` | `#7C3AED` | Hover primary |
|
||||||
|
| `--button-primary-text` | `#FFFFFF` | `#FFFFFF` | Текст primary |
|
||||||
|
| `--button-secondary-bg` | `#FFFFFF` | `rgba(255,255,255,0.04)` | Второстепенная поверхность |
|
||||||
|
| `--button-secondary-bg-hover` | `#F1F5F9` | `rgba(255,255,255,0.08)` | Hover secondary |
|
||||||
|
| `--button-secondary-border` | `#CBD5E1` | `rgba(255,255,255,0.10)` | Граница secondary |
|
||||||
|
| `--button-secondary-text` | `#334155` | `#F8FAFC` | Текст secondary |
|
||||||
|
| `--button-ghost-bg-hover` | `#F8FAFC` | `rgba(255,255,255,0.06)` | Hover ghost |
|
||||||
|
| `--button-danger-bg` | `#DC2626` | `#DC2626` | Удаление |
|
||||||
|
| `--button-danger-bg-hover` | `#B91C1C` | `#B91C1C` | Hover удаления |
|
||||||
|
| `--button-danger-subtle-bg` | `#FEF2F2` | `rgba(239,68,68,0.13)` | Риск без solid-заливки |
|
||||||
|
| `--button-danger-subtle-text` | `#B91C1C` | `#FECACA` | Текст risk subtle |
|
||||||
|
| `--button-focus-ring` | `rgba(79,70,229,0.35)` | `rgba(139,92,246,0.45)` | Focus outline |
|
||||||
|
|
||||||
|
Примечание: для всего продукта лучше выбрать один primary-акцент. Для текущей тёмной админки логично оставить фиолетовый `#8B5CF6`, а для светлой темы использовать более читаемый `#4F46E5`. Ролевые цвета преподавателя и студента лучше вынести из кнопок в расписание и бейджи.
|
||||||
|
|
||||||
|
### 5.2. Размеры
|
||||||
|
|
||||||
|
| Размер | Высота | Padding | Шрифт | Где применять |
|
||||||
|
|---|---:|---|---|---|
|
||||||
|
| `sm` | `32px` | `0 12px` | `13px / 600` | Таблицы, компактные действия строк |
|
||||||
|
| `md` | `40px` | `0 16px` | `14px / 600` | Формы, фильтры, модальные окна |
|
||||||
|
| `lg` | `48px` | `0 20px` | `15px / 700` | Вход, главный CTA экрана |
|
||||||
|
| `icon-sm` | `32x32` | `0` | icon `18px` | Табличные иконки |
|
||||||
|
| `icon-md` | `40x40` | `0` | icon `20px` | Верхние панели, модалки, навигация недель |
|
||||||
|
|
||||||
|
Базовый `border-radius`: `8px`.
|
||||||
|
|
||||||
|
Почему `8px`: интерфейс административный и рабочий. Радиус `8px` сохраняет мягкость, но выглядит собраннее, чем повсеместные `10px`, особенно в таблицах и плотных формах.
|
||||||
|
|
||||||
|
### 5.3. Варианты
|
||||||
|
|
||||||
|
#### Primary
|
||||||
|
|
||||||
|
Использовать для главного подтверждения.
|
||||||
|
|
||||||
|
| Состояние | Стиль |
|
||||||
|
|---|---|
|
||||||
|
| Default | Фон primary, белый текст, без видимой границы |
|
||||||
|
| Hover | Фон темнее на 8-12% |
|
||||||
|
| Focus | Видимый ring `2px` |
|
||||||
|
| Disabled | Нейтральный фон, muted-текст, `cursor: not-allowed` |
|
||||||
|
|
||||||
|
Примеры: `Войти`, `Создать`, `Сохранить правило`, `Создать сетку`.
|
||||||
|
|
||||||
|
#### Secondary
|
||||||
|
|
||||||
|
Использовать для обычных действий рядом с primary.
|
||||||
|
|
||||||
|
| Состояние | Стиль |
|
||||||
|
|---|---|
|
||||||
|
| Default | Surface-фон, тонкая граница, основной текст |
|
||||||
|
| Hover | Чуть более светлая/контрастная поверхность |
|
||||||
|
| Focus | Такой же focus ring, как у primary |
|
||||||
|
| Disabled | Opacity `0.5` |
|
||||||
|
|
||||||
|
Примеры: `Изменить`, `Обновить`, `Показать`, `Тест`.
|
||||||
|
|
||||||
|
#### Ghost
|
||||||
|
|
||||||
|
Использовать для тихих действий.
|
||||||
|
|
||||||
|
| Состояние | Стиль |
|
||||||
|
|---|---|
|
||||||
|
| Default | Прозрачный фон, muted/regular text |
|
||||||
|
| Hover | Лёгкий surface-hover |
|
||||||
|
| Focus | Ring без изменения иерархии |
|
||||||
|
|
||||||
|
Примеры: `Отмена`, `Очистить`, `Назад в панель`, `Свернуть`.
|
||||||
|
|
||||||
|
#### Danger
|
||||||
|
|
||||||
|
Использовать только для удаления.
|
||||||
|
|
||||||
|
| Состояние | Стиль |
|
||||||
|
|---|---|
|
||||||
|
| Default | Красная заливка, белый текст |
|
||||||
|
| Hover | Более тёмный красный |
|
||||||
|
| Focus | Красный или общий focus ring |
|
||||||
|
|
||||||
|
Примеры: `Удалить`.
|
||||||
|
|
||||||
|
#### Danger subtle
|
||||||
|
|
||||||
|
Использовать для рискованных действий без полного удаления.
|
||||||
|
|
||||||
|
| Состояние | Стиль |
|
||||||
|
|---|---|
|
||||||
|
| Default | Красный tint-фон, красный текст, мягкая border |
|
||||||
|
| Hover | Более насыщенный tint |
|
||||||
|
| Focus | Видимый ring |
|
||||||
|
|
||||||
|
Примеры: `Архивировать`, `Архив`, `Выйти`, `Удалить сетку`.
|
||||||
|
|
||||||
|
#### Icon button
|
||||||
|
|
||||||
|
Использовать для действий, где текст заменён иконкой.
|
||||||
|
|
||||||
|
| Параметр | Правило |
|
||||||
|
|---|---|
|
||||||
|
| Размер | `32x32` в таблицах, `40x40` в тулбарах |
|
||||||
|
| Иконка | 18-20px, один набор иконок |
|
||||||
|
| Текст | Только через `aria-label` |
|
||||||
|
| Форма | Квадрат с radius `8px`, не круг, кроме специальных случаев avatar/fab |
|
||||||
|
|
||||||
|
Примеры: `Переключить тему`, `Показать пароль`, `Закрыть`, `Предыдущая неделя`, `Следующая неделя`.
|
||||||
|
|
||||||
|
## 6. Маппинг текущих кнопок на новую систему
|
||||||
|
|
||||||
|
| Текущие кнопки | Новый тип | Размер |
|
||||||
|
|---|---|---|
|
||||||
|
| `Войти` | `Primary` | `lg` |
|
||||||
|
| `Создать`, `Добавить` | `Primary` | `md` |
|
||||||
|
| `Сохранить правило`, `Сохранить год`, `Сохранить семестр`, `Сохранить график`, `Сохранить сетку`, `Сохранить слот` | `Primary` | `md` |
|
||||||
|
| `Создать сетку` | `Primary` | `md` |
|
||||||
|
| `Изменить`, `Обновить`, `Показать`, `Тест`, `Сменить статус` | `Secondary` | `sm` в таблицах, `md` в формах |
|
||||||
|
| `Преподаватели`, `Подгруппы`, `Календарь`, `Сетка` | `Secondary` | `sm` |
|
||||||
|
| `Очистить`, `Очистить форму`, `Отмена`, `Свернуть`, `Из формы`, `Все` | `Ghost` | `sm` или `md` |
|
||||||
|
| `Точечные изменения / Просмотр`, `Назад в панель` | `Ghost navigation` | `md` |
|
||||||
|
| `Удалить` | `Danger` | `sm` в таблицах, `md` в модалках |
|
||||||
|
| `Архив`, `Архивировать`, `Выйти`, `Удалить сетку` | `Danger subtle` | `sm` или `md` |
|
||||||
|
| `Переключить тему`, `Показать пароль`, `Скрыть панель`, `Закрыть`, `‹`, `›` | `Icon button` | `icon-sm` или `icon-md` |
|
||||||
|
| Пункты сайдбара | `Nav item` | отдельный nav-компонент |
|
||||||
|
|
||||||
|
## 7. Правила композиции
|
||||||
|
|
||||||
|
### Формы
|
||||||
|
|
||||||
|
Primary-кнопка должна стоять в конце формы или action row. Рядом с ней допустимы secondary/ghost-действия, но не ещё одна primary без причины.
|
||||||
|
|
||||||
|
Рекомендуемый порядок:
|
||||||
|
|
||||||
|
| Сценарий | Порядок |
|
||||||
|
|---|---|
|
||||||
|
| Форма создания | `Очистить` как ghost, `Создать` как primary |
|
||||||
|
| Форма редактирования | `Отмена` как ghost, `Сохранить` как primary |
|
||||||
|
| Фильтр | `Сбросить` как ghost, `Показать` как secondary или primary, если это единственное действие блока |
|
||||||
|
|
||||||
|
### Таблицы
|
||||||
|
|
||||||
|
В строках таблицы использовать только `sm`.
|
||||||
|
|
||||||
|
Рекомендуемый порядок:
|
||||||
|
|
||||||
|
1. Основное безопасное действие: `Изменить`, `Преподаватели`, `Подгруппы`.
|
||||||
|
2. Дополнительное действие: `Календарь`, `Сетка`.
|
||||||
|
3. Рискованное действие: `Архивировать` или `Удалить`.
|
||||||
|
|
||||||
|
Удаление лучше отделять визуально от остальных кнопок небольшим gap.
|
||||||
|
|
||||||
|
### Модальные окна
|
||||||
|
|
||||||
|
В модалке primary должен соответствовать заголовку и цели модалки.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
| Заголовок | Primary | Secondary/Ghost |
|
||||||
|
|---|---|---|
|
||||||
|
| `Новая ручная сетка` | `Создать сетку` | `Отмена` |
|
||||||
|
| `Удалить слот` | `Удалить` как danger | `Отмена` |
|
||||||
|
| `Редактировать слот` | `Сохранить слот` | `Отмена` |
|
||||||
|
|
||||||
|
Кнопка закрытия всегда icon-only с `aria-label="Закрыть"`.
|
||||||
|
|
||||||
|
### Навигация
|
||||||
|
|
||||||
|
Сайдбарные пункты не должны наследовать стили action-кнопок. У них отдельная логика:
|
||||||
|
|
||||||
|
| Состояние | Стиль |
|
||||||
|
|---|---|
|
||||||
|
| Default | Прозрачный фон, muted-текст |
|
||||||
|
| Hover | Нейтральный surface-hover |
|
||||||
|
| Active | Акцентный tint-фон, accent-текст |
|
||||||
|
| Focus | Видимый ring/outline |
|
||||||
|
|
||||||
|
## 8. Главные исправления по приоритету
|
||||||
|
|
||||||
|
| Приоритет | Что сделать | Почему важно |
|
||||||
|
|---|---|---|
|
||||||
|
| P1 | Нормализовать `btn-primary`, `btn-secondary`, `btn-delete` между основной админкой и настройками | Один класс должен означать один визуальный контракт |
|
||||||
|
| P1 | Развести `Danger` и `Danger subtle` | `Удалить` и `Архивировать` не должны выглядеть одинаково опасными |
|
||||||
|
| P1 | Ввести размерную шкалу `sm/md/lg/icon` | Таблицы и формы станут визуально ровнее |
|
||||||
|
| P2 | Унифицировать `Переключить тему`, `Закрыть`, `‹`, `›`, `Показать пароль` как icon buttons | Повысит предсказуемость и доступность |
|
||||||
|
| P2 | Убрать ролевые primary-цвета из student/teacher кнопок | Одинаковые действия будут считываться одинаково |
|
||||||
|
| P3 | Заменить текстовые символы `◐`, `×`, `‹`, `›` на иконки единого набора | Интерфейс станет аккуратнее и доступнее |
|
||||||
|
|
||||||
|
## 9. Визуальный референс поведения
|
||||||
|
|
||||||
|
```text
|
||||||
|
Primary
|
||||||
|
[ Сохранить правило ]
|
||||||
|
Фон: акцентный, текст белый. Только главное действие.
|
||||||
|
|
||||||
|
Secondary
|
||||||
|
[ Изменить ]
|
||||||
|
Фон: поверхность, тонкая граница. Обычное действие.
|
||||||
|
|
||||||
|
Ghost
|
||||||
|
[ Отмена ]
|
||||||
|
Фон: прозрачный. Тихое действие без конкуренции.
|
||||||
|
|
||||||
|
Danger
|
||||||
|
[ Удалить ]
|
||||||
|
Фон: красный. Только необратимое действие.
|
||||||
|
|
||||||
|
Danger subtle
|
||||||
|
[ Архивировать ]
|
||||||
|
Фон: красный tint. Рискованное, но не полноценное удаление.
|
||||||
|
|
||||||
|
Icon
|
||||||
|
[ icon ]
|
||||||
|
Фиксированный квадрат. Доступное название через aria-label.
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Почему это решение улучшит интерфейс
|
||||||
|
|
||||||
|
Единая кнопочная система снижает когнитивную нагрузку. Пользователь перестаёт разгадывать каждый экран и начинает быстро узнавать роли: главное действие, второстепенное действие, отмена, удаление, навигация.
|
||||||
|
|
||||||
|
Разделение `Danger` и `Danger subtle` особенно важно для админ-панели. Сейчас `Архивировать` визуально конкурирует с `Удалить`, хотя риск у этих действий разный. После унификации красный solid будет означать только настоящее удаление.
|
||||||
|
|
||||||
|
Единая размерная шкала сделает таблицы спокойнее. В строках не будет скачков высоты и плотности, а формы и модалки начнут выглядеть как части одной системы.
|
||||||
|
|
||||||
|
Общий primary-цвет вернёт продукту целостность. Ролевые цвета можно сохранить там, где они помогают считывать расписание и принадлежность данных, но базовые действия должны оставаться стабильными.
|
||||||
|
|
||||||
|
## 11. Приложение: дополнительные скриншоты
|
||||||
|
|
||||||
|
| Раздел | Скриншот |
|
||||||
|
|---|---|
|
||||||
|
| Конструктор правил |  |
|
||||||
|
| Календарный график |  |
|
||||||
|
| Просмотр расписаний |  |
|
||||||
|
| Аудиторный фонд |  |
|
||||||
|
| Группы обучения |  |
|
||||||
|
| Структура вуза |  |
|
||||||
|
| Дисциплины |  |
|
||||||
|
| Пользователи |  |
|
||||||
|
| Временные слоты |  |
|
||||||
|
| Формы обучения |  |
|
||||||
|
| База данных |  |
|
||||||
@@ -317,16 +317,27 @@ CSS-переменные позволяют поддерживать светл
|
|||||||
:root {
|
:root {
|
||||||
--bg-primary: #ffffff;
|
--bg-primary: #ffffff;
|
||||||
--text-primary: #1a1a2e;
|
--text-primary: #1a1a2e;
|
||||||
--accent: #6366f1;
|
--accent: #4F46E5;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--bg-primary: #0f0f23;
|
--bg-primary: #0f0f23;
|
||||||
--text-primary: #e2e8f0;
|
--text-primary: #e2e8f0;
|
||||||
--accent: #818cf8;
|
--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`.
|
Переключение — через `theme-toggle.js`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -654,32 +654,6 @@
|
|||||||
gap: 0.45rem;
|
gap: 0.45rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-view-shell .btn-secondary {
|
|
||||||
min-height: 42px;
|
|
||||||
padding: 0.65rem 0.9rem;
|
|
||||||
border: 1px solid var(--bg-card-border);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: var(--bg-input);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font: inherit;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background var(--transition), border-color var(--transition), color var(--transition), transform var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-view-shell .btn-secondary:hover:not(:disabled) {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
border-color: var(--accent);
|
|
||||||
color: var(--accent-hover);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-view-shell .btn-secondary:disabled {
|
|
||||||
cursor: default;
|
|
||||||
opacity: 0.45;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-view-extra-filters {
|
.schedule-view-extra-filters {
|
||||||
margin-top: 0.9rem;
|
margin-top: 0.9rem;
|
||||||
border-top: 1px solid var(--bg-card-border);
|
border-top: 1px solid var(--bg-card-border);
|
||||||
@@ -944,8 +918,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-view-period-buttons .btn-secondary,
|
.schedule-view-period-buttons .btn,
|
||||||
.schedule-view-result-actions .btn-secondary {
|
.schedule-view-result-actions .btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,45 @@
|
|||||||
|
/* ===== Button Tokens ===== */
|
||||||
|
:root {
|
||||||
|
--button-primary-bg: #8B5CF6;
|
||||||
|
--button-primary-bg-hover: #7C3AED;
|
||||||
|
--button-primary-text: #FFFFFF;
|
||||||
|
--button-secondary-bg: rgba(255, 255, 255, 0.04);
|
||||||
|
--button-secondary-bg-hover: rgba(255, 255, 255, 0.08);
|
||||||
|
--button-secondary-border: rgba(255, 255, 255, 0.10);
|
||||||
|
--button-secondary-text: #F8FAFC;
|
||||||
|
--button-ghost-bg-hover: rgba(255, 255, 255, 0.06);
|
||||||
|
--button-danger-bg: #DC2626;
|
||||||
|
--button-danger-bg-hover: #B91C1C;
|
||||||
|
--button-danger-subtle-bg: rgba(239, 68, 68, 0.13);
|
||||||
|
--button-danger-subtle-bg-hover: rgba(239, 68, 68, 0.20);
|
||||||
|
--button-danger-subtle-border: rgba(239, 68, 68, 0.25);
|
||||||
|
--button-danger-subtle-text: #FECACA;
|
||||||
|
--button-focus-ring: rgba(139, 92, 246, 0.45);
|
||||||
|
--button-disabled-bg: rgba(148, 163, 184, 0.16);
|
||||||
|
--button-disabled-text: #94A3B8;
|
||||||
|
--button-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] {
|
||||||
|
--button-primary-bg: #4F46E5;
|
||||||
|
--button-primary-bg-hover: #4338CA;
|
||||||
|
--button-primary-text: #FFFFFF;
|
||||||
|
--button-secondary-bg: #FFFFFF;
|
||||||
|
--button-secondary-bg-hover: #F1F5F9;
|
||||||
|
--button-secondary-border: #CBD5E1;
|
||||||
|
--button-secondary-text: #334155;
|
||||||
|
--button-ghost-bg-hover: #F8FAFC;
|
||||||
|
--button-danger-bg: #DC2626;
|
||||||
|
--button-danger-bg-hover: #B91C1C;
|
||||||
|
--button-danger-subtle-bg: #FEF2F2;
|
||||||
|
--button-danger-subtle-bg-hover: #FEE2E2;
|
||||||
|
--button-danger-subtle-border: #FECACA;
|
||||||
|
--button-danger-subtle-text: #B91C1C;
|
||||||
|
--button-focus-ring: rgba(79, 70, 229, 0.35);
|
||||||
|
--button-disabled-bg: #CBD5E1;
|
||||||
|
--button-disabled-text: #64748B;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Cards ===== */
|
/* ===== Cards ===== */
|
||||||
.card {
|
.card {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
@@ -48,6 +90,189 @@
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Dashboard ===== */
|
||||||
|
.dashboard-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(180px, 1fr));
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-metric-card {
|
||||||
|
--metric-color: var(--accent);
|
||||||
|
--metric-bg: rgba(99, 102, 241, 0.1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
min-height: 112px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
border-left: 4px solid var(--metric-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-metric-card--students {
|
||||||
|
--metric-color: #10b981;
|
||||||
|
--metric-bg: rgba(16, 185, 129, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-metric-card--teachers {
|
||||||
|
--metric-color: #f59e0b;
|
||||||
|
--metric-bg: rgba(245, 158, 11, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-metric-card--classrooms {
|
||||||
|
--metric-color: #3b82f6;
|
||||||
|
--metric-bg: rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-metric-card .metric-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 48px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
color: var(--metric-color);
|
||||||
|
background: var(--metric-bg);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-middle {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 2fr) minmax(320px, 1fr);
|
||||||
|
gap: 1.25rem;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-monitor-card,
|
||||||
|
.dashboard-compact-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-monitor-card {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-card-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-time-slot {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--accent-hover);
|
||||||
|
background: var(--bg-hover);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-monitor-table {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-side-stack {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
gap: 1.25rem;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-compact-card {
|
||||||
|
min-height: 0;
|
||||||
|
padding: 1.25rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-card-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-badge-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.65rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-action-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0.6rem 0.85rem;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-action-link.btn-secondary {
|
||||||
|
color: var(--button-secondary-text);
|
||||||
|
background: var(--button-secondary-bg);
|
||||||
|
border: 1px solid var(--button-secondary-border);
|
||||||
|
border-radius: var(--button-radius);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-action-link.btn-secondary:hover {
|
||||||
|
color: var(--button-secondary-text);
|
||||||
|
background: var(--button-secondary-bg-hover);
|
||||||
|
border-color: var(--button-secondary-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.dashboard-metrics-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-middle {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-side-stack {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.dashboard-metrics-grid,
|
||||||
|
.dashboard-side-stack {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-card-header {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Form Structure ===== */
|
/* ===== Form Structure ===== */
|
||||||
.form-row {
|
.form-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -167,14 +392,34 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.department-workspace-grid {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.department-subject-import-card,
|
||||||
|
.department-teachers-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: stretch;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.department-teachers-card .card-header-row {
|
||||||
|
margin-bottom: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.department-teachers-card .metric-grid {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.metric-grid {
|
.metric-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.75rem;
|
gap: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-card,
|
.metric-card,
|
||||||
.department-comment-item {
|
.department-comment-item {
|
||||||
padding: 0.85rem;
|
padding: 0.8rem 0.85rem;
|
||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
border: 1px solid var(--bg-card-border);
|
border: 1px solid var(--bg-card-border);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
@@ -183,7 +428,7 @@
|
|||||||
.metric-card strong,
|
.metric-card strong,
|
||||||
.department-comment-item strong {
|
.department-comment-item strong {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.28rem;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,8 +544,9 @@
|
|||||||
|
|
||||||
.subject-import-row {
|
.subject-import-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(120px, 1fr) minmax(200px, 3fr) auto;
|
grid-template-columns: 160px minmax(260px, 1fr) auto;
|
||||||
gap: 0.75rem;
|
column-gap: 0.65rem;
|
||||||
|
row-gap: 0.75rem;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
@@ -382,16 +628,97 @@
|
|||||||
min-width: 260px;
|
min-width: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-slot-summary,
|
.schedule-slot-summary {
|
||||||
.schedule-semester-line {
|
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-semester-line {
|
.academic-year-semesters {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(220px, 1fr));
|
||||||
|
gap: 0.35rem;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-years-table tbody td {
|
||||||
|
padding-top: 0.6rem;
|
||||||
|
padding-bottom: 0.6rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-years-table thead th:first-child,
|
||||||
|
.academic-years-table thead th:nth-child(2),
|
||||||
|
.academic-years-table thead th:last-child {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-center-cell {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-semesters-cell {
|
||||||
|
min-width: 520px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-actions-cell {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-actions {
|
||||||
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
gap: 0.5rem;
|
gap: 0.4rem;
|
||||||
|
min-height: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-semester-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(220px, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.65rem;
|
||||||
|
padding: 0.5rem 0.65rem;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--bg-card-border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subject-import-code-field,
|
||||||
|
.subject-import-name-field {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subject-import-row .form-group label {
|
||||||
|
margin-bottom: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.academic-year-semesters {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-semesters-cell {
|
||||||
|
min-width: 360px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-semester-meta {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-semester-type {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-year-semester-period {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-inline-table {
|
.schedule-inline-table {
|
||||||
@@ -403,34 +730,33 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 905;
|
z-index: 905;
|
||||||
width: 42px;
|
width: 40px;
|
||||||
height: 74px;
|
height: 40px;
|
||||||
border: 1px solid var(--bg-card-border);
|
border: 1px solid var(--button-secondary-border);
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
border-radius: 10px 0 0 10px;
|
border-radius: var(--button-radius) 0 0 var(--button-radius);
|
||||||
background: color-mix(in srgb, var(--bg-card) 92%, var(--accent));
|
background: var(--button-secondary-bg);
|
||||||
color: var(--text-primary);
|
color: var(--button-secondary-text);
|
||||||
font: inherit;
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: 800;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.2);
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.2);
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
transition: right var(--transition), background var(--transition), border-color var(--transition), color var(--transition);
|
transition: right var(--transition), background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-visual-handle:hover,
|
.schedule-visual-handle:hover,
|
||||||
.schedule-visual-handle.open {
|
.schedule-visual-handle.open {
|
||||||
border-color: var(--accent);
|
border-color: var(--button-secondary-border);
|
||||||
background: color-mix(in srgb, var(--accent) 20%, var(--bg-card));
|
background: var(--button-secondary-bg-hover);
|
||||||
color: var(--accent-hover);
|
color: var(--button-secondary-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-visual-handle.open {
|
.schedule-visual-handle.open {
|
||||||
right: min(92vw, 1180px);
|
right: min(92vw, 1180px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schedule-visual-handle.open .schedule-visual-handle-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
.schedule-visual-backdrop {
|
.schedule-visual-backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -1868,108 +2194,222 @@ input[type="number"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Buttons ===== */
|
/* ===== Buttons ===== */
|
||||||
.btn-primary {
|
.btn {
|
||||||
position: relative;
|
display: inline-flex;
|
||||||
overflow: hidden;
|
align-items: center;
|
||||||
padding: 0.75rem 1.75rem;
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
|
gap: 0.45rem;
|
||||||
border: none;
|
min-width: 0;
|
||||||
border-radius: var(--radius-sm);
|
border: 1px solid transparent;
|
||||||
color: #fff;
|
border-radius: var(--button-radius);
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 0.95rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.02em;
|
line-height: 1;
|
||||||
cursor: pointer;
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: all var(--transition);
|
cursor: pointer;
|
||||||
box-shadow: 0 4px 15px var(--accent-glow);
|
user-select: none;
|
||||||
|
transition:
|
||||||
|
background var(--transition),
|
||||||
|
border-color var(--transition),
|
||||||
|
color var(--transition),
|
||||||
|
box-shadow var(--transition),
|
||||||
|
transform var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary::before {
|
.btn:hover:not(:disabled) {
|
||||||
content: '';
|
transform: translateY(-1px);
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: linear-gradient(rgba(255, 255, 255, 0.2), transparent);
|
|
||||||
border-radius: inherit;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity var(--transition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn:focus-visible {
|
||||||
transform: translateY(-2px);
|
outline: none;
|
||||||
box-shadow: 0 8px 25px var(--accent-glow);
|
box-shadow: 0 0 0 3px var(--button-focus-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover::before {
|
.btn:active:not(:disabled) {
|
||||||
opacity: 1;
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:active {
|
.btn:disabled,
|
||||||
transform: translateY(1px);
|
.btn[aria-disabled="true"] {
|
||||||
box-shadow: 0 2px 10px var(--accent-glow);
|
background: var(--button-disabled-bg);
|
||||||
|
border-color: transparent;
|
||||||
|
color: var(--button-disabled-text);
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.75;
|
||||||
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-delete {
|
.btn-sm {
|
||||||
padding: 0.35rem 0.7rem;
|
min-height: 32px;
|
||||||
background: rgba(248, 113, 113, 0.1);
|
padding: 0 12px;
|
||||||
border: 1px solid rgba(248, 113, 113, 0.2);
|
font-size: 13px;
|
||||||
border-radius: var(--radius-sm);
|
font-weight: 600;
|
||||||
color: var(--error);
|
}
|
||||||
|
|
||||||
|
.btn-md {
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lg {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 20px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-sm,
|
||||||
|
.btn-icon-md {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: var(--button-radius);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-sm {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-sm svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--button-primary-bg);
|
||||||
|
border-color: transparent;
|
||||||
|
color: var(--button-primary-text);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background: var(--button-primary-bg-hover);
|
||||||
|
color: var(--button-primary-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--button-secondary-bg);
|
||||||
|
border-color: var(--button-secondary-border);
|
||||||
|
color: var(--button-secondary-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
background: var(--button-secondary-bg-hover);
|
||||||
|
border-color: var(--button-secondary-border);
|
||||||
|
color: var(--button-secondary-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost:hover:not(:disabled) {
|
||||||
|
background: var(--button-ghost-bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--button-danger-bg);
|
||||||
|
border-color: transparent;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover:not(:disabled) {
|
||||||
|
background: var(--button-danger-bg-hover);
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-subtle {
|
||||||
|
background: var(--button-danger-subtle-bg);
|
||||||
|
border-color: var(--button-danger-subtle-border);
|
||||||
|
color: var(--button-danger-subtle-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-subtle:hover:not(:disabled) {
|
||||||
|
background: var(--button-danger-subtle-bg-hover);
|
||||||
|
border-color: var(--button-danger-subtle-border);
|
||||||
|
color: var(--button-danger-subtle-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-actions-cell {
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 88px 104px 104px 116px;
|
||||||
|
gap: 0.35rem;
|
||||||
|
justify-content: end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-action-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-restore-group {
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: var(--button-radius);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 0.8rem;
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background var(--transition), transform var(--transition);
|
transition: background var(--transition), transform var(--transition);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-delete:hover {
|
.btn-restore-group {
|
||||||
background: rgba(248, 113, 113, 0.2);
|
background: rgba(245, 158, 11, 0.1);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.24);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-restore-group:hover {
|
||||||
|
background: rgba(245, 158, 11, 0.2);
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon-toggle {
|
.btn-icon-toggle {
|
||||||
background: transparent;
|
width: 32px;
|
||||||
border: 1px solid var(--bg-card-border);
|
height: 32px;
|
||||||
color: var(--text-secondary);
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon-toggle:hover {
|
.btn-icon-toggle:hover {
|
||||||
background: var(--bg-card);
|
|
||||||
border-color: var(--accent);
|
|
||||||
color: var(--accent);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
box-shadow: 0 0 10px var(--accent-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-edit-classroom {
|
|
||||||
padding: 0.35rem 0.7rem;
|
|
||||||
background: rgba(99, 102, 241, 0.1);
|
|
||||||
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
color: var(--accent-hover);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-edit-classroom:hover {
|
|
||||||
background: rgba(99, 102, 241, 0.2);
|
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.group-actions .group-action-btn {
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Alerts ===== */
|
/* ===== Alerts ===== */
|
||||||
.form-alert {
|
.form-alert {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -2341,20 +2781,19 @@ tbody tr:hover {
|
|||||||
|
|
||||||
/* ===== Theme Toggle Button ===== */
|
/* ===== Theme Toggle Button ===== */
|
||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
width: 42px;
|
width: 40px;
|
||||||
height: 42px;
|
height: 40px;
|
||||||
border: none;
|
border-radius: var(--button-radius);
|
||||||
border-radius: 50%;
|
background: var(--button-secondary-bg);
|
||||||
background: var(--bg-card);
|
border: 1px solid var(--button-secondary-border);
|
||||||
border: 1px solid var(--bg-card-border);
|
color: var(--button-secondary-text);
|
||||||
color: var(--text-primary);
|
display: inline-flex;
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
|
padding: 0;
|
||||||
|
transition: background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition), transform var(--transition);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -2366,12 +2805,18 @@ tbody tr:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:hover {
|
.theme-toggle:hover {
|
||||||
transform: scale(1.1);
|
background: var(--button-secondary-bg-hover);
|
||||||
box-shadow: 0 4px 16px var(--accent-glow);
|
border-color: var(--button-secondary-border);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:active {
|
.theme-toggle:active {
|
||||||
transform: scale(0.95);
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px var(--button-focus-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle--fixed {
|
.theme-toggle--fixed {
|
||||||
|
|||||||
@@ -25,15 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-close-btn {
|
.sidebar-close-btn {
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.25rem;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
transition: all var(--transition);
|
transition: all var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,12 +347,7 @@
|
|||||||
/* ===== Mobile Menu Toggle ===== */
|
/* ===== Mobile Menu Toggle ===== */
|
||||||
.menu-toggle {
|
.menu-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 0.4rem;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
transition: background var(--transition);
|
transition: background var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,9 @@
|
|||||||
--text-primary: #0f172a;
|
--text-primary: #0f172a;
|
||||||
--text-secondary: #475569;
|
--text-secondary: #475569;
|
||||||
--text-placeholder: #94a3b8;
|
--text-placeholder: #94a3b8;
|
||||||
--accent: #6366f1;
|
--accent: #4F46E5;
|
||||||
--accent-hover: #4f46e5;
|
--accent-hover: #4338CA;
|
||||||
--accent-glow: rgba(99, 102, 241, 0.3);
|
--accent-glow: rgba(79, 70, 229, 0.3);
|
||||||
--accent-secondary: #d946ef;
|
--accent-secondary: #d946ef;
|
||||||
|
|
||||||
--error: #ef4444;
|
--error: #ef4444;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span>Magistr</span>
|
<span>Magistr</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="sidebar-close-btn" id="sidebar-close-btn" aria-label="Скрыть панель">
|
<button class="btn btn-icon-sm btn-ghost sidebar-close-btn" id="sidebar-close-btn" aria-label="Скрыть панель">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<polyline points="15 18 9 12 15 6" />
|
<polyline points="15 18 9 12 15 6" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
Общие настройки
|
Общие настройки
|
||||||
</a>
|
</a>
|
||||||
<div class="settings-menu-divider"></div>
|
<div class="settings-menu-divider"></div>
|
||||||
<button class="settings-menu-item settings-menu-item--danger" id="btn-logout">
|
<button class="btn btn-md btn-danger-subtle btn-logout settings-menu-item settings-menu-item--danger" id="btn-logout">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<button class="menu-toggle" id="menu-toggle" aria-label="Меню">
|
<button class="btn btn-icon-md btn-ghost menu-toggle" id="menu-toggle" aria-label="Меню">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<line x1="3" y1="6" x2="21" y2="6" />
|
<line x1="3" y1="6" x2="21" y2="6" />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function hideAlert(elementId) {
|
|||||||
|
|
||||||
export function applyRippleEffect() {
|
export function applyRippleEffect() {
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
const btn = e.target.closest('.btn-primary, .btn-delete, .btn-logout');
|
const btn = e.target.closest('.btn-primary, .btn-danger, .btn-danger-subtle, .btn-logout');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|
||||||
const rect = btn.getBoundingClientRect();
|
const rect = btn.getBoundingClientRect();
|
||||||
|
|||||||
@@ -217,12 +217,14 @@ export async function initAcademicCalendar() {
|
|||||||
}
|
}
|
||||||
academicYearsTbody.innerHTML = academicYears.map(year => `
|
academicYearsTbody.innerHTML = academicYears.map(year => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${escapeHtml(year.title)}</td>
|
<td class="academic-year-center-cell academic-year-title-cell">${escapeHtml(year.title)}</td>
|
||||||
<td>${escapeHtml(year.startDate)} - ${escapeHtml(year.endDate)}</td>
|
<td class="academic-year-center-cell academic-year-period-cell">${escapeHtml(formatDisplayDateRange(year.startDate, year.endDate))}</td>
|
||||||
<td>${renderSemesterList(year.semesters || [])}</td>
|
<td class="academic-year-semesters-cell">${renderSemesterList(year.semesters || [])}</td>
|
||||||
<td>
|
<td class="academic-year-actions-cell">
|
||||||
<button class="btn-edit-classroom btn-edit-year" data-id="${year.id}">Изменить</button>
|
<div class="academic-year-actions">
|
||||||
<button class="btn-delete btn-delete-year" data-id="${year.id}">Удалить</button>
|
<button class="btn btn-sm btn-secondary btn-edit-year" data-id="${year.id}">Изменить</button>
|
||||||
|
<button class="btn btn-sm btn-danger btn-delete-year" data-id="${year.id}">Удалить</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -230,12 +232,19 @@ export async function initAcademicCalendar() {
|
|||||||
|
|
||||||
function renderSemesterList(items) {
|
function renderSemesterList(items) {
|
||||||
if (!items.length) return '<span class="muted">Нет семестров</span>';
|
if (!items.length) return '<span class="muted">Нет семестров</span>';
|
||||||
return items.map(semester => `
|
return `<div class="academic-year-semesters">${items.map(semester => {
|
||||||
<div class="schedule-semester-line">
|
const title = semesterTypeTitle(semester);
|
||||||
${escapeHtml(semesterLabel(semester))}
|
const period = formatDisplayDateRange(semester.startDate, semester.endDate);
|
||||||
<button class="btn-edit-classroom btn-edit-semester" data-id="${semester.id}">Изменить</button>
|
return `
|
||||||
</div>
|
<div class="academic-year-semester-item">
|
||||||
`).join('');
|
<div class="academic-year-semester-meta">
|
||||||
|
<span class="academic-year-semester-type">${escapeHtml(title)}</span>
|
||||||
|
<span class="academic-year-semester-period">${escapeHtml(period)}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-secondary btn-edit-semester" data-id="${semester.id}" aria-label="Изменить ${escapeHtml(title)} семестр ${escapeHtml(period)}">Изменить</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('')}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveAcademicYear(event) {
|
async function saveAcademicYear(event) {
|
||||||
@@ -282,7 +291,7 @@ export async function initAcademicCalendar() {
|
|||||||
if (editSemesterButton) {
|
if (editSemesterButton) {
|
||||||
const semester = semesters.find(item => item.id == editSemesterButton.dataset.id);
|
const semester = semesters.find(item => item.id == editSemesterButton.dataset.id);
|
||||||
if (!semester) return;
|
if (!semester) return;
|
||||||
semesterFormTitle.textContent = 'Редактирование семестра';
|
semesterFormTitle.textContent = `Редактирование семестра: ${semesterTypeTitle(semester)}`;
|
||||||
semesterIdInput.value = semester.id;
|
semesterIdInput.value = semester.id;
|
||||||
semesterYearSelect.value = semester.academicYearId || '';
|
semesterYearSelect.value = semester.academicYearId || '';
|
||||||
semesterTypeSelect.value = normalizeSemesterType(semester.semesterType);
|
semesterTypeSelect.value = normalizeSemesterType(semester.semesterType);
|
||||||
@@ -391,9 +400,9 @@ export async function initAcademicCalendar() {
|
|||||||
<td>${escapeHtml(calendar.studyFormName || '-')}</td>
|
<td>${escapeHtml(calendar.studyFormName || '-')}</td>
|
||||||
<td>${escapeHtml(String(calendar.courseCount || '-'))}</td>
|
<td>${escapeHtml(String(calendar.courseCount || '-'))}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-edit-classroom btn-edit-calendar" data-id="${calendar.id}">Изменить</button>
|
<button class="btn btn-sm btn-secondary btn-edit-calendar" data-id="${calendar.id}">Изменить</button>
|
||||||
<button class="btn-edit-classroom btn-open-calendar-grid" data-id="${calendar.id}">Сетка</button>
|
<button class="btn btn-sm btn-secondary btn-open-calendar-grid" data-id="${calendar.id}">Сетка</button>
|
||||||
<button class="btn-delete btn-delete-calendar" data-id="${calendar.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-calendar" data-id="${calendar.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -1164,7 +1173,13 @@ export async function initAcademicCalendar() {
|
|||||||
|
|
||||||
function semesterLabel(semester) {
|
function semesterLabel(semester) {
|
||||||
const type = normalizeSemesterType(semester.semesterType);
|
const type = normalizeSemesterType(semester.semesterType);
|
||||||
return `${semester.academicYearTitle || '-'}, ${SEMESTER_LABELS[type] || type} (${semester.startDate} - ${semester.endDate})`;
|
return `${semester.academicYearTitle || '-'}, ${SEMESTER_LABELS[type] || type} (${formatDisplayDateRange(semester.startDate, semester.endDate)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function semesterTypeTitle(semester) {
|
||||||
|
const type = normalizeSemesterType(semester.semesterType);
|
||||||
|
const label = SEMESTER_LABELS[type] || type || 'семестр';
|
||||||
|
return label.charAt(0).toUpperCase() + label.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSemesterType(type) {
|
function normalizeSemesterType(type) {
|
||||||
@@ -1195,6 +1210,16 @@ export async function initAcademicCalendar() {
|
|||||||
return `${day}.${month}.${year}`;
|
return `${day}.${month}.${year}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDisplayDate(value) {
|
||||||
|
const [year, month, day] = String(value || '').split('-');
|
||||||
|
if (!year || !month || !day) return String(value || '');
|
||||||
|
return `${day}-${month}-${year}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDisplayDateRange(startDate, endDate) {
|
||||||
|
return `${formatDisplayDate(startDate)} — ${formatDisplayDate(endDate)}`;
|
||||||
|
}
|
||||||
|
|
||||||
function dateRangeCaption(rows) {
|
function dateRangeCaption(rows) {
|
||||||
if (!rows.length) return '';
|
if (!rows.length) return '';
|
||||||
const dates = rows.map(row => row.date).sort();
|
const dates = rows.map(row => row.date).sort();
|
||||||
|
|||||||
@@ -87,16 +87,16 @@ export async function initClassrooms() {
|
|||||||
<span class="badge ${c.isAvailable ? 'badge-available' : 'badge-unavailable'}">
|
<span class="badge ${c.isAvailable ? 'badge-available' : 'badge-unavailable'}">
|
||||||
${c.status === 'ARCHIVED' ? 'Архив' : (c.isAvailable ? 'Доступна' : 'Не доступна')}
|
${c.status === 'ARCHIVED' ? 'Архив' : (c.isAvailable ? 'Доступна' : 'Не доступна')}
|
||||||
</span>
|
</span>
|
||||||
<button class="btn-icon-toggle" data-id="${c.id}" data-current-status="${c.isAvailable}" title="Сменить статус">
|
<button class="btn btn-icon-sm btn-secondary btn-icon-toggle" data-id="${c.id}" data-current-status="${c.isAvailable}" title="Сменить статус" aria-label="Сменить статус">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right;">
|
<td style="text-align: right;">
|
||||||
<button class="btn-edit-classroom" data-id="${c.id}">Изменить</button>
|
<button class="btn btn-sm btn-secondary classroom-edit-action" data-id="${c.id}">Изменить</button>
|
||||||
${c.status === 'ARCHIVED'
|
${c.status === 'ARCHIVED'
|
||||||
? `<button class="btn-restore-classroom" data-id="${c.id}" style="margin-left: 0.5rem;">Восстановить</button>`
|
? `<button class="btn-restore-classroom" data-id="${c.id}" style="margin-left: 0.5rem;">Восстановить</button>`
|
||||||
: `<button class="btn-delete" data-id="${c.id}" style="margin-left: 0.5rem;">Архивировать</button>`}
|
: `<button class="btn btn-sm btn-danger-subtle btn-archive-classroom" data-id="${c.id}" style="margin-left: 0.5rem;">Архивировать</button>`}
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -125,9 +125,9 @@ export async function initClassrooms() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
classroomsTbody.addEventListener('click', async (e) => {
|
classroomsTbody.addEventListener('click', async (e) => {
|
||||||
const btnDelete = e.target.closest('.btn-delete');
|
const btnDelete = e.target.closest('.btn-archive-classroom');
|
||||||
const btnToggleStatus = e.target.closest('.btn-icon-toggle');
|
const btnToggleStatus = e.target.closest('.btn-icon-toggle');
|
||||||
const btnEdit = e.target.closest('.btn-edit-classroom');
|
const btnEdit = e.target.closest('.classroom-edit-action');
|
||||||
|
|
||||||
if (btnDelete) {
|
if (btnDelete) {
|
||||||
if (!confirm('Вывести аудиторию из эксплуатации? Историческое расписание сохранится.')) return;
|
if (!confirm('Вывести аудиторию из эксплуатации? Историческое расписание сохранится.')) return;
|
||||||
@@ -261,7 +261,7 @@ export async function initClassrooms() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>${eq.id}</td>
|
<td>${eq.id}</td>
|
||||||
<td>${escapeHtml(eq.name)}</td>
|
<td>${escapeHtml(eq.name)}</td>
|
||||||
<td><button class="btn-delete btn-delete-equipment" data-id="${eq.id}">Удалить</button></td>
|
<td><button class="btn btn-sm btn-danger btn-delete-equipment" data-id="${eq.id}">Удалить</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export async function initDepartmentWorkspace() {
|
|||||||
<td>${escapeHtml(subject.name || '-')}</td>
|
<td>${escapeHtml(subject.name || '-')}</td>
|
||||||
<td>${escapeHtml(subject.description || '-')}</td>
|
<td>${escapeHtml(subject.description || '-')}</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn-edit-classroom department-comments-open"
|
<button type="button" class="btn btn-sm btn-secondary department-comments-open"
|
||||||
data-subject-id="${escapeHtml(subject.id)}"
|
data-subject-id="${escapeHtml(subject.id)}"
|
||||||
data-subject-name="${escapeHtml(subject.name || '')}">
|
data-subject-name="${escapeHtml(subject.name || '')}">
|
||||||
Открыть
|
Открыть
|
||||||
@@ -120,7 +120,7 @@ export async function initDepartmentWorkspace() {
|
|||||||
<label>Название</label>
|
<label>Название</label>
|
||||||
<input type="text" class="subject-import-name" value="${escapeHtml(row.name || '')}" placeholder="Базы данных">
|
<input type="text" class="subject-import-name" value="${escapeHtml(row.name || '')}" placeholder="Базы данных">
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-delete subject-import-remove" data-index="${index}">Удалить</button>
|
<button type="button" class="btn btn-sm btn-danger subject-import-remove" data-index="${index}">Удалить</button>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,13 +171,15 @@ export async function initGroups() {
|
|||||||
<td>${escapeHtml(specialityLabel(group.specialtyId || group.specialityCode))}</td>
|
<td>${escapeHtml(specialityLabel(group.specialtyId || group.specialityCode))}</td>
|
||||||
<td>${escapeHtml(group.specialtyProfileName || '-')}</td>
|
<td>${escapeHtml(group.specialtyProfileName || '-')}</td>
|
||||||
<td><span class="badge ${statusBadgeClass(group)}">${escapeHtml(statusLabel(group))}</span></td>
|
<td><span class="badge ${statusBadgeClass(group)}">${escapeHtml(statusLabel(group))}</span></td>
|
||||||
<td style="text-align: right; white-space: nowrap;">
|
<td class="group-actions-cell">
|
||||||
<button class="btn-edit-classroom btn-edit-group" data-id="${group.id}">Изменить</button>
|
<div class="group-actions">
|
||||||
<button class="btn-primary btn-manage-subgroups" data-id="${group.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Подгруппы</button>
|
<button class="btn btn-sm btn-secondary btn-edit-group group-action-btn" data-id="${group.id}">Изменить</button>
|
||||||
<button class="btn-primary btn-manage-calendar" data-id="${group.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Календарь</button>
|
<button class="btn btn-sm btn-secondary btn-manage-subgroups group-action-btn" data-id="${group.id}">Подгруппы</button>
|
||||||
${group.status === 'ARCHIVED'
|
<button class="btn btn-sm btn-secondary btn-manage-calendar group-action-btn" data-id="${group.id}">Календарь</button>
|
||||||
? `<button class="btn-restore-group" data-id="${group.id}" style="margin-left: 0.5rem;">Восстановить</button>`
|
${group.status === 'ARCHIVED'
|
||||||
: `<button class="btn-delete" data-id="${group.id}" style="margin-left: 0.5rem;">Архив</button>`}
|
? `<button class="btn-restore-group group-action-btn" data-id="${group.id}">Восстановить</button>`
|
||||||
|
: `<button class="btn btn-sm btn-danger-subtle btn-archive-group group-action-btn" data-id="${group.id}">Архив</button>`}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -345,7 +347,7 @@ export async function initGroups() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleGroupTableClick(event) {
|
async function handleGroupTableClick(event) {
|
||||||
const btnDelete = event.target.closest('.btn-delete');
|
const btnDelete = event.target.closest('.btn-archive-group');
|
||||||
const btnEdit = event.target.closest('.btn-edit-group');
|
const btnEdit = event.target.closest('.btn-edit-group');
|
||||||
const btnRestore = event.target.closest('.btn-restore-group');
|
const btnRestore = event.target.closest('.btn-restore-group');
|
||||||
const btnSubgroups = event.target.closest('.btn-manage-subgroups');
|
const btnSubgroups = event.target.closest('.btn-manage-subgroups');
|
||||||
@@ -427,8 +429,8 @@ export async function initGroups() {
|
|||||||
<td>${escapeHtml(sub.name)}</td>
|
<td>${escapeHtml(sub.name)}</td>
|
||||||
<td>${escapeHtml(String(sub.studentCapacity ?? '-'))} чел.</td>
|
<td>${escapeHtml(String(sub.studentCapacity ?? '-'))} чел.</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-edit-classroom btn-edit-subgroup" data-id="${sub.id}">Изменить</button>
|
<button class="btn btn-sm btn-secondary btn-edit-subgroup" data-id="${sub.id}">Изменить</button>
|
||||||
<button class="btn-delete btn-delete-subgroup" data-id="${sub.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-subgroup" data-id="${sub.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -539,7 +541,7 @@ export async function initGroups() {
|
|||||||
<td>${escapeHtml(assignment.academicYearTitle)}</td>
|
<td>${escapeHtml(assignment.academicYearTitle)}</td>
|
||||||
<td>${escapeHtml(assignment.calendarTitle)}</td>
|
<td>${escapeHtml(assignment.calendarTitle)}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-delete btn-delete-assignment" data-id="${assignment.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-assignment" data-id="${assignment.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
@@ -247,8 +247,8 @@ export async function initSchedule() {
|
|||||||
<td>${renderRuleSlotsSummary(rule.slots)}</td>
|
<td>${renderRuleSlotsSummary(rule.slots)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="schedule-rule-table-actions">
|
<div class="schedule-rule-table-actions">
|
||||||
<button type="button" class="schedule-rule-table-button schedule-rule-table-button-edit btn-edit-rule" data-id="${rule.id}">Изменить</button>
|
<button type="button" class="btn btn-sm btn-secondary btn-edit-rule" data-id="${rule.id}">Изменить</button>
|
||||||
<button type="button" class="schedule-rule-table-button schedule-rule-table-button-delete btn-delete-rule" data-id="${rule.id}">Удалить</button>
|
<button type="button" class="btn btn-sm btn-danger btn-delete-rule" data-id="${rule.id}">Удалить</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -857,7 +857,7 @@ export async function initSchedule() {
|
|||||||
<div class="form-group"><label>Тип</label><select class="slot-lesson-type" required>${options(orderedLessonTypes().map(toLessonTypeOption), slot.lessonTypeId)}</select></div>
|
<div class="form-group"><label>Тип</label><select class="slot-lesson-type" required>${options(orderedLessonTypes().map(toLessonTypeOption), slot.lessonTypeId)}</select></div>
|
||||||
${isLaboratorySlot(slot) ? renderSubgroupField(slot, index) : ''}
|
${isLaboratorySlot(slot) ? renderSubgroupField(slot, index) : ''}
|
||||||
<div class="form-group"><label>Формат</label><select class="slot-format" required>${options([{ value: 'Очно', label: 'Очно' }, { value: 'Онлайн', label: 'Онлайн' }], slot.lessonFormat || 'Очно')}</select></div>
|
<div class="form-group"><label>Формат</label><select class="slot-format" required>${options([{ value: 'Очно', label: 'Очно' }, { value: 'Онлайн', label: 'Онлайн' }], slot.lessonFormat || 'Очно')}</select></div>
|
||||||
<button type="button" class="btn-delete schedule-slot-remove" data-index="${index}">Удалить</button>
|
<button type="button" class="btn btn-sm btn-danger schedule-slot-remove" data-index="${index}">Удалить</button>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ export async function initSubjects() {
|
|||||||
<td>${escapeHtml(departmentLabel(s.departmentId))}</td>
|
<td>${escapeHtml(departmentLabel(s.departmentId))}</td>
|
||||||
<td><div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">${teachersHtml}</div></td>
|
<td><div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">${teachersHtml}</div></td>
|
||||||
<td style="text-align: right; white-space: nowrap;">
|
<td style="text-align: right; white-space: nowrap;">
|
||||||
<button class="btn-primary btn-manage-teachers" data-id="${s.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Преподаватели</button>
|
<button class="btn btn-sm btn-secondary btn-manage-teachers" data-id="${s.id}">Преподаватели</button>
|
||||||
<button class="btn-delete btn-delete-subject" data-id="${s.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-subject" data-id="${s.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
@@ -175,7 +175,7 @@ export async function initSubjects() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td><strong>${escapeHtml(ts.fullName || ts.username)}</strong> <span class="text-muted">(${escapeHtml(ts.username)})</span></td>
|
<td><strong>${escapeHtml(ts.fullName || ts.username)}</strong> <span class="text-muted">(${escapeHtml(ts.username)})</span></td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-delete btn-delete-ts" data-user-id="${ts.userId}" data-subject-id="${ts.subjectId}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-ts" data-user-id="${ts.userId}" data-subject-id="${ts.subjectId}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
@@ -129,8 +129,8 @@ export async function initUniversityStructure() {
|
|||||||
<td>${escapeHtml(d.departmentName || d.name)}</td>
|
<td>${escapeHtml(d.departmentName || d.name)}</td>
|
||||||
<td>${escapeHtml(String(d.departmentCode || d.code))}</td>
|
<td>${escapeHtml(String(d.departmentCode || d.code))}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-edit-classroom btn-edit-department" data-id="${d.id}">Изменить</button>
|
<button class="btn btn-sm btn-secondary btn-edit-department" data-id="${d.id}">Изменить</button>
|
||||||
<button class="btn-delete btn-delete-department" data-id="${d.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-department" data-id="${d.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -147,9 +147,9 @@ export async function initUniversityStructure() {
|
|||||||
<td>${escapeHtml(s.specialityName || s.name)}</td>
|
<td>${escapeHtml(s.specialityName || s.name)}</td>
|
||||||
<td>${escapeHtml(s.specialityCode || s.specialtyCode || s.specialty_code)}</td>
|
<td>${escapeHtml(s.specialityCode || s.specialtyCode || s.specialty_code)}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-edit-classroom btn-edit-specialty" data-id="${s.id}">Изменить</button>
|
<button class="btn btn-sm btn-secondary btn-edit-specialty" data-id="${s.id}">Изменить</button>
|
||||||
<button class="btn-primary btn-manage-profiles" data-id="${s.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Профили</button>
|
<button class="btn btn-sm btn-secondary btn-manage-profiles" data-id="${s.id}">Профили</button>
|
||||||
<button class="btn-delete btn-delete-specialty" data-id="${s.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-specialty" data-id="${s.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -338,8 +338,8 @@ export async function initUniversityStructure() {
|
|||||||
<td>${escapeHtml(p.name)}</td>
|
<td>${escapeHtml(p.name)}</td>
|
||||||
<td>${escapeHtml(p.description || '-')}</td>
|
<td>${escapeHtml(p.description || '-')}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-edit-classroom btn-edit-profile" data-id="${p.id}">Изменить</button>
|
<button class="btn btn-sm btn-secondary btn-edit-profile" data-id="${p.id}">Изменить</button>
|
||||||
<button class="btn-delete btn-delete-profile" data-id="${p.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-profile" data-id="${p.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -453,8 +453,8 @@ export async function initUniversityStructure() {
|
|||||||
<td>${escapeHtml(p.name)}</td>
|
<td>${escapeHtml(p.name)}</td>
|
||||||
<td>${escapeHtml(p.description || '-')}</td>
|
<td>${escapeHtml(p.description || '-')}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-edit-classroom btn-edit-tab-profile" data-id="${p.id}">Изменить</button>
|
<button class="btn btn-sm btn-secondary btn-edit-tab-profile" data-id="${p.id}">Изменить</button>
|
||||||
<button class="btn-delete btn-delete-tab-profile" data-id="${p.id}">Удалить</button>
|
<button class="btn btn-sm btn-danger btn-delete-tab-profile" data-id="${p.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function initUsers() {
|
|||||||
<td>
|
<td>
|
||||||
${u.status === 'ARCHIVED'
|
${u.status === 'ARCHIVED'
|
||||||
? `<button class="btn-restore-user" data-id="${u.id}">Восстановить</button>`
|
? `<button class="btn-restore-user" data-id="${u.id}">Восстановить</button>`
|
||||||
: `<button class="btn-delete" data-id="${u.id}">Архивировать</button>`}
|
: `<button class="btn btn-sm btn-danger-subtle btn-archive-user" data-id="${u.id}">Архивировать</button>`}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -90,7 +90,7 @@ export async function initUsers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
usersTbody.addEventListener('click', async (e) => {
|
usersTbody.addEventListener('click', async (e) => {
|
||||||
const deleteBtn = e.target.closest('.btn-delete');
|
const deleteBtn = e.target.closest('.btn-archive-user');
|
||||||
if (!deleteBtn) return;
|
if (!deleteBtn) return;
|
||||||
|
|
||||||
if (!confirm('Архивировать пользователя? История расписания и связей сохранится.')) return;
|
if (!confirm('Архивировать пользователя? История расписания и связей сохранится.')) return;
|
||||||
|
|||||||
@@ -25,15 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-close-btn {
|
.sidebar-close-btn {
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.25rem;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
transition: all var(--transition);
|
transition: all var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,12 +257,7 @@
|
|||||||
/* ===== Mobile Menu Toggle ===== */
|
/* ===== Mobile Menu Toggle ===== */
|
||||||
.menu-toggle {
|
.menu-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 0.4rem;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
transition: background var(--transition);
|
transition: background var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,9 @@
|
|||||||
--text-primary: #0f172a;
|
--text-primary: #0f172a;
|
||||||
--text-secondary: #475569;
|
--text-secondary: #475569;
|
||||||
--text-placeholder: #94a3b8;
|
--text-placeholder: #94a3b8;
|
||||||
--accent: #6366f1;
|
--accent: #4F46E5;
|
||||||
--accent-hover: #4f46e5;
|
--accent-hover: #4338CA;
|
||||||
--accent-glow: rgba(99, 102, 241, 0.3);
|
--accent-glow: rgba(79, 70, 229, 0.3);
|
||||||
--accent-secondary: #d946ef;
|
--accent-secondary: #d946ef;
|
||||||
--error: #ef4444;
|
--error: #ef4444;
|
||||||
--success: #10b981;
|
--success: #10b981;
|
||||||
@@ -77,20 +77,19 @@ body {
|
|||||||
|
|
||||||
/* ===== Переключатель темы ===== */
|
/* ===== Переключатель темы ===== */
|
||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
width: 42px;
|
width: 40px;
|
||||||
height: 42px;
|
height: 40px;
|
||||||
border: none;
|
border-radius: var(--button-radius);
|
||||||
border-radius: 50%;
|
background: var(--button-secondary-bg);
|
||||||
background: var(--bg-card);
|
border: 1px solid var(--button-secondary-border);
|
||||||
border: 1px solid var(--bg-card-border);
|
color: var(--button-secondary-text);
|
||||||
color: var(--text-primary);
|
display: inline-flex;
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
|
padding: 0;
|
||||||
|
transition: background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition), transform var(--transition);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -102,12 +101,18 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:hover {
|
.theme-toggle:hover {
|
||||||
transform: scale(1.1);
|
background: var(--button-secondary-bg-hover);
|
||||||
box-shadow: 0 4px 16px var(--accent-glow);
|
border-color: var(--button-secondary-border);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:active {
|
.theme-toggle:active {
|
||||||
transform: scale(0.95);
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px var(--button-focus-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle--fixed {
|
.theme-toggle--fixed {
|
||||||
@@ -265,63 +270,6 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary,
|
|
||||||
.btn-secondary,
|
|
||||||
.btn-delete {
|
|
||||||
min-height: 40px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 0 0.9rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font: inherit;
|
|
||||||
font-weight: 700;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform var(--transition), background var(--transition), border-color var(--transition);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: var(--accent-hover);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: var(--bg-input);
|
|
||||||
border-color: var(--bg-card-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-delete {
|
|
||||||
background: rgba(239, 68, 68, 0.13);
|
|
||||||
border-color: rgba(239, 68, 68, 0.25);
|
|
||||||
color: #fecaca;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="light"] .btn-delete {
|
|
||||||
color: #991b1b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-delete:hover {
|
|
||||||
background: rgba(239, 68, 68, 0.2);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled,
|
|
||||||
.btn-secondary:disabled,
|
|
||||||
.btn-delete:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.45;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-alert {
|
.form-alert {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -614,7 +562,7 @@ body {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form .btn-primary {
|
.settings-form .btn {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,9 +578,16 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header-row .btn-primary,
|
.settings-header-row .btn,
|
||||||
.card-header-row .btn-secondary,
|
.card-header-row .btn,
|
||||||
.settings-form .btn-primary {
|
.settings-form .btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.settings-modal-close.btn-icon-sm {
|
||||||
|
width: 32px !important;
|
||||||
|
height: 32px !important;
|
||||||
|
min-height: 32px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: var(--button-radius) !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span>Настройки</span>
|
<span>Настройки</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="sidebar-close-btn" id="sidebar-close-btn" aria-label="Скрыть панель">
|
<button class="btn btn-icon-sm btn-ghost sidebar-close-btn" id="sidebar-close-btn" aria-label="Скрыть панель">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<polyline points="15 18 9 12 15 6" />
|
<polyline points="15 18 9 12 15 6" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<!-- Основная область -->
|
<!-- Основная область -->
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<button class="menu-toggle" id="menu-toggle" aria-label="Меню">
|
<button class="btn btn-icon-md btn-ghost menu-toggle" id="menu-toggle" aria-label="Меню">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<line x1="3" y1="6" x2="21" y2="6" />
|
<line x1="3" y1="6" x2="21" y2="6" />
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export async function initDatabase() {
|
|||||||
<td><code style="font-size: 0.82rem;">${escapeHtml(t.url)}</code></td>
|
<td><code style="font-size: 0.82rem;">${escapeHtml(t.url)}</code></td>
|
||||||
<td>${escapeHtml(t.username || '—')}</td>
|
<td>${escapeHtml(t.username || '—')}</td>
|
||||||
<td>${statusBadge}</td>
|
<td>${statusBadge}</td>
|
||||||
<td><button class="btn-delete" data-domain="${escapeHtml(t.domain)}">Удалить</button></td>
|
<td><button class="btn btn-sm btn-danger btn-delete-tenant" data-domain="${escapeHtml(t.domain)}">Удалить</button></td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ export async function initDatabase() {
|
|||||||
|
|
||||||
// === Удаление тенанта ===
|
// === Удаление тенанта ===
|
||||||
tenantsTbody.addEventListener('click', async (e) => {
|
tenantsTbody.addEventListener('click', async (e) => {
|
||||||
const btn = e.target.closest('.btn-delete');
|
const btn = e.target.closest('.btn-delete-tenant');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|
||||||
const domain = btn.dataset.domain;
|
const domain = btn.dataset.domain;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export async function initEduForms() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>${ef.id}</td>
|
<td>${ef.id}</td>
|
||||||
<td>${escapeHtml(ef.name)}</td>
|
<td>${escapeHtml(ef.name)}</td>
|
||||||
<td><button class="btn-delete" data-id="${ef.id}">Удалить</button></td>
|
<td><button class="btn btn-sm btn-danger btn-delete-edu-form" data-id="${ef.id}">Удалить</button></td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export async function initEduForms() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
efTbody.addEventListener('click', async (e) => {
|
efTbody.addEventListener('click', async (e) => {
|
||||||
const btn = e.target.closest('.btn-delete');
|
const btn = e.target.closest('.btn-delete-edu-form');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
if (!confirm('Удалить форму обучения?')) return;
|
if (!confirm('Удалить форму обучения?')) return;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ export async function initTimeSlotsSettings() {
|
|||||||
<td>${escapeHtml(trimTime(slot.endTime))}</td>
|
<td>${escapeHtml(trimTime(slot.endTime))}</td>
|
||||||
<td>${escapeHtml(String(slot.durationMinutes ?? '-'))}</td>
|
<td>${escapeHtml(String(slot.durationMinutes ?? '-'))}</td>
|
||||||
<td class="actions-cell">
|
<td class="actions-cell">
|
||||||
<button type="button" class="btn-secondary btn-edit-time-slot" data-id="${slot.id}">Изменить</button>
|
<button type="button" class="btn btn-sm btn-secondary btn-edit-time-slot" data-id="${slot.id}">Изменить</button>
|
||||||
<button type="button" class="btn-delete btn-delete-time-slot" data-id="${slot.id}">Удалить</button>
|
<button type="button" class="btn btn-sm btn-danger btn-delete-time-slot" data-id="${slot.id}">Удалить</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
@@ -61,10 +61,10 @@
|
|||||||
<label for="tenant-password">Пароль</label>
|
<label for="tenant-password">Пароль</label>
|
||||||
<input type="password" id="tenant-password" placeholder="••••••••" required>
|
<input type="password" id="tenant-password" placeholder="••••••••" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-primary" id="btn-test-connection" style="height: fit-content;">
|
<button type="button" class="btn btn-md btn-secondary" id="btn-test-connection" style="height: fit-content;">
|
||||||
Тест
|
Тест
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="btn-primary" style="height: fit-content;">
|
<button type="submit" class="btn btn-md btn-primary" style="height: fit-content;">
|
||||||
Добавить
|
Добавить
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<label for="new-ef-name">Название</label>
|
<label for="new-ef-name">Название</label>
|
||||||
<input type="text" id="new-ef-name" placeholder="Бакалавриат" required>
|
<input type="text" id="new-ef-name" placeholder="Бакалавриат" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary">Создать</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="create-ef-alert" role="alert"></div>
|
<div class="form-alert" id="create-ef-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<h2>Временные слоты</h2>
|
<h2>Временные слоты</h2>
|
||||||
<p>Настройка базовой, субботней и ручных сеток времени.</p>
|
<p>Настройка базовой, субботней и ручных сеток времени.</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-primary" id="time-slots-refresh">Обновить</button>
|
<button type="button" class="btn btn-md btn-secondary" id="time-slots-refresh">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-grid">
|
<div class="settings-grid">
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h3>Сетка времени</h3>
|
<h3>Сетка времени</h3>
|
||||||
<div class="scope-header-actions">
|
<div class="scope-header-actions">
|
||||||
<button type="button" class="btn-primary" id="time-slot-scope-open-create">Новая ручная сетка</button>
|
<button type="button" class="btn btn-md btn-primary" id="time-slot-scope-open-create">Новая ручная сетка</button>
|
||||||
<button type="button" class="btn-secondary" id="time-slot-scope-delete">Удалить сетку</button>
|
<button type="button" class="btn btn-md btn-danger-subtle" id="time-slot-scope-delete">Удалить сетку</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<article class="settings-card">
|
<article class="settings-card">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h3 id="time-slot-form-title">Новый слот</h3>
|
<h3 id="time-slot-form-title">Новый слот</h3>
|
||||||
<button type="button" class="btn-secondary" id="time-slot-reset">Очистить</button>
|
<button type="button" class="btn btn-md btn-ghost" id="time-slot-reset">Очистить</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="time-slot-form" class="settings-form" novalidate>
|
<form id="time-slot-form" class="settings-form" novalidate>
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
<input type="number" id="time-slot-duration" min="1" step="1" placeholder="90">
|
<input type="number" id="time-slot-duration" min="1" step="1" placeholder="90">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button type="submit" class="btn-primary">Сохранить слот</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить слот</button>
|
||||||
<div class="form-alert" id="time-slot-alert" role="alert"></div>
|
<div class="form-alert" id="time-slot-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
@@ -106,7 +106,12 @@
|
|||||||
<h3 id="time-slot-scope-modal-title">Новая ручная сетка</h3>
|
<h3 id="time-slot-scope-modal-title">Новая ручная сетка</h3>
|
||||||
<p class="settings-modal-subtitle">Создайте отдельную сетку времени, которую затем можно назначать датам в календарном графике.</p>
|
<p class="settings-modal-subtitle">Создайте отдельную сетку времени, которую затем можно назначать датам в календарном графике.</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="settings-modal-close" id="time-slot-scope-modal-close" aria-label="Закрыть">×</button>
|
<button type="button" class="btn btn-icon-sm btn-secondary settings-modal-close" id="time-slot-scope-modal-close" aria-label="Закрыть">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="time-slot-scope-form" class="settings-modal-form" novalidate>
|
<form id="time-slot-scope-form" class="settings-modal-form" novalidate>
|
||||||
@@ -116,8 +121,8 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="settings-modal-actions">
|
<div class="settings-modal-actions">
|
||||||
<button type="submit" class="btn-primary">Создать сетку</button>
|
<button type="submit" class="btn btn-md btn-primary">Создать сетку</button>
|
||||||
<button type="button" class="btn-secondary" id="time-slot-scope-modal-cancel">Отмена</button>
|
<button type="button" class="btn btn-md btn-ghost" id="time-slot-scope-modal-cancel">Отмена</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-alert" id="time-slot-scope-alert" role="alert"></div>
|
<div class="form-alert" id="time-slot-scope-alert" role="alert"></div>
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
<label for="academic-year-end">Окончание</label>
|
<label for="academic-year-end">Окончание</label>
|
||||||
<input type="date" id="academic-year-end" required>
|
<input type="date" id="academic-year-end" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Сохранить год</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить год</button>
|
||||||
<button type="button" class="btn-edit-classroom" id="academic-year-reset">Очистить</button>
|
<button type="button" class="btn btn-md btn-ghost" id="academic-year-reset">Очистить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="academic-year-alert" role="alert"></div>
|
<div class="form-alert" id="academic-year-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2>Учебные годы</h2>
|
<h2>Учебные годы</h2>
|
||||||
<button class="btn-primary" id="academic-years-refresh">Обновить</button>
|
<button class="btn btn-md btn-secondary" id="academic-years-refresh">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
@@ -72,8 +72,8 @@
|
|||||||
<label for="semester-end">Окончание</label>
|
<label for="semester-end">Окончание</label>
|
||||||
<input type="date" id="semester-end" required>
|
<input type="date" id="semester-end" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Сохранить семестр</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить семестр</button>
|
||||||
<button type="button" class="btn-edit-classroom" id="semester-reset">Очистить</button>
|
<button type="button" class="btn btn-md btn-ghost" id="semester-reset">Очистить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="semester-alert" role="alert"></div>
|
<div class="form-alert" id="semester-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<div class="card create-card">
|
<div class="card create-card">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2 id="academic-calendar-form-title">Календарный учебный график</h2>
|
<h2 id="academic-calendar-form-title">Календарный учебный график</h2>
|
||||||
<button type="button" class="btn-edit-classroom" id="academic-calendar-reset">Очистить</button>
|
<button type="button" class="btn btn-md btn-ghost" id="academic-calendar-reset">Очистить</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="academic-calendar-form" novalidate>
|
<form id="academic-calendar-form" novalidate>
|
||||||
<input type="hidden" id="academic-calendar-id">
|
<input type="hidden" id="academic-calendar-id">
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
<label for="academic-calendar-course-count">Курсов</label>
|
<label for="academic-calendar-course-count">Курсов</label>
|
||||||
<input type="number" id="academic-calendar-course-count" min="1" max="8" value="4" required>
|
<input type="number" id="academic-calendar-course-count" min="1" max="8" value="4" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Сохранить график</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить график</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="academic-calendar-alert" role="alert"></div>
|
<div class="form-alert" id="academic-calendar-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2>Календарные графики</h2>
|
<h2>Календарные графики</h2>
|
||||||
<button class="btn-primary" id="academic-calendars-refresh">Обновить</button>
|
<button class="btn btn-md btn-secondary" id="academic-calendars-refresh">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
@@ -161,8 +161,8 @@
|
|||||||
<option value="">Загрузка...</option>
|
<option value="">Загрузка...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-primary" id="calendar-editor-load">Загрузить сетку</button>
|
<button type="button" class="btn btn-md btn-secondary" id="calendar-editor-load">Загрузить сетку</button>
|
||||||
<button type="button" class="btn-primary" id="calendar-editor-save">Сохранить сетку</button>
|
<button type="button" class="btn btn-md btn-primary" id="calendar-editor-save">Сохранить сетку</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="calendar-fill-panel">
|
<div class="calendar-fill-panel">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
<option value="">Загрузка...</option>
|
<option value="">Загрузка...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-edit-classroom" id="calendar-fill-apply">Заполнить диапазон</button>
|
<button type="button" class="btn btn-md btn-secondary" id="calendar-fill-apply">Заполнить диапазон</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="calendar-editor-alert" role="alert"></div>
|
<div class="form-alert" id="calendar-editor-alert" role="alert"></div>
|
||||||
<div id="calendar-totals" class="calendar-totals"></div>
|
<div id="calendar-totals" class="calendar-totals"></div>
|
||||||
@@ -229,8 +229,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="calendar-day-dialog-actions">
|
<div class="calendar-day-dialog-actions">
|
||||||
<button type="button" class="btn-primary" id="calendar-day-dialog-time-save">Применить</button>
|
<button type="button" class="btn btn-md btn-primary" id="calendar-day-dialog-time-save">Применить</button>
|
||||||
<button type="button" class="btn-edit-classroom" id="calendar-day-dialog-time-clear">Убрать ручную сетку</button>
|
<button type="button" class="btn btn-md btn-danger-subtle" id="calendar-day-dialog-time-clear">Убрать ручную сетку</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="calendar-day-dialog-time-alert" role="alert"></div>
|
<div class="form-alert" id="calendar-day-dialog-time-alert" role="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header-row" style="margin-bottom: 1.5rem;">
|
<div class="card-header-row" style="margin-bottom: 1.5rem;">
|
||||||
<h2>Загруженность</h2>
|
<h2>Загруженность</h2>
|
||||||
<button type="button" class="btn-primary" id="workload-refresh">Обновить</button>
|
<button type="button" class="btn btn-md btn-secondary" id="workload-refresh">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-row workload-filter-row" style="margin-bottom: 2rem; align-items: flex-end; gap: 1.5rem;">
|
<div class="filter-row workload-filter-row" style="margin-bottom: 2rem; align-items: flex-end; gap: 1.5rem;">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="card create-card">
|
<div class="card create-card">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.5rem;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.5rem;">
|
||||||
<h2 style="margin: 0;">Новая аудитория</h2>
|
<h2 style="margin: 0;">Новая аудитория</h2>
|
||||||
<button type="button" class="btn-primary" id="btn-manage-equipments">Справочник оборудования</button>
|
<button type="button" class="btn btn-md btn-secondary" id="btn-manage-equipments">Справочник оборудования</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="create-classroom-form">
|
<form id="create-classroom-form">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="display: flex; align-items: flex-end;">
|
<div class="form-group" style="display: flex; align-items: flex-end;">
|
||||||
<button type="submit" class="btn-primary">Добавить</button>
|
<button type="submit" class="btn btn-md btn-primary">Добавить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="create-classroom-alert" role="alert"></div>
|
<div class="form-alert" id="create-classroom-alert" role="alert"></div>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="display: flex; align-items: flex-end;">
|
<div class="form-group" style="display: flex; align-items: flex-end;">
|
||||||
<button type="submit" class="btn-primary" style="width: 100%;">Сохранить</button>
|
<button type="submit" class="btn btn-md btn-primary" style="width: 100%;">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="edit-classroom-alert" role="alert"></div>
|
<div class="form-alert" id="edit-classroom-alert" role="alert"></div>
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
<label for="manage-equipment-name">Название оборудования</label>
|
<label for="manage-equipment-name">Название оборудования</label>
|
||||||
<input type="text" id="manage-equipment-name" placeholder="Проектор" required>
|
<input type="text" id="manage-equipment-name" placeholder="Проектор" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary" style="height: fit-content; margin-bottom: 0;">Добавить</button>
|
<button type="submit" class="btn btn-md btn-primary" style="height: fit-content; margin-bottom: 0;">Добавить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="manage-equipment-alert" role="alert"></div>
|
<div class="form-alert" id="manage-equipment-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -152,4 +152,4 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- ===== Dashboard Page ===== -->
|
<!-- ===== Dashboard Page ===== -->
|
||||||
<div class="dashboard-container" style="display: flex; flex-direction: column; gap: 1.5rem;">
|
<div class="dashboard-container">
|
||||||
<!-- Метрики (Информационные карточки) -->
|
<!-- Метрики (Информационные карточки) -->
|
||||||
<div class="metrics-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.25rem;">
|
<div class="dashboard-metrics-grid">
|
||||||
<div class="metric-card card" style="display: flex; align-items: center; gap: 1rem; padding: 1.25rem; border-left: 4px solid var(--primary);">
|
<div class="card dashboard-metric-card dashboard-metric-card--groups">
|
||||||
<div class="metric-icon" style="background: rgba(99, 102, 241, 0.1); color: var(--primary); padding: 0.75rem; border-radius: 12px; display: flex; align-items: center; justify-content: center;">
|
<div class="metric-icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
||||||
<circle cx="9" cy="7" r="4" />
|
<circle cx="9" cy="7" r="4" />
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="metric-card card" style="display: flex; align-items: center; gap: 1rem; padding: 1.25rem; border-left: 4px solid #10b981;">
|
<div class="card dashboard-metric-card dashboard-metric-card--students">
|
||||||
<div class="metric-icon" style="background: rgba(16, 185, 129, 0.1); color: #10b981; padding: 0.75rem; border-radius: 12px; display: flex; align-items: center; justify-content: center;">
|
<div class="metric-icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||||
<circle cx="9" cy="7" r="4" />
|
<circle cx="9" cy="7" r="4" />
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="metric-card card" style="display: flex; align-items: center; gap: 1rem; padding: 1.25rem; border-left: 4px solid #f59e0b;">
|
<div class="card dashboard-metric-card dashboard-metric-card--teachers">
|
||||||
<div class="metric-icon" style="background: rgba(245, 158, 11, 0.1); color: #f59e0b; padding: 0.75rem; border-radius: 12px; display: flex; align-items: center; justify-content: center;">
|
<div class="metric-icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||||
<circle cx="12" cy="7" r="4" />
|
<circle cx="12" cy="7" r="4" />
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="metric-card card" style="display: flex; align-items: center; gap: 1rem; padding: 1.25rem; border-left: 4px solid #3b82f6;">
|
<div class="card dashboard-metric-card dashboard-metric-card--classrooms">
|
||||||
<div class="metric-icon" style="background: rgba(59, 130, 246, 0.1); color: #3b82f6; padding: 0.75rem; border-radius: 12px; display: flex; align-items: center; justify-content: center;">
|
<div class="metric-icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<rect width="18" height="18" x="3" y="3" rx="2" />
|
<rect width="18" height="18" x="3" y="3" rx="2" />
|
||||||
<path d="M9 3v18M15 3v18M3 9h18M3 15h18" />
|
<path d="M9 3v18M15 3v18M3 9h18M3 15h18" />
|
||||||
@@ -60,14 +60,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Мониторинг в реальном времени и Быстрый переход -->
|
<!-- Мониторинг в реальном времени и Быстрый переход -->
|
||||||
<div class="dashboard-middle" style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.25rem; align-items: start;">
|
<div class="dashboard-middle">
|
||||||
<!-- Идет сейчас -->
|
<!-- Идет сейчас -->
|
||||||
<div class="card" style="min-height: 250px; display: flex; flex-direction: column;">
|
<div class="card dashboard-monitor-card">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
<div class="dashboard-card-header">
|
||||||
<h2 style="margin: 0;">Мониторинг пар (Идет сейчас)</h2>
|
<h2 style="margin: 0;">Мониторинг пар (Идет сейчас)</h2>
|
||||||
<div id="current-time-slot" style="background: var(--bg-secondary); padding: 0.35rem 0.75rem; border-radius: 20px; font-size: 0.85rem; font-weight: 600; color: var(--primary);">—</div>
|
<div id="current-time-slot" class="dashboard-time-slot">—</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap" style="flex: 1;">
|
<div class="table-wrap dashboard-monitor-table">
|
||||||
<table style="width: 100%; border-collapse: collapse;">
|
<table style="width: 100%; border-collapse: collapse;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -87,20 +87,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Свободные аудитории и Быстрые действия -->
|
<!-- Свободные аудитории и Быстрые действия -->
|
||||||
<div style="display: flex; flex-direction: column; gap: 1.25rem;">
|
<div class="dashboard-side-stack">
|
||||||
<!-- Свободные аудитории -->
|
<!-- Свободные аудитории -->
|
||||||
<div class="card" style="padding: 1.25rem; display: flex; flex-direction: column; gap: 0.75rem;">
|
<div class="card dashboard-compact-card">
|
||||||
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 600;">Свободные аудитории (ближайшие)</h3>
|
<h3 class="dashboard-card-title">Свободные аудитории (ближайшие)</h3>
|
||||||
<div id="free-classrooms-list" style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem;">
|
<div id="free-classrooms-list" class="dashboard-badge-list">
|
||||||
<div style="color: var(--text-secondary); font-size: 0.9rem;">Загрузка...</div>
|
<div style="color: var(--text-secondary); font-size: 0.9rem;">Загрузка...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Быстрые действия -->
|
<!-- Быстрые действия -->
|
||||||
<div class="card" style="padding: 1.25rem; display: flex; flex-direction: column; gap: 0.75rem;">
|
<div class="card dashboard-compact-card">
|
||||||
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 600;">Быстрый переход</h3>
|
<h3 class="dashboard-card-title">Быстрый переход</h3>
|
||||||
<div style="display: grid; grid-template-columns: 1fr; gap: 0.5rem; margin-top: 0.5rem;">
|
<div class="dashboard-actions">
|
||||||
<a href="#schedule" class="btn-primary" style="display: flex; align-items: center; justify-content: center; gap: 0.5rem; text-decoration: none; text-align: center; font-size: 0.9rem; padding: 0.6rem;">
|
<a href="#schedule" class="btn btn-md btn-primary dashboard-action-link">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
||||||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Конструктор расписания
|
Конструктор расписания
|
||||||
</a>
|
</a>
|
||||||
<a href="#schedule-view" class="btn-secondary" style="display: flex; align-items: center; justify-content: center; gap: 0.5rem; text-decoration: none; text-align: center; font-size: 0.9rem; padding: 0.6rem;">
|
<a href="#schedule-view" class="btn btn-md btn-secondary dashboard-action-link">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" />
|
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" />
|
||||||
<circle cx="12" cy="12" r="3" />
|
<circle cx="12" cy="12" r="3" />
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div style="color: var(--text-secondary); font-size: 0.85rem; margin-top: 0.25rem;">Анализ накладок и коллизий расписания на текущую неделю</div>
|
<div style="color: var(--text-secondary); font-size: 0.85rem; margin-top: 0.25rem;">Анализ накладок и коллизий расписания на текущую неделю</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-primary" id="btn-recheck-conflicts" style="font-size: 0.85rem; padding: 0.35rem 0.75rem;">Перепроверить</button>
|
<button class="btn btn-sm btn-secondary" id="btn-recheck-conflicts">Перепроверить</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="conflicts-container" style="display: flex; flex-direction: column; gap: 0.75rem; margin-top: 1rem;">
|
<div id="conflicts-container" style="display: flex; flex-direction: column; gap: 0.75rem; margin-top: 1rem;">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2>Рабочая область кафедры</h2>
|
<h2>Рабочая область кафедры</h2>
|
||||||
<button type="button" class="btn-primary" id="department-workspace-refresh">Обновить</button>
|
<button type="button" class="btn btn-md btn-secondary" id="department-workspace-refresh">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="schedule-view-filter-grid">
|
<div class="schedule-view-filter-grid">
|
||||||
@@ -33,11 +33,11 @@
|
|||||||
<div class="subject-import-slots-panel">
|
<div class="subject-import-slots-panel">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<label class="schedule-section-label" style="margin:0">Дисциплины для загрузки</label>
|
<label class="schedule-section-label" style="margin:0">Дисциплины для загрузки</label>
|
||||||
<button type="button" class="btn-edit-classroom" id="subject-import-add">Добавить строку</button>
|
<button type="button" class="btn btn-sm btn-secondary" id="subject-import-add">Добавить строку</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="subject-import-rows" class="subject-import-rows-list"></div>
|
<div id="subject-import-rows" class="subject-import-rows-list"></div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Загрузить</button>
|
<button type="submit" class="btn btn-md btn-secondary">Загрузить</button>
|
||||||
<div class="form-alert" id="department-subject-import-alert" role="alert"></div>
|
<div class="form-alert" id="department-subject-import-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<div class="card" id="department-comments-card" hidden>
|
<div class="card" id="department-comments-card" hidden>
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2 id="department-comments-title">Комментарии</h2>
|
<h2 id="department-comments-title">Комментарии</h2>
|
||||||
<button type="button" class="btn-edit-classroom" id="department-comments-close">Скрыть</button>
|
<button type="button" class="btn btn-sm btn-ghost" id="department-comments-close">Скрыть</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="department-comment-form" class="form-row">
|
<form id="department-comment-form" class="form-row">
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<label for="department-comment-text">Новый комментарий</label>
|
<label for="department-comment-text">Новый комментарий</label>
|
||||||
<input type="text" id="department-comment-text" placeholder="Добавьте примечание по дисциплине">
|
<input type="text" id="department-comment-text" placeholder="Добавьте примечание по дисциплине">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Сохранить</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="form-alert" id="department-comment-alert" role="alert"></div>
|
<div class="form-alert" id="department-comment-alert" role="alert"></div>
|
||||||
<div class="department-comment-list" id="department-comment-list">Выберите дисциплину</div>
|
<div class="department-comment-list" id="department-comment-list">Выберите дисциплину</div>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<option value="">Выберите специальность</option>
|
<option value="">Выберите специальность</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary">Создать</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="create-group-alert" role="alert"></div>
|
<div class="form-alert" id="create-group-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="display: flex; align-items: flex-end;">
|
<div class="form-group" style="display: flex; align-items: flex-end;">
|
||||||
<button type="submit" class="btn-primary" style="width: 100%;">Сохранить</button>
|
<button type="submit" class="btn btn-md btn-primary" style="width: 100%;">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="edit-group-alert" role="alert"></div>
|
<div class="form-alert" id="edit-group-alert" role="alert"></div>
|
||||||
@@ -162,8 +162,8 @@
|
|||||||
<input type="number" id="manage-subgroup-capacity" min="1" placeholder="12" required>
|
<input type="number" id="manage-subgroup-capacity" min="1" placeholder="12" required>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 0;">
|
<div style="display: flex; gap: 0.5rem; margin-bottom: 0;">
|
||||||
<button type="submit" class="btn-primary" id="btn-save-subgroup">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary" id="btn-save-subgroup">Создать</button>
|
||||||
<button type="button" class="btn-secondary" id="btn-cancel-subgroup-edit" style="display: none;">Отмена</button>
|
<button type="button" class="btn btn-md btn-ghost" id="btn-cancel-subgroup-edit" style="display: none;">Отмена</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="manage-subgroup-alert" role="alert"></div>
|
<div class="form-alert" id="manage-subgroup-alert" role="alert"></div>
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
<option value="">Выберите учебный год</option>
|
<option value="">Выберите учебный год</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary" style="height: fit-content; margin-bottom: 0;">Назначить</button>
|
<button type="submit" class="btn btn-md btn-primary" style="height: fit-content; margin-bottom: 0;">Назначить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="manage-calendar-alert" role="alert"></div>
|
<div class="form-alert" id="manage-calendar-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<h2>Расписания</h2>
|
<h2>Расписания</h2>
|
||||||
<span class="muted" id="schedule-view-period-label">Период не выбран</span>
|
<span class="muted" id="schedule-view-period-label">Период не выбран</span>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-primary" id="schedule-view-load">Показать</button>
|
<button type="button" class="btn btn-md btn-secondary" id="schedule-view-load">Показать</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="schedule-view-control-grid">
|
<div class="schedule-view-control-grid">
|
||||||
@@ -87,9 +87,17 @@
|
|||||||
<span class="muted" id="schedule-view-count">0 занятий</span>
|
<span class="muted" id="schedule-view-count">0 занятий</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="schedule-view-result-actions">
|
<div class="schedule-view-result-actions">
|
||||||
<button type="button" class="btn-secondary" id="schedule-view-result-prev" aria-label="Предыдущее расписание">‹</button>
|
<button type="button" class="btn btn-icon-md btn-secondary" id="schedule-view-result-prev" aria-label="Предыдущее расписание">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<polyline points="15 18 9 12 15 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<span id="schedule-view-active-label">Нет выбранного расписания</span>
|
<span id="schedule-view-active-label">Нет выбранного расписания</span>
|
||||||
<button type="button" class="btn-secondary" id="schedule-view-result-next" aria-label="Следующее расписание">›</button>
|
<button type="button" class="btn btn-icon-md btn-secondary" id="schedule-view-result-next" aria-label="Следующее расписание">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<polyline points="9 18 15 12 9 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="schedule-view-result-tabs" id="schedule-view-result-tabs"></div>
|
<div class="schedule-view-result-tabs" id="schedule-view-result-tabs"></div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="card create-card">
|
<div class="card create-card">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2 id="schedule-rule-form-title">Новое правило расписания</h2>
|
<h2 id="schedule-rule-form-title">Новое правило расписания</h2>
|
||||||
<button type="button" class="btn-edit-classroom" id="schedule-rule-reset">Очистить форму</button>
|
<button type="button" class="btn btn-sm btn-ghost" id="schedule-rule-reset">Очистить форму</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="schedule-rule-form" novalidate>
|
<form id="schedule-rule-form" novalidate>
|
||||||
<input type="hidden" id="schedule-rule-id">
|
<input type="hidden" id="schedule-rule-id">
|
||||||
@@ -79,13 +79,13 @@
|
|||||||
<div class="schedule-slots-panel">
|
<div class="schedule-slots-panel">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2>Слоты правила</h2>
|
<h2>Слоты правила</h2>
|
||||||
<button type="button" class="btn-edit-classroom" id="schedule-slot-add">Добавить слот</button>
|
<button type="button" class="btn btn-sm btn-secondary" id="schedule-slot-add">Добавить слот</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="schedule-rule-slots" class="schedule-slots-list"></div>
|
<div id="schedule-rule-slots" class="schedule-slots-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row schedule-rule-actions">
|
<div class="form-row schedule-rule-actions">
|
||||||
<button type="submit" class="btn-primary">Сохранить правило</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить правило</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="schedule-rule-alert" role="alert"></div>
|
<div class="form-alert" id="schedule-rule-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header-row">
|
<div class="card-header-row">
|
||||||
<h2>Правила динамического расписания</h2>
|
<h2>Правила динамического расписания</h2>
|
||||||
<button class="btn-primary" id="schedule-refresh">Обновить</button>
|
<button class="btn btn-md btn-secondary" id="schedule-refresh">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table id="schedule-table">
|
<table id="schedule-table">
|
||||||
@@ -119,10 +119,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="schedule-visual-handle"
|
class="btn btn-icon-md btn-secondary schedule-visual-handle"
|
||||||
id="schedule-visual-toggle"
|
id="schedule-visual-toggle"
|
||||||
aria-label="Показать визуальное расписание"
|
aria-label="Показать визуальное расписание"
|
||||||
aria-expanded="false">‹</button>
|
aria-expanded="false">
|
||||||
|
<svg class="schedule-visual-handle-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<polyline points="15 18 9 12 15 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class="schedule-visual-backdrop" id="schedule-visual-backdrop" hidden></div>
|
<div class="schedule-visual-backdrop" id="schedule-visual-backdrop" hidden></div>
|
||||||
|
|
||||||
@@ -133,7 +138,7 @@
|
|||||||
<h2>Визуальное расписание групп</h2>
|
<h2>Визуальное расписание групп</h2>
|
||||||
<span class="muted" id="schedule-visual-summary">Правила ещё не загружены</span>
|
<span class="muted" id="schedule-visual-summary">Правила ещё не загружены</span>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-edit-classroom" id="schedule-visual-close">Свернуть</button>
|
<button type="button" class="btn btn-sm btn-ghost" id="schedule-visual-close">Свернуть</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="schedule-visual-toolbar">
|
<div class="schedule-visual-toolbar">
|
||||||
<div class="schedule-visual-period-grid">
|
<div class="schedule-visual-period-grid">
|
||||||
@@ -153,8 +158,8 @@
|
|||||||
<div class="schedule-visual-filter-head">
|
<div class="schedule-visual-filter-head">
|
||||||
<span>Группы</span>
|
<span>Группы</span>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="btn-edit-classroom" id="schedule-visual-use-form">Из формы</button>
|
<button type="button" class="btn btn-sm btn-ghost" id="schedule-visual-use-form">Из формы</button>
|
||||||
<button type="button" class="btn-edit-classroom" id="schedule-visual-select-all">Все</button>
|
<button type="button" class="btn btn-sm btn-ghost" id="schedule-visual-select-all">Все</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="schedule-visual-group-filter" id="schedule-visual-group-filter">
|
<div class="schedule-visual-group-filter" id="schedule-visual-group-filter">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<option value="">Загрузка...</option>
|
<option value="">Загрузка...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Добавить</button>
|
<button type="submit" class="btn btn-md btn-primary">Добавить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="create-subject-alert" role="alert"></div>
|
<div class="form-alert" id="create-subject-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<option value="">Загрузка...</option>
|
<option value="">Загрузка...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary" style="height: fit-content; margin-bottom: 0;">Привязать</button>
|
<button type="submit" class="btn btn-md btn-primary" style="height: fit-content; margin-bottom: 0;">Привязать</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="manage-ts-alert" role="alert"></div>
|
<div class="form-alert" id="manage-ts-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -92,4 +92,4 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<label for="dept-code">Код кафедры</label>
|
<label for="dept-code">Код кафедры</label>
|
||||||
<input type="number" id="dept-code" placeholder="Например: 1" required>
|
<input type="number" id="dept-code" placeholder="Например: 1" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary">Создать</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="create-dept-alert" role="alert"></div>
|
<div class="form-alert" id="create-dept-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<label for="spec-code">Код специальности</label>
|
<label for="spec-code">Код специальности</label>
|
||||||
<input type="text" id="spec-code" placeholder="Например: 09.03.04" required>
|
<input type="text" id="spec-code" placeholder="Например: 09.03.04" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary">Создать</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="create-spec-alert" role="alert"></div>
|
<div class="form-alert" id="create-spec-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -115,8 +115,8 @@
|
|||||||
<input type="text" id="tab-profile-description" placeholder="Необязательно">
|
<input type="text" id="tab-profile-description" placeholder="Необязательно">
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
|
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
|
||||||
<button type="submit" class="btn-primary" id="btn-tab-save-profile">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary" id="btn-tab-save-profile">Создать</button>
|
||||||
<button type="button" class="btn-secondary" id="btn-tab-cancel-profile-edit" style="display: none;">Отмена</button>
|
<button type="button" class="btn btn-md btn-ghost" id="btn-tab-cancel-profile-edit" style="display: none;">Отмена</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="tab-profile-alert" role="alert"></div>
|
<div class="form-alert" id="tab-profile-alert" role="alert"></div>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
<label for="edit-dept-code">Код кафедры</label>
|
<label for="edit-dept-code">Код кафедры</label>
|
||||||
<input type="number" id="edit-dept-code" required>
|
<input type="number" id="edit-dept-code" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Сохранить</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="edit-dept-alert" role="alert"></div>
|
<div class="form-alert" id="edit-dept-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
<label for="edit-spec-code">Код специальности</label>
|
<label for="edit-spec-code">Код специальности</label>
|
||||||
<input type="text" id="edit-spec-code" required>
|
<input type="text" id="edit-spec-code" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Сохранить</button>
|
<button type="submit" class="btn btn-md btn-primary">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="edit-spec-alert" role="alert"></div>
|
<div class="form-alert" id="edit-spec-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -222,8 +222,8 @@
|
|||||||
<input type="text" id="manage-profile-description" placeholder="Необязательно">
|
<input type="text" id="manage-profile-description" placeholder="Необязательно">
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
|
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
|
||||||
<button type="submit" class="btn-primary" id="btn-save-profile">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary" id="btn-save-profile">Создать</button>
|
||||||
<button type="button" class="btn-secondary" id="btn-cancel-profile-edit" style="display: none;">Отмена</button>
|
<button type="button" class="btn btn-md btn-ghost" id="btn-cancel-profile-edit" style="display: none;">Отмена</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="manage-profile-alert" role="alert"></div>
|
<div class="form-alert" id="manage-profile-alert" role="alert"></div>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<label for="new-department">ID Кафедры</label>
|
<label for="new-department">ID Кафедры</label>
|
||||||
<input type="number" id="new-department" placeholder="ID" required>
|
<input type="number" id="new-department" placeholder="ID" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Создать</button>
|
<button type="submit" class="btn btn-md btn-primary">Создать</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-alert" id="create-alert" role="alert"></div>
|
<div class="form-alert" id="create-alert" role="alert"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||||
</svg>
|
</svg>
|
||||||
<input type="password" id="password" name="password" placeholder="Введите пароль" autocomplete="current-password" required>
|
<input type="password" id="password" name="password" placeholder="Введите пароль" autocomplete="current-password" required>
|
||||||
<button type="button" class="toggle-password" id="toggle-password" aria-label="Показать пароль">
|
<button type="button" class="btn btn-icon-md btn-secondary toggle-password" id="toggle-password" aria-label="Показать пароль">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<circle cx="12" cy="12" r="3"/>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
|
|
||||||
<div class="form-alert stagger-3" id="form-alert" role="alert"></div>
|
<div class="form-alert stagger-3" id="form-alert" role="alert"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn-submit stagger-4" id="btn-submit">
|
<button type="submit" class="btn btn-lg btn-primary btn-submit stagger-4" id="btn-submit">
|
||||||
<span class="btn-text">Войти</span>
|
<span class="btn-text">Войти</span>
|
||||||
<span class="btn-loader" hidden>
|
<span class="btn-loader" hidden>
|
||||||
<svg class="spinner" width="20" height="20" viewBox="0 0 24 24">
|
<svg class="spinner" width="20" height="20" viewBox="0 0 24 24">
|
||||||
|
|||||||
@@ -163,3 +163,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document
|
||||||
|
.querySelector('#login-form button[type="submit"]')
|
||||||
|
?.classList.add('btn', 'btn-lg', 'btn-primary');
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
--panel-border: #dbe3ef;
|
--panel-border: #dbe3ef;
|
||||||
--text: #162033;
|
--text: #162033;
|
||||||
--muted: #66758f;
|
--muted: #66758f;
|
||||||
--accent: #0f766e;
|
--accent: #4F46E5;
|
||||||
--accent-strong: #0b5d57;
|
--accent-strong: #4338CA;
|
||||||
--blue: #2563eb;
|
--blue: #2563eb;
|
||||||
--warning: #b45309;
|
--warning: #b45309;
|
||||||
--shadow: 0 12px 34px rgba(22, 32, 51, 0.08);
|
--shadow: 0 12px 34px rgba(22, 32, 51, 0.08);
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
--panel-border: #263449;
|
--panel-border: #263449;
|
||||||
--text: #edf3fb;
|
--text: #edf3fb;
|
||||||
--muted: #9cadc5;
|
--muted: #9cadc5;
|
||||||
--accent: #2dd4bf;
|
--accent: #8B5CF6;
|
||||||
--accent-strong: #5eead4;
|
--accent-strong: #7C3AED;
|
||||||
--blue: #60a5fa;
|
--blue: #60a5fa;
|
||||||
--warning: #f59e0b;
|
--warning: #f59e0b;
|
||||||
--shadow: 0 18px 44px rgba(0, 0, 0, 0.28);
|
--shadow: 0 18px 44px rgba(0, 0, 0, 0.28);
|
||||||
@@ -94,6 +94,7 @@
|
|||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
@@ -112,6 +113,47 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--panel);
|
||||||
|
border-color: var(--panel-border);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: color-mix(in srgb, var(--accent) 7%, var(--panel));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-subtle {
|
||||||
|
background: #FEF2F2;
|
||||||
|
border-color: #FECACA;
|
||||||
|
color: #B91C1C;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .btn-danger-subtle {
|
||||||
|
background: rgba(239, 68, 68, 0.13);
|
||||||
|
border-color: rgba(239, 68, 68, 0.25);
|
||||||
|
color: #FECACA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-md {
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(220px, 1fr) auto auto;
|
grid-template-columns: minmax(220px, 1fr) auto auto;
|
||||||
@@ -269,8 +311,12 @@
|
|||||||
<span id="week-label">Загрузка периода...</span>
|
<span id="week-label">Загрузка периода...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn" id="theme-toggle-local" type="button" aria-label="Переключить тему">◐</button>
|
<button class="btn btn-icon-md btn-secondary" id="theme-toggle-local" type="button" aria-label="Переключить тему">
|
||||||
<a class="btn" href="/" id="logout-link">Выйти</a>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-danger-subtle" href="/" id="logout-link">Выйти</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -279,9 +325,17 @@
|
|||||||
<option value="">Загрузка групп...</option>
|
<option value="">Загрузка групп...</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="week-nav">
|
<div class="week-nav">
|
||||||
<button class="btn" id="prev-week" type="button" aria-label="Предыдущая неделя">‹</button>
|
<button class="btn btn-icon-md btn-secondary" id="prev-week" type="button" aria-label="Предыдущая неделя">
|
||||||
<button class="btn btn-primary" id="today-week" type="button">Сегодня</button>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<button class="btn" id="next-week" type="button" aria-label="Следующая неделя">›</button>
|
<polyline points="15 18 9 12 15 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-md btn-primary" id="today-week" type="button">Сегодня</button>
|
||||||
|
<button class="btn btn-icon-md btn-secondary" id="next-week" type="button" aria-label="Следующая неделя">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<polyline points="9 18 15 12 9 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<input class="date-input" id="date-picker" type="date" aria-label="Дата недели">
|
<input class="date-input" id="date-picker" type="date" aria-label="Дата недели">
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -25,6 +25,24 @@
|
|||||||
--accent-hover: #a78bfa;
|
--accent-hover: #a78bfa;
|
||||||
--accent-glow: rgba(139, 92, 246, 0.4);
|
--accent-glow: rgba(139, 92, 246, 0.4);
|
||||||
--accent-secondary: #ec4899;
|
--accent-secondary: #ec4899;
|
||||||
|
--button-primary-bg: #8B5CF6;
|
||||||
|
--button-primary-bg-hover: #7C3AED;
|
||||||
|
--button-primary-text: #FFFFFF;
|
||||||
|
--button-secondary-bg: rgba(255, 255, 255, 0.04);
|
||||||
|
--button-secondary-bg-hover: rgba(255, 255, 255, 0.08);
|
||||||
|
--button-secondary-border: rgba(255, 255, 255, 0.10);
|
||||||
|
--button-secondary-text: #F8FAFC;
|
||||||
|
--button-ghost-bg-hover: rgba(255, 255, 255, 0.06);
|
||||||
|
--button-danger-bg: #DC2626;
|
||||||
|
--button-danger-bg-hover: #B91C1C;
|
||||||
|
--button-danger-subtle-bg: rgba(239, 68, 68, 0.13);
|
||||||
|
--button-danger-subtle-bg-hover: rgba(239, 68, 68, 0.20);
|
||||||
|
--button-danger-subtle-border: rgba(239, 68, 68, 0.25);
|
||||||
|
--button-danger-subtle-text: #FECACA;
|
||||||
|
--button-focus-ring: rgba(139, 92, 246, 0.45);
|
||||||
|
--button-disabled-bg: rgba(148, 163, 184, 0.16);
|
||||||
|
--button-disabled-text: #94A3B8;
|
||||||
|
--button-radius: 8px;
|
||||||
|
|
||||||
/* Status Colors */
|
/* Status Colors */
|
||||||
--error: #ef4444;
|
--error: #ef4444;
|
||||||
@@ -48,10 +66,27 @@
|
|||||||
--text-primary: #0f172a;
|
--text-primary: #0f172a;
|
||||||
--text-secondary: #475569;
|
--text-secondary: #475569;
|
||||||
--text-placeholder: #94a3b8;
|
--text-placeholder: #94a3b8;
|
||||||
--accent: #6366f1;
|
--accent: #4F46E5;
|
||||||
--accent-hover: #4f46e5;
|
--accent-hover: #4338CA;
|
||||||
--accent-glow: rgba(99, 102, 241, 0.3);
|
--accent-glow: rgba(79, 70, 229, 0.3);
|
||||||
--accent-secondary: #d946ef;
|
--accent-secondary: #d946ef;
|
||||||
|
--button-primary-bg: #4F46E5;
|
||||||
|
--button-primary-bg-hover: #4338CA;
|
||||||
|
--button-primary-text: #FFFFFF;
|
||||||
|
--button-secondary-bg: #FFFFFF;
|
||||||
|
--button-secondary-bg-hover: #F1F5F9;
|
||||||
|
--button-secondary-border: #CBD5E1;
|
||||||
|
--button-secondary-text: #334155;
|
||||||
|
--button-ghost-bg-hover: #F8FAFC;
|
||||||
|
--button-danger-bg: #DC2626;
|
||||||
|
--button-danger-bg-hover: #B91C1C;
|
||||||
|
--button-danger-subtle-bg: #FEF2F2;
|
||||||
|
--button-danger-subtle-bg-hover: #FEE2E2;
|
||||||
|
--button-danger-subtle-border: #FECACA;
|
||||||
|
--button-danger-subtle-text: #B91C1C;
|
||||||
|
--button-focus-ring: rgba(79, 70, 229, 0.35);
|
||||||
|
--button-disabled-bg: #CBD5E1;
|
||||||
|
--button-disabled-text: #64748B;
|
||||||
--error: #ef4444;
|
--error: #ef4444;
|
||||||
--success: #10b981;
|
--success: #10b981;
|
||||||
--warning: #f59e0b;
|
--warning: #f59e0b;
|
||||||
@@ -389,59 +424,19 @@ body {
|
|||||||
|
|
||||||
/* ===== Submit Button ===== */
|
/* ===== Submit Button ===== */
|
||||||
.btn-submit {
|
.btn-submit {
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.95rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
|
|
||||||
color: #fff;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
transition:
|
|
||||||
transform var(--transition),
|
|
||||||
box-shadow var(--transition),
|
|
||||||
opacity var(--transition);
|
|
||||||
box-shadow: 0 4px 15px var(--accent-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
|
||||||
background: linear-gradient(rgba(255,255,255,0.2), transparent);
|
|
||||||
border-radius: inherit;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 25px var(--accent-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit:hover::before {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-submit:active {
|
.btn-submit:active {
|
||||||
transform: translateY(1px);
|
transform: translateY(0);
|
||||||
box-shadow: 0 2px 10px var(--accent-glow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-submit:disabled {
|
.btn-submit:disabled {
|
||||||
opacity: 0.6;
|
background: var(--button-disabled-bg);
|
||||||
|
color: var(--button-disabled-text);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
transform: none;
|
transform: none;
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Ripple Effect ===== */
|
/* ===== Ripple Effect ===== */
|
||||||
@@ -480,13 +475,12 @@ body {
|
|||||||
|
|
||||||
/* ===== Theme Toggle Button ===== */
|
/* ===== Theme Toggle Button ===== */
|
||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
width: 42px;
|
width: 40px;
|
||||||
height: 42px;
|
height: 40px;
|
||||||
border: none;
|
border-radius: var(--button-radius);
|
||||||
border-radius: 50%;
|
background: var(--button-secondary-bg);
|
||||||
background: var(--bg-card);
|
border: 1px solid var(--button-secondary-border);
|
||||||
border: 1px solid var(--bg-card-border);
|
color: var(--button-secondary-text);
|
||||||
color: var(--text-primary);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -505,12 +499,13 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:hover {
|
.theme-toggle:hover {
|
||||||
transform: scale(1.1);
|
background: var(--button-secondary-bg-hover);
|
||||||
box-shadow: 0 4px 16px var(--accent-glow);
|
transform: translateY(-1px);
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:active {
|
.theme-toggle:active {
|
||||||
transform: scale(0.95);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle--fixed {
|
.theme-toggle--fixed {
|
||||||
@@ -540,4 +535,95 @@ body {
|
|||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: var(--button-radius);
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition), transform var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px var(--button-focus-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lg {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 20px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--button-primary-bg);
|
||||||
|
border-color: transparent;
|
||||||
|
color: var(--button-primary-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background: var(--button-primary-bg-hover);
|
||||||
|
color: var(--button-primary-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--button-secondary-bg);
|
||||||
|
border-color: var(--button-secondary-border);
|
||||||
|
color: var(--button-secondary-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
background: var(--button-secondary-bg-hover);
|
||||||
|
color: var(--button-secondary-text);
|
||||||
|
}
|
||||||
|
#login-form .btn.btn-primary {
|
||||||
|
min-height: 48px !important;
|
||||||
|
padding: 0 20px !important;
|
||||||
|
border-radius: var(--button-radius) !important;
|
||||||
|
background: var(--button-primary-bg) !important;
|
||||||
|
color: var(--button-primary-text) !important;
|
||||||
|
font-size: 15px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-form .btn.btn-primary:hover {
|
||||||
|
background: var(--button-primary-bg-hover) !important;
|
||||||
|
}
|
||||||
|
#toggle-password.btn-icon-md {
|
||||||
|
width: 40px !important;
|
||||||
|
height: 40px !important;
|
||||||
|
min-height: 40px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: var(--button-radius) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
--panel-border: #d8dee8;
|
--panel-border: #d8dee8;
|
||||||
--text: #172033;
|
--text: #172033;
|
||||||
--muted: #67758d;
|
--muted: #67758d;
|
||||||
--accent: #1d4ed8;
|
--accent: #4F46E5;
|
||||||
--accent-strong: #1e40af;
|
--accent-strong: #4338CA;
|
||||||
--green: #047857;
|
--green: #047857;
|
||||||
--orange: #b45309;
|
--orange: #b45309;
|
||||||
--shadow: 0 12px 30px rgba(23, 32, 51, 0.08);
|
--shadow: 0 12px 30px rgba(23, 32, 51, 0.08);
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
--panel-border: #2a374b;
|
--panel-border: #2a374b;
|
||||||
--text: #eef4fb;
|
--text: #eef4fb;
|
||||||
--muted: #a2b0c5;
|
--muted: #a2b0c5;
|
||||||
--accent: #60a5fa;
|
--accent: #8B5CF6;
|
||||||
--accent-strong: #93c5fd;
|
--accent-strong: #7C3AED;
|
||||||
--green: #34d399;
|
--green: #34d399;
|
||||||
--orange: #f59e0b;
|
--orange: #f59e0b;
|
||||||
--shadow: 0 18px 42px rgba(0, 0, 0, 0.28);
|
--shadow: 0 18px 42px rgba(0, 0, 0, 0.28);
|
||||||
@@ -107,6 +107,7 @@
|
|||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
@@ -125,6 +126,47 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--panel);
|
||||||
|
border-color: var(--panel-border);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: color-mix(in srgb, var(--accent) 7%, var(--panel));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-subtle {
|
||||||
|
background: #FEF2F2;
|
||||||
|
border-color: #FECACA;
|
||||||
|
color: #B91C1C;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .btn-danger-subtle {
|
||||||
|
background: rgba(239, 68, 68, 0.13);
|
||||||
|
border-color: rgba(239, 68, 68, 0.25);
|
||||||
|
color: #FECACA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-md svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-md {
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.date-input {
|
.date-input {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
}
|
}
|
||||||
@@ -275,16 +317,28 @@
|
|||||||
<span id="week-label">Загрузка периода...</span>
|
<span id="week-label">Загрузка периода...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn" id="theme-toggle-local" type="button" aria-label="Переключить тему">◐</button>
|
<button class="btn btn-icon-md btn-secondary" id="theme-toggle-local" type="button" aria-label="Переключить тему">
|
||||||
<a class="btn" href="/" id="logout-link">Выйти</a>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-danger-subtle" href="/" id="logout-link">Выйти</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="toolbar">
|
<section class="toolbar">
|
||||||
<div class="week-nav">
|
<div class="week-nav">
|
||||||
<button class="btn" id="prev-week" type="button" aria-label="Предыдущая неделя">‹</button>
|
<button class="btn btn-icon-md btn-secondary" id="prev-week" type="button" aria-label="Предыдущая неделя">
|
||||||
<button class="btn btn-primary" id="today-week" type="button">Сегодня</button>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<button class="btn" id="next-week" type="button" aria-label="Следующая неделя">›</button>
|
<polyline points="15 18 9 12 15 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-md btn-primary" id="today-week" type="button">Сегодня</button>
|
||||||
|
<button class="btn btn-icon-md btn-secondary" id="next-week" type="button" aria-label="Следующая неделя">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<polyline points="9 18 15 12 9 6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<input class="date-input" id="date-picker" type="date" aria-label="Дата недели">
|
<input class="date-input" id="date-picker" type="date" aria-label="Дата недели">
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
const isDark = () => document.documentElement.getAttribute('data-theme') !== 'light';
|
const isDark = () => document.documentElement.getAttribute('data-theme') !== 'light';
|
||||||
|
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = 'theme-toggle';
|
btn.className = 'btn btn-icon-md btn-secondary theme-toggle';
|
||||||
btn.setAttribute('aria-label', 'Переключить тему');
|
btn.setAttribute('aria-label', 'Переключить тему');
|
||||||
btn.innerHTML = isDark() ? moonSVG : sunSVG;
|
btn.innerHTML = isDark() ? moonSVG : sunSVG;
|
||||||
|
|
||||||
|
|||||||