import { apiClient } from '../shared/api-client.js'; import { formatDate, formatIsoDate, toIsoDate, trimTime, weekDates } from '../shared/dates.js'; export const WORKSPACE_TABS = Object.freeze(['schedule', 'preferences', 'requests']); export const PREFERENCE_BRUSHES = Object.freeze(['SOFT_PREFERRED', 'SOFT_UNWANTED', 'HARD_UNAVAILABLE']); export const CHANGE_REQUEST_TYPES = Object.freeze(['MOVE', 'CHANGE_CLASSROOM', 'CANCEL']); export function lessonKey(lesson) { return `${lesson.scheduleRuleSlotId}:${lesson.date}`; } export function collectSlots(lessons) { const slots = new Map(); (lessons || []).forEach(lesson => { slots.set(lesson.timeSlotOrder, { order: lesson.timeSlotOrder, label: `${trimTime(lesson.startTime)} - ${trimTime(lesson.endTime)}` }); }); if (!slots.size) { slots.set(1, { order: 1, label: 'Расписание' }); } return [...slots.values()].sort((left, right) => left.order - right.order); } export function buildDesktopWeekModel(weekStart, lessons) { const slots = collectSlots(lessons); const lessonsByKey = new Map(); (lessons || []).forEach(lesson => { lessonsByKey.set(`${lesson.date}:${lesson.timeSlotOrder}`, lesson); }); const days = weekDates(weekStart).map(date => ({ date, isoDate: toIsoDate(date), label: formatDate(date) })); return { days, slots, lessonsByKey }; } export function lessonsByDate(lessons) { const byDate = new Map(); (lessons || []).forEach(lesson => { if (!byDate.has(lesson.date)) byDate.set(lesson.date, []); byDate.get(lesson.date).push(lesson); }); return byDate; } export function buildTeacherLessonChips(lesson) { const groups = Array.isArray(lesson.groupNames) ? lesson.groupNames.join(', ') : ''; const subgroups = Array.isArray(lesson.subgroupNames) && lesson.subgroupNames.length ? lesson.subgroupNames.join(', ') : lesson.subgroupName; return [ `${trimTime(lesson.startTime)} - ${trimTime(lesson.endTime)}`, lesson.lessonTypeName, subgroups, lesson.classroomName, groups ].filter(Boolean); } export function canRequestChange(lesson) { return Boolean(lesson.scheduleRuleSlotId && !lesson.scheduleOverrideId); } export function absenceStatusLabel(status) { return { PENDING: 'На подтверждении', APPROVED: 'Согласовано', REJECTED: 'Отклонено', RESOLVED: 'Замены выполнены', CANCELLED: 'Отменено' }[status] || status || '—'; } export function absenceStatusClass(status) { return `is-${String(status || '').toLowerCase()}`; } export function preferenceTypeLabel(type) { return { HARD_UNAVAILABLE: 'Строго недоступен', SOFT_PREFERRED: 'Предпочтительно', SOFT_UNWANTED: 'Нежелательно', CONSECUTIVE: 'Пары подряд', NO_GAPS: 'Без окон' }[type] || type || '—'; } export function preferenceTypeClass(type) { return { HARD_UNAVAILABLE: 'is-hard', SOFT_PREFERRED: 'is-preferred', SOFT_UNWANTED: 'is-unwanted' }[type] || ''; } export function preferenceTypeSymbol(type) { return { HARD_UNAVAILABLE: '×', SOFT_PREFERRED: '✓', SOFT_UNWANTED: '!' }[type] || '•'; } export function preferenceStatusLabel(status) { return { PENDING: 'На рассмотрении', APPROVED: 'Согласовано', REJECTED: 'Отклонено', CANCELLED: 'Отменено' }[status] || status || '—'; } export function changeTypeLabel(type) { return { MOVE: 'Перенос', CHANGE_CLASSROOM: 'Смена аудитории', CANCEL: 'Отмена' }[type] || type || '—'; } export function changeRequestTarget(item) { if (item.requestType === 'MOVE') { return `${formatIsoDate(item.targetLessonDate || item.lessonDate)}, ${trimTime(item.requestedStartTime)}–${trimTime(item.requestedEndTime)}`; } if (item.requestType === 'CHANGE_CLASSROOM') { return item.requestedClassroomName; } return 'Отмена занятия'; } export function semesterLabel(semester) { const type = semester.semesterType === 'AUTUMN' ? 'осенний' : semester.semesterType === 'SPRING' ? 'весенний' : semester.semesterType; return `${semester.academicYearTitle} · ${type} · ${formatIsoDate(semester.startDate)}–${formatIsoDate(semester.endDate)}`; } export function resolveCurrentSemester(semesters, todayIso) { return (semesters || []).find(semester => semester.startDate <= todayIso && semester.endDate >= todayIso) || null; } export function resolveSelectedSemester(semesters, semesterId) { return (semesters || []).find(semester => String(semester.id) === String(semesterId)); } export function activeWeekPreferences(preferences) { return (preferences || []).filter(item => ['PENDING', 'APPROVED'].includes(item.status) && item.dayOfWeek); } export function activeDatePreferences(preferences) { return (preferences || []).filter(item => item.preferenceDate && item.status !== 'CANCELLED'); } export function findCompactPreference(preferences, type) { return (preferences || []).find( candidate => candidate.preferenceType === type && ['PENDING', 'APPROVED'].includes(candidate.status) ) || null; } export function findWeekPreference(preferences, dayOfWeek, timeSlotId) { return (preferences || []).find( candidate => ['PENDING', 'APPROVED'].includes(candidate.status) && candidate.dayOfWeek === dayOfWeek && candidate.timeSlotId === timeSlotId ) || null; } export function pendingCount(items) { return (items || []).filter(item => item.status === 'PENDING').length; } export function formatInstant(value) { if (!value) return '—'; return new Date(value).toLocaleString('ru-RU', { dateStyle: 'short', timeStyle: 'short' }); } export function validateAbsence({ startDate = '', endDate = '', reason = '' } = {}) { if (!startDate || !endDate || !reason.trim()) { return 'Заполните период и причину отсутствия'; } if (endDate < startDate) { return 'Дата окончания не может быть раньше даты начала'; } return ''; } export function buildChangeRequestPayload({ lesson, requestType, targetDate = '', timeSlotId = '', classroomId = '', reason = '' }) { return { baseRuleSlotId: lesson.scheduleRuleSlotId, lessonDate: lesson.date, requestType, targetLessonDate: requestType === 'MOVE' ? targetDate : null, requestedTimeSlotId: requestType === 'MOVE' ? Number(timeSlotId) || null : null, requestedClassroomId: requestType === 'CHANGE_CLASSROOM' ? Number(classroomId) || null : null, reason: reason.trim() }; } export function candidateOptions(items, emptyLabel) { const available = (items || []).filter(item => item.available); return available.length > 0 ? available : [{ id: '', label: emptyLabel }]; } export async function fetchTeacherSchedule({ teacherId, startDate, endDate }, client = apiClient) { return client.get( `/api/schedule?teacherId=${encodeURIComponent(teacherId)}&startDate=${startDate}&endDate=${endDate}`, { errorMessage: 'Не удалось загрузить расписание' } ); } export async function fetchPreferenceMeta(client = apiClient) { return client.get('/api/teacher-preferences/meta', { errorMessage: 'Не удалось загрузить параметры семестра' }); } export async function fetchPreferences(semesterId, client = apiClient) { return client.get( `/api/teacher-preferences?semesterId=${encodeURIComponent(semesterId)}`, { errorMessage: 'Не удалось загрузить пожелания' } ); } export async function createPreference(payload, client = apiClient) { return client.post( '/api/teacher-preferences', payload, { errorMessage: 'Не удалось сохранить пожелание' } ); } export async function deletePreference(id, client = apiClient) { return client.delete( `/api/teacher-preferences/${encodeURIComponent(id)}`, { errorMessage: 'Не удалось отменить пожелание' } ); } export async function fetchChangeRequests(client = apiClient) { return client.get('/api/teacher-change-requests', { errorMessage: 'Не удалось загрузить заявки' }); } export async function createChangeRequest(payload, client = apiClient) { return client.post( '/api/teacher-change-requests', payload, { errorMessage: 'Не удалось отправить заявку' } ); } export async function cancelChangeRequest(id, client = apiClient) { return client.delete( `/api/teacher-change-requests/${encodeURIComponent(id)}`, { errorMessage: 'Не удалось отозвать заявку' } ); } export async function fetchChangeRequestCandidates({ baseRuleSlotId, lessonDate, targetDate = '' }, client = apiClient) { const params = new URLSearchParams({ baseRuleSlotId: String(baseRuleSlotId), lessonDate }); if (targetDate) params.set('targetDate', targetDate); return client.get( `/api/teacher-change-requests/candidates?${params}`, { errorMessage: 'Не удалось проверить допустимые варианты' } ); } export async function fetchAbsences(client = apiClient) { return client.get('/api/teacher-absences', { errorMessage: 'Не удалось загрузить заявки об отсутствии' }); } export async function createAbsence({ startDate, endDate, reason }, client = apiClient) { return client.post( '/api/teacher-absences', { startDate, endDate, reason }, { errorMessage: 'Не удалось отправить заявку об отсутствии' } ); } export async function cancelAbsence(id, client = apiClient) { return client.delete( `/api/teacher-absences/${encodeURIComponent(id)}`, { errorMessage: 'Не удалось отменить заявку' } ); }