"use client"; import { useState } from "react"; import { useSearchParams, useRouter, usePathname } from "next/navigation"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { EVENT_CONFIG, EventType, formatDuration } from "@/lib/event-config"; import { EventIcon } from "@/components/event-icon"; import { useBaby } from "@/contexts/baby-context"; import { format, isToday, isYesterday } from "date-fns"; import { fr } from "date-fns/locale"; import { Trash2, StickyNote, Pencil, CheckSquare, Square, XCircle, ImageIcon, Copy } from "lucide-react"; import Image from "next/image"; import { EventModal } from "@/components/event-modal"; interface Event { id: string; type: EventType; startedAt: string; endedAt?: string; notes?: string; metadata?: Record; user: { id: string; name: string }; } const PAGE_SIZE = 50; function groupByDay(events: Event[]): Record { const groups: Record = {}; for (const ev of events) { const key = format(new Date(ev.startedAt), "yyyy-MM-dd"); if (!groups[key]) groups[key] = []; groups[key].push(ev); } return groups; } function dayLabel(key: string): string { const d = new Date(key); if (isToday(d)) return "Aujourd'hui"; if (isYesterday(d)) return "Hier"; return format(d, "EEEE d MMMM", { locale: fr }); } function getEventSummary(event: Event): string { const meta = event.metadata ?? {}; if (event.type === "BREASTFEED") { const side = meta.breastSide === "left" ? "gauche" : meta.breastSide === "right" ? "droite" : "les deux"; if (event.endedAt) return `${formatDuration(new Date(event.startedAt), new Date(event.endedAt))} · ${side}`; return side; } if (event.type === "BOTTLE") return [meta.volume ? `${meta.volume} mL` : "", meta.bottleType === "maternal" ? "maternel" : "artificiel"].filter(Boolean).join(" · "); if (event.type === "PUMP") { const side = meta.side === "left" ? "gauche" : meta.side === "right" ? "droite" : "les deux"; const parts = [meta.volume ? `${meta.volume} mL` : "", side]; if (event.endedAt) parts.unshift(formatDuration(new Date(event.startedAt), new Date(event.endedAt))); return parts.filter(Boolean).join(" · "); } if (event.type === "DIAPER_STOOL") return [String(meta.color ?? ""), meta.leaked ? "débordement" : ""].filter(Boolean).join(" · "); if (event.type === "DIAPER_WET") return meta.leaked ? "débordement" : ""; if (event.type === "TEMPERATURE") return meta.value ? `${meta.value}°${meta.unit ?? "C"}` : ""; if (event.type === "MEDICATION") return String(meta.name ?? ""); if (event.type === "SYMPTOM") { const tags = (meta.tags as string[] | undefined) ?? []; return tags.length > 0 ? tags.join(", ") : ""; } if ((event.type === "NAP" || event.type === "NIGHT_SLEEP") && event.endedAt) { return formatDuration(new Date(event.startedAt), new Date(event.endedAt)); } if (event.type === "OTHER") return event.notes ? event.notes.slice(0, 50) : ""; return ""; } export default function TimelinePage() { const qc = useQueryClient(); const { selectedBaby } = useBaby(); const [expandedId, setExpandedId] = useState(null); const [editingNotes, setEditingNotes] = useState<{ id: string; notes: string } | null>(null); const [editingEvent, setEditingEvent] = useState(null); const [savedNoteId, setSavedNoteId] = useState(null); const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); const filterType = (searchParams.get("type") as EventType | null) ?? ""; const [page, setPage] = useState(0); const [selectMode, setSelectMode] = useState(false); const [selected, setSelected] = useState>(new Set()); const [duplicating, setDuplicating] = useState(null); // sourceDate being duplicated const [dupTarget, setDupTarget] = useState(""); // target date string const [dupLoading, setDupLoading] = useState(false); const [dupError, setDupError] = useState(""); const { data: eventsData, isLoading } = useQuery({ queryKey: ["events", selectedBaby?.id, filterType, page], queryFn: () => fetch(`/api/events?babyId=${selectedBaby!.id}&limit=${PAGE_SIZE}&offset=${page * PAGE_SIZE}${filterType ? `&type=${filterType}` : ""}`).then( (r) => r.json() ), enabled: !!selectedBaby?.id, refetchInterval: 10_000, }); const events: Event[] = eventsData?.events ?? []; const total: number = eventsData?.total ?? 0; const hasMore = (page + 1) * PAGE_SIZE < total; const grouped = groupByDay(events); const days = Object.keys(grouped).sort((a, b) => b.localeCompare(a)); async function deleteEvent(id: string) { await fetch(`/api/events/${id}`, { method: "DELETE" }); qc.invalidateQueries({ queryKey: ["events"] }); } async function duplicateDay(sourceDate: string, targetDate: string) { if (!selectedBaby?.id) return; setDupLoading(true); setDupError(""); const res = await fetch("/api/events/duplicate-day", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ babyId: selectedBaby.id, sourceDate, targetDate }), }); setDupLoading(false); if (!res.ok) { const data = await res.json(); setDupError(data.error ?? "Erreur"); return; } setDuplicating(null); setDupTarget(""); setDupError(""); qc.invalidateQueries({ queryKey: ["events"] }); } async function deleteSelected() { if (selected.size === 0) return; await Promise.all([...selected].map((id) => fetch(`/api/events/${id}`, { method: "DELETE" }))); setSelected(new Set()); setSelectMode(false); qc.invalidateQueries({ queryKey: ["events"] }); } function toggleSelect(id: string) { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } async function saveNotes(id: string, notes: string) { await fetch(`/api/events/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ notes }), }); qc.invalidateQueries({ queryKey: ["events"] }); setEditingNotes(null); setSavedNoteId(id); setTimeout(() => setSavedNoteId(null), 2000); } function handleFilterChange(t: EventType | "") { const params = new URLSearchParams(searchParams.toString()); if (t === "" || t === filterType) { params.delete("type"); } else { params.set("type", t); } router.replace(`${pathname}?${params.toString()}`, { scroll: false }); setPage(0); } return (

Journal{selectedBaby ? ` — ${selectedBaby.name}` : ""}

{/* Filter chips */}
{(Object.keys(EVENT_CONFIG) as EventType[]).map((t) => ( ))}
{selectMode && (
{selected.size} sélectionné{selected.size > 1 ? "s" : ""}
)} {isLoading && (
{[1, 2, 3].map((g) => (
{[1, 2, 3, 4].map((r) => (
))}
))}
)}
{days.map((day) => (
{dayLabel(day)}
{grouped[day].length} {!selectMode && ( )}
{duplicating === day && (

Copier les {grouped[day].length} événements vers…

{ setDupTarget(e.target.value); setDupError(""); }} min={format(new Date(), "yyyy-MM-dd")} className="flex-1 px-3 py-1.5 text-sm rounded-lg border border-indigo-200 dark:border-indigo-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 focus:outline-none focus:ring-2 focus:ring-indigo-400" />
{dupError &&

{dupError}

}
)}
{grouped[day].map((ev) => { const cfg = EVENT_CONFIG[ev.type]; const isExpanded = expandedId === ev.id; const summary = getEventSummary(ev); const isSelected = selected.has(ev.id); return (
{isExpanded && (

Début

{format(new Date(ev.startedAt), "HH:mm")}

{ev.endedAt && (

Fin

{format(new Date(ev.endedAt), "HH:mm")}

)}

Noté par

{ev.user.name}

{editingNotes?.id === ev.id ? (