/** * @file exportData.ts * @description Client-side "Download CSV / JSON" for list pages (Sessions, * Activity, a session's events): `toCsv` turns rows into RFC 4180 CSV (and * neutralizes spreadsheet formulas), `downloadText` saves a string as a file. * @author Nguyễn Ngọc Trí Vĩ */ /** One CSV cell: quoted when needed; a leading =, +, - or @ is prefixed with `'` * so a spreadsheet never runs a value from a session as a formula. */ function cell(value: unknown): string { if (value == null) return ""; let s = typeof value === "object" ? JSON.stringify(value) : String(value); if (typeof value === "string" && /^[=+\-@\t\r]/.test(s)) s = `'${s}`; return /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; } export function toCsv( rows: readonly T[], columns: readonly (keyof T & string)[] ) { const lines = [columns.join(",")]; for (const row of rows) lines.push(columns.map((c) => cell(row[c])).join(",")); return lines.join("\r\n") + "\r\n"; } export function downloadText(filename: string, text: string, mime: string): void { const url = URL.createObjectURL(new Blob([text], { type: mime })); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } /** `name-YYYY-MM-DD.ext` */ export function exportName(name: string, ext: "csv" | "json"): string { return `${name}-${new Date().toISOString().slice(0, 10)}.${ext}`; }