"use client" import { useEffect, useRef, useState, type KeyboardEvent, type ReactNode } from "react" import { addHours } from "date-fns" import { FocusableStepValue, handleStepAdjustKeyDown, StepAdjustGroup, StepAdjustTimeDecreaseButton, StepAdjustTimeIncreaseButton, } from "@/components/agenda/agenda-step-adjust" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Switch } from "@/components/ui/switch" import { formatAgendaStepDurationMinutes } from "@/lib/agenda/agenda-date" import type { AgendaDurationStep } from "@/lib/agenda/agenda-settings-types" import { cn } from "@/lib/utils" function toDateInput(d: Date): string { const y = d.getFullYear() const m = String(d.getMonth() + 1).padStart(2, "0") const day = String(d.getDate()).padStart(2, "0") return `${y}-${m}-${day}` } function toTimeInput(d: Date): string { return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}` } function fromDateAndTime(date: string, time: string): Date | null { const [y, mo, d] = date.split("-").map(Number) const [h, mi] = time.split(":").map(Number) if (![y, mo, d, h, mi].every(Number.isFinite)) return null const parsed = new Date(y, mo - 1, d, h, mi, 0, 0) return Number.isNaN(parsed.getTime()) ? null : parsed } function tabAt(base: number | undefined, offset: number): number | undefined { return base !== undefined ? base + offset : undefined } const AGENDA_STEP_GROUP_CLASS = "w-fit gap-0 border-border/70 bg-mail-surface px-0.5 py-0.5" const AGENDA_STEP_BUTTON_CLASS = "size-6 shrink-0 [&_svg]:size-3.5" const AGENDA_STEP_VALUE_SLOT_CLASS = "flex h-8 w-[3.875rem] min-w-[3.875rem] max-w-[3.875rem] shrink-0 items-center justify-center" const AGENDA_STEP_VALUE_CLASS = "w-full min-w-0 px-0 text-center text-sm leading-none tabular-nums" const AGENDA_TIME_INPUT_CLASS = cn( AGENDA_STEP_VALUE_CLASS, "h-full border-0 bg-transparent font-medium shadow-none focus-visible:ring-0", "[&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none", "[&::-webkit-datetime-edit]:m-0 [&::-webkit-datetime-edit]:p-0 [&::-webkit-datetime-edit]:text-center", "[&::-webkit-datetime-edit-fields-wrapper]:flex [&::-webkit-datetime-edit-fields-wrapper]:w-full [&::-webkit-datetime-edit-fields-wrapper]:items-center [&::-webkit-datetime-edit-fields-wrapper]:justify-center", "[&::-webkit-datetime-edit-hour-field]:p-0 [&::-webkit-datetime-edit-minute-field]:p-0 [&::-webkit-datetime-edit-text]:p-0", ) const AGENDA_DURATION_VALUE_CLASS = "text-center text-sm font-medium leading-none tabular-nums" function ScheduleLabeledRow({ label, children, }: { label: string children: ReactNode }) { return (
{label}
{children}
) } function AgendaStepTimeInput({ value, tabIndex, onChange, onKeyDown, }: { value: string tabIndex?: number onChange: (value: string) => void onKeyDown: (e: KeyboardEvent) => void }) { return (
onChange(e.target.value)} onKeyDown={onKeyDown} className={AGENDA_TIME_INPUT_CLASS} />
) } /** Nombre de tab stops internes (base inclusif → dernier = base + count - 1). */ export function agendaScheduleFieldCount(options: { allDay: boolean showAllDayToggle: boolean compact: boolean }): number { if (options.compact) { if (options.allDay) return 1 return 3 } if (options.allDay) { return options.showAllDayToggle ? 3 : 2 } return options.showAllDayToggle ? 6 : 5 } export function AgendaEventScheduleFields({ start, end, allDay, stepMinutes, onChange, showAllDayToggle = false, onAllDayChange, compact = false, showRowLabels = false, className, tabIndexBase, }: { start: Date end: Date allDay: boolean stepMinutes: AgendaDurationStep onChange: (start: Date, end: Date) => void showAllDayToggle?: boolean onAllDayChange?: (allDay: boolean) => void compact?: boolean /** Modale avancée : libellés Début / Fin à gauche des deux lignes. */ showRowLabels?: boolean className?: string tabIndexBase?: number }) { const startDate = toDateInput(start) const endDate = toDateInput(end) const startTime = toTimeInput(start) const endTime = toTimeInput(end) const [endDateOpen, setEndDateOpen] = useState(false) const endDateInputRef = useRef(null) useEffect(() => { setEndDateOpen(false) }, [startDate, endDate, allDay, compact]) useEffect(() => { if (!endDateOpen) return const timer = window.setTimeout(() => endDateInputRef.current?.focus(), 0) return () => window.clearTimeout(timer) }, [endDateOpen]) const durationMinutes = Math.max( stepMinutes, Math.round((end.getTime() - start.getTime()) / 60_000), ) const showEndDateField = startDate !== endDate || endDateOpen const apply = (nextStart: Date, nextEnd: Date) => { if (nextEnd.getTime() <= nextStart.getTime()) { onChange(nextStart, addHours(nextStart, 1)) return } onChange(nextStart, nextEnd) } const patchStartDate = (date: string) => { const nextStart = fromDateAndTime(date, allDay ? "00:00" : startTime) ?? start let nextEnd = end if (date > endDate) nextEnd = fromDateAndTime(date, allDay ? "00:00" : endTime) ?? nextStart apply(nextStart, nextEnd) } const patchEndDate = (date: string) => { const nextEnd = fromDateAndTime(date, allDay ? "00:00" : endTime) ?? end apply(start, nextEnd) } const patchStartTime = (time: string) => { const nextStart = fromDateAndTime(startDate, time) if (!nextStart) return apply(nextStart, end.getTime() <= nextStart.getTime() ? addHours(nextStart, 1) : end) } const patchEndTime = (time: string) => { const nextEnd = fromDateAndTime(endDate, time) if (!nextEnd) return apply(start, nextEnd.getTime() <= start.getTime() ? addHours(start, 1) : nextEnd) } const shiftStart = (deltaMinutes: number) => { const nextStart = new Date(start.getTime() + deltaMinutes * 60_000) const durationMs = end.getTime() - start.getTime() apply(nextStart, new Date(nextStart.getTime() + durationMs)) } const shiftEnd = (deltaMinutes: number) => { const nextEnd = new Date(end.getTime() + deltaMinutes * 60_000) if (nextEnd.getTime() <= start.getTime()) return apply(start, nextEnd) } const shiftDuration = (deltaMinutes: number) => { const nextDuration = Math.max(stepMinutes, durationMinutes + deltaMinutes) apply(start, new Date(start.getTime() + nextDuration * 60_000)) } const startDateInput = ( patchStartDate(e.target.value)} className="h-9 w-fit" /> ) const startTimeControl = ( shiftStart(-stepMinutes)} label={`Reculer le début de ${stepMinutes} minutes`} className={AGENDA_STEP_BUTTON_CLASS} /> handleStepAdjustKeyDown( e, () => shiftStart(-stepMinutes), () => shiftStart(stepMinutes), ) } /> shiftStart(stepMinutes)} label={`Avancer le début de ${stepMinutes} minutes`} className={AGENDA_STEP_BUTTON_CLASS} /> ) const endTimeControl = ( shiftEnd(-stepMinutes)} label={`Reculer la fin de ${stepMinutes} minutes`} className={AGENDA_STEP_BUTTON_CLASS} /> handleStepAdjustKeyDown( e, () => shiftEnd(-stepMinutes), () => shiftEnd(stepMinutes), ) } /> shiftEnd(stepMinutes)} label={`Avancer la fin de ${stepMinutes} minutes`} className={AGENDA_STEP_BUTTON_CLASS} /> ) const durationControl = ( shiftDuration(-stepMinutes)} onIncrease={() => shiftDuration(stepMinutes)} decreaseDisabled={durationMinutes <= stepMinutes} increaseDisabled={false} decreaseLabel={`Réduire la durée de ${stepMinutes} minutes`} increaseLabel={`Augmenter la durée de ${stepMinutes} minutes`} className={AGENDA_STEP_GROUP_CLASS} buttonClassName={AGENDA_STEP_BUTTON_CLASS} valueWrapperClassName={AGENDA_STEP_VALUE_SLOT_CLASS} valueClassName={AGENDA_DURATION_VALUE_CLASS} /> ) const endTimeLabel = ( {endTime} ) const endDateControl = showEndDateField ? ( patchEndDate(e.target.value)} className="h-9 w-fit" /> ) : ( ) const allDayToggle = showAllDayToggle && onAllDayChange ? ( ) : null if (compact && allDay) { return (
{startDateInput} {startDate !== endDate ? endDateControl : null}
Toute la journée
) } if (compact) { return (
{startDateInput} {startTimeControl}
{durationControl} {endTimeLabel}
) } const scheduleRow = (label: string | null, children: ReactNode) => showRowLabels && label ? ( {children} ) : (
{children}
) if (allDay) { return (
{scheduleRow("Début", startDateInput)} {scheduleRow("Fin", endDateControl)} {allDayToggle}
) } return (
{scheduleRow("Début", ( <> {startDateInput} {startTimeControl} ))} {scheduleRow("Fin", ( <> {endDateControl} {endTimeControl} {durationControl} ))} {allDayToggle}
) }