"use client"; import { useState, useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, LineChart, Line, CartesianGrid, } from "recharts"; import { useBaby } from "@/contexts/baby-context"; import { format, subDays, startOfDay, endOfDay } from "date-fns"; import { fr } from "date-fns/locale"; import { EventType } from "@/lib/event-config"; interface Event { id: string; type: EventType; startedAt: string; endedAt?: string; } function buildWeeklyData(events: Event[], days: number) { const labelFormat = days <= 7 ? "EEE" : "d/M"; return Array.from({ length: days }, (_, i) => { const d = subDays(new Date(), days - 1 - i); const start = startOfDay(d).getTime(); const end = endOfDay(d).getTime(); const dayEvents = events.filter((e) => { const t = new Date(e.startedAt).getTime(); return t >= start && t <= end; }); const feeds = dayEvents.filter((e) => e.type === "BREASTFEED" || e.type === "BOTTLE").length; const diapers = dayEvents.filter((e) => e.type === "DIAPER_WET" || e.type === "DIAPER_STOOL").length; const sleepMins = dayEvents .filter((e) => (e.type === "NAP" || e.type === "NIGHT_SLEEP") && e.endedAt) .reduce((acc, e) => acc + (new Date(e.endedAt!).getTime() - new Date(e.startedAt).getTime()) / 60000, 0); return { label: format(d, labelFormat, { locale: fr }), feeds, diapers, sleep: Math.round(sleepMins / 60 * 10) / 10 }; }); } function formatDuration(totalMins: number): string { const h = Math.floor(totalMins / 60); const m = Math.round(totalMins % 60); return `${h}h ${m.toString().padStart(2, "0")}min`; } interface SleepStats { totalHours: number; avgPerDay: number; longestStretchMins: number; } function computeSleepStats(events: Event[], days: number): SleepStats { const sleepEvents = events.filter( (e) => (e.type === "NAP" || e.type === "NIGHT_SLEEP") && e.endedAt ); const durations = sleepEvents.map( (e) => (new Date(e.endedAt!).getTime() - new Date(e.startedAt).getTime()) / 60000 ); const totalMins = durations.reduce((acc, d) => acc + d, 0); const longestStretchMins = durations.length > 0 ? Math.max(...durations) : 0; return { totalHours: Math.round((totalMins / 60) * 10) / 10, avgPerDay: Math.round((totalMins / 60 / days) * 10) / 10, longestStretchMins: Math.round(longestStretchMins), }; } interface FeedingStats { breastfeedMinutes: number; bottleCount: number; } function computeFeedingStats(events: Event[]): FeedingStats { const breastfeedEvents = events.filter( (e) => e.type === "BREASTFEED" && e.endedAt ); const breastfeedMinutes = Math.round( breastfeedEvents.reduce( (acc, e) => acc + (new Date(e.endedAt!).getTime() - new Date(e.startedAt).getTime()) / 60000, 0 ) ); const bottleCount = events.filter((e) => e.type === "BOTTLE").length; return { breastfeedMinutes, bottleCount }; } function computeFeedInterval(events: Event[]): string { const feeds = events .filter((e) => e.type === "BREASTFEED" || e.type === "BOTTLE") .map((e) => new Date(e.startedAt).getTime()) .sort((a, b) => a - b); if (feeds.length < 2) return "—"; const intervals = feeds.slice(1).map((t, i) => t - feeds[i]); const avgMs = intervals.reduce((a, b) => a + b, 0) / intervals.length; const hours = Math.floor(avgMs / 3600000); const mins = Math.round((avgMs % 3600000) / 60000); return `${hours}h${mins.toString().padStart(2, "0")}`; } function useDarkMode() { const [isDark, setIsDark] = useState(false); useEffect(() => { const check = () => setIsDark(document.documentElement.classList.contains("dark")); check(); const obs = new MutationObserver(check); obs.observe(document.documentElement, { attributeFilter: ["class"] }); return () => obs.disconnect(); }, []); return isDark; } const DAY_OPTIONS = [7, 14, 30, 90] as const; type DaysOption = typeof DAY_OPTIONS[number]; function daysLabel(days: DaysOption): string { if (days === 7) return "7j"; if (days === 14) return "14j"; if (days === 30) return "30j"; return "90j"; } function periodSubtitle(days: DaysOption): string { if (days === 7) return "7 derniers jours"; if (days === 14) return "14 derniers jours"; if (days === 30) return "30 derniers jours"; return "90 derniers jours"; } export default function StatsPage() { const { selectedBaby: baby } = useBaby(); const isDark = useDarkMode(); const [days, setDays] = useState(7); const tooltipStyle = { borderRadius: 8, border: `1px solid ${isDark ? "#334155" : "#e2e8f0"}`, background: isDark ? "#1e293b" : "#fff", color: isDark ? "#f1f5f9" : "#0f172a", boxShadow: "0 4px 16px rgba(0,0,0,0.12)", fontSize: 12, }; const cursorFill = isDark ? "rgba(255,255,255,0.04)" : "#f8fafc"; const { data: eventsData } = useQuery({ queryKey: ["events", baby?.id, "stats", days], queryFn: () => fetch(`/api/events?babyId=${baby!.id}&limit=500&dateFrom=${subDays(new Date(), days - 1).toISOString()}`).then((r) => r.json()), enabled: !!baby?.id, }); const events: Event[] = eventsData?.events ?? []; const weeklyData = buildWeeklyData(events, days); const feedInterval = computeFeedInterval(events); const sleepStats = computeSleepStats(events, days); const feedingStats = computeFeedingStats(events); const today = events.filter((e) => new Date(e.startedAt).toDateString() === new Date().toDateString()); const todayFeeds = today.filter((e) => e.type === "BREASTFEED" || e.type === "BOTTLE").length; const todayDiapers = today.filter((e) => e.type === "DIAPER_WET" || e.type === "DIAPER_STOOL").length; const todaySleepMins = today .filter((e) => (e.type === "NAP" || e.type === "NIGHT_SLEEP") && e.endedAt) .reduce((acc, e) => acc + (new Date(e.endedAt!).getTime() - new Date(e.startedAt).getTime()) / 60000, 0); return (

Statistiques

{periodSubtitle(days)}

{DAY_OPTIONS.map((opt) => ( ))}
{/* Today summary */}
{[ { label: "Repas", value: todayFeeds, color: "text-rose-600", bg: "bg-rose-50 dark:bg-rose-950", border: "border-rose-100 dark:border-rose-900" }, { label: "Couches", value: todayDiapers, color: "text-amber-600", bg: "bg-amber-50 dark:bg-amber-950", border: "border-amber-100 dark:border-amber-900" }, { label: "Sommeil", value: `${Math.round(todaySleepMins / 60 * 10) / 10}h`, color: "text-violet-600", bg: "bg-violet-50 dark:bg-violet-950", border: "border-violet-100 dark:border-violet-900" }, ].map(({ label, value, color, bg, border }) => (
{value}
{label} · aujourd'hui
))}
{/* Feed interval */}

Intervalle moyen entre les repas

{periodSubtitle(days)}

{feedInterval}
{/* Sleep summary */}

Résumé du sommeil

{periodSubtitle(days)}

Total

{formatDuration(sleepStats.totalHours * 60)}

Moy. / jour

{formatDuration(sleepStats.avgPerDay * 60)}

Plus longue nuit

{sleepStats.longestStretchMins > 0 ? formatDuration(sleepStats.longestStretchMins) : "—"}

{/* Feeding summary */}

Résumé des repas

{periodSubtitle(days)}

Durée allaitement

{feedingStats.breastfeedMinutes > 0 ? formatDuration(feedingStats.breastfeedMinutes) : "—"}

Biberons

{feedingStats.bottleCount}

Intervalle moy.

{feedInterval}

{/* Feeds */}

Repas par jour

[val, "Repas"]} contentStyle={tooltipStyle} cursor={{ fill: cursorFill }} />
{/* Diapers */}

Couches par jour

[val, "Couches"]} contentStyle={tooltipStyle} cursor={{ fill: cursorFill }} />
{/* Sleep */}

Sommeil par jour (heures)

[`${val}h`, "Sommeil"]} contentStyle={tooltipStyle} />
); }