Зеркалирование "Создать занятие" в "Расписание занятий" из "Пользователи", визуальные изменения этих модалок
This commit is contained in:
@@ -312,4 +312,33 @@ details.table-item .content td{
|
|||||||
details.table-item .content{
|
details.table-item .content{
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Контейнер занятий преподавателя в модалках ===== */
|
||||||
|
.cs-modal-table .lessons-container {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(99, 102, 241, 0.55) rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cs-modal-table .lessons-container::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cs-modal-table .lessons-container::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cs-modal-table .lessons-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(99, 102, 241, 0.55);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid rgba(0, 0, 0, 0);
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cs-modal-table .lessons-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(99, 102, 241, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { api } from '../api.js';
|
import { api } from '../api.js';
|
||||||
import { escapeHtml } from '../utils.js';
|
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
|
||||||
|
|
||||||
export async function initSchedule() {
|
export async function initSchedule() {
|
||||||
const tbody = document.getElementById('schedule-tbody');
|
const tbody = document.getElementById('schedule-tbody');
|
||||||
@@ -20,7 +20,6 @@ export async function initSchedule() {
|
|||||||
|
|
||||||
// ===================== Фильтрация =====================
|
// ===================== Фильтрация =====================
|
||||||
|
|
||||||
// Извлечение отображаемого значения поля для фильтрации
|
|
||||||
function getDisplayValue(lesson, key) {
|
function getDisplayValue(lesson, key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'teacher':
|
case 'teacher':
|
||||||
@@ -38,20 +37,17 @@ export async function initSchedule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Собрать уникальные значения из данных
|
|
||||||
function getUniqueValues(key) {
|
function getUniqueValues(key) {
|
||||||
const vals = new Set();
|
const vals = new Set();
|
||||||
lessonsData.forEach(lesson => {
|
lessonsData.forEach(lesson => {
|
||||||
vals.add(getDisplayValue(lesson, key));
|
vals.add(getDisplayValue(lesson, key));
|
||||||
});
|
});
|
||||||
// Для дней — сортируем по порядку
|
|
||||||
if (key === 'day') {
|
if (key === 'day') {
|
||||||
return [...vals].sort((a, b) => (dayOrder[a.toLowerCase()] ?? 99) - (dayOrder[b.toLowerCase()] ?? 99));
|
return [...vals].sort((a, b) => (dayOrder[a.toLowerCase()] ?? 99) - (dayOrder[b.toLowerCase()] ?? 99));
|
||||||
}
|
}
|
||||||
return [...vals].sort((a, b) => a.localeCompare(b, 'ru'));
|
return [...vals].sort((a, b) => a.localeCompare(b, 'ru'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применить все фильтры
|
|
||||||
function applyFilters(lessons) {
|
function applyFilters(lessons) {
|
||||||
return lessons.filter(lesson => {
|
return lessons.filter(lesson => {
|
||||||
for (const key of Object.keys(activeFilters)) {
|
for (const key of Object.keys(activeFilters)) {
|
||||||
@@ -79,7 +75,6 @@ export async function initSchedule() {
|
|||||||
|
|
||||||
function onDocumentClick(e) {
|
function onDocumentClick(e) {
|
||||||
if (currentPopup && !currentPopup.contains(e.target)) {
|
if (currentPopup && !currentPopup.contains(e.target)) {
|
||||||
// Проверяем, не кликнули ли по иконке фильтра
|
|
||||||
if (!e.target.closest('.filter-icon')) {
|
if (!e.target.closest('.filter-icon')) {
|
||||||
closePopup();
|
closePopup();
|
||||||
}
|
}
|
||||||
@@ -87,7 +82,6 @@ export async function initSchedule() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openFilterPopup(th, filterKey) {
|
function openFilterPopup(th, filterKey) {
|
||||||
// Если уже открыт этот же — закрыть
|
|
||||||
if (currentPopup && currentPopup.dataset.filterKey === filterKey) {
|
if (currentPopup && currentPopup.dataset.filterKey === filterKey) {
|
||||||
closePopup();
|
closePopup();
|
||||||
return;
|
return;
|
||||||
@@ -97,19 +91,16 @@ export async function initSchedule() {
|
|||||||
const uniqueValues = getUniqueValues(filterKey);
|
const uniqueValues = getUniqueValues(filterKey);
|
||||||
const currentFilter = activeFilters[filterKey];
|
const currentFilter = activeFilters[filterKey];
|
||||||
|
|
||||||
// Создаём попап
|
|
||||||
const popup = document.createElement('div');
|
const popup = document.createElement('div');
|
||||||
popup.className = 'filter-popup';
|
popup.className = 'filter-popup';
|
||||||
popup.dataset.filterKey = filterKey;
|
popup.dataset.filterKey = filterKey;
|
||||||
|
|
||||||
// Поисковое поле
|
|
||||||
const searchInput = document.createElement('input');
|
const searchInput = document.createElement('input');
|
||||||
searchInput.type = 'text';
|
searchInput.type = 'text';
|
||||||
searchInput.className = 'filter-search';
|
searchInput.className = 'filter-search';
|
||||||
searchInput.placeholder = 'Поиск...';
|
searchInput.placeholder = 'Поиск...';
|
||||||
popup.appendChild(searchInput);
|
popup.appendChild(searchInput);
|
||||||
|
|
||||||
// Кнопки «Выбрать все» / «Сбросить»
|
|
||||||
const btnRow = document.createElement('div');
|
const btnRow = document.createElement('div');
|
||||||
btnRow.className = 'filter-btn-row';
|
btnRow.className = 'filter-btn-row';
|
||||||
|
|
||||||
@@ -133,7 +124,6 @@ export async function initSchedule() {
|
|||||||
btnRow.appendChild(btnNone);
|
btnRow.appendChild(btnNone);
|
||||||
popup.appendChild(btnRow);
|
popup.appendChild(btnRow);
|
||||||
|
|
||||||
// Список чекбоксов
|
|
||||||
const listWrap = document.createElement('div');
|
const listWrap = document.createElement('div');
|
||||||
listWrap.className = 'filter-list';
|
listWrap.className = 'filter-list';
|
||||||
|
|
||||||
@@ -146,7 +136,6 @@ export async function initSchedule() {
|
|||||||
const cb = document.createElement('input');
|
const cb = document.createElement('input');
|
||||||
cb.type = 'checkbox';
|
cb.type = 'checkbox';
|
||||||
cb.value = val;
|
cb.value = val;
|
||||||
// Если фильтр активен — отмечаем только выбранные; если нет — все отмечены
|
|
||||||
cb.checked = currentFilter ? currentFilter.has(val) : true;
|
cb.checked = currentFilter ? currentFilter.has(val) : true;
|
||||||
|
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
@@ -160,7 +149,6 @@ export async function initSchedule() {
|
|||||||
|
|
||||||
popup.appendChild(listWrap);
|
popup.appendChild(listWrap);
|
||||||
|
|
||||||
// Кнопка «Применить»
|
|
||||||
const btnApply = document.createElement('button');
|
const btnApply = document.createElement('button');
|
||||||
btnApply.className = 'filter-btn-apply';
|
btnApply.className = 'filter-btn-apply';
|
||||||
btnApply.textContent = 'Применить';
|
btnApply.textContent = 'Применить';
|
||||||
@@ -171,7 +159,6 @@ export async function initSchedule() {
|
|||||||
if (cb.checked) selected.add(cb.value);
|
if (cb.checked) selected.add(cb.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Если все выбраны — снимаем фильтр
|
|
||||||
if (selected.size === uniqueValues.length) {
|
if (selected.size === uniqueValues.length) {
|
||||||
delete activeFilters[filterKey];
|
delete activeFilters[filterKey];
|
||||||
th.classList.remove('filter-active');
|
th.classList.remove('filter-active');
|
||||||
@@ -185,7 +172,6 @@ export async function initSchedule() {
|
|||||||
});
|
});
|
||||||
popup.appendChild(btnApply);
|
popup.appendChild(btnApply);
|
||||||
|
|
||||||
// Поиск по чекбоксам
|
|
||||||
searchInput.addEventListener('input', () => {
|
searchInput.addEventListener('input', () => {
|
||||||
const query = searchInput.value.toLowerCase();
|
const query = searchInput.value.toLowerCase();
|
||||||
listWrap.querySelectorAll('.filter-item').forEach(item => {
|
listWrap.querySelectorAll('.filter-item').forEach(item => {
|
||||||
@@ -194,28 +180,22 @@ export async function initSchedule() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Предотвращаем всплытие кликов внутри попапа (чтобы не срабатывала сортировка th)
|
|
||||||
popup.addEventListener('click', (e) => e.stopPropagation());
|
popup.addEventListener('click', (e) => e.stopPropagation());
|
||||||
searchInput.addEventListener('click', (e) => e.stopPropagation());
|
searchInput.addEventListener('click', (e) => e.stopPropagation());
|
||||||
|
|
||||||
// Позиционируем попап под th
|
|
||||||
th.style.position = 'relative';
|
th.style.position = 'relative';
|
||||||
th.appendChild(popup);
|
th.appendChild(popup);
|
||||||
currentPopup = popup;
|
currentPopup = popup;
|
||||||
|
|
||||||
// Фокус на поиск
|
|
||||||
setTimeout(() => searchInput.focus(), 50);
|
setTimeout(() => searchInput.focus(), 50);
|
||||||
|
|
||||||
// Закрытие по клику вне
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.addEventListener('click', onDocumentClick, true);
|
document.addEventListener('click', onDocumentClick, true);
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчики кликов по заголовкам с фильтрами (клик по всей ячейке)
|
|
||||||
table.querySelectorAll('thead th.filterable').forEach(th => {
|
table.querySelectorAll('thead th.filterable').forEach(th => {
|
||||||
th.addEventListener('click', (e) => {
|
th.addEventListener('click', (e) => {
|
||||||
// Не открываем попап при клике внутри самого попапа
|
|
||||||
if (e.target.closest('.filter-popup')) return;
|
if (e.target.closest('.filter-popup')) return;
|
||||||
const filterKey = th.dataset.filterKey;
|
const filterKey = th.dataset.filterKey;
|
||||||
openFilterPopup(th, filterKey);
|
openFilterPopup(th, filterKey);
|
||||||
@@ -249,7 +229,6 @@ export async function initSchedule() {
|
|||||||
case 'week':
|
case 'week':
|
||||||
return (lesson.week || '').toLowerCase();
|
return (lesson.week || '').toLowerCase();
|
||||||
case 'time': {
|
case 'time': {
|
||||||
// Составной ключ: день + время для правильной сортировки
|
|
||||||
const d = (lesson.day || '').toLowerCase();
|
const d = (lesson.day || '').toLowerCase();
|
||||||
const dayNum = dayOrder[d] ?? 99;
|
const dayNum = dayOrder[d] ?? 99;
|
||||||
const t = lesson.time || '99:99';
|
const t = lesson.time || '99:99';
|
||||||
@@ -287,10 +266,8 @@ export async function initSchedule() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Навешиваем обработчики клика на заголовки (сортировка)
|
|
||||||
table.querySelectorAll('thead th.sortable').forEach(th => {
|
table.querySelectorAll('thead th.sortable').forEach(th => {
|
||||||
th.addEventListener('click', (e) => {
|
th.addEventListener('click', (e) => {
|
||||||
// Не сортируем, если кликнули по иконке фильтра или внутри попапа
|
|
||||||
if (e.target.closest('.filter-icon') || e.target.closest('.filter-popup')) return;
|
if (e.target.closest('.filter-icon') || e.target.closest('.filter-popup')) return;
|
||||||
|
|
||||||
const key = th.dataset.sortKey;
|
const key = th.dataset.sortKey;
|
||||||
@@ -310,7 +287,7 @@ export async function initSchedule() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===================== Загрузка и рендер =====================
|
// ===================== Загрузка и рендер таблицы =====================
|
||||||
|
|
||||||
async function loadSchedule() {
|
async function loadSchedule() {
|
||||||
try {
|
try {
|
||||||
@@ -318,21 +295,20 @@ export async function initSchedule() {
|
|||||||
lessonsData = lessons;
|
lessonsData = lessons;
|
||||||
renderSchedule(lessons);
|
renderSchedule(lessons);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
tbody.innerHTML = `<tr><td colspan="8" class="loading-row">Ошибка загрузки: ${escapeHtml(e.message)}</td></tr>`;
|
tbody.innerHTML = `<tr><td colspan="11" class="loading-row">Ошибка загрузки: ${escapeHtml(e.message)}</td></tr>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSchedule(lessons) {
|
function renderSchedule(lessons) {
|
||||||
if (!lessons || !lessons.length) {
|
if (!lessons || !lessons.length) {
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="loading-row">Нет занятий</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="11" class="loading-row">Нет занятий</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сначала фильтруем, потом сортируем
|
|
||||||
const filtered = applyFilters(lessons);
|
const filtered = applyFilters(lessons);
|
||||||
|
|
||||||
if (!filtered.length) {
|
if (!filtered.length) {
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="loading-row">Нет занятий по выбранным фильтрам</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="11" class="loading-row">Нет занятий по выбранным фильтрам</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,5 +342,343 @@ export async function initSchedule() {
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadSchedule();
|
// ===================== Модалки добавления занятия =====================
|
||||||
|
|
||||||
|
const overlay = document.getElementById('sch-overlay');
|
||||||
|
const modalForm = document.getElementById('sch-modal-form');
|
||||||
|
const modalLessons = document.getElementById('sch-modal-lessons');
|
||||||
|
const btnAddLesson = document.getElementById('sch-btn-add-lesson');
|
||||||
|
const btnClose = document.getElementById('sch-modal-close');
|
||||||
|
const addForm = document.getElementById('sch-add-lesson-form');
|
||||||
|
|
||||||
|
const schTeacherSelect = document.getElementById('sch-teacher');
|
||||||
|
const schGroupSelect = document.getElementById('sch-group');
|
||||||
|
const schDisciplineSelect = document.getElementById('sch-discipline');
|
||||||
|
const schClassroomSelect = document.getElementById('sch-classroom');
|
||||||
|
const schDaySelect = document.getElementById('sch-day');
|
||||||
|
const schTimeSelect = document.getElementById('sch-time');
|
||||||
|
const schTypeSelect = document.getElementById('sch-type');
|
||||||
|
const schWeekUpper = document.getElementById('sch-week-upper');
|
||||||
|
const schWeekLower = document.getElementById('sch-week-lower');
|
||||||
|
const schFormatOffline = document.getElementById('sch-format-offline');
|
||||||
|
|
||||||
|
const schTeacherName = document.getElementById('sch-teacher-name');
|
||||||
|
const schLessonsContainer = document.getElementById('sch-lessons-container');
|
||||||
|
|
||||||
|
let groups = [];
|
||||||
|
let subjects = [];
|
||||||
|
let classrooms = [];
|
||||||
|
let teachers = [];
|
||||||
|
|
||||||
|
const weekdaysTimes = [
|
||||||
|
"8:00-9:30", "9:40-11:10", "11:40-13:10",
|
||||||
|
"13:20-14:50", "15:00-16:30", "16:50-18:20", "18:30-19:00"
|
||||||
|
];
|
||||||
|
const saturdayTimes = [
|
||||||
|
"8:20-9:50", "10:00-11:30", "11:40-13:10", "13:20-14:50"
|
||||||
|
];
|
||||||
|
|
||||||
|
// ===== Загрузка справочников =====
|
||||||
|
async function loadGroups() {
|
||||||
|
try {
|
||||||
|
groups = await api.get('/api/groups');
|
||||||
|
schGroupSelect.innerHTML = '<option value="">Выберите группу</option>' +
|
||||||
|
groups.map(g => {
|
||||||
|
let text = escapeHtml(g.name);
|
||||||
|
if (g.groupSize) text += ` (числ: ${g.groupSize} чел.)`;
|
||||||
|
return `<option value="${g.id}">${text}</option>`;
|
||||||
|
}).join('');
|
||||||
|
} catch (e) { console.error('Ошибка загрузки групп:', e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSubjects() {
|
||||||
|
try {
|
||||||
|
subjects = await api.get('/api/subjects');
|
||||||
|
schDisciplineSelect.innerHTML = '<option value="">Выберите дисциплину</option>' +
|
||||||
|
subjects.map(s => `<option value="${s.id}">${escapeHtml(s.name)}</option>`).join('');
|
||||||
|
} catch (e) { console.error('Ошибка загрузки дисциплин:', e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadClassrooms() {
|
||||||
|
try {
|
||||||
|
classrooms = await api.get('/api/classrooms');
|
||||||
|
renderClassroomOptions();
|
||||||
|
} catch (e) { console.error('Ошибка загрузки аудиторий:', e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTeachers() {
|
||||||
|
try {
|
||||||
|
teachers = await api.get('/api/users/teachers');
|
||||||
|
schTeacherSelect.innerHTML = '<option value="">Выберите преподавателя</option>' +
|
||||||
|
teachers.map(t => `<option value="${t.id}">${escapeHtml(t.fullName || t.username)}</option>`).join('');
|
||||||
|
} catch (e) { console.error('Ошибка загрузки преподавателей:', e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderClassroomOptions() {
|
||||||
|
if (!classrooms || classrooms.length === 0) {
|
||||||
|
schClassroomSelect.innerHTML = '<option value="">Нет доступных аудиторий</option>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedGroupId = schGroupSelect.value;
|
||||||
|
const selectedGroup = groups?.find(g => g.id == selectedGroupId);
|
||||||
|
const groupSize = selectedGroup?.groupSize || 0;
|
||||||
|
|
||||||
|
schClassroomSelect.innerHTML = '<option value="">Выберите аудиторию</option>' +
|
||||||
|
classrooms.map(c => {
|
||||||
|
let text = escapeHtml(c.name);
|
||||||
|
if (c.capacity) text += ` (вместимость: ${c.capacity} чел.)`;
|
||||||
|
if (c.isAvailable === false) {
|
||||||
|
text += ` ❌ Занята`;
|
||||||
|
} else if (selectedGroupId && groupSize > 0 && c.capacity && groupSize > c.capacity) {
|
||||||
|
text += ` ⚠️ Недостаточно места`;
|
||||||
|
}
|
||||||
|
return `<option value="${c.id}">${text}</option>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
schGroupSelect.addEventListener('change', () => renderClassroomOptions());
|
||||||
|
|
||||||
|
function updateTimeOptions(dayValue) {
|
||||||
|
let times = [];
|
||||||
|
if (dayValue === "Суббота") {
|
||||||
|
times = saturdayTimes;
|
||||||
|
} else if (dayValue && dayValue !== '') {
|
||||||
|
times = weekdaysTimes;
|
||||||
|
} else {
|
||||||
|
schTimeSelect.innerHTML = '<option value="">Сначала выберите день</option>';
|
||||||
|
schTimeSelect.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
schTimeSelect.innerHTML = '<option value="">Выберите время</option>' +
|
||||||
|
times.map(t => `<option value="${t}">${t}</option>`).join('');
|
||||||
|
schTimeSelect.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
schDaySelect.addEventListener('change', function () {
|
||||||
|
updateTimeOptions(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== Автозаполнение преподавателя из фильтра =====
|
||||||
|
function getFilteredTeacherId() {
|
||||||
|
const teacherFilter = activeFilters['teacher'];
|
||||||
|
if (teacherFilter && teacherFilter.size === 1) {
|
||||||
|
const teacherName = [...teacherFilter][0];
|
||||||
|
// Сопоставляем по username, fullName и их комбинациям
|
||||||
|
const match = teachers.find(t =>
|
||||||
|
t.username === teacherName ||
|
||||||
|
t.fullName === teacherName ||
|
||||||
|
(t.fullName || t.username) === teacherName
|
||||||
|
);
|
||||||
|
return match ? String(match.id) : '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Загрузка занятий преподавателя =====
|
||||||
|
async function loadTeacherLessons(teacherId) {
|
||||||
|
const teacher = teachers.find(t => t.id == teacherId);
|
||||||
|
const name = teacher ? (teacher.fullName || teacher.username) : '';
|
||||||
|
schTeacherName.textContent = name
|
||||||
|
? `Занятия преподавателя: ${name}`
|
||||||
|
: 'Занятия преподавателя';
|
||||||
|
|
||||||
|
modalLessons.style.display = '';
|
||||||
|
schLessonsContainer.innerHTML = '<div class="loading-lessons">Загрузка занятий...</div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lessons = await api.get(`/api/users/lessons/${teacherId}`);
|
||||||
|
|
||||||
|
if (!lessons || !Array.isArray(lessons) || lessons.length === 0) {
|
||||||
|
schLessonsContainer.innerHTML = '<div class="no-lessons">У преподавателя пока нет занятий</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const daysOrder = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'];
|
||||||
|
const lessonsByDay = {};
|
||||||
|
lessons.forEach(l => {
|
||||||
|
if (!lessonsByDay[l.day]) lessonsByDay[l.day] = [];
|
||||||
|
lessonsByDay[l.day].push(l);
|
||||||
|
});
|
||||||
|
Object.keys(lessonsByDay).forEach(day => {
|
||||||
|
lessonsByDay[day].sort((a, b) => a.time.localeCompare(b.time));
|
||||||
|
});
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
daysOrder.forEach(day => {
|
||||||
|
if (!lessonsByDay[day]) return;
|
||||||
|
html += `<div class="lesson-day-divider">${day}</div>`;
|
||||||
|
lessonsByDay[day].forEach(lesson => {
|
||||||
|
html += `
|
||||||
|
<div class="lesson-card">
|
||||||
|
<div class="lesson-card-header">
|
||||||
|
<span class="lesson-group">${escapeHtml(lesson.groupName)}</span>
|
||||||
|
<span class="lesson-time">${escapeHtml(lesson.time)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="lesson-card-body">
|
||||||
|
<div class="lesson-subject">${escapeHtml(lesson.subjectName)}</div>
|
||||||
|
<div class="lesson-details">
|
||||||
|
<span class="lesson-detail-item">${escapeHtml(lesson.typeLesson)}</span>
|
||||||
|
<span class="lesson-detail-item">${escapeHtml(lesson.lessonFormat)}</span>
|
||||||
|
<span class="lesson-detail-item">${escapeHtml(lesson.week)}</span>
|
||||||
|
<span class="lesson-detail-item">${escapeHtml(lesson.classroomName)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
schLessonsContainer.innerHTML = html;
|
||||||
|
} catch (e) {
|
||||||
|
schLessonsContainer.innerHTML = `<div class="no-lessons">Ошибка загрузки: ${escapeHtml(e.message)}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== При смене преподавателя — подгрузить его занятия =====
|
||||||
|
schTeacherSelect.addEventListener('change', function () {
|
||||||
|
const teacherId = this.value;
|
||||||
|
if (teacherId) {
|
||||||
|
loadTeacherLessons(teacherId);
|
||||||
|
} else {
|
||||||
|
modalLessons.style.display = 'none';
|
||||||
|
schLessonsContainer.innerHTML = '<div class="no-lessons">Выберите преподавателя для просмотра занятий</div>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== Открытие / закрытие оверлея =====
|
||||||
|
function openOverlay() {
|
||||||
|
// Автозаполнение преподавателя из фильтра таблицы
|
||||||
|
const autoTeacherId = getFilteredTeacherId();
|
||||||
|
if (autoTeacherId) {
|
||||||
|
schTeacherSelect.value = autoTeacherId;
|
||||||
|
loadTeacherLessons(autoTeacherId);
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay.classList.add('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOverlay() {
|
||||||
|
overlay.classList.remove('open');
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
addForm.reset();
|
||||||
|
schTeacherSelect.value = '';
|
||||||
|
schGroupSelect.value = '';
|
||||||
|
schDisciplineSelect.value = '';
|
||||||
|
schClassroomSelect.value = '';
|
||||||
|
schDaySelect.value = '';
|
||||||
|
schTypeSelect.value = '';
|
||||||
|
schTimeSelect.innerHTML = '<option value="">Сначала выберите день</option>';
|
||||||
|
schTimeSelect.disabled = true;
|
||||||
|
if (schWeekUpper) schWeekUpper.checked = false;
|
||||||
|
if (schWeekLower) schWeekLower.checked = false;
|
||||||
|
if (schFormatOffline) schFormatOffline.checked = true;
|
||||||
|
modalLessons.style.display = 'none';
|
||||||
|
schLessonsContainer.innerHTML = '<div class="no-lessons">Выберите преподавателя для просмотра занятий</div>';
|
||||||
|
hideAlert('sch-add-alert');
|
||||||
|
}
|
||||||
|
|
||||||
|
btnAddLesson.addEventListener('click', openOverlay);
|
||||||
|
btnClose.addEventListener('click', closeOverlay);
|
||||||
|
|
||||||
|
// Закрытие по клику на оверлей (мимо модалок)
|
||||||
|
overlay.addEventListener('click', (e) => {
|
||||||
|
if (e.target === overlay || e.target.classList.contains('cs-overlay-scroll')) {
|
||||||
|
closeOverlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Закрытие по Escape
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && overlay.classList.contains('open')) {
|
||||||
|
closeOverlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== Отправка формы =====
|
||||||
|
addForm.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
hideAlert('sch-add-alert');
|
||||||
|
|
||||||
|
const teacherId = schTeacherSelect.value;
|
||||||
|
const groupId = schGroupSelect.value;
|
||||||
|
const subjectId = schDisciplineSelect.value;
|
||||||
|
const classroomId = schClassroomSelect.value;
|
||||||
|
const lessonType = schTypeSelect.value;
|
||||||
|
const dayOfWeek = schDaySelect.value;
|
||||||
|
const timeSlot = schTimeSelect.value;
|
||||||
|
const lessonFormat = document.querySelector('input[name="schLessonFormat"]:checked')?.value;
|
||||||
|
|
||||||
|
if (!teacherId) { showAlert('sch-add-alert', 'Выберите преподавателя', 'error'); return; }
|
||||||
|
if (!groupId) { showAlert('sch-add-alert', 'Выберите группу', 'error'); return; }
|
||||||
|
if (!subjectId) { showAlert('sch-add-alert', 'Выберите дисциплину', 'error'); return; }
|
||||||
|
if (!classroomId) { showAlert('sch-add-alert', 'Выберите аудиторию', 'error'); return; }
|
||||||
|
if (!dayOfWeek) { showAlert('sch-add-alert', 'Выберите день недели', 'error'); return; }
|
||||||
|
if (!timeSlot) { showAlert('sch-add-alert', 'Выберите время', 'error'); return; }
|
||||||
|
|
||||||
|
const weekUpperChecked = schWeekUpper?.checked || false;
|
||||||
|
const weekLowerChecked = schWeekLower?.checked || false;
|
||||||
|
|
||||||
|
if (!weekUpperChecked && !weekLowerChecked) {
|
||||||
|
showAlert('sch-add-alert', 'Не выбран тип недели', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let weekType = null;
|
||||||
|
if (weekUpperChecked && weekLowerChecked) weekType = 'Обе';
|
||||||
|
else if (weekUpperChecked) weekType = 'Верхняя';
|
||||||
|
else if (weekLowerChecked) weekType = 'Нижняя';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.post('/api/users/lessons/create', {
|
||||||
|
teacherId: parseInt(teacherId),
|
||||||
|
groupId: parseInt(groupId),
|
||||||
|
subjectId: parseInt(subjectId),
|
||||||
|
classroomId: parseInt(classroomId),
|
||||||
|
typeLesson: lessonType,
|
||||||
|
lessonFormat: lessonFormat,
|
||||||
|
day: dayOfWeek,
|
||||||
|
week: weekType,
|
||||||
|
time: timeSlot
|
||||||
|
});
|
||||||
|
|
||||||
|
showAlert('sch-add-alert', 'Занятие добавлено ✓', 'success');
|
||||||
|
|
||||||
|
// Очистить все поля кроме преподавателя (для массового добавления)
|
||||||
|
schGroupSelect.selectedIndex = 0;
|
||||||
|
schDisciplineSelect.selectedIndex = 0;
|
||||||
|
schClassroomSelect.selectedIndex = 0;
|
||||||
|
schTypeSelect.selectedIndex = 0;
|
||||||
|
schDaySelect.selectedIndex = 0;
|
||||||
|
schTimeSelect.innerHTML = '<option value="">Сначала выберите день</option>';
|
||||||
|
schTimeSelect.disabled = true;
|
||||||
|
schWeekUpper.checked = false;
|
||||||
|
schWeekLower.checked = false;
|
||||||
|
document.querySelector('input[name="schLessonFormat"][value="Очно"]').checked = true;
|
||||||
|
|
||||||
|
// Обновить занятия преподавателя в модалке 2
|
||||||
|
if (teacherId) {
|
||||||
|
await loadTeacherLessons(teacherId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновить основную таблицу
|
||||||
|
await loadSchedule();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
hideAlert('sch-add-alert');
|
||||||
|
}, 4000);
|
||||||
|
} catch (err) {
|
||||||
|
showAlert('sch-add-alert', err.message || 'Ошибка добавления занятия', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===================== Инициализация =====================
|
||||||
|
await Promise.all([
|
||||||
|
loadSchedule(),
|
||||||
|
loadGroups(),
|
||||||
|
loadSubjects(),
|
||||||
|
loadClassrooms(),
|
||||||
|
loadTeachers()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,9 @@ const ROLE_BADGE = { ADMIN: 'badge-admin', TEACHER: 'badge-teacher', STUDENT: 'b
|
|||||||
export async function initUsers() {
|
export async function initUsers() {
|
||||||
const usersTbody = document.getElementById('users-tbody');
|
const usersTbody = document.getElementById('users-tbody');
|
||||||
const createForm = document.getElementById('create-form');
|
const createForm = document.getElementById('create-form');
|
||||||
const modalBackdrop = document.getElementById('modal-backdrop');
|
|
||||||
|
// ===== Оверлей (cs-overlay) =====
|
||||||
|
const usersOverlay = document.getElementById('users-overlay');
|
||||||
|
|
||||||
// ===== 1-е модальное окно: Добавить занятие =====
|
// ===== 1-е модальное окно: Добавить занятие =====
|
||||||
const modalAddLesson = document.getElementById('modal-add-lesson');
|
const modalAddLesson = document.getElementById('modal-add-lesson');
|
||||||
@@ -28,7 +30,6 @@ export async function initUsers() {
|
|||||||
|
|
||||||
// ===== 2-е модальное окно: Просмотр занятий =====
|
// ===== 2-е модальное окно: Просмотр занятий =====
|
||||||
const modalViewLessons = document.getElementById('modal-view-lessons');
|
const modalViewLessons = document.getElementById('modal-view-lessons');
|
||||||
const modalViewLessonsClose = document.getElementById('modal-view-lessons-close');
|
|
||||||
const lessonsContainer = document.getElementById('lessons-container');
|
const lessonsContainer = document.getElementById('lessons-container');
|
||||||
const modalTeacherName = document.getElementById('modal-teacher-name');
|
const modalTeacherName = document.getElementById('modal-teacher-name');
|
||||||
|
|
||||||
@@ -56,36 +57,6 @@ export async function initUsers() {
|
|||||||
"13:20-14:50"
|
"13:20-14:50"
|
||||||
];
|
];
|
||||||
|
|
||||||
// =========================================================
|
|
||||||
// СИНХРОНИЗАЦИЯ ВЫСОТЫ 1-й МОДАЛКИ -> CSS переменная
|
|
||||||
// =========================================================
|
|
||||||
const addLessonContent = document.querySelector('#modal-add-lesson .modal-content');
|
|
||||||
|
|
||||||
function setAddLessonHeightVar(px) {
|
|
||||||
const h = Math.max(0, Math.ceil(px || 0));
|
|
||||||
document.documentElement.style.setProperty('--add-lesson-height', `${h}px`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncAddLessonHeight() {
|
|
||||||
if (!addLessonContent) return;
|
|
||||||
|
|
||||||
if (!modalAddLesson?.classList.contains('open')) {
|
|
||||||
// если первая модалка закрыта — "шапки" нет
|
|
||||||
setAddLessonHeightVar(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setAddLessonHeightVar(addLessonContent.getBoundingClientRect().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Авто-обновление при любом изменении размеров первой модалки
|
|
||||||
if (addLessonContent && 'ResizeObserver' in window) {
|
|
||||||
const ro = new ResizeObserver(() => syncAddLessonHeight());
|
|
||||||
ro.observe(addLessonContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => syncAddLessonHeight());
|
|
||||||
|
|
||||||
// =========================================================
|
// =========================================================
|
||||||
// Загрузка справочников
|
// Загрузка справочников
|
||||||
// =========================================================
|
// =========================================================
|
||||||
@@ -225,25 +196,15 @@ export async function initUsers() {
|
|||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBackdrop() {
|
// ===== Открытие / закрытие оверлея =====
|
||||||
if(!modalBackdrop) return;
|
function openOverlay() {
|
||||||
const anyOpen =
|
if (usersOverlay) usersOverlay.classList.add('open');
|
||||||
modalAddLesson?.classList.contains('open') ||
|
|
||||||
modalViewLessons?.classList.contains('open');
|
|
||||||
|
|
||||||
modalBackdrop.classList.toggle('open', anyOpen);
|
|
||||||
}
|
}
|
||||||
// Клик мимо модалок закроет их, если не надо, то закомментить этот код
|
function closeOverlay() {
|
||||||
modalBackdrop?.addEventListener('click', () => {
|
if (usersOverlay) usersOverlay.classList.remove('open');
|
||||||
if (modalAddLesson?.classList.contains('open')) {
|
if (modalViewLessons) modalViewLessons.style.display = 'none';
|
||||||
modalAddLesson.classList.remove('open');
|
|
||||||
resetLessonForm();
|
resetLessonForm();
|
||||||
syncAddLessonHeight();
|
}
|
||||||
}
|
|
||||||
if (modalViewLessons?.classList.contains('open')) {
|
|
||||||
closeViewLessonsModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// =========================================================
|
// =========================================================
|
||||||
// 1-я модалка: добавление занятия
|
// 1-я модалка: добавление занятия
|
||||||
@@ -270,9 +231,7 @@ export async function initUsers() {
|
|||||||
lessonDaySelect.value = '';
|
lessonDaySelect.value = '';
|
||||||
updateTimeOptions('');
|
updateTimeOptions('');
|
||||||
|
|
||||||
modalAddLesson.classList.add('open');
|
openOverlay();
|
||||||
updateBackdrop();
|
|
||||||
requestAnimationFrame(() => syncAddLessonHeight());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addLessonForm.addEventListener('submit', async (e) => {
|
addLessonForm.addEventListener('submit', async (e) => {
|
||||||
@@ -289,15 +248,20 @@ export async function initUsers() {
|
|||||||
|
|
||||||
const lessonFormat = document.querySelector('input[name="lessonFormat"]:checked')?.value;
|
const lessonFormat = document.querySelector('input[name="lessonFormat"]:checked')?.value;
|
||||||
|
|
||||||
if (!groupId) { showAlert('add-lesson-alert', 'Выберите группу', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; }
|
if (!groupId) { showAlert('add-lesson-alert', 'Выберите группу', 'error'); return; }
|
||||||
if (!subjectId) { showAlert('add-lesson-alert', 'Выберите дисциплину', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; }
|
if (!subjectId) { showAlert('add-lesson-alert', 'Выберите дисциплину', 'error'); return; }
|
||||||
if (!classroomId) { showAlert('add-lesson-alert', 'Выберите аудиторию', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; }
|
if (!classroomId) { showAlert('add-lesson-alert', 'Выберите аудиторию', 'error'); return; }
|
||||||
if (!dayOfWeek) { showAlert('add-lesson-alert', 'Выберите день недели', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; }
|
if (!dayOfWeek) { showAlert('add-lesson-alert', 'Выберите день недели', 'error'); return; }
|
||||||
if (!timeSlot) { showAlert('add-lesson-alert', 'Выберите время', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; }
|
if (!timeSlot) { showAlert('add-lesson-alert', 'Выберите время', 'error'); return; }
|
||||||
|
|
||||||
const weekUpperChecked = weekUpper?.checked || false;
|
const weekUpperChecked = weekUpper?.checked || false;
|
||||||
const weekLowerChecked = weekLower?.checked || false;
|
const weekLowerChecked = weekLower?.checked || false;
|
||||||
|
|
||||||
|
if (!weekUpperChecked && !weekLowerChecked) {
|
||||||
|
showAlert('add-lesson-alert', 'Не выбран тип недели', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let weekType = null;
|
let weekType = null;
|
||||||
if (weekUpperChecked && weekLowerChecked) weekType = 'Обе';
|
if (weekUpperChecked && weekLowerChecked) weekType = 'Обе';
|
||||||
else if (weekUpperChecked) weekType = 'Верхняя';
|
else if (weekUpperChecked) weekType = 'Верхняя';
|
||||||
@@ -316,57 +280,45 @@ export async function initUsers() {
|
|||||||
time: timeSlot
|
time: timeSlot
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modalViewLessons?.classList.contains('open') && currentLessonsTeacherId == userId) {
|
if (modalViewLessons?.style.display !== 'none' && currentLessonsTeacherId == userId) {
|
||||||
await loadTeacherLessons(currentLessonsTeacherId, currentLessonsTeacherName);
|
await loadTeacherLessons(currentLessonsTeacherId, currentLessonsTeacherName);
|
||||||
}
|
}
|
||||||
|
|
||||||
showAlert('add-lesson-alert', 'Занятие добавлено', 'success');
|
showAlert('add-lesson-alert', 'Занятие добавлено ✓', 'success');
|
||||||
|
|
||||||
lessonGroupSelect.value = '';
|
lessonGroupSelect.selectedIndex = 0;
|
||||||
lessonDisciplineSelect.value = '';
|
lessonDisciplineSelect.selectedIndex = 0;
|
||||||
lessonClassroomSelect.value = '';
|
lessonClassroomSelect.selectedIndex = 0;
|
||||||
lessonTypeSelect.value = '';
|
lessonTypeSelect.selectedIndex = 0;
|
||||||
lessonDaySelect.value = '';
|
lessonDaySelect.selectedIndex = 0;
|
||||||
lessonTimeSelect.value = '';
|
lessonTimeSelect.innerHTML = '<option value="">Сначала выберите день</option>';
|
||||||
lessonTimeSelect.disabled = true;
|
lessonTimeSelect.disabled = true;
|
||||||
|
|
||||||
weekUpper.checked = false;
|
weekUpper.checked = false;
|
||||||
weekLower.checked = false;
|
weekLower.checked = false;
|
||||||
document.querySelector('input[name="lessonFormat"][value="Очно"]').checked = true;
|
document.querySelector('input[name="lessonFormat"][value="Очно"]').checked = true;
|
||||||
|
|
||||||
requestAnimationFrame(() => syncAddLessonHeight());
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hideAlert('add-lesson-alert');
|
hideAlert('add-lesson-alert');
|
||||||
syncAddLessonHeight();
|
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlert('add-lesson-alert', err.message || 'Ошибка добавления занятия', 'error');
|
showAlert('add-lesson-alert', err.message || 'Ошибка добавления занятия', 'error');
|
||||||
requestAnimationFrame(() => syncAddLessonHeight());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
lessonDaySelect.addEventListener('change', function () {
|
lessonDaySelect.addEventListener('change', function () {
|
||||||
updateTimeOptions(this.value);
|
updateTimeOptions(this.value);
|
||||||
requestAnimationFrame(() => syncAddLessonHeight());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modalAddLessonClose) {
|
if (modalAddLessonClose) {
|
||||||
modalAddLessonClose.addEventListener('click', () => {
|
modalAddLessonClose.addEventListener('click', () => closeOverlay());
|
||||||
modalAddLesson.classList.remove('open');
|
|
||||||
resetLessonForm();
|
|
||||||
syncAddLessonHeight();
|
|
||||||
updateBackdrop();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modalAddLesson) {
|
// Клик по оверлею (мимо модалок) закрывает всё
|
||||||
modalAddLesson.addEventListener('click', (e) => {
|
if (usersOverlay) {
|
||||||
if (e.target === modalAddLesson) {
|
usersOverlay.querySelector('.cs-overlay-scroll')?.addEventListener('click', (e) => {
|
||||||
modalAddLesson.classList.remove('open');
|
if (e.target.classList.contains('cs-overlay-scroll')) {
|
||||||
resetLessonForm();
|
closeOverlay();
|
||||||
syncAddLessonHeight();
|
|
||||||
updateBackdrop();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -481,48 +433,20 @@ export async function initUsers() {
|
|||||||
currentLessonsTeacherId = teacherId;
|
currentLessonsTeacherId = teacherId;
|
||||||
currentLessonsTeacherName = teacherName || '';
|
currentLessonsTeacherName = teacherName || '';
|
||||||
|
|
||||||
|
if (modalViewLessons) modalViewLessons.style.display = '';
|
||||||
loadTeacherLessons(teacherId, teacherName);
|
loadTeacherLessons(teacherId, teacherName);
|
||||||
|
|
||||||
requestAnimationFrame(() => syncAddLessonHeight());
|
|
||||||
|
|
||||||
modalViewLessons.classList.add('open');
|
|
||||||
updateBackdrop();
|
|
||||||
// document.body.style.overflow = 'hidden';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeViewLessonsModal() {
|
function closeViewLessonsModal() {
|
||||||
modalViewLessons.classList.remove('open');
|
if (modalViewLessons) modalViewLessons.style.display = 'none';
|
||||||
updateBackdrop();
|
|
||||||
// document.body.style.overflow = '';
|
|
||||||
|
|
||||||
currentLessonsTeacherId = null;
|
currentLessonsTeacherId = null;
|
||||||
currentLessonsTeacherName = '';
|
currentLessonsTeacherName = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modalViewLessonsClose) {
|
|
||||||
modalViewLessonsClose.addEventListener('click', closeViewLessonsModal);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modalViewLessons) {
|
|
||||||
modalViewLessons.addEventListener('click', (e) => {
|
|
||||||
if (e.target === modalViewLessons) closeViewLessonsModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key !== 'Escape') return;
|
if (e.key !== 'Escape') return;
|
||||||
|
if (usersOverlay?.classList.contains('open')) {
|
||||||
if (modalAddLesson?.classList.contains('open')) {
|
closeOverlay();
|
||||||
modalAddLesson.classList.remove('open');
|
|
||||||
resetLessonForm();
|
|
||||||
syncAddLessonHeight();
|
|
||||||
updateBackdrop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modalViewLessons?.classList.contains('open')) {
|
|
||||||
closeViewLessonsModal();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Расписание занятий</h2>
|
<div class="card-header-row">
|
||||||
|
<h2>Расписание занятий</h2>
|
||||||
|
<button class="btn-primary" id="sch-btn-add-lesson">Добавить занятие</button>
|
||||||
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table id="schedule-table">
|
<table id="schedule-table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -35,9 +38,142 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="schedule-tbody">
|
<tbody id="schedule-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="8" class="loading-row">Загрузка...</td>
|
<td colspan="11" class="loading-row">Загрузка...</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ===== Оверлей для модалок добавления занятия ===== -->
|
||||||
|
<div class="cs-overlay" id="sch-overlay">
|
||||||
|
<div class="cs-overlay-scroll">
|
||||||
|
|
||||||
|
<!-- Модалка 1: Форма добавления -->
|
||||||
|
<div class="cs-modal cs-modal-form card" id="sch-modal-form">
|
||||||
|
<div class="cs-modal-header">
|
||||||
|
<h2>Добавить занятие</h2>
|
||||||
|
<button class="btn-close-panel" id="sch-modal-close">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="sch-add-lesson-form">
|
||||||
|
<div class="form-row" style="align-items: flex-end; gap: 1rem; flex-wrap: wrap; width: 100%; justify-content: space-between;">
|
||||||
|
|
||||||
|
<!-- Преподаватель -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 220px">
|
||||||
|
<label for="sch-teacher">Преподаватель</label>
|
||||||
|
<select id="sch-teacher" required>
|
||||||
|
<option value="">Выберите преподавателя</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Группа -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 190px">
|
||||||
|
<label for="sch-group">Группа</label>
|
||||||
|
<select id="sch-group" required>
|
||||||
|
<option value="">Выберите группу</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Дисциплина -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 220px">
|
||||||
|
<label for="sch-discipline">Дисциплина</label>
|
||||||
|
<select id="sch-discipline" required>
|
||||||
|
<option value="">Выберите дисциплину</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Аудитория -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 215px">
|
||||||
|
<label for="sch-classroom">Аудитория</label>
|
||||||
|
<select id="sch-classroom" required>
|
||||||
|
<option value="">Выберите аудиторию</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- День недели -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 170px">
|
||||||
|
<label for="sch-day">День недели</label>
|
||||||
|
<select id="sch-day" required>
|
||||||
|
<option value="">Выберите день</option>
|
||||||
|
<option value="Понедельник">Понедельник</option>
|
||||||
|
<option value="Вторник">Вторник</option>
|
||||||
|
<option value="Среда">Среда</option>
|
||||||
|
<option value="Четверг">Четверг</option>
|
||||||
|
<option value="Пятница">Пятница</option>
|
||||||
|
<option value="Суббота">Суббота</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Неделя -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 192px">
|
||||||
|
<label>Неделя</label>
|
||||||
|
<div style="display: flex; gap: 0.2rem;">
|
||||||
|
<label class="btn-checkbox">
|
||||||
|
<input type="checkbox" name="schWeekType" value="Верхняя" id="sch-week-upper">
|
||||||
|
<span class="checkbox-btn">Верхняя</span>
|
||||||
|
</label>
|
||||||
|
<label class="btn-checkbox">
|
||||||
|
<input type="checkbox" name="schWeekType" value="Нижняя" id="sch-week-lower">
|
||||||
|
<span class="checkbox-btn">Нижняя</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Тип занятия -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 160px">
|
||||||
|
<label for="sch-type">Тип занятия</label>
|
||||||
|
<select id="sch-type" required>
|
||||||
|
<option value="">Выберите тип</option>
|
||||||
|
<option value="Практическая работа">Практическая</option>
|
||||||
|
<option value="Лекция">Лекция</option>
|
||||||
|
<option value="Лабораторная работа">Лабораторная</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Формат занятия -->
|
||||||
|
<div class="form-group" style="flex: 0 1 auto; max-width: 170px">
|
||||||
|
<label>Формат занятия</label>
|
||||||
|
<div style="display: flex; gap: 0.2rem;">
|
||||||
|
<label class="btn-checkbox">
|
||||||
|
<input type="radio" name="schLessonFormat" value="Очно" id="sch-format-offline" checked>
|
||||||
|
<span class="checkbox-btn">Очно</span>
|
||||||
|
</label>
|
||||||
|
<label class="btn-checkbox">
|
||||||
|
<input type="radio" name="schLessonFormat" value="Онлайн" id="sch-format-online">
|
||||||
|
<span class="checkbox-btn">Онлайн</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Время занятия -->
|
||||||
|
<div class="form-group" style="flex: 0 0 auto; max-width: 235px">
|
||||||
|
<label for="sch-time">Время занятия</label>
|
||||||
|
<select id="sch-time" required disabled>
|
||||||
|
<option value="">Сначала выберите день</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопка Сохранить -->
|
||||||
|
<div class="form-group" style="flex: 0 0 auto;">
|
||||||
|
<button type="submit" class="btn-primary" style="white-space: nowrap;">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-alert" id="sch-add-alert" role="alert" style="margin-top: 1rem;"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Модалка 2: Занятия выбранного преподавателя -->
|
||||||
|
<div class="cs-modal cs-modal-table card" id="sch-modal-lessons" style="display:none;">
|
||||||
|
<div class="cs-modal-header">
|
||||||
|
<h2 id="sch-teacher-name">Занятия преподавателя</h2>
|
||||||
|
</div>
|
||||||
|
<div class="lessons-container" id="sch-lessons-container">
|
||||||
|
<div class="no-lessons">Выберите преподавателя для просмотра занятий</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,187 +1,189 @@
|
|||||||
<!-- ===== Users Tab ===== -->
|
<!-- ===== Users Tab ===== -->
|
||||||
<div class="card create-card">
|
<div class="card create-card">
|
||||||
<h2>Новый пользователь</h2>
|
<h2>Новый пользователь</h2>
|
||||||
<form id="create-form">
|
<form id="create-form">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-username">Имя пользователя</label>
|
<label for="new-username">Имя пользователя</label>
|
||||||
<input type="text" id="new-username" placeholder="username" required>
|
<input type="text" id="new-username" placeholder="username" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-password">Пароль</label>
|
<label for="new-password">Пароль</label>
|
||||||
<input type="text" id="new-password" placeholder="password" required>
|
<input type="text" id="new-password" placeholder="password" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-role">Роль</label>
|
<label for="new-role">Роль</label>
|
||||||
<select id="new-role">
|
<select id="new-role">
|
||||||
<option value="STUDENT">Студент</option>
|
<option value="STUDENT">Студент</option>
|
||||||
<option value="TEACHER">Преподаватель</option>
|
<option value="TEACHER">Преподаватель</option>
|
||||||
<option value="ADMIN">Администратор</option>
|
<option value="ADMIN">Администратор</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-fullname">ФИО пользователя</label>
|
<label for="new-fullname">ФИО пользователя</label>
|
||||||
<input type="text" id="new-fullname" placeholder="Иванов Иван Иванович" required>
|
<input type="text" id="new-fullname" placeholder="Иванов Иван Иванович" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-jobtitle">Должность</label>
|
<label for="new-jobtitle">Должность</label>
|
||||||
<input type="text" id="new-jobtitle" placeholder="Студент / Доцент" required>
|
<input type="text" id="new-jobtitle" placeholder="Студент / Доцент" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<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-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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Все пользователи</h2>
|
<h2>Все пользователи</h2>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table id="users-table">
|
<table id="users-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Имя пользователя</th>
|
<th>Имя пользователя</th>
|
||||||
<th>ФИО</th>
|
<th>ФИО</th>
|
||||||
<th>Должность</th>
|
<th>Должность</th>
|
||||||
<th>Кафедра</th>
|
<th>Кафедра</th>
|
||||||
<th>Роль</th>
|
<th>Роль</th>
|
||||||
<th colspan="2">Действия</th>
|
<th colspan="2">Действия</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="users-tbody">
|
<tbody id="users-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="8" class="loading-row">Загрузка...</td>
|
<td colspan="8" class="loading-row">Загрузка...</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Lesson Modal -->
|
<!-- ===== Оверлей для модалок добавления/просмотра занятий ===== -->
|
||||||
<div class="modal-overlay" id="modal-add-lesson">
|
<div class="cs-overlay" id="users-overlay">
|
||||||
<div class="modal-content card">
|
<div class="cs-overlay-scroll">
|
||||||
<h2>Добавить занятие</h2>
|
|
||||||
<button class="modal-close" id="modal-add-lesson-close">×</button>
|
<!-- Модалка 1: Форма добавления -->
|
||||||
<form id="add-lesson-form">
|
<div class="cs-modal cs-modal-form card" id="modal-add-lesson">
|
||||||
<input type="hidden" id="lesson-user-id">
|
<div class="cs-modal-header">
|
||||||
|
<h2>Добавить занятие</h2>
|
||||||
<!-- Один общий ряд для всех элементов -->
|
<button class="btn-close-panel" id="modal-add-lesson-close">×</button>
|
||||||
<div class="form-row" style="align-items: flex-end; gap: 1rem; flex-wrap: wrap; width: 100%; justify-content: space-between;">
|
</div>
|
||||||
|
<form id="add-lesson-form">
|
||||||
<!-- Группа -->
|
<input type="hidden" id="lesson-user-id">
|
||||||
<div class="form-group" style="flex: 0 1 auto; max-width: 190px">
|
|
||||||
<label for="lesson-group">Группа</label>
|
<!-- Один общий ряд для всех элементов -->
|
||||||
<select id="lesson-group" required>
|
<div class="form-row" style="align-items: flex-end; gap: 1rem; flex-wrap: wrap; width: 100%; justify-content: space-between;">
|
||||||
<option value="">Выберите группу</option>
|
|
||||||
</select>
|
<!-- Группа -->
|
||||||
</div>
|
<div class="form-group" style="flex: 0 1 auto; max-width: 190px">
|
||||||
|
<label for="lesson-group">Группа</label>
|
||||||
<!-- Дисциплина -->
|
<select id="lesson-group" required>
|
||||||
<div class="form-group" style="flex: 0 1 auto; max-width: 220px">
|
<option value="">Выберите группу</option>
|
||||||
<label for="lesson-discipline">Дисциплина</label>
|
</select>
|
||||||
<select id="lesson-discipline" required>
|
</div>
|
||||||
<option value="">Выберите дисциплину</option>
|
|
||||||
</select>
|
<!-- Дисциплина -->
|
||||||
</div>
|
<div class="form-group" style="flex: 0 1 auto; max-width: 220px">
|
||||||
|
<label for="lesson-discipline">Дисциплина</label>
|
||||||
<!-- Аудитория -->
|
<select id="lesson-discipline" required>
|
||||||
<div class="form-group" style="flex: 0 1 auto; max-width: 215px">
|
<option value="">Выберите дисциплину</option>
|
||||||
<label for="lesson-classroom">Аудитория</label>
|
</select>
|
||||||
<select id="lesson-classroom" required>
|
</div>
|
||||||
<option value="">Выберите аудиторию</option>
|
|
||||||
</select>
|
<!-- Аудитория -->
|
||||||
</div>
|
<div class="form-group" style="flex: 0 1 auto; max-width: 215px">
|
||||||
|
<label for="lesson-classroom">Аудитория</label>
|
||||||
<!-- День недели -->
|
<select id="lesson-classroom" required>
|
||||||
<div class="form-group" style="flex: 0 1 auto; max-width: 170px">
|
<option value="">Выберите аудиторию</option>
|
||||||
<label for="lesson-day">День недели</label>
|
</select>
|
||||||
<select id="lesson-day" required>
|
</div>
|
||||||
<option value="">Выберите день</option>
|
|
||||||
<option value="Понедельник">Понедельник</option>
|
<!-- День недели -->
|
||||||
<option value="Вторник">Вторник</option>
|
<div class="form-group" style="flex: 0 1 auto; max-width: 170px">
|
||||||
<option value="Среда">Среда</option>
|
<label for="lesson-day">День недели</label>
|
||||||
<option value="Четверг">Четверг</option>
|
<select id="lesson-day" required>
|
||||||
<option value="Пятница">Пятница</option>
|
<option value="">Выберите день</option>
|
||||||
<option value="Суббота">Суббота</option>
|
<option value="Понедельник">Понедельник</option>
|
||||||
</select>
|
<option value="Вторник">Вторник</option>
|
||||||
</div>
|
<option value="Среда">Среда</option>
|
||||||
|
<option value="Четверг">Четверг</option>
|
||||||
<!-- Тип недели (ВЕРТИКАЛЬНО) -->
|
<option value="Пятница">Пятница</option>
|
||||||
<div class="form-group" style="flex: 0 1 auto; max-width: 192px">
|
<option value="Суббота">Суббота</option>
|
||||||
<label>Неделя</label>
|
</select>
|
||||||
<div style="display: flex; gap: 0.2rem;">
|
</div>
|
||||||
<label class="btn-checkbox">
|
|
||||||
<input type="checkbox" name="weekType" value="Верхняя" id="week-upper">
|
<!-- Тип недели -->
|
||||||
<span class="checkbox-btn">Верхняя</span>
|
<div class="form-group" style="flex: 0 1 auto; max-width: 192px">
|
||||||
</label>
|
<label>Неделя</label>
|
||||||
<label class="btn-checkbox">
|
<div style="display: flex; gap: 0.2rem;">
|
||||||
<input type="checkbox" name="weekType" value="Нижняя" id="week-lower">
|
<label class="btn-checkbox">
|
||||||
<span class="checkbox-btn">Нижняя</span>
|
<input type="checkbox" name="weekType" value="Верхняя" id="week-upper">
|
||||||
</label>
|
<span class="checkbox-btn">Верхняя</span>
|
||||||
</div>
|
</label>
|
||||||
</div>
|
<label class="btn-checkbox">
|
||||||
|
<input type="checkbox" name="weekType" value="Нижняя" id="week-lower">
|
||||||
<!-- Тип занятия -->
|
<span class="checkbox-btn">Нижняя</span>
|
||||||
<div class="form-group" style="flex: 0 1 auto; max-width: 160px">
|
</label>
|
||||||
<label for="lesson-type">Тип занятия</label>
|
</div>
|
||||||
<select id="lesson-type" required>
|
</div>
|
||||||
<option value="">Выберите тип</option>
|
|
||||||
<option value="Практическая работа">Практическая</option>
|
<!-- Тип занятия -->
|
||||||
<option value="Лекция">Лекция</option>
|
<div class="form-group" style="flex: 0 1 auto; max-width: 160px">
|
||||||
<option value="Лабораторная работа">Лабораторная</option>
|
<label for="lesson-type">Тип занятия</label>
|
||||||
</select>
|
<select id="lesson-type" required>
|
||||||
</div>
|
<option value="">Выберите тип</option>
|
||||||
|
<option value="Практическая работа">Практическая</option>
|
||||||
<!-- Формат занятия (ВЕРТИКАЛЬНО) -->
|
<option value="Лекция">Лекция</option>
|
||||||
<div class="form-group" style="flex: 0 1 auto; max-width: 170px">
|
<option value="Лабораторная работа">Лабораторная</option>
|
||||||
<label>Формат занятия</label>
|
</select>
|
||||||
<div style="display: flex; gap: 0.2rem;">
|
</div>
|
||||||
<label class="btn-checkbox">
|
|
||||||
<input type="radio" name="lessonFormat" value="Очно" id="format-offline" checked>
|
<!-- Формат занятия -->
|
||||||
<span class="checkbox-btn">Очно</span>
|
<div class="form-group" style="flex: 0 1 auto; max-width: 170px">
|
||||||
</label>
|
<label>Формат занятия</label>
|
||||||
<label class="btn-checkbox">
|
<div style="display: flex; gap: 0.2rem;">
|
||||||
<input type="radio" name="lessonFormat" value="Онлайн" id="format-online">
|
<label class="btn-checkbox">
|
||||||
<span class="checkbox-btn">Онлайн</span>
|
<input type="radio" name="lessonFormat" value="Очно" id="format-offline" checked>
|
||||||
</label>
|
<span class="checkbox-btn">Очно</span>
|
||||||
</div>
|
</label>
|
||||||
</div>
|
<label class="btn-checkbox">
|
||||||
|
<input type="radio" name="lessonFormat" value="Онлайн" id="format-online">
|
||||||
<!-- Время занятия -->
|
<span class="checkbox-btn">Онлайн</span>
|
||||||
<div class="form-group" style="flex: 0 0 auto; max-width: 235px">
|
</label>
|
||||||
<label for="lesson-time">Время занятия</label>
|
</div>
|
||||||
<select id="lesson-time" required disabled>
|
</div>
|
||||||
<option value="">Сначала выберите день</option>
|
|
||||||
</select>
|
<!-- Время занятия -->
|
||||||
</div>
|
<div class="form-group" style="flex: 0 0 auto; max-width: 235px">
|
||||||
|
<label for="lesson-time">Время занятия</label>
|
||||||
<!-- Кнопка Сохранить (в том же ряду) -->
|
<select id="lesson-time" required disabled>
|
||||||
<div class="form-group" style="flex: 0 0 auto;">
|
<option value="">Сначала выберите день</option>
|
||||||
<button type="submit" class="btn-primary" style="white-space: nowrap;">Сохранить</button>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div> <!-- Закрытие form-row -->
|
<!-- Кнопка Сохранить -->
|
||||||
|
<div class="form-group" style="flex: 0 0 auto;">
|
||||||
<div class="form-alert" id="add-lesson-alert" role="alert" style="margin-top: 1rem;"></div>
|
<button type="submit" class="btn-primary" style="white-space: nowrap;">Сохранить</button>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
|
||||||
<!-- View Teacher Lessons Modal -->
|
</div>
|
||||||
<div class="modal-overlay" id="modal-view-lessons">
|
|
||||||
<div class="modal-content view-lessons-modal">
|
<div class="form-alert" id="add-lesson-alert" role="alert" style="margin-top: 1rem;"></div>
|
||||||
<div class="modal-header">
|
</form>
|
||||||
<h2 id="modal-teacher-name">Занятия преподавателя</h2>
|
</div>
|
||||||
<button class="modal-close" id="modal-view-lessons-close">×</button>
|
|
||||||
</div>
|
<!-- Модалка 2: Просмотр занятий преподавателя -->
|
||||||
|
<div class="cs-modal cs-modal-table card" id="modal-view-lessons" style="display:none;">
|
||||||
<div class="lessons-container" id="lessons-container">
|
<div class="cs-modal-header">
|
||||||
<!-- Фильтры по дням (добавим позже) -->
|
<h2 id="modal-teacher-name">Занятия преподавателя</h2>
|
||||||
<div class="loading-lessons">Загрузка занятий...</div>
|
</div>
|
||||||
</div>
|
<div class="lessons-container" id="lessons-container">
|
||||||
</div>
|
<div class="loading-lessons">Загрузка занятий...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-backdrop"></div>
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user