/** * Stundenplan API client. All requests go through /api/school/* which proxies * to the school-service Gin server (port 8084). Auth token, if available, is * passed via Authorization: Bearer; for now no token = upstream 401. */ import type { TimetableClass, TimetablePeriod, TimetableRoom, TimetableSubject, TimetableTeacher, TimetableCurriculum, TimetableAssignment, CreateTimetableClass, CreateTimetablePeriod, CreateTimetableRoom, CreateTimetableSubject, CreateTimetableTeacher, CreateTimetableCurriculum, CreateTimetableAssignment, TeacherUnavailableDay, TeacherUnavailableWindow, TeacherMaxHoursDay, TeacherMaxHoursWeek, TeacherExcludedSubject, TeacherExcludedRoom, SubjectMinDayGap, SubjectMaxConsecutive, SubjectContiguousWhenRepeated, SubjectPreferredPeriod, SubjectDoubleLesson, ClassMaxHoursDay, ClassNoGaps, RoomRequiresType, RoomUnavailable, TimetableSolution, TimetableLesson, CreateTimetableSolution, } from '@/app/stundenplan/types' const TOKEN_KEY = 'bp_stundenplan_jwt' export function setStundenplanToken(token: string): void { if (typeof window !== 'undefined') localStorage.setItem(TOKEN_KEY, token) } export function getStundenplanToken(): string { if (typeof window === 'undefined') return '' return localStorage.getItem(TOKEN_KEY) || '' } async function apiFetch(endpoint: string, options: RequestInit = {}): Promise { const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record | undefined), } const token = getStundenplanToken() if (token) headers['Authorization'] = `Bearer ${token}` const res = await fetch(`/api/school${endpoint}`, { ...options, headers }) if (!res.ok) { const errData = await res.json().catch(() => ({ error: 'Unknown error' })) throw new Error(errData.error || errData.detail || `HTTP ${res.status}`) } if (res.status === 204) return undefined as T return res.json() } // ---------- Stammdaten ---------- export const classesApi = { list: () => apiFetch('/timetable/classes'), create: (data: CreateTimetableClass) => apiFetch('/timetable/classes', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/classes/${id}`, { method: 'DELETE' }), } export const periodsApi = { list: () => apiFetch('/timetable/periods'), create: (data: CreateTimetablePeriod) => apiFetch('/timetable/periods', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/periods/${id}`, { method: 'DELETE' }), } export const roomsApi = { list: () => apiFetch('/timetable/rooms'), create: (data: CreateTimetableRoom) => apiFetch('/timetable/rooms', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/rooms/${id}`, { method: 'DELETE' }), } export const subjectsApi = { list: () => apiFetch('/timetable/subjects'), create: (data: CreateTimetableSubject) => apiFetch('/timetable/subjects', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/subjects/${id}`, { method: 'DELETE' }), } export const teachersApi = { list: () => apiFetch('/timetable/teachers'), create: (data: CreateTimetableTeacher) => apiFetch('/timetable/teachers', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/teachers/${id}`, { method: 'DELETE' }), } export const curriculumApi = { list: () => apiFetch('/timetable/curriculum'), create: (data: CreateTimetableCurriculum) => apiFetch('/timetable/curriculum', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/curriculum/${id}`, { method: 'DELETE' }), } export const assignmentsApi = { list: () => apiFetch('/timetable/assignments'), create: (data: CreateTimetableAssignment) => apiFetch('/timetable/assignments', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/assignments/${id}`, { method: 'DELETE' }), } // ---------- Constraints ---------- /** * Factory that builds a list/create/remove triple for a constraint endpoint. * The 15 constraint tables share the same CRUD shape; only TItem differs. */ function constraintApi(path: string) { return { list: () => apiFetch(`/timetable/constraints/${path}`), create: (data: TCreate) => apiFetch(`/timetable/constraints/${path}`, { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/constraints/${path}/${id}`, { method: 'DELETE' }), } } export const teacherUnavailableDayApi = constraintApi>('teacher/unavailable-day') export const teacherUnavailableWindowApi = constraintApi>('teacher/unavailable-window') export const teacherMaxHoursDayApi = constraintApi>('teacher/max-hours-day') export const teacherMaxHoursWeekApi = constraintApi>('teacher/max-hours-week') export const teacherExcludedSubjectApi = constraintApi>('teacher/excluded-subject') export const teacherExcludedRoomApi = constraintApi>('teacher/excluded-room') export const subjectMinDayGapApi = constraintApi>('subject/min-day-gap') export const subjectMaxConsecutiveApi = constraintApi>('subject/max-consecutive') export const subjectContiguousWhenRepeatedApi = constraintApi>('subject/contiguous-when-repeated') export const subjectPreferredPeriodApi = constraintApi>('subject/preferred-period') export const subjectDoubleLessonApi = constraintApi>('subject/double-lesson') export const classMaxHoursDayApi = constraintApi>('class/max-hours-day') export const classNoGapsApi = constraintApi>('class/no-gaps') export const roomRequiresTypeApi = constraintApi>('room/requires-type') export const roomUnavailableApi = constraintApi>('room/unavailable') // ---------- Solutions ---------- export const solutionsApi = { list: () => apiFetch('/timetable/solutions'), get: (id: string) => apiFetch(`/timetable/solutions/${id}`), create: (data: CreateTimetableSolution) => apiFetch('/timetable/solutions', { method: 'POST', body: JSON.stringify(data) }), remove: (id: string) => apiFetch(`/timetable/solutions/${id}`, { method: 'DELETE' }), lessons: (id: string) => apiFetch(`/timetable/solutions/${id}/lessons`), } export const lessonsApi = { pin: (id: string, pinned: boolean) => apiFetch<{ message: string; pinned: boolean }>(`/timetable/lessons/${id}/pin`, { method: 'PUT', body: JSON.stringify({ pinned }), }), }