/** * Trouve-KA — panneau client /stats (kit ka-ui/stats v2, contrat SPEC.md) * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * * Ordre des sections (SPEC §1) : KPI → période → jauges → évolutions * (séries + stats, multi-courbes, empilées) → répartitions → distributions → * calendriers (heatmap 26 sem. + horaire 7×24) → tableaux → records → * fraîcheur. PdfButton v2 = menu des 5 rapports. */ "use client"; import { useCallback, useEffect, useState } from "react"; import { BarChart, CalendarHeatmap, DataTable, Donut, EmptyBlock, Fraicheur, GaugeCard, Histogram, HourHeatmap, KpiCard, LineChart, MultiLineChart, PdfButton, PeriodSelector, RecordCard, StackedBarChart, StatSummary, type BreakItem, type Distribution, type Gauge, type HourCell, type Kpi, type MultiSerie, type RecordFact, type Serie, type StackedSerie, type TableSpec, } from "@/ka/stats/kacharts"; type Breakdown = { id: string; title: string; kind?: "donut" | "bar" | "bars"; items: BreakItem[] }; type Dashboard = { updated: string; period: { from: string; to: string; label: string }; kpis: Kpi[]; gauges?: Gauge[]; series: Serie[]; multiseries?: MultiSerie[]; stacked?: StackedSerie[]; breakdowns: Breakdown[]; distributions?: Distribution[]; heatmap?: { title: string; cells: { date: string; value: number }[] }; hourly?: { title: string; cells: HourCell[] }; tables: TableSpec[]; records: RecordFact[]; }; /** Les séries clés reçoivent un résumé min/max/moy/méd/σ sous la courbe. */ const STAT_SERIES = new Set(["cumul", "crawlees", "indexees", "recherches"]); export function StatsPanel() { const [period, setPeriod] = useState("30j"); const [custom, setCustom] = useState<{ from: string; to: string }>({ from: "", to: "" }); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const hasCustom = Boolean(custom.from && custom.to); const load = useCallback(async () => { setLoading(true); setError(false); try { const p = new URLSearchParams({ period }); if (hasCustom) { p.set("from", custom.from); p.set("to", custom.to); } const res = await fetch(`/api/stats/dashboard?${p}`); if (!res.ok) throw new Error(`Erreur ${res.status}`); setData((await res.json()) as Dashboard); } catch { setError(true); } finally { setLoading(false); } }, [period, custom.from, custom.to, hasCustom]); useEffect(() => { void load(); }, [load]); const topQueries = data?.tables.find((t) => t.id === "topq"); const otherTables = data?.tables.filter((t) => t.id !== "topq") ?? []; return (

Transparence

Statistiques du moteur

Le tableau de bord complet du moteur de recherche du Groupe KA : indexation des sites de l'écosystème, crawl, erreurs et recherches — calculé sur les données réelles du moteur, rien d'inventé.

{data && void load()} />}
{ setCustom({ from: "", to: "" }); setPeriod(p); }} custom={custom} onCustom={(from, to) => setCustom({ from, to })} />
{error && (
Statistiques indisponibles

Le moteur n'a pas pu répondre. Réessayez dans un instant.

)} {!error && !data && loading && (

Chargement des statistiques…

)} {data && (
{/* 1. Bandeau KPI (+ sparklines) */}
{data.kpis.map((k) => ( ))}
{/* 3. Jauges : taux & couvertures */} {data.gauges && data.gauges.length > 0 && (
{data.gauges.map((g) => ( ))}
)} {/* 4. Évolutions : séries, multi-courbes, empilées */}
{data.series.map((s, i) => (
{STAT_SERIES.has(s.id) && }
))} {(data.multiseries ?? []).map((ms) => (
))} {(data.stacked ?? []).map((st) => (
))}
{/* 5. Répartitions */}
{data.breakdowns.map((b) => b.kind === "bar" || b.kind === "bars" ? ( ) : ( ), )}
{/* Distributions / histogrammes */} {data.distributions && data.distributions.length > 0 && (
{data.distributions.map((d) => ( ))}
)} {/* 7. Calendriers : 26 semaines + activité horaire 7×24 */} {data.heatmap && (
)} {data.hourly && (
)} {/* 8. Tableaux détaillés */}
{topQueries ? ( ) : ( )} {otherTables.map((t) => ( ))}
{/* 9. Records & faits marquants */} {data.records.length > 0 && (

Records & faits marquants

{data.records.map((r) => ( ))}
)} {/* 10. Fraîcheur (rappel en bas de page) */}
void load()} />
)}
); }