spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { Ranking, RankingRow } from "@dci/core";2import { DASH, fmtCompact, fmtInt, fmtMw } from "@/lib/format";3import { entityHref } from "@/lib/routes";45export const SCOPE_LABEL: Record<Ranking["scope"], string> = {6 countries: "Countries",7 metros: "Markets",8 operators: "Operators",9 facilities: "Facilities",10};1112export const SCOPE_ORDER: Ranking["scope"][] = ["countries", "metros", "operators", "facilities"];1314/** Format a ranking value according to the ranking unit. */15export function fmtRankValue(v: number | null | undefined, unit: string): string {16 if (v === null || v === undefined || !Number.isFinite(v)) return DASH;17 const u = unit.toLowerCase();18 if (u === "mw") return fmtMw(v);19 if (u.includes("per") || u.includes("kw") || u.includes("$")) return `${fmtCompact(v, 2)} ${unit}`;20 return fmtInt(v);21}2223/** First sentence of the methodology text (for excerpts). */24export function firstSentence(text: string): string {25 const m = text.match(/^.*?[.!?](\s|$)/);26 return (m ? m[0] : text).trim();27}2829/** "Secondary: known MW." → "Known MW" */30export function secondaryLabel(methodology: string): string {31 const m = methodology.match(/Secondary:\s*([^.]+)\./i);32 if (!m) return "Secondary";33 const s = m[1]!.trim();34 return s.charAt(0).toUpperCase() + s.slice(1);35}3637/** Site href for a ranking row — the API already returns a site path; fall back to entityHref. */38export function rowHref(row: RankingRow, scope: Ranking["scope"]): string {39 if (row.href && row.href.startsWith("/")) return row.href;40 const type = scope === "countries" ? "country" : scope === "metros" ? "metro" : scope === "operators" ? "operator" : "facility";41 return entityHref(type, row.slug, row.name);42}43