spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { t, tOpt } from '@/i18n';4import { isNotBuilt, safe } from '@/lib/api';5import { apiExplore } from '@/lib/api-explore';6import { compact, formatValue, grouped, isNum } from '@/lib/format';7import { routes } from '@/lib/site';8import type { RegionItem, RegionResponse } from '@/lib/types-explore';9import { NotBuiltState } from '@/components/data/empty-state';10import { Section } from '@/components/data/section';11import { PageHeader } from '@/components/explore/page-header';1213export const revalidate = 900;1415export const metadata: Metadata = {16 title: t('regions.title'),17 description: t('regions.sub'),18 alternates: { canonical: routes.regions() },19};2021const KIND_ORDER = ['world', 'region', 'continent', 'income', 'org', 'custom'] as const;2223export default async function RegionsPage() {24 let items: RegionItem[] = [];25 let notBuilt = false;26 try {27 items = (await apiExplore.regions()).items;28 } catch (e) {29 if (isNotBuilt(e)) notBuilt = true;30 else throw e;31 }32 if (notBuilt) {33 return (34 <>35 <PageHeader title={t('regions.title')} lede={t('regions.sub')} />36 <NotBuiltState />37 </>38 );39 }40 // Third figure (median life expectancy) comes from the group detail — fetched in parallel, cached by ISR.41 const details = await Promise.all(items.map((g) => safe(apiExplore.region(g.slug ?? g.id))));42 const detailById = new Map<string, RegionResponse | null>(items.map((g, i) => [g.id, details[i] ?? null]));4344 const groups = KIND_ORDER.map((kind) => ({ kind, rows: items.filter((g) => (g.kind ?? 'custom') === kind) })).filter((s) => s.rows.length);45 const other = items.filter((g) => !KIND_ORDER.includes((g.kind ?? 'custom') as (typeof KIND_ORDER)[number]));46 if (other.length) groups.push({ kind: 'custom', rows: other });4748 return (49 <>50 <PageHeader title={t('regions.title')} lede={t('regions.sub')} meta={`${grouped(items.length)} groups`} />51 {groups.map(({ kind, rows }, gi) => (52 <Section key={kind} id={kind} title={tOpt(`regions.kind.${kind}`, kind)} className={gi === 0 ? 'border-t-0' : undefined} tight={kind === 'world'}>53 <ul className="grid gap-x-8 sm:grid-cols-2 lg:grid-cols-3">54 {rows.map((g) => {55 const d = detailById.get(g.id);56 const le = d?.aggregates['life-expectancy'];57 const gdppc = d?.aggregates['gdp-per-capita'];58 return (59 <li key={g.id} className="border-t border-rule">60 <Link href={routes.region(g.slug ?? g.id)} className="group flex min-h-[64px] flex-col justify-center py-3">61 <span className="flex items-baseline justify-between gap-3">62 <span className="text-sm font-semibold text-ink group-hover:text-accent">{g.name}</span>63 <span className="tnum shrink-0 text-xs text-ink-3">{t('regions.members', { n: g.n_members ?? d?.n_members ?? 0 })}</span>64 </span>65 <dl className="tnum mt-1 grid grid-cols-3 gap-x-3 text-xs">66 <div className="min-w-0">67 <dt className="text-2xs leading-tight text-ink-3">68 {t('regions.population')} <span className="text-ink-3/80">({t('regions.sum')})</span>69 </dt>70 <dd className="truncate text-ink">{isNum(g.population_latest) ? compact(g.population_latest) : t('common.na')}</dd>71 </div>72 <div className="min-w-0">73 <dt className="text-2xs leading-tight text-ink-3">74 {t('regions.gdp')} <span className="text-ink-3/80">({t('regions.sum')})</span>75 </dt>76 <dd className="truncate text-ink">{isNum(g.gdp_latest) ? `US$${compact(g.gdp_latest)}` : t('common.na')}</dd>77 </div>78 <div className="min-w-0">79 <dt className="text-2xs leading-tight text-ink-3">80 {le ? `${t('regions.lifeExpectancy')} (${t('regions.median')})` : gdppc ? `${t('common.gdpPerCapita')} (${t('regions.weighted_mean')})` : t('regions.lifeExpectancy')}81 </dt>82 <dd className="truncate text-ink">{le ? formatValue(le.value, le.indicator) : gdppc ? formatValue(gdppc.value, gdppc.indicator) : t('common.na')}</dd>83 </div>84 </dl>85 </Link>86 </li>87 );88 })}89 </ul>90 </Section>91 ))}92 </>93 );94}95