'use client'; import { ArrowDown, ArrowUp } from 'lucide-react'; import Link from 'next/link'; import { useMemo, useState } from 'react'; import { t } from '@/i18n'; import { cn } from '@/lib/cn'; import { grouped } from '@/lib/format'; import { routes } from '@/lib/site'; import type { AdminCoverageCountry, AdminCoverageIndicator } from '@/lib/types-explore'; function useSort(rows: T[], initial: { key: keyof T & string; dir: 'asc' | 'desc' }) { const [sort, setSort] = useState<{ key: string; dir: 'asc' | 'desc' }>(initial); const sorted = useMemo(() => { const copy = [...rows]; const get = (r: T): unknown => (r as Record)[sort.key]; copy.sort((a, b) => { const va = get(a); const vb = get(b); if (va == null && vb == null) return 0; if (va == null) return 1; if (vb == null) return -1; const cmp = typeof va === 'number' && typeof vb === 'number' ? va - vb : String(va).localeCompare(String(vb)); return sort.dir === 'asc' ? cmp : -cmp; }); return copy; }, [rows, sort]); const toggle = (key: string) => setSort((s) => (s.key === key ? { key, dir: s.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'desc' })); return { sorted, sort, toggle }; } function Th({ k, label, sort, toggle, numeric }: { k: string; label: string; sort: { key: string; dir: 'asc' | 'desc' }; toggle: (k: string) => void; numeric?: boolean }) { const active = sort.key === k; return ( ); } /** Coverage matrix: indicators × countries (two sortable tables, filter box). */ export function CoverageTable({ indicators, countries }: { indicators: AdminCoverageIndicator[]; countries: AdminCoverageCountry[] }) { const [view, setView] = useState<'indicators' | 'countries'>('indicators'); const [q, setQ] = useState(''); const ql = q.trim().toLowerCase(); const ind = useSort(indicators.filter((r) => !ql || r.indicator_id.includes(ql) || (r.topic ?? '').includes(ql) || (r.primary_source_id ?? '').includes(ql)), { key: 'n_countries', dir: 'asc' }); const cty = useSort(countries.filter((r) => !ql || r.id.toLowerCase().includes(ql) || (r.short_name ?? '').toLowerCase().includes(ql)), { key: 'coverage_pct', dir: 'desc' }); return (
{(['indicators', 'countries'] as const).map((v) => ( ))}
setQ(e.target.value)} placeholder={t('search.placeholder.short')} aria-label={t('nav.search')} className="h-9 w-56 rounded-sm border border-rule bg-surface px-2 text-xs outline-none focus:border-accent" />
{view === 'indicators' ? ( {ind.sorted.map((r) => ( ))}
{r.indicator_id} {r.topic} {r.primary_source_id} {r.n_countries != null ? grouped(r.n_countries) : '—'} {r.n_latest != null ? grouped(r.n_latest) : '—'} {r.n_observations != null ? grouped(r.n_observations) : '—'} {r.first_year && r.last_year ? `${r.first_year}–${r.last_year}` : '—'} {r.latest_year ?? '—'} {r.latest_source_updated_at?.slice(0, 10) ?? '—'}
) : ( {cty.sorted.map((r) => ( ))}
{r.short_name ?? r.id} {r.id} {r.n_indicators != null ? grouped(r.n_indicators) : '—'} {r.n_observations != null ? grouped(r.n_observations) : '—'} {r.latest_year ?? '—'} {r.coverage_pct != null ? `${r.coverage_pct.toFixed(1)} %` : '—'}
)}
); }