import { useState, useEffect, useCallback } from "react"; import type { DayContentProps } from "react-day-picker"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { Calendar as CalendarIcon, Clock, X } from "lucide-react"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; import { cn } from "@/lib/utils"; import { ScrollArea } from "@/components/ui/scroll-area"; import { DayAgendaPanel } from "@/components/day-agenda-panel"; import { usePublicationAgenda } from "@/hooks/use-publication-agenda"; import { busyHours, conflictsFor, dayKey, groupByPage, occupiesSlot } from "@shared/agenda"; interface DateTimePickerProps { value: Date | undefined; onChange: (date: Date | undefined) => void; /** Magasins visés : leurs publications déjà prévues sont signalées. */ selectedPageIds?: string[]; placeholder?: string; } const OPTIMAL_HOURS = [8, 12, 13, 18, 19, 20]; export function DateTimePicker({ value, onChange, selectedPageIds = [], placeholder = "Sélectionner une date" }: DateTimePickerProps) { const [selectedDate, setSelectedDate] = useState(value); const [selectedHour, setSelectedHour] = useState(value?.getHours() || 8); const [selectedMinute, setSelectedMinute] = useState(value?.getMinutes() || 0); const [month, setMonth] = useState(value ?? new Date()); const { itemsByDay } = usePublicationAgenda(month); // Sync internal state with parent value prop useEffect(() => { if (value) { setSelectedDate(value); setMonth(value); setSelectedHour(value.getHours()); setSelectedMinute(value.getMinutes()); } else { setSelectedDate(undefined); setSelectedHour(8); setSelectedMinute(0); } }, [value]); const hours = Array.from({ length: 24 }, (_, i) => i); const minutes = [0, 15, 30, 45]; const isOptimalHour = (hour: number) => OPTIMAL_HOURS.includes(hour); const handleDateSelect = (date: Date | undefined) => { setSelectedDate(date); if (date) { const newDate = new Date(date); newDate.setHours(selectedHour); newDate.setMinutes(selectedMinute); onChange(newDate); } else { onChange(undefined); } }; const handleTimeChange = (hour: number, minute: number) => { setSelectedHour(hour); setSelectedMinute(minute); if (selectedDate) { const newDate = new Date(selectedDate); newDate.setHours(hour); newDate.setMinutes(minute); onChange(newDate); } }; // Publications qui occupent encore un créneau, jour par jour const activeItemsOn = useCallback( (date: Date) => (itemsByDay.get(dayKey(date)) ?? []).filter(occupiesSlot), [itemsByDay] ); const isSelectedStoreBusy = (date: Date) => conflictsFor(activeItemsOn(date), selectedPageIds).length > 0; const isOtherStoreBusy = (date: Date) => activeItemsOn(date).length > 0 && !isSelectedStoreBusy(date); const selectedDayItems = selectedDate ? itemsByDay.get(dayKey(selectedDate)) ?? [] : []; const hoursTaken = busyHours(selectedDayItems, selectedPageIds); const selectedKey = selectedPageIds.join(","); // Pastille du jour : publications sur les magasins choisis, sinon sur les autres const AgendaDayContent = useCallback( ({ date }: DayContentProps) => { const items = activeItemsOn(date); const mine = conflictsFor(items, selectedKey ? selectedKey.split(",") : []).length; const title = groupByPage(items) .map(({ page, items: pageItems }) => `${page.pageName} : ${pageItems.length}`) .join("\n"); return ( {date.getDate()} {items.length > 0 && ( 0 ? "bg-destructive text-destructive-foreground" : "bg-muted-foreground/70 text-background" )} > {mine || items.length} )} ); }, [activeItemsOn, selectedKey] ); return (
date < new Date(new Date().setHours(0, 0, 0, 0))} data-testid="calendar-picker" />
Déjà prévu sur un magasin sélectionné
Prévu sur d'autres magasins
{selectedPageIds.length === 0 && (

Cochez un magasin pour repérer ses jours déjà occupés.

)}
{value && ( )}
{selectedDate && (
Heures
{hours.map((hour) => { const taken = hoursTaken.get(hour) ?? 0; return ( ); })}
Minutes
{minutes.map((minute) => ( ))}
Heures optimales (8h, 12h-13h, 18h-20h)
{hoursTaken.size > 0 && (
Déjà prévu sur vos magasins
)}
)} {selectedDate && ( )}
); }