'use client'; import { useState, useTransition } from 'react'; import { earlyClosing, lateOpening } from '@/lib/schedule/exceptions'; import type { ExceptionKind, Slot } from '@/lib/schedule/types'; import { deleteException, saveException, saveExceptionRange } from './actions'; export type UpcomingDay = { date: string; label: string; isToday: boolean; isClosed: boolean; hoursText: string; kind: ExceptionKind; note: string | null; /** Only a stored exception can be removed; holidays and vacations live elsewhere. */ canDelete: boolean; }; type Props = { canEdit: boolean; today: string; todayLabel: string; usualSlots: Slot[]; usualText: string; effectiveText: string; todayIsException: boolean; upcoming: UpcomingDay[]; }; const KIND_LABELS: Record = { MANUAL: { text: 'manuelle', className: 'bg-[var(--accent-tint)] text-[var(--accent-ink)]' }, HOLIDAY: { text: 'jour férié', className: 'bg-[var(--surface-muted)] text-[var(--ink-muted)]' }, VACATION: { text: 'vacances', className: 'bg-[var(--surface-muted)] text-[var(--ink-muted)]' }, }; export default function ExceptionsManager({ canEdit, today, todayLabel, usualSlots, usualText, effectiveText, todayIsException, upcoming, }: Props) { const [pending, startTransition] = useTransition(); const [message, setMessage] = useState<{ kind: 'ok' | 'error'; text: string } | null>(null); function run(work: () => Promise<{ ok: true } | { ok: false; error: string }>, success: string) { setMessage(null); startTransition(async () => { const result = await work(); setMessage(result.ok ? { kind: 'ok', text: success } : { kind: 'error', text: result.error }); }); } return (
{message ? (

{message.text}

) : null} {canEdit ? ( ) : null} {canEdit ? : null}

À venir

{upcoming.length === 0 ? (

Aucun changement prévu dans les deux prochains mois.

) : (
    {upcoming.map((day) => { const badge = KIND_LABELS[day.kind ?? 'MANUAL'] ?? KIND_LABELS.MANUAL!; return (
  • {day.label} {day.isToday ? ' (aujourd’hui)' : ''} {day.hoursText} {badge.text} {day.note ? ( « {day.note} » ) : null} {canEdit && day.canDelete ? ( ) : null}
  • ); })}
)}
); } /** * The two-click path. This is the gesture the shop actually makes, from a * phone, standing behind the counter — so it sits at the top and asks for as * little as possible. */ function TodayPanel({ today, todayLabel, usualSlots, usualText, effectiveText, todayIsException, pending, run, }: { today: string; todayLabel: string; usualSlots: Slot[]; usualText: string; effectiveText: string; todayIsException: boolean; pending: boolean; run: (work: () => Promise<{ ok: true } | { ok: false; error: string }>, success: string) => void; }) { const [openFrom, setOpenFrom] = useState('14:00'); const [closeAt, setCloseAt] = useState('16:00'); const isOpenToday = usualSlots.length > 0; const lateResult = lateOpening(usualSlots, openFrom); const earlyResult = earlyClosing(usualSlots, closeAt); function apply(slots: Slot[], note: string, success: string) { run( () => saveException({ date: today, isClosed: slots.length === 0, slots, reason: 'TEMPORARY', noteFr: note, }), success, ); } return (

Modifier les horaires d’aujourd’hui

{todayLabel} — habituellement {usualText}. {todayIsException ? ` Actuellement : ${effectiveText}.` : ''}

{isOpenToday ? ( <>
setOpenFrom(event.target.value)} className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] bg-[var(--surface)] px-2 py-2 text-base tabular-nums" /> → {lateResult.length === 0 ? 'fermé' : summarise(lateResult)}
setCloseAt(event.target.value)} className="rounded-[var(--radius-sm)] border border-[var(--line-strong)] bg-[var(--surface)] px-2 py-2 text-base tabular-nums" /> → {earlyResult.length === 0 ? 'fermé' : summarise(earlyResult)}
) : (

La boutique est déjà fermée aujourd’hui : pour une ouverture exceptionnelle, utilisez le formulaire ci-dessous.

)}
); } /** A specific date, or a short run of days. Longer closures are holidays. */ function RangePanel({ pending, run, }: { pending: boolean; run: (work: () => Promise<{ ok: true } | { ok: false; error: string }>, success: string) => void; }) { const [start, setStart] = useState(''); const [end, setEnd] = useState(''); const [note, setNote] = useState(''); return (

Fermer une date ou une période

Pour une fermeture longue (congés annuels), utilisez plutôt la page Vacances.

); } function summarise(slots: Slot[]): string { return slots.map((slot) => `${slot.open} – ${slot.close}`).join(' · '); }