SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
4.6 KB · 95 lines tsx
Raw Blame History
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