'use server'; import { revalidatePath } from 'next/cache'; import { diffOf, recordAudit } from '@/lib/audit'; import { requireAdmin } from '@/lib/auth/actions'; import { prisma } from '@/lib/db'; import type { Slot } from '@/lib/schedule/types'; import { changedDays, DAY_NAMES_FR, hasErrors, normaliseWeek, toDraft, validateWeek, type WeekDraft, } from '@/lib/schedule/week'; export type SaveWeekResult = | { ok: true; changed: number[] } | { ok: false; error: string; dayErrors?: Record }; /** * Persists the reference week. * * Validation runs again here even though the form already checked: the client * is a convenience, not a guarantee, and this is the schedule the shop window * shows. */ export async function saveWeek(draft: WeekDraft): Promise { const gate = await requireAdmin(); if (!gate.ok) { return { ok: false, error: gate.error }; } const week = normaliseWeek(draft); const errors = validateWeek(week); if (hasErrors(errors)) { return { ok: false, error: 'Certains horaires sont invalides.', dayErrors: errors }; } const existing = await prisma.weeklySchedule.findMany(); const before = toDraft( existing.map((day) => ({ dayOfWeek: day.dayOfWeek, isClosed: day.isClosed, slots: (day.slots as Slot[] | null) ?? [], })), ); const changed = changedDays(before, week); if (changed.length === 0) { // Nothing moved: no write, no audit entry, no needless screen redraw. return { ok: true, changed: [] }; } await prisma.$transaction( week.map((day) => prisma.weeklySchedule.upsert({ where: { dayOfWeek: day.dayOfWeek }, update: { isClosed: day.isClosed, slots: day.slots }, create: { dayOfWeek: day.dayOfWeek, isClosed: day.isClosed, slots: day.slots }, }), ), ); await recordAudit({ userEmail: gate.value.email, action: 'weekly_schedule.update', entity: 'WeeklySchedule', diff: diffOf(summary(before), summary(week)), }); revalidatePath('/admin'); revalidatePath('/admin/horaires'); return { ok: true, changed }; } /** * One readable line per day for the audit trail. Storing the raw rows would * make the diff accurate and unreadable; the point of the log is that a human * can see what changed. */ function summary(week: WeekDraft): Record { return Object.fromEntries( week.map((day) => [ DAY_NAMES_FR[day.dayOfWeek] ?? String(day.dayOfWeek), day.isClosed || day.slots.length === 0 ? 'Fermé' : day.slots.map((slot) => `${slot.open}–${slot.close}`).join(' · '), ]), ); }