42 lines
1.3 KiB
TypeScript
42 lines
1.3 KiB
TypeScript
/**
|
|
* Reusable CSV export utilities.
|
|
*
|
|
* Usage:
|
|
* const csv = buildCSV(headers, rows);
|
|
* downloadCSV(csv, "my-export");
|
|
*/
|
|
|
|
/** Escape a cell value for CSV (RFC 4180). */
|
|
function escapeCell(value: unknown): string {
|
|
const str = String(value ?? "");
|
|
return `"${str.replace(/"/g, '""')}"`;
|
|
}
|
|
|
|
/**
|
|
* Build a CSV string from headers and rows.
|
|
*
|
|
* Each row is an array of cell values (string | number | boolean | null | undefined).
|
|
*/
|
|
export function buildCSV(headers: string[], rows: unknown[][]): string {
|
|
return [headers, ...rows].map((row) => row.map(escapeCell).join(",")).join("\n");
|
|
}
|
|
|
|
/**
|
|
* Trigger a browser download of a CSV string.
|
|
*
|
|
* @param content The CSV string content
|
|
* @param filename Base filename without extension (date suffix is appended automatically)
|
|
*/
|
|
export function downloadCSV(content: string, filename: string): void {
|
|
const blob = new Blob([content], { type: "text/csv;charset=utf-8;" });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
const now = new Date();
|
|
const dateStamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
link.download = `${filename}-${dateStamp}.csv`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
} |