import { api } from '../api.js'; import { escapeHtml, showAlert, hideAlert } from '../utils.js'; const ROLE_DEPARTMENT = 'DEPARTMENT'; export async function initDepartmentWorkspace() { const role = localStorage.getItem('role'); const userDepartmentId = localStorage.getItem('departmentId'); const departmentField = document.getElementById('department-workspace-department-field'); const departmentSelect = document.getElementById('department-workspace-department'); const startInput = document.getElementById('department-workspace-start'); const endInput = document.getElementById('department-workspace-end'); const refreshButton = document.getElementById('department-workspace-refresh'); const importForm = document.getElementById('department-subject-import-form'); const importRowsContainer = document.getElementById('subject-import-rows'); const importAddButton = document.getElementById('subject-import-add'); const assignTeacherForm = document.getElementById('department-teacher-assign-form'); const assignTeacherSelect = document.getElementById('department-teacher-select'); const assignTeacherComment = document.getElementById('department-teacher-assign-comment'); const teacherRequestForm = document.getElementById('department-teacher-request-form'); const teacherRequestsTbody = document.getElementById('department-teacher-requests-tbody'); const teacherRequestCount = document.getElementById('department-teacher-request-count'); const subjectsTbody = document.getElementById('department-subjects-tbody'); const subjectCount = document.getElementById('department-subject-count'); const commentsCard = document.getElementById('department-comments-card'); const commentsTitle = document.getElementById('department-comments-title'); const commentsList = document.getElementById('department-comment-list'); const commentsForm = document.getElementById('department-comment-form'); const commentsSubjectInput = document.getElementById('department-comment-subject-id'); const commentsTextInput = document.getElementById('department-comment-text'); const commentsCloseButton = document.getElementById('department-comments-close'); let lastTeachers = null; let lastWorkload = null; let allTeachers = []; let teacherRequests = null; let teachersErrorMessage = ''; let workloadErrorMessage = ''; setDefaultDates(startInput, endInput); refreshButton?.addEventListener('click', loadWorkspace); departmentSelect?.addEventListener('change', loadWorkspace); startInput?.addEventListener('change', handleStartDateChange); endInput?.addEventListener('change', loadWorkload); importForm?.addEventListener('submit', importSubjects); assignTeacherForm?.addEventListener('submit', assignTeacherToDepartment); teacherRequestForm?.addEventListener('submit', submitTeacherRequest); importAddButton?.addEventListener('click', () => renderImportRows([...readImportRows(), { code: '', name: '' }])); importRowsContainer?.addEventListener('click', (event) => { const removeBtn = event.target.closest('.subject-import-remove'); if (removeBtn) { const rows = readImportRows(); rows.splice(Number(removeBtn.dataset.index), 1); renderImportRows(rows.length ? rows : [{ code: '', name: '' }]); } }); commentsForm?.addEventListener('submit', saveComment); commentsCloseButton?.addEventListener('click', () => { commentsCard.hidden = true; commentsSubjectInput.value = ''; }); subjectsTbody?.addEventListener('click', event => { const button = event.target.closest('.department-comments-open'); if (button) { openComments(button.dataset.subjectId, button.dataset.subjectName); } }); try { await loadDepartments(role, userDepartmentId); if (role === ROLE_DEPARTMENT) { departmentField.hidden = true; departmentSelect.value = userDepartmentId || ''; } await loadAllTeachers(); renderImportRows([{ code: '', name: '' }]); await loadWorkspace(); } catch (error) { showAlert('department-workspace-alert', error.message || 'Ошибка загрузки кабинета кафедры', 'error'); } async function loadWorkspace() { hideAlert('department-workspace-alert'); lastTeachers = null; lastWorkload = null; teachersErrorMessage = ''; workloadErrorMessage = ''; renderTeachers(); teacherRequests = null; renderTeacherRequests(); await Promise.all([loadSubjects(), loadTeachers(), loadWorkload(), loadTeacherRequests()]); } async function loadSubjects() { const departmentId = selectedDepartmentId(); subjectsTbody.innerHTML = 'Загрузка...'; subjectCount.textContent = 'Загрузка...'; try { const subjects = await api.get(`/api/department/subjects?departmentId=${encodeURIComponent(departmentId || '')}`); subjectCount.textContent = subjectCountLabel(subjects.length); if (!subjects.length) { subjectsTbody.innerHTML = 'Дисциплины не найдены'; return; } subjectsTbody.innerHTML = subjects.map(subject => ` ${escapeHtml(subject.code || '-')} ${escapeHtml(subject.name || '-')} ${escapeHtml(subject.description || '-')} `).join(''); } catch (error) { subjectsTbody.innerHTML = `${escapeHtml(error.message || 'Ошибка загрузки дисциплин')}`; subjectCount.textContent = '0 дисциплин'; } } function renderImportRows(rows) { if (!importRowsContainer) return; importRowsContainer.innerHTML = (rows || [{ code: '', name: '' }]).map((row, index) => `
`).join(''); } function readImportRows() { if (!importRowsContainer) return []; return Array.from(importRowsContainer.querySelectorAll('.subject-import-row')).map(row => ({ code: (row.querySelector('.subject-import-code')?.value || '').trim(), name: (row.querySelector('.subject-import-name')?.value || '').trim() })); } async function importSubjects(event) { event.preventDefault(); const alertId = 'department-subject-import-alert'; hideAlert(alertId); const departmentId = selectedDepartmentId(); const rows = readImportRows() .filter(item => item.name) .map(item => ({ code: item.code, name: item.name, departmentId: Number(departmentId) })); if (!departmentId) { showAlert(alertId, 'Выберите кафедру', 'error'); return; } if (!rows.length) { showAlert(alertId, 'Заполните название хотя бы одной дисциплины', 'error'); return; } try { const result = await api.post('/api/department/subjects/import', rows); showAlert(alertId, result.message || 'Дисциплины загружены', 'success'); renderImportRows([{ code: '', name: '' }]); await loadSubjects(); } catch (error) { showAlert(alertId, error.message || 'Ошибка загрузки дисциплин', 'error'); } } async function assignTeacherToDepartment(event) { event.preventDefault(); const alertId = 'department-teacher-assign-alert'; hideAlert(alertId); const teacherId = assignTeacherSelect?.value; const departmentId = selectedDepartmentId(); if (!departmentId) { showAlert(alertId, 'Выберите кафедру', 'error'); return; } if (!teacherId) { showAlert(alertId, 'Выберите преподавателя', 'error'); return; } try { await api.post(`/api/department/teachers/${encodeURIComponent(teacherId)}/assignments`, { departmentId: Number(departmentId), comment: assignTeacherComment?.value.trim() || null }); showAlert(alertId, 'Преподаватель привязан к кафедре', 'success'); if (assignTeacherComment) assignTeacherComment.value = ''; await Promise.all([loadTeachers(), loadWorkload()]); } catch (error) { showAlert(alertId, error.message || 'Ошибка привязки преподавателя', 'error'); } } async function submitTeacherRequest(event) { event.preventDefault(); const alertId = 'department-teacher-request-alert'; hideAlert(alertId); const departmentId = selectedDepartmentId(); const username = document.getElementById('teacher-request-username')?.value.trim(); const fullName = document.getElementById('teacher-request-fullname')?.value.trim(); const jobTitle = document.getElementById('teacher-request-jobtitle')?.value.trim(); const comment = document.getElementById('teacher-request-comment')?.value.trim(); if (!departmentId) { showAlert(alertId, 'Выберите кафедру', 'error'); return; } if (!username || !fullName) { showAlert(alertId, 'Заполните логин и ФИО преподавателя', 'error'); return; } try { await api.post('/api/department/teacher-requests', { departmentId: Number(departmentId), username, fullName, jobTitle, comment }); showAlert(alertId, 'Заявка отправлена администратору', 'success'); teacherRequestForm.reset(); await loadTeacherRequests(); } catch (error) { showAlert(alertId, error.message || 'Ошибка отправки заявки', 'error'); } } async function openComments(subjectId, subjectName) { commentsCard.hidden = false; commentsTitle.textContent = `Комментарии: ${subjectName || 'дисциплина'}`; commentsSubjectInput.value = subjectId; commentsTextInput.value = ''; hideAlert('department-comment-alert'); commentsList.textContent = 'Загрузка...'; try { const comments = await api.get(`/api/department/subjects/${subjectId}/comments`); commentsList.innerHTML = comments.length ? comments.map(comment => `
${escapeHtml(comment.authorName || 'Автор не указан')} ${escapeHtml(formatDateTime(comment.createdAt))}

${escapeHtml(comment.comment)}

`).join('') : '
Комментариев пока нет
'; } catch (error) { commentsList.innerHTML = `
${escapeHtml(error.message || 'Ошибка загрузки комментариев')}
`; } } async function saveComment(event) { event.preventDefault(); const subjectId = commentsSubjectInput.value; const comment = commentsTextInput.value.trim(); if (!subjectId) { showAlert('department-comment-alert', 'Выберите дисциплину', 'error'); return; } if (!comment) { showAlert('department-comment-alert', 'Комментарий не должен быть пустым', 'error'); return; } try { await api.post(`/api/department/subjects/${subjectId}/comments`, { comment }); showAlert('department-comment-alert', 'Комментарий сохранён', 'success'); await openComments(subjectId, commentsTitle.textContent.replace('Комментарии: ', '')); } catch (error) { showAlert('department-comment-alert', error.message || 'Ошибка сохранения комментария', 'error'); } } async function loadTeachers() { lastTeachers = null; teachersErrorMessage = ''; renderTeachers(); renderTeacherSelect(); try { const teachers = await api.get(`/api/department/teachers?departmentId=${encodeURIComponent(selectedDepartmentId() || '')}`); lastTeachers = Array.isArray(teachers) ? teachers : []; renderTeachers(); renderTeacherSelect(); } catch (error) { lastTeachers = []; teachersErrorMessage = error.message || 'Ошибка загрузки преподавателей'; renderTeachers(); renderTeacherSelect(); } } async function loadAllTeachers() { try { const teachers = await api.get('/api/users/teachers'); allTeachers = Array.isArray(teachers) ? teachers : []; renderTeacherSelect(); } catch (error) { allTeachers = []; if (assignTeacherSelect) { assignTeacherSelect.innerHTML = ``; } } } async function loadTeacherRequests() { teacherRequests = null; renderTeacherRequests(); try { const requests = await api.get(`/api/department/teacher-requests?departmentId=${encodeURIComponent(selectedDepartmentId() || '')}`); teacherRequests = Array.isArray(requests) ? requests : []; renderTeacherRequests(); } catch (error) { teacherRequests = []; if (teacherRequestsTbody) { teacherRequestsTbody.innerHTML = `${escapeHtml(error.message || 'Ошибка загрузки заявок')}`; } if (teacherRequestCount) teacherRequestCount.textContent = '0 заявок'; } } async function loadWorkload() { if (!startInput.value || !endInput.value) { lastWorkload = []; workloadErrorMessage = 'Выберите начало и окончание периода'; renderTeachers(); return; } const params = new URLSearchParams({ startDate: startInput.value, endDate: endInput.value }); if (selectedDepartmentId()) { params.set('departmentId', selectedDepartmentId()); } lastWorkload = null; workloadErrorMessage = ''; renderTeachers(); try { const workload = await api.get(`/api/workload/teachers?${params}`); lastWorkload = Array.isArray(workload) ? workload : []; renderTeachers(); } catch (error) { lastWorkload = []; workloadErrorMessage = error.message || 'Ошибка расчёта нагрузки'; renderTeachers(); } } async function handleStartDateChange() { if (startInput.value && endInput.value && startInput.value > endInput.value) { endInput.value = ''; } await loadWorkload(); } function renderTeachers() { const container = document.getElementById('department-teachers-list'); if (!container) return; if (lastTeachers === null || lastWorkload === null) { container.textContent = 'Загрузка...'; return; } const teachers = Array.isArray(lastTeachers) ? lastTeachers : []; const workload = Array.isArray(lastWorkload) ? lastWorkload : []; const workloadByTeacher = new Map(); const usedWorkloadKeys = new Set(); workload .filter(item => item && (item.id || item.name)) .forEach(item => { workloadKeys(item).forEach(key => { if (!workloadByTeacher.has(key)) { workloadByTeacher.set(key, item); } }); }); const rows = teachers.map(teacher => { const workloadItem = workloadKeys(teacher) .map(key => workloadByTeacher.get(key)) .find(Boolean); if (workloadItem) { workloadKeys(workloadItem).forEach(key => usedWorkloadKeys.add(key)); } return renderTeacherMetric({ name: teacher.fullName || teacher.username || '-', meta: teacher.jobTitle || 'Должность не указана', workload: workloadItem }, workloadErrorMessage); }); workload .filter(item => item && (item.id || item.name)) .filter(item => workloadKeys(item).every(key => !usedWorkloadKeys.has(key))) .forEach(item => { rows.push(renderTeacherMetric({ name: item.name || '-', meta: 'Есть занятия в расписании кафедры', workload: item }, workloadErrorMessage)); }); if (rows.length) { container.innerHTML = rows.join(''); return; } container.innerHTML = `
${escapeHtml(teachersErrorMessage || 'Преподаватели не найдены')}
`; } function renderTeacherSelect() { if (!assignTeacherSelect) return; if (lastTeachers === null) { assignTeacherSelect.innerHTML = ''; return; } const assignedIds = new Set((lastTeachers || []).map(teacher => String(teacher.id))); const availableTeachers = allTeachers.filter(teacher => !assignedIds.has(String(teacher.id))); assignTeacherSelect.innerHTML = '' + availableTeachers.map(teacher => ` `).join(''); if (!availableTeachers.length) { assignTeacherSelect.innerHTML = ''; } } function renderTeacherRequests() { if (!teacherRequestsTbody) return; if (teacherRequests === null) { teacherRequestsTbody.innerHTML = 'Загрузка...'; if (teacherRequestCount) teacherRequestCount.textContent = 'Загрузка...'; return; } if (teacherRequestCount) teacherRequestCount.textContent = requestCountLabel(teacherRequests.length); if (!teacherRequests.length) { teacherRequestsTbody.innerHTML = 'Заявок пока нет'; return; } teacherRequestsTbody.innerHTML = teacherRequests.map(request => ` ${escapeHtml(requestStatusLabel(request.status))} ${escapeHtml(request.username || '-')} ${escapeHtml(request.fullName || '-')} ${escapeHtml(request.jobTitle || '-')} ${escapeHtml(request.comment || '-')} ${escapeHtml(request.reviewComment || formatDateTime(request.reviewedAt) || '-')} `).join(''); } function selectedDepartmentId() { return role === ROLE_DEPARTMENT ? userDepartmentId : departmentSelect.value; } } async function loadDepartments(role, userDepartmentId) { const departments = await api.get('/api/departments'); fillSelect('department-workspace-department', departments, item => item.id, item => item.departmentName, 'Выберите кафедру'); if (role === ROLE_DEPARTMENT && userDepartmentId) { document.getElementById('department-workspace-department').value = userDepartmentId; } else if (!document.getElementById('department-workspace-department').value && departments.length) { document.getElementById('department-workspace-department').value = departments[0].id; } } function fillSelect(id, items, valueFn, labelFn, emptyLabel) { const select = document.getElementById(id); if (!select) return; select.innerHTML = `` + (items || []).map(item => ``).join(''); } function setDefaultDates(startInput, endInput) { const today = new Date(); const nextWeek = new Date(); nextWeek.setDate(today.getDate() + 7); startInput.value = toIsoDate(today); endInput.value = toIsoDate(nextWeek); } function toIsoDate(date) { return date.toISOString().slice(0, 10); } function formatDateTime(value) { if (!value) return '-'; return String(value).replace('T', ' ').slice(0, 16); } function subjectCountLabel(count) { const tail = count % 100; if (tail >= 11 && tail <= 14) return `${count} дисциплин`; if (count % 10 === 1) return `${count} дисциплина`; if ([2, 3, 4].includes(count % 10)) return `${count} дисциплины`; return `${count} дисциплин`; } function requestCountLabel(count) { const tail = count % 100; if (tail >= 11 && tail <= 14) return `${count} заявок`; if (count % 10 === 1) return `${count} заявка`; if ([2, 3, 4].includes(count % 10)) return `${count} заявки`; return `${count} заявок`; } function requestStatusLabel(status) { return { PENDING: 'На рассмотрении', APPROVED: 'Одобрена', REJECTED: 'Отклонена' }[status] || status || '-'; } function requestStatusClass(status) { return { PENDING: 'badge-ef', APPROVED: 'badge-student', REJECTED: 'badge-admin' }[status] || 'badge-ef'; } function renderTeacherMetric(item, workloadErrorMessage) { return `
${escapeHtml(item.name || '-')} ${escapeHtml(item.meta || 'Должность не указана')} ${escapeHtml(workloadLabel(item.workload, workloadErrorMessage))}
`; } function workloadLabel(item, workloadErrorMessage) { if (workloadErrorMessage) { return workloadErrorMessage; } if (!item) { return '0 ак. ч., 0 занятий за период'; } const hours = String(item.academicHours ?? 0); const lessons = String(item.lessonCount ?? 0); return `${hours} ак. ч., ${lessons} занятий за период`; } function workloadKeys(item) { return [ item.id == null ? null : `id:${item.id}`, normalizeTeacherName(item.fullName || item.name || item.username) ].filter(Boolean); } function normalizeTeacherName(value) { return value ? `name:${String(value).trim().toLowerCase()}` : null; }