// Auteur : Simon-Pierre Boucher — contact@spboucher.ai /** * Tableau de bord /stats v2 — module Stats commun Groupe KA (src/ka/stats/ * SPEC.md §1), monté sur le kit kacharts : bandeau KPI (sparklines), jauges, * courbes + stats de séries, multi-courbes, barres empilées, répartitions, * distributions, géographie, calendrier d'activité, tableaux, records, * fraîcheur + PdfButton (5 rapports). Données : /api/stats/dashboard * (instantané du rôle 2026 + corpus de ventes réelles — period non applicable). */ "use client"; import { useCallback, useState } from "react"; import { KpiCard, GaugeCard, LineChart, MultiLineChart, StackedBarChart, BarChart, Donut, Histogram, CalendarHeatmap, StatSummary, DataTable, RecordCard, PdfButton, Fraicheur, type Kpi, type Serie, type MultiSerie, type StackedSerie, type BreakItem, type Distribution, type Gauge, type TableSpec, type RecordFact, } from "@/ka/stats/kacharts"; import { useLang } from "./LangContext"; export interface DashboardPayload { updated: string; period: { label: string; applicable?: boolean }; kpis: Kpi[]; gauges?: Gauge[]; series?: Serie[]; multiseries?: MultiSerie[]; stacked?: StackedSerie[]; breakdowns?: { id: string; title: string; kind: "donut" | "bars"; items: BreakItem[] }[]; distributions?: Distribution[]; geo?: { title: string; items: BreakItem[] }; heatmap?: { title: string; cells: { date: string; value: number }[] }; tables?: TableSpec[]; records?: RecordFact[]; } function SectionTitle({ kicker, title }: { kicker: string; title: string }) { return (
{kicker}

{title}

); } export default function StatsDashboard({ initial }: { initial: DashboardPayload }) { const { lang } = useLang(); const fr = lang === "fr"; const [data, setData] = useState(initial); const refresh = useCallback(async () => { try { const r = await fetch("/api/stats/dashboard?period=tout", { cache: "no-store" }); if (r.ok) setData(await r.json()); } catch { /* on garde les données courantes */ } }, []); const keySeries = new Set(["valeur_millesime", "ventes_mois", "prix_median_mois"]); return (
{/* ---- fraîcheur + rapports PDF (5 modes) ---- */}

{fr ? `${data.period.label} · corpus de ventes 2021-2026 · périodes non applicables (instantané)` : `${data.period.label} · 2021-2026 sales corpus · periods not applicable (snapshot)`}

{/* ---- 1. bandeau KPI ---- */}
{data.kpis.map((k) => )}
{/* ---- 3. jauges ---- */} {!!data.gauges?.length && ( <>
{data.gauges.map((g) => )}
)} {/* ---- 4. évolutions ---- */} {!!data.series?.length && ( <>
{data.series.map((s) => (
{keySeries.has(s.id) && }
))}
)} {!!data.multiseries?.length && ( <>
{data.multiseries.map((ms) => )}
)} {!!data.stacked?.length && (
{data.stacked.map((st) => )}
)} {/* ---- 5. répartitions & distributions ---- */} {(!!data.breakdowns?.length || !!data.distributions?.length) && ( <>
{data.breakdowns?.map((b) => b.kind === "donut" ? : )} {data.distributions?.map((d) => )}
)} {/* ---- 6. géographie ---- */} {!!data.geo?.items?.length && ( <> )} {/* ---- 7. calendrier d'activité ---- */} {!!data.heatmap?.cells?.length && ( <> )} {/* ---- 8. tableaux détaillés ---- */} {!!data.tables?.length && ( <>
{data.tables.map((t) => )}
)} {/* ---- 9. records ---- */} {!!data.records?.length && ( <>
{data.records.map((r) => )}
)} {/* ---- 10. fraîcheur (rappel de bas de page) ---- */}
); }