import { api } from '../api.js'; import { escapeHtml, showAlert, hideAlert } from '../utils.js'; // Ключ для хранения данных в sessionStorage const STORAGE_KEY = 'department_schedule_blocks'; export async function initDepartment() { const form = document.getElementById('department-schedule-form'); const departmentSelect = document.getElementById('filter-department'); const container = document.getElementById('schedule-blocks-container'); let departments = []; // Загрузка кафедр try { departments = await api.get('/api/departments'); departmentSelect.innerHTML = '' + departments.map(d => ``).join(''); } catch (e) { departmentSelect.innerHTML = ''; } // ===== Восстанавливаем ранее загруженные таблицы из sessionStorage ===== restoreScheduleBlocks(); form.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert('schedule-form-alert'); const departmentId = departmentSelect.value; const period = document.getElementById('filter-period').value; const semesterType = document.querySelector('input[name="semesterType"]:checked')?.value; if (!departmentId || !period || !semesterType) { showAlert('schedule-form-alert', 'Заполните все поля', 'error'); return; } const deptName = departmentSelect.options[departmentSelect.selectedIndex].text; try { const params = new URLSearchParams({ departmentId, semesterType, period }); const data = await api.get(`/api/department/schedule?${params.toString()}`); const semesterName = semesterType === 'spring' ? 'весенний' : (semesterType === 'autumn' ? 'осенний' : semesterType); const periodName = period.replace('-', '/'); renderScheduleBlock(deptName, semesterName, periodName, data, departmentId, semesterType, period); // НЕ сбрасываем форму — фильтры остаются заполненными (fix #3) } catch (err) { showAlert('schedule-form-alert', err.message || 'Ошибка загрузки данных', 'error'); } }); // ===== Уникальный ключ для таблицы по параметрам ===== function blockKey(departmentId, semesterType, period) { return `${departmentId}_${semesterType}_${period}`; } // ===== Рендер блока таблицы (с дедупликацией — fix #6) ===== function renderScheduleBlock(deptName, semester, period, schedule, departmentId, semesterType, rawPeriod) { const key = blockKey(departmentId, semesterType, rawPeriod); // Удаляем ранее загруженный блок с тем же ключом const existing = container.querySelector(`[data-block-key="${key}"]`); if (existing) existing.remove(); const details = document.createElement('details'); details.className = 'table-item'; details.open = true; details.setAttribute('data-block-key', key); details.innerHTML = `
Данные к составлению расписания Кафедра: ${escapeHtml(deptName)} Семестр: ${escapeHtml(semester)} Уч. год: ${escapeHtml(period)}
${Array.isArray(schedule) ? schedule.length : 0} записей
${renderRows(schedule)}
Специальность Курс/семестр Группа Дисциплина Вид занятий Часов в неделю Деление на подгруппы Преподаватель
`; container.prepend(details); // Сохраняем в sessionStorage saveScheduleBlock(key, { deptName, semester, period, schedule, departmentId, semesterType, rawPeriod }); } function renderRows(schedule) { if (!Array.isArray(schedule) || schedule.length === 0) { return 'Нет данных'; } return schedule.map(r => ` ${escapeHtml(r.specialityCode || '-')} ${(() => { const course = r.groupCourse || '-'; const semester = r.semester || '-'; if (course === '-' && semester === '-') return '-'; return `${course} | ${semester}`; })()} ${escapeHtml(r.groupName || '-')} ${escapeHtml(r.subjectName || '-')} ${escapeHtml(r.lessonType || '-')} ${escapeHtml(r.numberOfHours || '-')} ${r.division === true ? '✓' : ''} ${(() => { const jobTitle = r.teacherJobTitle || '-'; const teacherName = r.teacherName || '-'; if (jobTitle === '-' && teacherName === '-') return '-'; return `${jobTitle}, ${teacherName}`; })()} `).join(''); } // ===== Persistence: sessionStorage (fix #4) ===== function saveScheduleBlock(key, blockData) { try { const stored = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '{}'); stored[key] = blockData; sessionStorage.setItem(STORAGE_KEY, JSON.stringify(stored)); } catch (e) { console.warn('Ошибка сохранения в sessionStorage:', e); } } function restoreScheduleBlocks() { try { const stored = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '{}'); const keys = Object.keys(stored); if (keys.length === 0) return; keys.forEach(key => { const b = stored[key]; renderScheduleBlock(b.deptName, b.semester, b.period, b.schedule, b.departmentId, b.semesterType, b.rawPeriod); }); } catch (e) { console.warn('Ошибка восстановления из sessionStorage:', e); } } // ========================================================= // ЛОГИКА ДЛЯ ФУНКЦИОНАЛА "СОЗДАТЬ ЗАПИСЬ (К/Ф)" // Два модальных окна поверх всего контента в одном оверлее // ========================================================= const btnCreateSchedule = document.getElementById('btn-create-schedule'); const csOverlay = document.getElementById('cs-overlay'); const modalCreateSchedule = document.getElementById('modal-create-schedule'); const modalCreateScheduleClose = document.getElementById('modal-create-schedule-close'); const formCreateSchedule = document.getElementById('create-schedule-form'); const modalViewSchedules = document.getElementById('modal-view-schedules'); const btnSaveSchedules = document.getElementById('btn-save-schedules'); const preparedSchedulesTbody = document.getElementById('prepared-schedules-tbody'); const csGroupSelect = document.getElementById('cs-group'); const csSubjectSelect = document.getElementById('cs-subject'); const csTeacherSelect = document.getElementById('cs-teacher'); const csDepartmentIdInput = document.getElementById('cs-department-id'); let preparedSchedules = []; let csGroups = []; let csSubjects = []; let csTeachers = []; const SEMESTER_LABELS = { autumn: 'Осенний', spring: 'Весенний' }; const LESSON_TYPE_LABELS = { 1: 'Лекция', 2: 'Практическая работа', 3: 'Лабораторная работа' }; const localDepartmentId = localStorage.getItem('departmentId'); // ===== Загрузка справочников ===== async function loadDictionariesForSchedule() { try { csGroups = await api.get('/api/groups'); csGroupSelect.innerHTML = '' + csGroups.map(g => ``).join(''); csSubjects = await api.get('/api/subjects'); csSubjectSelect.innerHTML = '' + csSubjects.map(s => ``).join(''); // Загрузка преподавателей: сначала по кафедре, при ошибке — все преподаватели csTeachers = []; if (localDepartmentId) { try { csTeachers = await api.get(`/api/users/teachers/${localDepartmentId}`); } catch (e) { console.warn('Не удалось загрузить преподавателей для кафедры, загружаем всех:', e); } } // Фолбэк: загружаем всех преподавателей if (!Array.isArray(csTeachers) || csTeachers.length === 0) { try { csTeachers = await api.get('/api/users/teachers'); } catch (e2) { console.error('Ошибка загрузки всех преподавателей:', e2); } } if (Array.isArray(csTeachers) && csTeachers.length > 0) { csTeacherSelect.innerHTML = '' + csTeachers.map(t => ``).join(''); } else { csTeacherSelect.innerHTML = ''; } } catch (e) { console.error('Ошибка загрузки справочников:', e); } } loadDictionariesForSchedule(); // ===== Открытие / Закрытие оверлея ===== function openOverlay() { csOverlay.classList.add('open'); document.body.style.overflow = 'hidden'; } function closeOverlay() { csOverlay.classList.remove('open'); document.body.style.overflow = ''; hideAlert('create-schedule-alert'); hideAlert('save-schedules-alert'); } function updateTableVisibility() { modalViewSchedules.style.display = preparedSchedules.length > 0 ? '' : 'none'; } // ===== Кнопка «Создать запись» ===== btnCreateSchedule.addEventListener('click', () => { if (localDepartmentId) { csDepartmentIdInput.value = localDepartmentId; } else { showAlert('schedule-form-alert', 'Требуется перезайти (отсутствует ID кафедры)', 'error'); return; } openOverlay(); }); // ===== Закрытие ===== modalCreateScheduleClose.addEventListener('click', closeOverlay); csOverlay.addEventListener('click', (e) => { if (e.target === csOverlay || e.target.classList.contains('cs-overlay-scroll')) { closeOverlay(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && csOverlay.classList.contains('open')) { closeOverlay(); } }); // ===== Рендер таблицы подготовленных записей ===== function renderPreparedSchedules() { if (preparedSchedules.length === 0) { preparedSchedulesTbody.innerHTML = 'Нет записей'; return; } preparedSchedulesTbody.innerHTML = preparedSchedules.map((s, index) => { const groupName = csGroups.find(g => g.id == s.groupId)?.name || s.groupId; const subjectName = csSubjects.find(sub => sub.id == s.subjectsId)?.name || s.subjectsId; const teacherName = csTeachers.find(t => t.id == s.teacherId)?.fullName || csTeachers.find(t => t.id == s.teacherId)?.username || s.teacherId; const lessonTypeName = LESSON_TYPE_LABELS[s.lessonTypeId] || 'Неизвестно'; const semLabel = SEMESTER_LABELS[s.semesterType] || s.semesterType; const periodDisplay = s.period.replace('-', '/'); const divText = s.isDivision ? '✓' : ''; const hasError = !!s._errorMsg; const rowStyle = hasError ? ' style="background: rgba(239, 68, 68, 0.08);"' : ''; let row = ` ${escapeHtml(periodDisplay)} ${escapeHtml(semLabel)} ${escapeHtml(String(groupName))} ${escapeHtml(String(subjectName))} ${escapeHtml(lessonTypeName)} ${s.numberOfHours} ${divText} ${escapeHtml(String(teacherName))} `; if (hasError) { row += ` ⚠ ${escapeHtml(s._errorMsg)} `; } return row; }).join(''); } // ===== Удаление строки из таблицы ===== preparedSchedulesTbody.addEventListener('click', (e) => { if (e.target.classList.contains('btn-delete')) { const idx = parseInt(e.target.getAttribute('data-index'), 10); preparedSchedules.splice(idx, 1); renderPreparedSchedules(); updateTableVisibility(); } }); // ===== Очистка полей формы (частичная) ===== function clearFormFields() { document.getElementById('cs-hours').value = ''; document.getElementById('cs-division').checked = false; } // ===== Добавление записи в список ===== formCreateSchedule.addEventListener('submit', (e) => { e.preventDefault(); hideAlert('create-schedule-alert'); const depId = csDepartmentIdInput.value; const period = document.getElementById('cs-period').value; const semesterType = document.querySelector('input[name="csSemesterType"]:checked')?.value; const groupId = csGroupSelect.value; const subjectId = csSubjectSelect.value; const lessonTypeId = document.getElementById('cs-lesson-type').value; const hours = document.getElementById('cs-hours').value; const isDivision = document.getElementById('cs-division').checked; const teacherId = csTeacherSelect.value; if (!period || !semesterType || !groupId || !subjectId || !lessonTypeId || !hours || !teacherId) { showAlert('create-schedule-alert', 'Заполните все обязательные поля', 'error'); return; } const newRecord = { departmentId: Number(depId), groupId: Number(groupId), subjectsId: Number(subjectId), lessonTypeId: Number(lessonTypeId), numberOfHours: Number(hours), isDivision: isDivision, teacherId: Number(teacherId), semesterType: semesterType, period: period }; // Проверка на дубликат const isDuplicate = preparedSchedules.some(s => s.period === newRecord.period && s.semesterType === newRecord.semesterType && s.groupId === newRecord.groupId && s.subjectsId === newRecord.subjectsId && s.lessonTypeId === newRecord.lessonTypeId && s.numberOfHours === newRecord.numberOfHours && s.isDivision === newRecord.isDivision && s.teacherId === newRecord.teacherId ); if (isDuplicate) { showAlert('create-schedule-alert', 'Такая запись уже есть в списке', 'error'); return; } preparedSchedules.push(newRecord); clearFormFields(); showAlert('create-schedule-alert', 'Запись добавлена ✓', 'success'); setTimeout(() => hideAlert('create-schedule-alert'), 4000); // fix #1: 4 секунды renderPreparedSchedules(); updateTableVisibility(); }); // ===== Сохранение в БД ===== btnSaveSchedules.addEventListener('click', async () => { if (preparedSchedules.length === 0) { showAlert('save-schedules-alert', 'Нет записей для сохранения', 'error'); return; } btnSaveSchedules.disabled = true; btnSaveSchedules.textContent = 'Сохранение...'; hideAlert('save-schedules-alert'); let errors = 0; let saved = 0; const failedRecords = []; for (const record of preparedSchedules) { try { await api.post('/api/department/schedule/create', record); saved++; } catch (err) { console.error('Ошибка сохранения записи:', err); errors++; const isDuplicate = err.status === 409 || (err.message && err.message.toLowerCase().includes('уже существует')); failedRecords.push({ ...record, _errorMsg: isDuplicate ? 'Такая запись уже есть в базе данных' : (err.message || 'Ошибка сохранения') }); } } btnSaveSchedules.disabled = false; btnSaveSchedules.textContent = 'Сохранить в БД'; if (errors === 0) { showAlert('save-schedules-alert', `Все записи (${saved}) успешно сохранены!`, 'success'); preparedSchedules = []; renderPreparedSchedules(); updateTableVisibility(); setTimeout(closeOverlay, 2000); } else { preparedSchedules = failedRecords; renderPreparedSchedules(); if (saved > 0) { showAlert('save-schedules-alert', `Сохранено: ${saved}. Ошибок: ${errors}. Проблемные записи отмечены в таблице.`, 'error'); } else { showAlert('save-schedules-alert', `Не удалось сохранить. Ошибок: ${errors}. Проблемные записи отмечены в таблице.`, 'error'); } } }); }