"use client"; import React, { useEffect, useState } from "react"; import { Loader2, ExternalLink, FileText, RefreshCw } from "lucide-react"; import { ScatterChart, Scatter, ZAxis, LineChart, Line, ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, } from "recharts"; import type { CBGovernance } from "@/app/api/central-bank-sources/route"; const CCY_ISO: Record = { USD: "us", EUR: "eu", GBP: "gb", JPY: "jp", CHF: "ch", CAD: "ca", AUD: "au", NZD: "nz", }; const ORDER = ["USD", "EUR", "GBP", "JPY", "CHF", "CAD", "AUD", "NZD"]; function Flag({ ccy }: { ccy: string }) { return (
{ccy} { (e.target as HTMLImageElement).style.display = "none"; }} />
); } // ── Dot plot Fed : scatter (année × taux, taille = nb de participants) ──────── function FedDotPlotChart({ dotPlot }: { dotPlot: NonNullable }) { const yearIndex = new Map(dotPlot.years.map((y, i) => [y, i])); const scatterData = dotPlot.dots.map(d => ({ x: yearIndex.get(d.year) ?? 0, y: d.rate, z: d.count, year: d.year })); const medianData = dotPlot.years.map((y, i) => ({ x: i, y: dotPlot.medianByYear[y] })); const allRates = dotPlot.dots.map(d => d.rate); const minY = Math.min(...allRates, ...Object.values(dotPlot.medianByYear)) - 0.2; const maxY = Math.max(...allRates, ...Object.values(dotPlot.medianByYear)) + 0.2; return ( i)} tickFormatter={(i: number) => dotPlot.years[i] ?? ""} tick={{ fontSize: 8, fill: "#64748b" }} axisLine={false} tickLine={false} /> `${v.toFixed(1)}%`} tick={{ fontSize: 8, fill: "#64748b" }} axisLine={false} tickLine={false} width={34} /> { const p = payload?.[0]?.payload as { year: string; y: number; z: number } | undefined; if (!p) return null; return (

{p.year} · {p.y.toFixed(3)}%

{p.z} participant{p.z > 1 ? "s" : ""}

); }} />
); } // ── Évolution : médiane "année courante" + "long terme" au fil des SEP ──────── function FedEvolutionChart({ dotPlot }: { dotPlot: NonNullable }) { const data = dotPlot.history.map(h => { const entries = Object.entries(h.medianByYear); const nearTerm = entries.find(([y]) => y !== "Longer run")?.[1] ?? null; const longRun = h.medianByYear["Longer run"] ?? null; return { date: h.date.slice(2, 7), nearTerm, longRun }; }); return ( `${v.toFixed(1)}%`} /> { if (!payload?.length) return null; const items = payload as { dataKey: string; value: number }[]; return (

{label}

{items.map(it => (

{it.dataKey === "longRun" ? "Long terme" : "Année courante"} : {it.value.toFixed(2)}%

))}
); }} />
); } // ── Prévisions macro (comment la BC voit sa propre économie) ────────────────── // PIB + inflation par échéance, publiées par la BC elle-même (SEP Fed, // Eurosystem staff projections, BoJ Outlook Report, MPR BoC, SMP RBA…). function fmtForecastYear(y: string): string { const m = y.match(/^(\d{4})-(\d{2})$/); if (!m) return y; const MONTH: Record = { "06": "Jun", "12": "Dec" }; return `${MONTH[m[2]] ?? m[2]}’${m[1].slice(2)}`; } function ForecastBlock({ forecast }: { forecast: NonNullable }) { const years = forecast.years; const hasGdp = Object.values(forecast.gdp).some(v => v != null); const hasInfl = Object.values(forecast.inflation).some(v => v != null); if (!years.length || (!hasGdp && !hasInfl)) return null; return (

Prévisions — comment la BC voit son économie

{years.map(y => ( ))} {hasGdp && ( {years.map(y => { const v = forecast.gdp[y]; return ( ); })} )} {hasInfl && ( {years.map(y => { const v = forecast.inflation[y]; return ( ); })} )}
{fmtForecastYear(y)}
PIB {v != null ? `${v > 0 ? "+" : ""}${v}%` : "—"}
Inflation {v != null ? `${v > 0 ? "+" : ""}${v}%` : "—"}

{forecast.label} · publié {forecast.asOf} {forecast.isProxy && forecast.proxyLabel ? ` · ${forecast.proxyLabel}` : ""}

); } // ── Carte banque centrale ────────────────────────────────────────────────────── function CBCard({ g }: { g: CBGovernance }) { const hasVote = g.voteSummary !== null; return (

{g.bankName}

{g.countryLabel} · {g.currency}

{hasVote ? ( <>

Dernière réunion

{g.meetingDate ?? "—"}

Taux

{g.rateLevel ?? "—"}

Vote

{g.voteSummary}

{g.voteDetail && (

{g.voteDetail}

)}
{g.dotPlot && (

Dot plot — projections des membres

Évolution (dernières publications SEP)

Année courante Long terme
)} {g.forecast && } ) : (

{g.fetchError === "Scraping non encore implémenté pour cette banque" ? "Vote / rapport non encore automatisés pour cette banque" : `Données indisponibles (${g.fetchError ?? "erreur"})`}

)}
{g.reportPdfUrl && ( Rapport PDF )} Site officiel
); } // ── Onglet principal ──────────────────────────────────────────────────────────── export default function CentralBankSourcesTab() { const [data, setData] = useState | null>(null); const [loading, setLoading] = useState(true); const [fetchedAt, setFetchedAt] = useState(null); const load = React.useCallback(async () => { setLoading(true); try { const res = await fetch("/api/central-bank-sources", { cache: "no-store" }); const json = await res.json(); setData(json.data); setFetchedAt(json.fetchedAt); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); return (

Sources banques centrales

Vote de la dernière réunion · dot plot (Fed) · rapport de politique monétaire · site officiel

{fetchedAt && MAJ {new Date(fetchedAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })}}
{loading && !data ? (
) : (
{ORDER.map(ccy => { const g = data?.[ccy]; return g ? : null; })}
)}
); }