'use client'; import { Maximize2 } from 'lucide-react'; import Link from 'next/link'; import { useMemo, useState } from 'react'; import { t } from '@/i18n'; import { formatValue, grouped } from '@/lib/format'; import { routes } from '@/lib/site'; import type { FormatSpec } from '@/lib/types'; import type { FramesResponse } from '@/lib/types-analytics'; import { ChoroplethView, classFor, legendFromBreaks, type ChoroplethFeature } from '@/components/charts/choropleth-view'; import { SourceLine } from '@/components/charts/source-line'; import { YearSlider } from '@/components/controls/year-slider'; import type { ProvenancePayload } from '@/components/data/provenance-context'; import { EmptyState } from '@/components/data/empty-state'; /** Geometry the server computes once (no values): one entry per drawn country. */ export interface BaseFeature { iso3: string | null; name: string; slug: string | null; flag: string | null; d: string; } /** * Indicator world map with a time machine: every year comes from one `/indicators/{slug}/frames` payload * (pooled quantile legend, so colours stay comparable while scrubbing), so the slider and play button need no * further requests. Click/tap → country page. Hatched = no data for the selected year. */ export function IndicatorMap({ slug, geometry, sphere, frames, spec, payload, initialYear }: { slug: string; geometry: BaseFeature[]; sphere: string; frames: FramesResponse | null; spec: FormatSpec; payload: ProvenancePayload | null; initialYear?: number | null }) { const years = frames?.years ?? []; const [year, setYear] = useState(initialYear && years.includes(initialYear) ? initialYear : years[years.length - 1] ?? new Date().getUTCFullYear()); const idx = years.indexOf(year); const { features, legend, k, n } = useMemo(() => { if (!frames || idx < 0) return { features: [] as ChoroplethFeature[], legend: [], k: 1, n: 0 }; const breaks = frames.legend.breaks.slice(0, 6); let count = 0; const feats: ChoroplethFeature[] = geometry.map((g) => { const v = g.iso3 ? frames.values[g.iso3]?.[idx] : undefined; if (typeof v === 'number') count++; return { ...g, value: typeof v === 'number' ? v : null, cls: typeof v === 'number' ? classFor(v, breaks) : null }; }); return { features: feats, legend: legendFromBreaks(breaks, frames.legend.min, frames.legend.max, spec), k: breaks.length + 1, n: count }; }, [frames, idx, geometry, spec]); if (!frames || !years.length) return ; const summary = t('chart.summary.map', { name: spec.name ?? slug, year, n, min: formatValue(frames.legend.min, spec), max: formatValue(frames.legend.max, spec) }); const title = t('chart.map.legend', { name: spec.name ?? slug, year }); return (
{n === 0 ? : }
{years[0]}–{years[years.length - 1]} · {t('indicator.map.n', { n: grouped(n) })} {t('home.map.openExplorer')}

{t('indicator.map.legendNote')}

); }