/** * Generic CSV export utility. * Generates a CSV string from an array of objects and triggers a download. */ function escapeCsvValue(value: unknown): string { if (value == null) return ""; const str = String(value); // Wrap in quotes if it contains comma, quote, or newline if (str.includes(",") || str.includes('"') || str.includes("\n")) { return `"${str.replace(/"/g, '""')}"`; } return str; } interface CsvColumn { header: string; accessor: (row: T) => unknown; } export function generateCsv(rows: T[], columns: CsvColumn[]): string { const header = columns.map((c) => escapeCsvValue(c.header)).join(","); const body = rows .map((row) => columns.map((col) => escapeCsvValue(col.accessor(row))).join(",")) .join("\n"); return `${header}\n${body}`; } export function downloadCsv(csvContent: string, filename: string): void { const blob = new Blob(["\uFEFF" + csvContent], { 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(); document.body.removeChild(link); URL.revokeObjectURL(url); }