'use client'; import { useEffect, useRef, useState } from 'react'; import { t } from '@/i18n'; import { clientCompare } from '@/lib/client-api-compare'; import { displayValue } from '@/lib/format'; import type { IndicatorCard, RankingRow } from '@/lib/types'; import { staleYear } from '@/components/charts/ranked-bars'; /** Small client-side fetch queue: at most 3 previews in flight, and no more requests after a 429. */ const queue: Array<() => void> = []; let inFlight = 0; let blocked = false; function schedule(job: () => Promise) { const run = () => { inFlight++; job().finally(() => { inFlight--; queue.shift()?.(); }); }; if (inFlight < 3) run(); else queue.push(run); } /** * "Top 3" mini preview (flag ยท value) for a ranking. `initial` rows come from the server for featured * indicators; otherwise the preview loads when it scrolls into view (IntersectionObserver, throttled queue). */ export function Top3Preview({ slug, spec, initial }: { slug: string; spec: IndicatorCard; initial?: RankingRow[] | null }) { const ref = useRef(null); const [rows, setRows] = useState(initial); const [visible, setVisible] = useState(initial !== undefined); useEffect(() => { if (visible || !ref.current) return; const el = ref.current; if (typeof IntersectionObserver === 'undefined') return; const io = new IntersectionObserver( (entries) => { if (entries.some((e) => e.isIntersecting)) { setVisible(true); io.disconnect(); } }, { rootMargin: '200px 0px' }, ); io.observe(el); return () => io.disconnect(); }, [visible]); useEffect(() => { if (!visible || rows !== undefined || blocked) return; let cancelled = false; schedule(async () => { if (cancelled || blocked) return; try { const r = await clientCompare.rankingTop(slug, 3); if (!cancelled) setRows(r.rows); } catch (e) { if ((e as { status?: number }).status === 429) blocked = true; if (!cancelled) setRows(null); } }); return () => { cancelled = true; }; }, [visible, rows, slug]); return (
{rows === undefined ? ( {visible ? t('rankings.preview.loading') : ''} ) : rows === null || rows.length === 0 ? ( {t('rankings.preview.na')} ) : (
    {rows.slice(0, 3).map((r) => { const stale = staleYear(r.year, Math.max(0, ...rows.map((x) => x.year ?? 0))); return (
  1. {r.rank} {r.country.flag} {r.country.name} {displayValue(r.value, spec, r.formatted)} {stale ? {stale} : null}
  2. ); })}
)}
); }