export type CsvColumn = { title: string; value: (row: T) => unknown; }; function csvCell(value: unknown) { if (value == null) return ''; const text = typeof value === 'object' ? JSON.stringify(value) : String(value); return `"${text.replace(/"/g, '""')}"`; } export function buildCsv(columns: CsvColumn[], rows: T[]) { return [ columns.map((item) => csvCell(item.title)).join(','), ...rows.map((row) => columns.map((column) => csvCell(column.value(row))).join(',')) ].join('\n'); } export function downloadCsv(filename: string, csv: string) { const blob = new Blob(['\uFEFF', csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); }