/** * ============================================================================= * Job·Ka — Groupe KA * Auteur : Simon-Pierre Boucher * Contact : contact@spboucher.ai * Fichier : frontend/src/pages/Stats.tsx * Rôle : Tableau de bord analytique — module Stats commun Groupe KA v2 * (contrat ../ka/stats/SPEC.md, composants ../ka/stats/kacharts.tsx). * PDF (5 rapports) + fraîcheur, KPI + sparklines, jauges, courbes * (N-1) + stats de séries, multi-courbes, barres empilées, * répartitions, distributions, géographie, calendrier + heatmap * horaire, tableaux, records. * Créé : 2026-08-17 Modifié : 2026-08-19 * ============================================================================= */ import { useCallback, useEffect, useState } from "react"; import { BarChart, BreakItem, CalendarHeatmap, DataTable, Distribution, Donut, EmptyBlock, Fraicheur, Gauge, GaugeCard, Histogram, HourCell, HourHeatmap, Kpi, KpiCard, LineChart, MultiLineChart, MultiSerie, PdfButton, PeriodSelector, RecordCard, RecordFact, Serie, StackedBarChart, StackedSerie, StatSummary, TableSpec, } from "../ka/stats/kacharts"; interface Dashboard { updated: string; period: { from: string | null; to: string | null; label: string }; kpis: Kpi[]; gauges?: Gauge[]; series: Serie[]; multiseries?: MultiSerie[]; stacked?: StackedSerie[]; breakdowns: { id: string; title: string; kind?: string; items: BreakItem[] }[]; distributions?: Distribution[]; geo?: { title: string; items: BreakItem[] }; heatmap?: { title: string; cells: { date: string; value: number }[] }; hourly?: { title: string; cells: HourCell[] }; tables: TableSpec[]; records: RecordFact[]; } function SectionTitle({ children }: { children: string }) { return (

{children}

); } export default function StatsPage() { const [period, setPeriod] = useState("30j"); const [custom, setCustom] = useState<{ from: string; to: string }>({ from: "", to: "" }); const [dash, setDash] = useState(null); const [loading, setLoading] = useState(true); const [err, setErr] = useState(null); const customOk = Boolean(custom.from && custom.to); const load = useCallback(() => { setLoading(true); setErr(null); const p = new URLSearchParams({ period }); if (customOk) { p.set("from", custom.from); p.set("to", custom.to); } fetch(`/api/stats/dashboard?${p}`) .then((r) => { if (!r.ok) throw new Error(`API ${r.status}`); return r.json(); }) .then((d: Dashboard) => setDash(d)) .catch((e) => setErr(String(e?.message ?? e))) .finally(() => setLoading(false)); }, [period, custom.from, custom.to, customOk]); useEffect(() => { document.title = "Statistiques de l'emploi — Job-Ka · Un service Groupe KA"; load(); }, [load]); const donuts = dash?.breakdowns?.filter((b) => b.kind === "donut") ?? []; const barBreaks = dash?.breakdowns?.filter((b) => b.kind !== "donut") ?? []; return (
{/* --- en-tête : titre + PDF (5 rapports) + fraîcheur ------------------ */}

Groupe KA · Job·Ka

Statistiques du marché de l'emploi

{dash && (

Période : {dash.period.label} {dash.period.from ? ` (${dash.period.from} → ${dash.period.to})` : ""}

)}
{dash && }
{err && (
Impossible de charger les statistiques.

{err}

)} {/* --- bandeau KPI (sparklines) ---------------------------------------- */} {dash && dash.kpis.length > 0 && (
{dash.kpis.map((k) => )}
)} {!dash && loading && (
Chargement des statistiques…
)} {/* --- sélecteur de période --------------------------------------------- */}
{ setPeriod(p); setCustom({ from: "", to: "" }); }} custom={custom} onCustom={(from, to) => setCustom({ from, to })} />
{dash && (
{/* --- jauges (complétude des fiches) ------------------------------ */} {dash.gauges && dash.gauges.length > 0 && (
{dash.gauges.map((g) => )}
)} {/* --- évolutions : courbes / barres + stats de séries -------------- */} {dash.series?.length ? ( dash.series.map((s) => (
)) ) : ( )} {/* --- multi-courbes (top secteurs) ---------------------------------- */} {dash.multiseries?.map((ms) => )} {/* --- barres empilées (ajouts par source) --------------------------- */} {dash.stacked?.map((st) => )} {/* --- répartitions (anneaux + barres avec deltas) ------------------- */} {(donuts.length > 0 || barBreaks.length > 0) && (
{donuts.map((b) => )} {barBreaks.map((b) => )}
)} {/* --- distributions (histogrammes) ---------------------------------- */} {dash.distributions && dash.distributions.length > 0 && (
{dash.distributions.map((d) => )}
)} {/* --- géographie ------------------------------------------------------ */} {dash.geo && } {/* --- calendrier de chaleur + activité horaire ------------------------ */} {dash.heatmap && } {dash.hourly && } {/* --- tableaux détaillés ---------------------------------------------- */} {dash.tables?.map((t) => )} {/* --- records & faits marquants ---------------------------------------- */} {dash.records?.length > 0 && (
Records & faits marquants
{dash.records.map((r) => )}
)}
)}
); }