import type { Metadata } from 'next'; import Link from 'next/link'; import { t, tOpt } from '@/i18n'; import { ApiError, isNotBuilt } from '@/lib/api'; import { apiPlatform } from '@/lib/api-platform'; import { compact, formatDate, grouped } from '@/lib/format'; import { routes } from '@/lib/site'; import { topicById } from '@/lib/topics'; import type { UpdatesResponse } from '@/lib/types-analytics'; import { cn } from '@/lib/cn'; import { EmptyState, NotBuiltState } from '@/components/data/empty-state'; import { FreshnessBadge } from '@/components/data/freshness-badge'; import { Section } from '@/components/data/section'; import { PageHeader } from '@/components/explore/page-header'; export const revalidate = 600; export const metadata: Metadata = { title: t('updates.title'), description: t('updates.description'), alternates: { canonical: routes.updates() }, }; const STATUS_TONE: Record = { ok: 'border-up/40 text-up', partial: 'border-warn/40 text-warn', failed: 'border-down/40 text-down', stale: 'border-warn/40 text-warn', unknown: 'border-rule text-ink-3', }; export default async function UpdatesPage() { let data: UpdatesResponse | null = null; let missing = false; try { data = await apiPlatform.updates(); } catch (e) { if (isNotBuilt(e)) return ; if (e instanceof ApiError && e.status === 404) missing = true; else throw e; } const now = Date.parse(data?.meta.generated_at ?? '') || Date.now(); return ( <> {missing || !data ? ( ) : ( <> {/* Snapshot ticker */}
{[ [t('updates.snapshot.built'), formatDate(data.snapshot.built_at), null], [t('updates.snapshot.observations'), compact(data.snapshot.observations), null], [t('updates.snapshot.indicators'), grouped(data.snapshot.indicators), null], [t('updates.snapshot.countries'), grouped(data.snapshot.countries), null], [t('updates.snapshot.changed'), grouped(data.snapshot.values_changed), t('updates.snapshot.changedHint')], ].map(([k, v, hint]) => (
{k}
{v}
))}
{/* Desktop table */} {data.sources.map((s) => ( ))}
{t('updates.col.source')} {t('updates.col.status')} {t('updates.col.lastImport')} {t('updates.col.vintage')} {t('updates.col.datasets')} {t('updates.col.indicators')} {t('updates.col.observations')} {t('updates.col.latestYear')} {t('updates.col.changed')} {t('updates.col.countries')}
{s.source.name ?? s.source.id} {s.source.organization} {tOpt(`updates.status.${s.status}`, s.status)} {formatDate(s.last_success_at ?? s.last_retrieved_at)} {formatDate(s.source_updated_at)} {grouped(s.n_datasets)} {grouped(s.n_indicators)} {compact(s.n_observations)} {s.latest_year ?? t('common.na')} {grouped(s.values_changed)} {grouped(s.countries_affected)}
{/* Phone: one block per source */}
    {data.sources.map((s) => (
  • {s.source.name ?? s.source.id} {tOpt(`updates.status.${s.status}`, s.status)}
    {t('updates.col.lastImport')}
    {formatDate(s.last_success_at ?? s.last_retrieved_at)}
    {t('updates.col.vintage')}
    {formatDate(s.source_updated_at)}
    {t('updates.col.observations')}
    {compact(s.n_observations)}
    {t('updates.col.latestYear')}
    {s.latest_year ?? t('common.na')}
    {t('updates.col.changed')}
    {grouped(s.values_changed)}
    {t('updates.col.countries')}
    {grouped(s.countries_affected)}
  • ))}

{t('updates.schedule')}

{data.recent_runs.length === 0 ? (

{t('updates.none')}

) : (
    {data.recent_runs.map((r, i) => (
  1. {tOpt(`source.run.${r.status ?? 'unknown'}`, r.status ?? '')} {r.connector} · {r.dataset} {r.message ? {r.message} : null} {r.rows_valid != null ? `${compact(r.rows_valid)} ${t('updates.col.rows').toLowerCase()}` : ''} {r.warnings ? ` · ${r.warnings} ⚠` : ''} {r.errors ? ` · ${r.errors} ✕` : ''} {formatDate(r.finished_at ?? r.started_at)}
  2. ))}
)}
    {data.indicators_recently_updated.slice(0, 15).map((ind) => (
  • {ind.name ?? ind.slug} {topicById(ind.topic ?? '')?.short ?? ind.topic} {ind.primary_source_id ? ` · ${ind.primary_source_id}` : ''} {formatDate(ind.latest_source_updated_at)} {ind.last_year ? → {ind.last_year} : null}
  • ))}
)} ); }