- add period-scoped holiday architecture end-to-end - model: scope `SchoolHoliday` to `academic_period_id` - migrations: add holiday-period scoping, academic-period archive lifecycle, and merge migration head - API: extend holidays with manual CRUD, period validation, duplicate prevention, and overlap merge/conflict handling - recurrence: regenerate holiday exceptions using period-scoped holiday sets - improve frontend settings and holiday workflows - bind holiday import/list/manual CRUD to selected academic period - show detailed import outcomes (inserted/updated/merged/skipped/conflicts) - fix file-picker UX (visible selected filename) - align settings controls/dialogs with defined frontend design rules - scope appointments/dashboard holiday loading to active period - add shared date formatting utility - strengthen academic period lifecycle handling - add archive/restore/delete flow and backend validations/blocker checks - extend API client support for lifecycle operations - release/docs updates and cleanup - bump user-facing version to `2026.1.0-alpha.15` with new changelog entry - add tech changelog entry for alpha.15 backend changes - refactor README to concise index and archive historical implementation docs - fix Copilot instruction link diagnostics via local `.github` design-rules reference
89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
export type Holiday = {
|
|
id: number;
|
|
academic_period_id?: number | null;
|
|
name: string;
|
|
start_date: string;
|
|
end_date: string;
|
|
region?: string | null;
|
|
source_file_name?: string | null;
|
|
imported_at?: string | null;
|
|
};
|
|
|
|
export async function listHolidays(region?: string, academicPeriodId?: number | null) {
|
|
const params = new URLSearchParams();
|
|
if (region) {
|
|
params.set('region', region);
|
|
}
|
|
if (academicPeriodId != null) {
|
|
params.set('academicPeriodId', String(academicPeriodId));
|
|
}
|
|
const query = params.toString();
|
|
const url = query ? `/api/holidays?${query}` : '/api/holidays';
|
|
const res = await fetch(url);
|
|
const data = await res.json();
|
|
if (!res.ok || data.error) throw new Error(data.error || 'Fehler beim Laden der Ferien');
|
|
return data as { holidays: Holiday[] };
|
|
}
|
|
|
|
export async function uploadHolidaysCsv(file: File, academicPeriodId: number) {
|
|
const form = new FormData();
|
|
form.append('file', file);
|
|
form.append('academicPeriodId', String(academicPeriodId));
|
|
const res = await fetch('/api/holidays/upload', { method: 'POST', body: form });
|
|
const data = await res.json();
|
|
if (!res.ok || data.error) throw new Error(data.error || 'Fehler beim Import der Ferien');
|
|
return data as {
|
|
success: boolean;
|
|
inserted: number;
|
|
updated: number;
|
|
merged_overlaps?: number;
|
|
skipped_duplicates?: number;
|
|
conflicts?: string[];
|
|
academic_period_id?: number | null;
|
|
};
|
|
}
|
|
|
|
export type HolidayInput = {
|
|
name: string;
|
|
start_date: string;
|
|
end_date: string;
|
|
region?: string | null;
|
|
academic_period_id?: number | null;
|
|
};
|
|
|
|
export type HolidayMutationResult = {
|
|
success: boolean;
|
|
holiday?: Holiday;
|
|
regenerated_events: number;
|
|
merged?: boolean;
|
|
};
|
|
|
|
export async function createHoliday(data: HolidayInput): Promise<HolidayMutationResult> {
|
|
const res = await fetch('/api/holidays', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
});
|
|
const json = await res.json();
|
|
if (!res.ok || json.error) throw new Error(json.error || 'Fehler beim Erstellen');
|
|
return json;
|
|
}
|
|
|
|
export async function updateHoliday(id: number, data: Partial<HolidayInput>): Promise<HolidayMutationResult> {
|
|
const res = await fetch(`/api/holidays/${id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
});
|
|
const json = await res.json();
|
|
if (!res.ok || json.error) throw new Error(json.error || 'Fehler beim Aktualisieren');
|
|
return json;
|
|
}
|
|
|
|
export async function deleteHoliday(id: number): Promise<{ success: boolean; regenerated_events: number }> {
|
|
const res = await fetch(`/api/holidays/${id}`, { method: 'DELETE' });
|
|
const json = await res.json();
|
|
if (!res.ok || json.error) throw new Error(json.error || 'Fehler beim Löschen');
|
|
return json;
|
|
}
|