const money = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0, }); const decimal = new Intl.NumberFormat('en-GB', { maximumFractionDigits: 1 }); export const formatMoney = (value: number): string => money.format(value); export const formatDecimal = (value: number): string => decimal.format(value); /** A shipment carries its own currency, so the symbol is not assumed to be €. */ export function formatAmount(value: number, currency: string): string { try { return new Intl.NumberFormat('en-GB', { style: 'currency', currency, maximumFractionDigits: 2, }).format(value); } catch { return `${formatNumber(value)} ${currency}`; } } export function formatNumber(value: number, maximumFractionDigits = 2): string { return new Intl.NumberFormat('en-GB', { maximumFractionDigits }).format(value); } /** The backend already scores out of 100 — `recompute.py` multiplies by 100.0. */ export const formatScore = (score: number): string => `${formatNumber(score, 1)}%`; /** `2026-09-22` as `22.09.2026`; anything unparseable is returned untouched. */ export function formatDate(value: string | null | undefined): string { if (!value) return '—'; const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value); return match ? `${match[3]}.${match[2]}.${match[1]}` : value; } /** `22.09.2026` (or with `/` or `-`) as `2026-09-22`; null unless it is a real day. */ export function parseDate(text: string): string | null { const match = /^(\d{1,2})[./-](\d{1,2})[./-](\d{4})$/.exec(text.trim()); if (!match) return null; const [day, month, year] = [Number(match[1]), Number(match[2]), Number(match[3])]; const date = new Date(year, month - 1, day); if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) { return null; } return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; } /** The local calendar day — `toISOString` would give the UTC one, which in * Europe is still yesterday for the first hour or two after midnight. */ export function today(): string { const now = new Date(); return [ now.getFullYear(), String(now.getMonth() + 1).padStart(2, '0'), String(now.getDate()).padStart(2, '0'), ].join('-'); } /** The same rule as `normalize_header` in `importer/utils.py`, so a column * header matches here exactly when it matches on the server. */ export const normalizeHeader = (value: string): string => value.trim().toLowerCase().replace(/[^a-z0-9]/g, ''); /** `6109100010` as `6109 10 00 10`, the way a declarant reads it. */ export function formatCode(code: string | null | undefined): string { if (!code) return ''; const digits = code.replace(/\D/g, ''); if (digits.length < 6) return code; return [digits.slice(0, 4), digits.slice(4, 6), digits.slice(6, 8), digits.slice(8, 10)] .filter(Boolean) .join(' '); }