"use client"; import { useEffect, useMemo, useState } from "react"; import { dateFr, dateTimeFr, prob } from "@/lib/format"; import { PARTY_IDS, partyVar, partyLabel } from "@/lib/parties"; import { SeatMeter } from "./charts"; interface Run { id: number; date: string; completedAt: string | null; modelVersion: string; pollCount: number; parties: Record; pMinority: number } export function ForecastHistory({ majority, total }: { majority: number; total: number }) { const [runs, setRuns] = useState([]); const [i, setI] = useState(0); useEffect(() => { fetch("/api/forecast/history").then((r) => r.json()).then((d) => { setRuns(d.runs ?? []); setI(Math.max(0, (d.runs?.length ?? 1) - 1)); }).catch(() => {}); }, []); const run = runs[i]; const W = 640, H = 120; const path = useMemo(() => { if (runs.length < 2) return null; const x = (k: number) => 8 + (k / (runs.length - 1)) * (W - 16); const y = (p: number) => 6 + (1 - p) * (H - 12); return PARTY_IDS.map((id) => ({ id, d: runs.map((r, k) => `${k ? "L" : "M"}${x(k)},${y(r.parties[id]?.pMajority ?? 0)}`).join(" ") })); }, [runs]); if (!run) return
Historique en cours de constitution : un point par jour de recalcul.
; return (
setI(Number(e.target.value))} className="flex-1 min-w-[200px]" aria-label="Date du run" />
{dateFr(run.date)} run #{run.id} · {run.pollCount} sondages · modèle {run.modelVersion}
{dateFr(runs[0].date, { day: "numeric", month: "short" })}{dateFr(runs[runs.length - 1].date, { day: "numeric", month: "short" })}
[id, run.parties[id]?.seats ?? 0]))} total={total} majority={majority} />
{PARTY_IDS.slice().sort((a, b) => (run.parties[b]?.seats ?? 0) - (run.parties[a]?.seats ?? 0)).map((id) => (
{partyLabel(id)}
{run.parties[id]?.seats} {run.parties[id]?.seats80?.join("–")}
{run.parties[id]?.vote?.toFixed(1)} % · maj. {prob(run.parties[id]?.pMajority)}
))}
{path && (
P(majorité) par run
{[0, 0.5, 1].map((p) => )} {path.map((p) => )}
)}
Run terminé {dateTimeFr(run.completedAt)}.
); }