import { apiClient } from '../shared/api-client.js'; import { defaultSchedulePeriod } from '../../admin/js/schedule-period.js'; import { toIsoDate } from '../shared/dates.js'; export const SUBJECT_TABLE_COLUMNS = 4; export const DEPARTMENT_REQUEST_TABLE_COLUMNS = 6; // --- Справочники и период --- export function fetchDepartments(client = apiClient) { return client.get('/api/departments', { errorMessage: 'Не удалось загрузить список кафедр' }); } // Селектор кафедры кабинета подписывает варианты только названием кафедры. export function departmentOptionLabel(department) { return department.departmentName || department.name || department.id; } export function fetchSemesters(client = apiClient) { return client.get('/api/schedule/semesters', { errorMessage: 'Не удалось загрузить семестры' }); } export function defaultWorkloadPeriod(semesters, date = new Date()) { return defaultSchedulePeriod(semesters, toIsoDate(date)); } export function fallbackWorkloadPeriod(date = new Date()) { const end = new Date(date); end.setDate(date.getDate() + 7); return { startDate: toIsoDate(date), endDate: toIsoDate(end) }; } // --- Дисциплины кафедры --- export function departmentSubjectsEndpoint(departmentId) { return `/api/department/subjects?departmentId=${encodeURIComponent(departmentId || '')}`; } export function fetchDepartmentSubjects(departmentId, client = apiClient) { return client.get(departmentSubjectsEndpoint(departmentId), { errorMessage: 'Ошибка загрузки дисциплин' }); } export function importSubjectsPayload(rows, departmentId) { if (!departmentId) return { error: 'Выберите кафедру' }; const payload = (rows || []) .filter(item => item.name) .map(item => ({ code: item.code, name: item.name, departmentId: Number(departmentId) })); if (!payload.length) return { error: 'Заполните название хотя бы одной дисциплины' }; return { payload }; } export function importSubjects(rows, client = apiClient) { return client.post('/api/department/subjects/import', rows, { errorMessage: 'Ошибка загрузки дисциплин' }); } // --- Комментарии к дисциплинам --- export function fetchSubjectComments(subjectId, client = apiClient) { return client.get(`/api/department/subjects/${encodeURIComponent(subjectId)}/comments`, { errorMessage: 'Ошибка загрузки комментариев' }); } export function saveSubjectComment(subjectId, comment, client = apiClient) { return client.post( `/api/department/subjects/${encodeURIComponent(subjectId)}/comments`, { comment }, { errorMessage: 'Ошибка сохранения комментария' } ); } // --- Преподаватели и нагрузка --- export function departmentTeachersEndpoint(departmentId) { return `/api/department/teachers?departmentId=${encodeURIComponent(departmentId || '')}`; } export function fetchDepartmentTeachers(departmentId, client = apiClient) { return client.get(departmentTeachersEndpoint(departmentId), { errorMessage: 'Ошибка загрузки преподавателей' }); } export function fetchAllTeachers(client = apiClient) { return client.get('/api/users/teachers', { errorMessage: 'Ошибка загрузки преподавателей' }); } export function availableTeachers(allTeachers, assignedTeachers) { const assignedIds = new Set((assignedTeachers || []).map(teacher => String(teacher.id))); return (allTeachers || []).filter(teacher => !assignedIds.has(String(teacher.id))); } export function teacherOptionLabel(teacher) { return teacher.fullName || teacher.username || `ID ${teacher.id}`; } export function assignTeacherError(departmentId, teacherId) { if (!departmentId) return 'Выберите кафедру'; if (!teacherId) return 'Выберите преподавателя'; return null; } export function assignTeacher(teacherId, departmentId, comment, client = apiClient) { return client.post( `/api/department/teachers/${encodeURIComponent(teacherId)}/assignments`, { departmentId: Number(departmentId), comment: comment || null }, { errorMessage: 'Ошибка привязки преподавателя' } ); } export function teacherWorkloadEndpoint({ startDate, endDate, departmentId }) { const params = new URLSearchParams({ startDate, endDate }); if (departmentId) params.set('departmentId', departmentId); return `/api/workload/teachers?${params}`; } export function fetchTeacherWorkload(params, client = apiClient) { return client.get(teacherWorkloadEndpoint(params), { errorMessage: 'Ошибка расчёта нагрузки' }); } // --- Заявки кафедры --- export function departmentTeacherRequestsEndpoint(departmentId) { return `/api/department/teacher-requests?departmentId=${encodeURIComponent(departmentId || '')}`; } export function fetchDepartmentTeacherRequests(departmentId, client = apiClient) { return client.get(departmentTeacherRequestsEndpoint(departmentId), { errorMessage: 'Ошибка загрузки заявок' }); } export function teacherRequestPayload(fields, departmentId) { if (!departmentId) return { error: 'Выберите кафедру' }; const username = (fields.username || '').trim(); const fullName = (fields.fullName || '').trim(); if (!username || !fullName) return { error: 'Заполните логин и ФИО преподавателя' }; return { payload: { departmentId: Number(departmentId), username, fullName, jobTitle: (fields.jobTitle || '').trim(), comment: (fields.comment || '').trim() } }; } export function submitTeacherRequest(payload, client = apiClient) { return client.post('/api/department/teacher-requests', payload, { errorMessage: 'Ошибка отправки заявки' }); } // --- Метки --- export 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} дисциплин`; } export function requestStatusLabel(status) { return { PENDING: 'На рассмотрении', APPROVED: 'Одобрена', REJECTED: 'Отклонена' }[status] || status || '-'; } export function requestStatusClass(status) { return { PENDING: 'badge-ef', APPROVED: 'badge-student', REJECTED: 'badge-admin' }[status] || 'badge-ef'; } export function formatDateTime(value) { if (!value) return '-'; return String(value).replace('T', ' ').slice(0, 16); } // --- Сопоставление преподавателей и нагрузки --- export function normalizeTeacherName(value) { return value ? `name:${String(value).trim().toLowerCase()}` : null; } export function workloadKeys(item) { return [ item.id == null ? null : `id:${item.id}`, normalizeTeacherName(item.fullName || item.name || item.username) ].filter(Boolean); } export function workloadLabel(item, errorMessage) { if (errorMessage) return errorMessage; if (!item) return '0 ак. ч., 0 занятий за период'; return `${item.academicHours ?? 0} ак. ч., ${item.lessonCount ?? 0} занятий за период`; } // Воспроизводит legacy-логику сводки: преподаватели кафедры, затем записи // нагрузки, не привязанные ни к одному преподавателю (есть занятия в расписании). export function teacherMetrics(teachers, workload) { 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 { name: teacher.fullName || teacher.username || '-', meta: teacher.jobTitle || 'Должность не указана', workload: workloadItem }; }); (workload || []) .filter(item => item && (item.id || item.name)) .filter(item => workloadKeys(item).every(key => !usedWorkloadKeys.has(key))) .forEach(item => { rows.push({ name: item.name || '-', meta: 'Есть занятия в расписании кафедры', workload: item }); }); return rows; }