export const CSV_SEPARATOR = ';' function formatCsvNumber(value) { if (typeof value !== 'number' || !Number.isFinite(value)) return '' const normalized = Object.is(value, -0) ? 0 : value return normalized.toLocaleString('pt-BR', { useGrouping: false, maximumFractionDigits: 15, }) } export function toCsvCell(value, separator = CSV_SEPARATOR) { let text = '' if (typeof value === 'number') { text = formatCsvNumber(value) } else if (value instanceof Date) { text = Number.isNaN(value.getTime()) ? '' : value.toISOString() } else { text = String(value ?? '') } const escaped = text.replace(/"/g, '""') if (escaped.includes('"') || escaped.includes('\n') || escaped.includes('\r') || escaped.includes(separator)) { return `"${escaped}"` } return escaped } export function buildCsvBlob(columns, rows, separator = CSV_SEPARATOR) { if (!Array.isArray(columns) || columns.length === 0) return null const header = columns.map((col) => toCsvCell(col, separator)).join(separator) const lines = [header] ;(rows || []).forEach((row) => { const values = columns.map((col, index) => { if (Array.isArray(row)) return toCsvCell(row[index], separator) return toCsvCell(row?.[col], separator) }) lines.push(values.join(separator)) }) const csv = `\uFEFF${lines.join('\n')}` return new Blob([csv], { type: 'text/csv;charset=utf-8;' }) } export function tableToCsvBlob(table, separator = CSV_SEPARATOR) { if (!table || !Array.isArray(table.columns) || !Array.isArray(table.rows) || table.columns.length === 0) return null return buildCsvBlob(table.columns, table.rows, separator) }