import { useEffect, useRef, useState } from 'react'; import { getSession } from '../../../admin/js/api.js'; import { addDays, toIsoDate } from '../../shared/dates.js'; import { requestCountLabel } from '../admin-service.js'; import { DEPARTMENT_REQUEST_TABLE_COLUMNS, SUBJECT_TABLE_COLUMNS, assignTeacher, assignTeacherError, availableTeachers, defaultWorkloadPeriod, departmentOptionLabel, fetchAllTeachers, fetchDepartmentSubjects, fetchDepartmentTeacherRequests, fetchDepartmentTeachers, fetchDepartments, fetchSemesters, fetchSubjectComments, fetchTeacherWorkload, formatDateTime, importSubjects, importSubjectsPayload, requestStatusClass, requestStatusLabel, saveSubjectComment, subjectCountLabel, submitTeacherRequest, teacherMetrics, teacherOptionLabel, teacherRequestPayload, workloadLabel } from '../department-workspace-service.js'; const ROLE_DEPARTMENT = 'DEPARTMENT'; export function DepartmentWorkspaceTab() { const [session] = useState(() => getSession()); const role = session?.role; const userDepartmentId = session?.departmentId; const isDepartmentRole = role === ROLE_DEPARTMENT; const departmentIdRef = useRef(''); const startDateRef = useRef(toIsoDate(new Date())); const endDateRef = useRef(toIsoDate(addDays(new Date(), 7))); const [departments, setDepartments] = useState([]); const [departmentId, setDepartmentId] = useState(''); const [startDate, setStartDate] = useState(startDateRef.current); const [endDate, setEndDate] = useState(endDateRef.current); const [workspaceAlert, setWorkspaceAlert] = useState({ text: '', isError: false }); const [subjects, setSubjects] = useState({ loading: true, items: [], error: '' }); const [teachers, setTeachers] = useState({ items: null, error: '' }); const [workload, setWorkload] = useState({ items: null, error: '' }); const [requests, setRequests] = useState({ items: null, error: '' }); const [allTeachers, setAllTeachers] = useState([]); const [allTeachersError, setAllTeachersError] = useState(''); const [importRows, setImportRows] = useState([{ code: '', name: '' }]); const [importAlert, setImportAlert] = useState({ text: '', isError: false }); const [assignTeacherId, setAssignTeacherId] = useState(''); const [assignComment, setAssignComment] = useState(''); const [assignAlert, setAssignAlert] = useState({ text: '', isError: false }); const [requestForm, setRequestForm] = useState({ username: '', fullName: '', jobTitle: '', comment: '' }); const [requestAlert, setRequestAlert] = useState({ text: '', isError: false }); const [comments, setComments] = useState(null); const [commentText, setCommentText] = useState(''); const [commentAlert, setCommentAlert] = useState({ text: '', isError: false }); function currentDepartmentId() { return isDepartmentRole ? userDepartmentId : departmentIdRef.current; } useEffect(() => { let active = true; (async () => { try { const loaded = await fetchDepartments(); if (!active) return; const list = loaded || []; setDepartments(list); if (isDepartmentRole) { departmentIdRef.current = userDepartmentId || ''; setDepartmentId(departmentIdRef.current); } else if (list.length) { departmentIdRef.current = String(list[0].id); setDepartmentId(departmentIdRef.current); } try { const semesters = await fetchSemesters(); const period = defaultWorkloadPeriod(semesters, new Date()); startDateRef.current = period.startDate; endDateRef.current = period.endDate; if (active) { setStartDate(period.startDate); setEndDate(period.endDate); } } catch { // Текущий диапазон остаётся безопасным fallback, если справочник // семестров недоступен. } try { const loadedTeachers = await fetchAllTeachers(); if (active) setAllTeachers(Array.isArray(loadedTeachers) ? loadedTeachers : []); } catch (error) { if (active) setAllTeachersError(error.message || 'Ошибка загрузки преподавателей'); } if (!active) return; await loadWorkspace(); } catch (error) { if (!active) return; setWorkspaceAlert({ text: error.message || 'Ошибка загрузки кабинета кафедры', isError: true }); } })(); return () => { active = false; }; }, []); async function loadWorkspace() { setWorkspaceAlert({ text: '', isError: false }); setTeachers({ items: null, error: '' }); setWorkload({ items: null, error: '' }); setRequests({ items: null, error: '' }); await Promise.all([loadSubjects(), loadTeachers(), loadWorkload(), loadTeacherRequests()]); } async function loadSubjects() { setSubjects({ loading: true, items: [], error: '' }); try { const items = await fetchDepartmentSubjects(currentDepartmentId()); setSubjects({ loading: false, items: Array.isArray(items) ? items : [], error: '' }); } catch (error) { setSubjects({ loading: false, items: [], error: error.message || 'Ошибка загрузки дисциплин' }); } } async function loadTeachers() { setTeachers({ items: null, error: '' }); try { const items = await fetchDepartmentTeachers(currentDepartmentId()); setTeachers({ items: Array.isArray(items) ? items : [], error: '' }); } catch (error) { setTeachers({ items: [], error: error.message || 'Ошибка загрузки преподавателей' }); } } async function loadWorkload() { if (!startDateRef.current || !endDateRef.current) { setWorkload({ items: [], error: 'Выберите начало и окончание периода' }); return; } setWorkload({ items: null, error: '' }); try { const items = await fetchTeacherWorkload({ startDate: startDateRef.current, endDate: endDateRef.current, departmentId: currentDepartmentId() }); setWorkload({ items: Array.isArray(items) ? items : [], error: '' }); } catch (error) { setWorkload({ items: [], error: error.message || 'Ошибка расчёта нагрузки' }); } } async function loadTeacherRequests() { setRequests({ items: null, error: '' }); try { const items = await fetchDepartmentTeacherRequests(currentDepartmentId()); setRequests({ items: Array.isArray(items) ? items : [], error: '' }); } catch (error) { setRequests({ items: [], error: error.message || 'Ошибка загрузки заявок' }); } } function handleDepartmentChange(value) { departmentIdRef.current = value; setDepartmentId(value); void loadWorkspace(); } function handleStartDateChange(value) { startDateRef.current = value; setStartDate(value); if (value && endDateRef.current && value > endDateRef.current) { endDateRef.current = ''; setEndDate(''); } void loadWorkload(); } function handleEndDateChange(value) { endDateRef.current = value; setEndDate(value); void loadWorkload(); } // --- Загрузка дисциплин --- function addImportRow() { setImportRows(current => [...current, { code: '', name: '' }]); } function updateImportRow(index, patch) { setImportRows(current => current.map((row, rowIndex) => ( rowIndex === index ? { ...row, ...patch } : row ))); } function removeImportRow(index) { setImportRows(current => { const rows = current.filter((_, rowIndex) => rowIndex !== index); return rows.length ? rows : [{ code: '', name: '' }]; }); } async function submitImport(event) { event.preventDefault(); setImportAlert({ text: '', isError: false }); const rows = importRows.map(row => ({ code: (row.code || '').trim(), name: (row.name || '').trim() })); const { payload, error } = importSubjectsPayload(rows, currentDepartmentId()); if (error) { setImportAlert({ text: error, isError: true }); return; } try { const result = await importSubjects(payload); setImportAlert({ text: result?.message || 'Дисциплины загружены', isError: false }); setImportRows([{ code: '', name: '' }]); await loadSubjects(); } catch (err) { setImportAlert({ text: err.message || 'Ошибка загрузки дисциплин', isError: true }); } } // --- Привязка преподавателя --- async function submitAssign(event) { event.preventDefault(); setAssignAlert({ text: '', isError: false }); const error = assignTeacherError(currentDepartmentId(), assignTeacherId); if (error) { setAssignAlert({ text: error, isError: true }); return; } try { await assignTeacher(assignTeacherId, currentDepartmentId(), assignComment.trim()); setAssignAlert({ text: 'Преподаватель привязан к кафедре', isError: false }); setAssignComment(''); await Promise.all([loadTeachers(), loadWorkload()]); } catch (err) { setAssignAlert({ text: err.message || 'Ошибка привязки преподавателя', isError: true }); } } // --- Заявка на преподавателя --- async function submitRequest(event) { event.preventDefault(); setRequestAlert({ text: '', isError: false }); const { payload, error } = teacherRequestPayload(requestForm, currentDepartmentId()); if (error) { setRequestAlert({ text: error, isError: true }); return; } try { await submitTeacherRequest(payload); setRequestAlert({ text: 'Заявка отправлена администратору', isError: false }); setRequestForm({ username: '', fullName: '', jobTitle: '', comment: '' }); await loadTeacherRequests(); } catch (err) { setRequestAlert({ text: err.message || 'Ошибка отправки заявки', isError: true }); } } // --- Комментарии к дисциплинам --- async function openComments(subjectId, subjectName) { setComments({ subjectId, subjectName, items: null, error: '' }); setCommentText(''); setCommentAlert({ text: '', isError: false }); try { const items = await fetchSubjectComments(subjectId); setComments(current => ( current && current.subjectId === subjectId ? { ...current, items: Array.isArray(items) ? items : [], error: '' } : current )); } catch (error) { setComments(current => ( current && current.subjectId === subjectId ? { ...current, items: [], error: error.message || 'Ошибка загрузки комментариев' } : current )); } } async function submitComment(event) { event.preventDefault(); const subjectId = comments?.subjectId; const comment = commentText.trim(); if (!subjectId) { setCommentAlert({ text: 'Выберите дисциплину', isError: true }); return; } if (!comment) { setCommentAlert({ text: 'Комментарий не должен быть пустым', isError: true }); return; } try { await saveSubjectComment(subjectId, comment); setCommentAlert({ text: 'Комментарий сохранён', isError: false }); await openComments(subjectId, comments.subjectName); } catch (error) { setCommentAlert({ text: error.message || 'Ошибка сохранения комментария', isError: true }); } } const metricRows = teachers.items === null || workload.items === null ? null : teacherMetrics(teachers.items, workload.items); const freeTeachers = teachers.items === null ? null : availableTeachers(allTeachers, teachers.items); const requestsCountLabel = requests.error ? '0 заявок' : (requests.items === null ? 'Загрузка...' : requestCountLabel(requests.items.length)); const subjectsCountLabel = subjects.error ? '0 дисциплин' : (subjects.loading ? 'Загрузка...' : subjectCountLabel(subjects.items.length)); return ( <>

Рабочая область кафедры

{!isDepartmentRole && (
)}
handleStartDateChange(event.target.value)} />
handleEndDateChange(event.target.value)} />

Загрузка дисциплин

{importRows.map((row, index) => (
updateImportRow(index, { code: event.target.value })} />
updateImportRow(index, { name: event.target.value })} />
))}

Добавить преподавателя

setAssignComment(event.target.value)} />

Заявка на преподавателя

setRequestForm(current => ( { ...current, username: event.target.value } ))} />
setRequestForm(current => ( { ...current, fullName: event.target.value } ))} />
setRequestForm(current => ( { ...current, jobTitle: event.target.value } ))} />
setRequestForm(current => ( { ...current, comment: event.target.value } ))} />

Преподаватели и нагрузка

{metricRows === null ? ( 'Загрузка...' ) : metricRows.length === 0 ? (
{teachers.error || 'Преподаватели не найдены'}
) : metricRows.map((row, index) => (
{row.name || '-'} {row.meta || 'Должность не указана'} {workloadLabel(row.workload, workload.error)}
))}

Заявки кафедры

{requestsCountLabel}
{requests.items === null && !requests.error ? ( ) : requests.error ? ( ) : requests.items.length === 0 ? ( ) : requests.items.map(request => ( ))}
Статус Логин ФИО Должность Комментарий Рассмотрение
Загрузка...
{requests.error}
Заявок пока нет
{requestStatusLabel(request.status)} {request.username || '-'} {request.fullName || '-'} {request.jobTitle || '-'} {request.comment || '-'} {request.reviewComment || formatDateTime(request.reviewedAt) || '-'}

Дисциплины кафедры

{subjectsCountLabel}
{subjects.loading ? ( ) : subjects.error ? ( ) : subjects.items.length === 0 ? ( ) : subjects.items.map(subject => ( ))}
Код Название Описание Комментарии
Загрузка...
{subjects.error}
Дисциплины не найдены
{subject.code || '-'} {subject.name || '-'} {subject.description || '-'}
{comments && (

Комментарии: {comments.subjectName || 'дисциплина'}

setCommentText(event.target.value)} />
{comments.items === null ? ( 'Загрузка...' ) : comments.error ? (
{comments.error}
) : comments.items.length === 0 ? (
Комментариев пока нет
) : comments.items.map((comment, index) => (
{comment.authorName || 'Автор не указан'} {formatDateTime(comment.createdAt)}

{comment.comment}

))}
)} ); }