"use client"; import { useEffect, useMemo, useState } from "react"; import { prob, timeFr } from "@/lib/format"; import { PARTY_IDS, partyVar, partyLabel } from "@/lib/parties"; import { SeatMeter } from "./charts"; import { MapSection } from "./map-section"; import { Card, Empty } from "./ui"; interface Snap { id: number; fetchedAt: string; reporting: number; final: boolean; simulated: boolean } interface State { snapshot: Snap; ridings: Record; nowcast: { parties: Record; p_minority: number; majority: number } | null } export function Replay() { const [snaps, setSnaps] = useState([]); const [i, setI] = useState(0); const [state, setState] = useState(null); const [playing, setPlaying] = useState(false); useEffect(() => { fetch("/api/live/replay").then((r) => r.json()).then((d) => { setSnaps(d.snapshots ?? []); setI(Math.max(0, (d.snapshots?.length ?? 1) - 1)); }).catch(() => {}); }, []); useEffect(() => { if (!snaps[i]) return; fetch(`/api/live/replay?t=${encodeURIComponent(snaps[i].fetchedAt)}`).then((r) => r.json()).then(setState).catch(() => {}); }, [snaps, i]); useEffect(() => { if (!playing) return; const t = setInterval(() => setI((k) => { if (k >= snaps.length - 1) { setPlaying(false); return k; } return k + 1; }), 700); return () => clearInterval(t); }, [playing, snaps.length]); const liveState = useMemo(() => { const o: Record = {}; Object.entries(state?.ridings ?? {}).forEach(([c, r]) => { o[c] = { status: r.final ? "FINAL" : r.bureaux[0] > 0 ? "LEADING" : "NOT_STARTED", leader: r.leader, bureaux: r.bureaux, marginPct: null }; }); return o; }, [state]); const seats = useMemo(() => { const s: Record = {}; Object.values(state?.ridings ?? {}).forEach((r) => { if (r.leader && r.bureaux[0] > 0) s[r.leader] = (s[r.leader] ?? 0) + 1; }); return s; }, [state]); if (snaps.length === 0) return Le replay se remplit le soir du vote (ou en mode simulation).; const cur = snaps[i]; return (
{ setPlaying(false); setI(Number(e.target.value)); }} className="flex-1 min-w-[200px]" aria-label="Moment" />{timeFr(cur.fetchedAt)} · {cur.reporting} circ. rapportent{cur.simulated ? " · simulation" : ""}{cur.final ? " · FINAL" : ""}
{timeFr(snaps[0].fetchedAt)}{timeFr(snaps[snaps.length - 1].fetchedAt)}
{state?.nowcast &&
{PARTY_IDS.map((id) => (
{partyLabel(id)}
en tête {state.nowcast!.parties[id]?.total_now ?? 0} · nowcast {state.nowcast!.parties[id]?.seats_median}
P(maj) {prob(state.nowcast!.parties[id]?.p_majority)}
))}
}
); }