/** Formatting helpers. All API timestamps are UTC ISO strings; we render UTC explicitly (orbital convention). */ const nf0 = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }); const nf1 = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1, minimumFractionDigits: 1 }); const nf2 = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2, minimumFractionDigits: 2 }); const nfCompact = new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1 }); export function fmtInt(v: number | string | null | undefined): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; return Number.isFinite(n) ? nf0.format(n) : '—'; } export function fmt1(v: number | string | null | undefined): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; return Number.isFinite(n) ? nf1.format(n) : '—'; } export function fmt2(v: number | string | null | undefined): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; return Number.isFinite(n) ? nf2.format(n) : '—'; } export function fmtCompact(v: number | string | null | undefined): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; return Number.isFinite(n) ? nfCompact.format(n) : '—'; } export function fmtKm(v: number | string | null | undefined, digits = 0): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; if (!Number.isFinite(n)) return '—'; return `${digits ? nf1.format(n) : nf0.format(n)} km`; } export function fmtDeg(v: number | string | null | undefined): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; return Number.isFinite(n) ? `${nf2.format(n)}°` : '—'; } export function fmtMinutes(v: number | string | null | undefined): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; if (!Number.isFinite(n)) return '—'; if (n >= 1440) return `${nf1.format(n / 1440)} d`; if (n >= 120) return `${nf1.format(n / 60)} h`; return `${nf1.format(n)} min`; } export function fmtPct(v: number | string | null | undefined, digits = 1): string { if (v === null || v === undefined || v === '') return '—'; const n = typeof v === 'string' ? Number(v) : v; return Number.isFinite(n) ? `${n.toFixed(digits)}%` : '—'; } export function fmtDate(v: string | null | undefined): string { if (!v) return '—'; const d = new Date(v.length === 10 ? `${v}T00:00:00Z` : v); if (Number.isNaN(d.getTime())) return '—'; return d.toLocaleDateString('en-GB', { year: 'numeric', month: 'short', day: '2-digit', timeZone: 'UTC' }); } export function fmtDateTime(v: string | null | undefined): string { if (!v) return '—'; const d = new Date(v); if (Number.isNaN(d.getTime())) return '—'; return `${d.toLocaleDateString('en-GB', { year: 'numeric', month: 'short', day: '2-digit', timeZone: 'UTC' })} ${d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' })} UTC`; } export function fmtAgo(v: string | null | undefined, now: number = Date.now()): string { if (!v) return 'unavailable'; const t = new Date(v).getTime(); if (Number.isNaN(t)) return 'unavailable'; const s = Math.max(0, Math.round((now - t) / 1000)); if (s < 60) return 'just now'; const m = Math.round(s / 60); if (m < 60) return `${m} min ago`; const h = Math.round(m / 60); if (h < 48) return `${h} h ago`; const d = Math.round(h / 24); if (d < 60) return `${d} d ago`; return fmtDate(v); } export function fmtYear(v: string | null | undefined): string { return v ? v.slice(0, 4) : '—'; } export function titleCase(s: string | null | undefined): string { if (!s) return '—'; return s.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); } export function num(v: number | string | null | undefined): number | null { if (v === null || v === undefined || v === '') return null; const n = typeof v === 'string' ? Number(v) : v; return Number.isFinite(n) ? n : null; }