'use client'; import { Check, Code2, Download, ExternalLink } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { t } from '@/i18n'; import { displayValue, formatDate, formatPeriod } from '@/lib/format'; import { routes } from '@/lib/site'; import type { ObservationStatus } from '@/lib/types'; import { BottomSheet } from './bottom-sheet'; import { StatusBadge } from './freshness-badge'; import { useProvenance } from './provenance-context'; import { QualityBadges, QualityStrip, deriveBadges } from './quality-badge'; /** * Global provenance sheet 2.0: mounted once in the root layout; opened by , chart source lines and any * component calling `useProvenance().open(payload)`. Layout: value block (indicator · country · period · value), * SOURCE / DATASET / SERIES / SOURCE UPDATED / RETRIEVED / LICENCE, transform + status, quality badges, actions * [Open original] [API] [Download series] [Indicator page]. */ export function ProvenanceSheet() { const { payload, close } = useProvenance(); const open = !!payload; const ind = payload?.indicator; const v = payload?.value ?? null; const p = v?.provenance ?? null; const country = payload?.country ?? null; const download = payload?.downloadHref ?? (country ? routes.countryDownload(country.id) : ind ? routes.indicatorDownload(ind.slug) : null); const apiPath = ind ? (country ? `/api/v1/countries/${country.id}/series/${ind.slug}` : `/api/v1/indicators/${ind.slug}`) : null; const [copied, setCopied] = useState(false); useEffect(() => { if (!open) setCopied(false); }, [open]); const copyApi = async () => { if (!apiPath) return; const url = `${window.location.origin}${apiPath}`; try { await navigator.clipboard.writeText(url); setCopied(true); setTimeout(() => setCopied(false), 1800); } catch { /* clipboard unavailable — the link below still opens the docs */ } }; const badges = payload?.quality?.badges ?? (v ? deriveBadges({ latestYear: v.year ?? null, firstYear: payload?.quality?.firstYear ?? null, points: payload?.quality?.points ?? null, hasForecast: v.is_forecast, referenceYear: payload?.quality?.referenceYear ?? null }) : []); return ( {t('prov.title')}}> {payload && ind ? (
{/* Value block */}
{ind.name}
{country ? (

{country.flag ? {country.flag} : null} {country.name}

) : (

{t('common.world')}

)} {v ? (
{displayValue(v.value, ind, v.formatted)} {formatPeriod(v.period, ind.frequency)} {v.status ? : null} {v.is_forecast ? {t('common.forecast')} : null} {v.is_estimate ? {t('common.estimate')} : null}
) : (

{t('prov.noValue')}

)} {v?.is_forecast ?

{t('prov.status.forecastNote')}

: null}
{badges.length || payload.quality ? (
{t('prov.quality')}
{payload.quality ? : }

{t('quality.noScore')}

) : null} {ind.methodology || ind.description ? (
{ind.methodology ? t('prov.methodology') : t('prov.definition')}

{ind.methodology ?? ind.description}

) : null}
{p?.url ? ( {t('prov.openSource')} ) : null} {apiPath ? ( void copyApi()} className="tap inline-flex items-center gap-1.5 rounded-sm border border-rule px-3 text-sm hover:bg-surface-2" title={t('prov.apiHint')} aria-live="polite"> {copied ? : } {copied ? t('prov.copiedApi') : t('prov.copyApi')} ) : null} {download ? ( {t('prov.downloadSeries')} ) : null} {t('prov.indicatorPage')} →
{apiPath ? {apiPath} : null}
) : null}
); } function Row({ k, v, mono, strong }: { k: string; v: string; mono?: boolean; strong?: boolean }) { return (
{k}
{v}
); }