import { api } from '../api.js'; import { escapeHtml, showAlert, hideAlert } from '../utils.js'; const ROLE_LABELS = { ADMIN: 'Администратор', TEACHER: 'Преподаватель', STUDENT: 'Студент' }; const ROLE_BADGE = { ADMIN: 'badge-admin', TEACHER: 'badge-teacher', STUDENT: 'badge-student' }; export async function initUsers() { const usersTbody = document.getElementById('users-tbody'); const createForm = document.getElementById('create-form'); const modalBackdrop = document.getElementById('modal-backdrop'); // ===== 1-е модальное окно: Добавить занятие ===== const modalAddLesson = document.getElementById('modal-add-lesson'); const modalAddLessonClose = document.getElementById('modal-add-lesson-close'); const addLessonForm = document.getElementById('add-lesson-form'); const lessonGroupSelect = document.getElementById('lesson-group'); const lessonDisciplineSelect = document.getElementById('lesson-discipline'); const lessonClassroomSelect = document.getElementById('lesson-classroom'); const lessonTypeSelect = document.getElementById('lesson-type'); const lessonOnlineFormat = document.getElementById('format-online'); const lessonOfflineFormat = document.getElementById('format-offline'); const lessonUserId = document.getElementById('lesson-user-id'); const lessonDaySelect = document.getElementById('lesson-day'); const weekUpper = document.getElementById('week-upper'); const weekLower = document.getElementById('week-lower'); const lessonTimeSelect = document.getElementById('lesson-time'); // ===== 2-е модальное окно: Просмотр занятий ===== const modalViewLessons = document.getElementById('modal-view-lessons'); const modalViewLessonsClose = document.getElementById('modal-view-lessons-close'); const lessonsContainer = document.getElementById('lessons-container'); const modalTeacherName = document.getElementById('modal-teacher-name'); let currentLessonsTeacherId = null; let currentLessonsTeacherName = ''; // ===== Данные ===== let groups = []; let subjects = []; let classrooms = []; 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" ]; // ========================================================= // СИНХРОНИЗАЦИЯ ВЫСОТЫ 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()); // ========================================================= // Загрузка справочников // ========================================================= async function loadGroups() { try { groups = await api.get('/api/groups'); renderGroupOptions(); } catch (e) { console.error('Ошибка загрузки групп:', e); } } async function loadSubjects() { try { subjects = await api.get('/api/subjects'); renderSubjectOptions(); } catch (e) { console.error('Ошибка загрузки дисциплин:', e); } } async function loadClassrooms() { try { classrooms = await api.get('/api/classrooms'); renderClassroomsOptions(); } catch (e) { console.error('Ошибка загрузки аудиторий:', e); } } function renderGroupOptions() { if (!groups || groups.length === 0) { lessonGroupSelect.innerHTML = ''; return; } lessonGroupSelect.innerHTML = '' + groups.map(g => { let optionText = escapeHtml(g.name); if (g.groupSize) optionText += ` (численность: ${g.groupSize} чел.)`; return ``; }).join(''); } function renderSubjectOptions() { lessonDisciplineSelect.innerHTML = '' + subjects.map(s => ``).join(''); } function renderClassroomsOptions() { if (!classrooms || classrooms.length === 0) { lessonClassroomSelect.innerHTML = ''; return; } const selectedGroupId = lessonGroupSelect.value; const selectedGroup = groups?.find(g => g.id == selectedGroupId); const groupSize = selectedGroup?.groupSize || 0; lessonClassroomSelect.innerHTML = '' + classrooms.map(c => { let optionText = escapeHtml(c.name); if (c.capacity) optionText += ` (вместимость: ${c.capacity} чел.)`; if (c.isAvailable === false) { optionText += ` ❌ Занята`; } else if (selectedGroupId && groupSize > 0 && c.capacity && groupSize > c.capacity) { optionText += ` ⚠️ Недостаточно места`; } return ``; }).join(''); } lessonGroupSelect.addEventListener('change', function () { renderClassroomsOptions(); requestAnimationFrame(() => syncAddLessonHeight()); }); function updateTimeOptions(dayValue) { let times = []; if (dayValue === "Суббота") { times = saturdayTimes; } else if (dayValue && dayValue !== '') { times = weekdaysTimes; } else { lessonTimeSelect.innerHTML = ''; lessonTimeSelect.disabled = true; return; } lessonTimeSelect.innerHTML = '' + times.map(t => ``).join(''); lessonTimeSelect.disabled = false; } // ========================================================= // Пользователи // ========================================================= async function loadUsers() { try { const users = await api.get('/api/users'); renderUsers(users); } catch (e) { usersTbody.innerHTML = 'Ошибка загрузки: ' + escapeHtml(e.message) + ''; } } function renderUsers(users) { if (!users || !users.length) { usersTbody.innerHTML = 'Нет пользователей'; return; } usersTbody.innerHTML = users.map(u => ` ${u.id} ${escapeHtml(u.username)} ${ROLE_LABELS[u.role] || escapeHtml(u.role)} `).join(''); } function updateBackdrop() { if(!modalBackdrop) return; const anyOpen = modalAddLesson?.classList.contains('open') || modalViewLessons?.classList.contains('open'); modalBackdrop.classList.toggle('open', anyOpen); } // Клик мимо модалок закроет их, если не надо, то закомментить этот код modalBackdrop?.addEventListener('click', () => { if (modalAddLesson?.classList.contains('open')) { modalAddLesson.classList.remove('open'); resetLessonForm(); syncAddLessonHeight(); } if (modalViewLessons?.classList.contains('open')) { closeViewLessonsModal(); } }); // ========================================================= // 1-я модалка: добавление занятия // ========================================================= function resetLessonForm() { addLessonForm.reset(); lessonUserId.value = ''; if (weekUpper) weekUpper.checked = false; if (weekLower) weekLower.checked = false; if (lessonOfflineFormat) lessonOfflineFormat.checked = true; if (lessonOnlineFormat) lessonOnlineFormat.checked = false; lessonTimeSelect.innerHTML = ''; lessonTimeSelect.disabled = true; hideAlert('add-lesson-alert'); } function openAddLessonModal(userId) { lessonUserId.value = userId; lessonDaySelect.value = ''; updateTimeOptions(''); modalAddLesson.classList.add('open'); updateBackdrop(); requestAnimationFrame(() => syncAddLessonHeight()); } addLessonForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert('add-lesson-alert'); const userId = lessonUserId.value; const groupId = lessonGroupSelect.value; const subjectId = lessonDisciplineSelect.value; const classroomId = lessonClassroomSelect.value; const lessonType = lessonTypeSelect.value; const dayOfWeek = lessonDaySelect.value; const timeSlot = lessonTimeSelect.value; const lessonFormat = document.querySelector('input[name="lessonFormat"]:checked')?.value; if (!groupId) { showAlert('add-lesson-alert', 'Выберите группу', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } if (!subjectId) { showAlert('add-lesson-alert', 'Выберите дисциплину', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } if (!classroomId) { showAlert('add-lesson-alert', 'Выберите аудиторию', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } if (!dayOfWeek) { showAlert('add-lesson-alert', 'Выберите день недели', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } if (!timeSlot) { showAlert('add-lesson-alert', 'Выберите время', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } const weekUpperChecked = weekUpper?.checked || false; const weekLowerChecked = weekLower?.checked || false; 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(userId), groupId: parseInt(groupId), subjectId: parseInt(subjectId), classroomId: parseInt(classroomId), typeLesson: lessonType, lessonFormat: lessonFormat, day: dayOfWeek, week: weekType, time: timeSlot }); if (modalViewLessons?.classList.contains('open') && currentLessonsTeacherId == userId) { await loadTeacherLessons(currentLessonsTeacherId, currentLessonsTeacherName); } showAlert('add-lesson-alert', 'Занятие добавлено', 'success'); lessonGroupSelect.value = ''; lessonDisciplineSelect.value = ''; lessonClassroomSelect.value = ''; lessonTypeSelect.value = ''; lessonDaySelect.value = ''; lessonTimeSelect.value = ''; lessonTimeSelect.disabled = true; weekUpper.checked = false; weekLower.checked = false; document.querySelector('input[name="lessonFormat"][value="Очно"]').checked = true; requestAnimationFrame(() => syncAddLessonHeight()); setTimeout(() => { hideAlert('add-lesson-alert'); syncAddLessonHeight(); }, 3000); } catch (err) { showAlert('add-lesson-alert', err.message || 'Ошибка добавления занятия', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); } }); lessonDaySelect.addEventListener('change', function () { updateTimeOptions(this.value); requestAnimationFrame(() => syncAddLessonHeight()); }); if (modalAddLessonClose) { modalAddLessonClose.addEventListener('click', () => { modalAddLesson.classList.remove('open'); resetLessonForm(); syncAddLessonHeight(); updateBackdrop(); }); } if (modalAddLesson) { modalAddLesson.addEventListener('click', (e) => { if (e.target === modalAddLesson) { modalAddLesson.classList.remove('open'); resetLessonForm(); syncAddLessonHeight(); updateBackdrop(); } }); } // ========================================================= // Создание пользователя // ========================================================= createForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert('create-alert'); const username = document.getElementById('new-username').value.trim(); const password = document.getElementById('new-password').value; const role = document.getElementById('new-role').value; if (!username || !password) { showAlert('create-alert', 'Заполните все поля', 'error'); return; } try { const data = await api.post('/api/users', { username, password, role }); showAlert('create-alert', `Пользователь "${escapeHtml(data.username)}" создан`, 'success'); createForm.reset(); loadUsers(); } catch (err) { showAlert('create-alert', err.message || 'Ошибка соединения', 'error'); } }); // ========================================================= // Инициализация // ========================================================= await Promise.all([loadUsers(), loadGroups(), loadSubjects(), loadClassrooms()]); // ========================================================= // 2-я модалка: просмотр занятий // ========================================================= async function loadTeacherLessons(teacherId, teacherName) { try { lessonsContainer.innerHTML = '
Загрузка занятий...
'; modalTeacherName.textContent = teacherName ? `Занятия преподавателя: ${teacherName}` : 'Занятия преподавателя'; const lessons = await api.get(`/api/users/lessons/${teacherId}`); if (!lessons || lessons.length === 0) { lessonsContainer.innerHTML = '
У преподавателя пока нет занятий
'; return; } const daysOrder = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота']; const lessonsByDay = {}; lessons.forEach(lesson => { if (!lessonsByDay[lesson.day]) lessonsByDay[lesson.day] = []; lessonsByDay[lesson.day].push(lesson); }); 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 += `
${day}
`; lessonsByDay[day].forEach(lesson => { html += `
${escapeHtml(lesson.groupName)} ${escapeHtml(lesson.time)}
${escapeHtml(lesson.subjectName)}
${escapeHtml(lesson.typeLesson)} ${escapeHtml(lesson.lessonFormat)} ${escapeHtml(lesson.week)} ${escapeHtml(lesson.classroomName)}
`; }); }); lessonsContainer.innerHTML = html; } catch (e) { lessonsContainer.innerHTML = `
Ошибка загрузки: ${escapeHtml(e.message)}
`; console.error('Ошибка загрузки занятий:', e); } } function openViewLessonsModal(teacherId, teacherName) { currentLessonsTeacherId = teacherId; currentLessonsTeacherName = teacherName || ''; loadTeacherLessons(teacherId, teacherName); requestAnimationFrame(() => syncAddLessonHeight()); modalViewLessons.classList.add('open'); updateBackdrop(); // document.body.style.overflow = 'hidden'; } function closeViewLessonsModal() { modalViewLessons.classList.remove('open'); updateBackdrop(); // document.body.style.overflow = ''; currentLessonsTeacherId = null; currentLessonsTeacherName = ''; } if (modalViewLessonsClose) { modalViewLessonsClose.addEventListener('click', closeViewLessonsModal); } if (modalViewLessons) { modalViewLessons.addEventListener('click', (e) => { if (e.target === modalViewLessons) closeViewLessonsModal(); }); } document.addEventListener('keydown', (e) => { if (e.key !== 'Escape') return; if (modalAddLesson?.classList.contains('open')) { modalAddLesson.classList.remove('open'); resetLessonForm(); syncAddLessonHeight(); updateBackdrop(); return; } if (modalViewLessons?.classList.contains('open')) { closeViewLessonsModal(); return; } }); // ========================================================= // ЕДИНЫЙ обработчик кликов по таблице (ВАЖНО: без дубля) // ========================================================= usersTbody.addEventListener('click', async (e) => { const deleteBtn = e.target.closest('.btn-delete'); if (deleteBtn) { if (!confirm('Удалить пользователя?')) return; try { await api.delete('/api/users/' + deleteBtn.dataset.id); loadUsers(); } catch (err) { alert(err.message || 'Ошибка удаления'); } return; } const addLessonBtn = e.target.closest('.btn-add-lesson'); if (addLessonBtn) { e.preventDefault(); const teacherId = addLessonBtn.dataset.id; const teacherName = addLessonBtn.dataset.name; openAddLessonModal(teacherId); openViewLessonsModal(teacherId, teacherName); return; } const viewLessonsBtn = e.target.closest('.btn-view-lessons'); if (viewLessonsBtn) { e.preventDefault(); const teacherId = viewLessonsBtn.dataset.id; const teacherName = viewLessonsBtn.dataset.name; openViewLessonsModal(teacherId, teacherName); return; } }); }