import { auth } from '@/lib/auth'; import { prisma } from '@/lib/db'; import { addCivilDays, toCivilDate } from '@/lib/schedule/civil'; import { loadScheduleContext, loadSettings } from '@/lib/schedule/context'; import { formatDateFr, formatHoursText } from '@/lib/schedule/format'; import { resolveCivilDay } from '@/lib/schedule/resolver'; import { loadTemplates } from '@/lib/templates'; import type { ScheduleContext } from '@/lib/schedule/types'; import ExceptionsManager, { type SelectableDay, type UpcomingDay } from './ExceptionsManager'; export const metadata = { title: 'Changements ponctuels — ITA ITO' }; /** How far ahead the list looks. Far enough for a season, short enough to read. */ const HORIZON_DAYS = 60; export default async function ExceptionsPage() { const [session, settings, templates] = await Promise.all([ auth(), loadSettings(), loadTemplates(), ]); const now = new Date(); const context = await loadScheduleContext(now, settings); const today = toCivilDate(now, settings.timezone); const stored = await prisma.scheduleException.findMany({ where: { date: { gte: new Date(`${today}T00:00:00.000Z`) } }, select: { date: true }, }); const deletable = new Set(stored.map((row) => row.date.toISOString().slice(0, 10))); const upcoming = collectUpcoming(today, context, deletable); const days = collectSelectableDays(today, context); return (

Changements ponctuels

Une fermeture ou un horaire différent pour une date précise. Ces changements priment sur les horaires fixes.

); } /** * Each day the quick actions can target, with the hours it would normally * have. * * "Opens later" is late relative to the day's usual hours, and those differ * from one weekday to the next — so the whole window is resolved here rather * than asking the server again every time the date changes. Sixty small * entries cost less than a round trip per keystroke. */ function collectSelectableDays(today: string, context: ScheduleContext): SelectableDay[] { const withoutExceptions: ScheduleContext = { ...context, exceptions: [] }; return Array.from({ length: HORIZON_DAYS + 1 }, (_, offset) => { const date = addCivilDays(today, offset); const usual = resolveCivilDay(date, withoutExceptions); const effective = resolveCivilDay(date, context); return { date, label: formatDateFr(date), usualSlots: usual.slots, usualText: usual.slots.length > 0 ? formatHoursText(usual.slots) : 'Fermé', effectiveText: effective.slots.length > 0 ? formatHoursText(effective.slots) : 'Fermé', isException: effective.isException, }; }); } /** * Every day ahead that departs from the reference week, whatever the cause. * * Built by resolving each day rather than by listing the exception table, * because vacations and public holidays are never materialised as rows — so * the table alone would quietly omit most of what is actually in effect. */ function collectUpcoming( today: string, context: ScheduleContext, deletable: Set, ): UpcomingDay[] { const days: UpcomingDay[] = []; for (let offset = 0; offset <= HORIZON_DAYS; offset += 1) { const date = addCivilDays(today, offset); const day = resolveCivilDay(date, context); if (!day.isException) { continue; } days.push({ date, label: formatDateFr(date), isToday: offset === 0, isClosed: !day.isOpen, hoursText: day.slots.length > 0 ? formatHoursText(day.slots) : 'Fermé', kind: day.exceptionKind ?? 'MANUAL', note: day.noteFr, canDelete: deletable.has(date), }); } return days; }