'use client'; import Link from 'next/link'; import { useEffect, useMemo, useState } from 'react'; import { t } from '@/i18n'; import { clientExplore } from '@/lib/client-api-explore'; import { cn } from '@/lib/cn'; import { routes } from '@/lib/site'; import type { IndicatorCard, RankingResponse } from '@/lib/types'; import type { RegionResponse } from '@/lib/types-explore'; import { RankedBars, rankedRowFromCountry, staleYear, type RankedBarRow } from '@/components/charts/ranked-bars'; import { EmptyState } from '@/components/data/empty-state'; /** * Member ranking for a group: indicator picker (headline indicators + a few more) → RankedBars of the members. * The default indicator comes server-side from `/regions/{slug}` (`ranking`); others are fetched from * `/rankings/{indicator}?group={slug}` client-side. */ export function MemberRanking({ groupSlug, groupName, initial, options }: { groupSlug: string; groupName: string; initial: RegionResponse['ranking']; options: IndicatorCard[] }) { const [indicator, setIndicator] = useState(initial.indicator.slug); const [cache, setCache] = useState>({}); const [loading, setLoading] = useState(false); const all = useMemo(() => { const seen = new Set(); return [initial.indicator, ...options].filter((o) => (seen.has(o.slug) ? false : (seen.add(o.slug), true))); }, [initial.indicator, options]); useEffect(() => { if (indicator === initial.indicator.slug || cache[indicator] !== undefined) return; const ctrl = new AbortController(); setLoading(true); clientExplore .ranking(indicator, groupSlug, 80, ctrl.signal) .then((r) => setCache((c) => ({ ...c, [indicator]: r }))) .catch(() => setCache((c) => ({ ...c, [indicator]: null }))) .finally(() => { if (!ctrl.signal.aborted) setLoading(false); }); return () => ctrl.abort(); }, [indicator, groupSlug, initial.indicator.slug, cache]); const isDefault = indicator === initial.indicator.slug; // `undefined` = not fetched yet (loading), `null` = fetch failed. const fetched = isDefault ? undefined : cache[indicator]; const spec: IndicatorCard = fetched ? fetched.indicator : all.find((o) => o.slug === indicator) ?? initial.indicator; const srcRows = isDefault ? initial.rows : fetched?.rows ?? []; const refYear = Math.max(0, ...srcRows.map((r) => r.year ?? 0)); const rows: RankedBarRow[] = isDefault ? initial.rows.map((r) => rankedRowFromCountry(r.country, r.value, r.rank, r.rank_world && r.n_world ? t('region.ranking.worldRank', { rank: r.rank_world, n: r.n_world }) : null, staleYear(r.year, refYear))) : fetched ? fetched.rows.map((r) => rankedRowFromCountry(r.country, r.value, r.rank, r.change_1y?.formatted ?? null, staleYear(r.year, refYear))) : []; const prov = isDefault ? initial.rows[0]?.provenance ?? null : fetched?.rows[0]?.provenance ?? null; const empty = (!isDefault && fetched === null) || (rows.length === 0 && !loading && (isDefault || fetched !== undefined)); return (
{t('region.ranking.full', { group: groupName })} →
{empty ? ( ) : rows.length ? ( ) : (
{t('common.loading')}
)}
); }