spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { HBars } from '@/components/charts/charts';4import { DEFAULT_METRIC, findMetric, METRICS, type Row } from '@/components/stats/rankings-config';5import { Chips, Derived, InlineRank, Note } from '@/components/stats/shared';6import { Container, PageHeader, Section } from '@/components/ui/section';7import { Unavailable } from '@/components/ui/unavailable';8import { api, safe } from '@/lib/api';9import { fmtAgo, fmtInt } from '@/lib/format';10import { routes, SITE_NAME, SITE_URL } from '@/lib/site';1112export const revalidate = 600;1314type Search = Promise<{ metric?: string | string[] }>;1516function pick(v: string | string[] | undefined): string | undefined {17 return Array.isArray(v) ? v[0] : v;18}1920export async function generateMetadata({ searchParams }: { searchParams: Search }): Promise<Metadata> {21 const m = findMetric(pick((await searchParams).metric) ?? DEFAULT_METRIC);22 const url = `${SITE_URL}${m.key === DEFAULT_METRIC ? routes.rankings() : routes.rankings(m.key)}`;23 const title = `${m.title} — Rankings`;24 return {25 title,26 description: m.description,27 alternates: { canonical: url },28 openGraph: { title: `${title} | ${SITE_NAME}`, description: m.description, url, type: 'website', siteName: SITE_NAME },29 twitter: { card: 'summary_large_image', title: `${title} | ${SITE_NAME}`, description: m.description },30 };31}3233export default async function RankingsPage({ searchParams }: { searchParams: Search }) {34 const metricKey = pick((await searchParams).metric) ?? DEFAULT_METRIC;35 const m = findMetric(metricKey);36 const res = await safe(api.rankings(m.key, 50));37 const rows: Row[] = res?.data.rows ?? [];38 const top = rows.slice(0, 10).map((r) => ({ label: m.name(r), value: m.primary(r), href: m.href(r) ?? undefined }));39 const chips = METRICS.map((x) => ({ href: x.key === DEFAULT_METRIC ? routes.rankings() : routes.rankings(x.key), label: x.label, active: x.key === m.key }));40 const hasDerived = m.columns.some((c) => c.derived);4142 return (43 <Container>44 <PageHeader eyebrow="Rankings" title={m.title} lede={m.description}>45 <div className="mt-6">46 <Chips items={chips} ariaLabel="Ranking metric" />47 </div>48 </PageHeader>4950 {m.warn && <Note tone="warn" className="mb-6">{m.warn}</Note>}5152 {!res ? (53 <div className="py-6">54 <Unavailable what="Ranking" />55 </div>56 ) : rows.length === 0 ? (57 <div className="py-6">58 <Unavailable what="Ranking rows" />59 </div>60 ) : (61 <>62 <Section eyebrow="Top 10" title={m.primaryLabel} className="pt-0 md:pt-0">63 <HBars data={top} valueFormat={m.primaryFormat ?? fmtInt} />64 </Section>6566 <Section eyebrow={`${fmtInt(rows.length)} rows`} title="Full ranking" className="pt-0 md:pt-0">67 <div className="overflow-x-hidden">68 <table className="data-table stack text-sm">69 <thead>70 <tr>71 {m.columns.map((c) => (72 <th key={c.key} className={c.num ? 'num' : undefined}>73 {c.label}74 {c.derived && <Derived className="ml-1.5 normal-case" />}75 </th>76 ))}77 </tr>78 </thead>79 <tbody>80 {rows.map((r, i) => (81 <tr key={`${m.key}-${i}`}>82 {m.columns.map((c, ci) => (83 <td key={c.key} data-label={ci === 0 ? undefined : c.label} className={[c.num ? 'num tnum' : '', ci === 0 ? 'primary' : ''].join(' ').trim() || undefined}>84 {ci === 0 ? (85 <div className="flex items-start">86 <InlineRank n={i + 1} />87 {c.render(r)}88 </div>89 ) : (90 c.render(r)91 )}92 </td>93 ))}94 </tr>95 ))}96 </tbody>97 </table>98 </div>99 {(m.note || hasDerived) && (100 <Note className="mt-4">101 {m.note ?? (102 <>103 Columns marked <Derived className="mx-1" /> are computed by SatelliteIndex.104 </>105 )}{' '}106 <Link href={routes.methodology()} className="text-accent hover:underline">Methodology</Link>107 </Note>108 )}109 <p className="mono mt-3 text-xs text-ink-3">Generated {fmtAgo(res.meta.generated_at)} · limit 50 · source: SatelliteIndex canonical catalogue (CelesTrak SATCAT + GP)</p>110 </Section>111 </>112 )}113 </Container>114 );115}116