// Currency formatting for the client, mirroring the server's utils/money.js. // // The API sends money in two units: bill / summary values are serialized as // DOLLARS (the server calls fromCents before responding), while raw bank // transaction amounts arrive as integer CENTS. So there are two entry points — // formatUSD(dollars) and formatCentsUSD(cents) — matching the server's // formatUSD / formatCentsUSD split. USD / en-US throughout (the app is USD-only, // and this matches the server). Inputs are coerced defensively so null, '', // undefined, or NaN never render as "$NaN". const DASH = '—'; function toNumber(value) { const n = Number(value); if (!Number.isFinite(n)) return 0; return n === 0 ? 0 : n; // normalize -0 → +0 so it never renders as "-$0.00" } function isBlank(value) { return value === null || value === undefined || value === ''; } /** * Format a DOLLAR amount → "$1,234.56". * @param {number|string|null} dollars * @param {{ whole?: boolean, dash?: boolean }} [opts] * whole — drop the cents ("$1,235"); dash — render blank input as "—" not "$0.00". */ export function formatUSD(dollars, { whole = false, dash = false } = {}) { if (dash && isBlank(dollars)) return DASH; return toNumber(dollars).toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: whole ? 0 : 2, maximumFractionDigits: whole ? 0 : 2, }); } /** Whole-dollar convenience → "$1,235". */ export function formatUSDWhole(dollars, opts = {}) { return formatUSD(dollars, { ...opts, whole: true }); } /** * Format an integer-CENTS amount (e.g. a bank transaction) → "$12.34". * @param {number|string|null} cents * @param {{ signed?: boolean, dash?: boolean, currency?: string }} [opts] * signed — prefix "+"/"-" (income vs expense); dash — blank input → "—"; * currency — ISO code (defaults USD). */ export function formatCentsUSD(cents, { signed = false, dash = false, currency = 'USD' } = {}) { if (dash && isBlank(cents)) return DASH; const c = toNumber(cents); const body = (Math.abs(c) / 100).toLocaleString('en-US', { style: 'currency', currency: currency || 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2, }); if (signed) return (c < 0 ? '-' : '+') + body; return (c < 0 ? '-' : '') + body; }