// Formatters e helpers compartilhados via window globals. // Os dados de domínio (tenant, análises, créditos, etc.) vêm 100% da API. const fmtDateTime = new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }); const fmtDate = new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short' }); // fmtBRL e fmtNum são wrappers safe: se o valor for null/undefined/NaN // devolvem "—" em vez de "R$ NaN" / "NaN". Pydantic devolve Decimal como // string em alguns JSONs, então casteamos pra Number antes. const _bvf = new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }); const _nvf = new Intl.NumberFormat('pt-BR'); const _isNum = (v) => v !== null && v !== undefined && v !== '' && !Number.isNaN(Number(v)); const fmtBRL = { format: (v) => (_isNum(v) ? _bvf.format(Number(v)) : '—') }; const fmtNum = { format: (v) => (_isNum(v) ? _nvf.format(Number(v)) : '—') }; const fmtPct = (v) => (_isNum(v) ? new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 4, style: 'percent' }).format(Number(v)) : '—'); function relativeTime(iso) { const t = new Date(iso).getTime(); const now = Date.now(); const diff = Math.round((t - now) / 1000); const abs = Math.abs(diff); const rtf = new Intl.RelativeTimeFormat('pt-BR', { numeric: 'auto' }); if (abs < 60) return rtf.format(Math.round(diff), 'second'); if (abs < 3600) return rtf.format(Math.round(diff / 60), 'minute'); if (abs < 86400) return rtf.format(Math.round(diff / 3600), 'hour'); if (abs < 86400 * 30) return rtf.format(Math.round(diff / 86400), 'day'); return fmtDate.format(new Date(iso)); } function uuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } function shortId(id) { return id ? id.slice(0, 8) : ''; } Object.assign(window, { fmtDateTime, fmtDate, fmtBRL, fmtNum, fmtPct, relativeTime, uuid, shortId, });