From 2ccdb14999f336b5db1e7d14a749cfc47d65d670 Mon Sep 17 00:00:00 2001 From: riefive Date: Tue, 18 Nov 2025 14:43:14 +0700 Subject: [PATCH] fix: add comments --- app/lib/download.ts | 119 ++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/app/lib/download.ts b/app/lib/download.ts index d5df2dcd..03757d75 100644 --- a/app/lib/download.ts +++ b/app/lib/download.ts @@ -7,66 +7,75 @@ * @param delimiter - csv delimiter (default is comma) * @param addBOM - add UTF-8 BOM to the file to make Excel detect UTF-8 correctly */ +/* + Usage examples: + // 1) With headers and array of objects + // downloadCsv(['name', 'age'], [{name: 'Alice', age: 25}, {name: 'Bob', age: 30}], 'people.csv'); + // 2) Without headers (automatically uses object keys) + // downloadCsv(null, [{name: 'Alice', age: 25}, {name: 'Bob', age: 30}], 'people.csv'); + // 3) With array-of-arrays + // downloadCsv(['col1', 'col2'], [['a', 'b'], ['c', 'd']], 'matrix.csv'); + */ export function downloadCsv( - headers: string[] | null, - data: Array | any[]>, - filename = 'data.csv', - delimiter = ',', - addBOM = true, + headers: string[] | null, + data: Array | any[]>, + filename = 'data.csv', + delimiter = ',', + addBOM = true, ) { - if (!Array.isArray(data) || data.length === 0) { - // still create an empty CSV containing only headers - const csvHeader = headers ? headers.join(delimiter) : ''; - const csvString = addBOM ? '\uFEFF' + csvHeader : csvHeader; - const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.setAttribute('download', filename); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - return; - } + if (!Array.isArray(data) || data.length === 0) { + // still create an empty CSV containing only headers + const csvHeader = headers ? headers.join(delimiter) : '' + const csvString = addBOM ? '\uFEFF' + csvHeader : csvHeader + const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.setAttribute('download', filename) + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + return + } - // if headers not provided and rows are objects, take keys from first object - let _headers: string[] | null = headers; - if (!_headers) { - const firstRow = data[0]; - if (typeof firstRow === 'object' && !Array.isArray(firstRow)) { - _headers = Object.keys(firstRow); - } else if (Array.isArray(firstRow)) { - // if rows are arrays and no headers provided, we won't add header row - _headers = null; - } - } + // if headers not provided and rows are objects, take keys from first object + let _headers: string[] | null = headers + if (!_headers) { + const firstRow = data[0] + if (typeof firstRow === 'object' && !Array.isArray(firstRow)) { + _headers = Object.keys(firstRow) + } else if (Array.isArray(firstRow)) { + // if rows are arrays and no headers provided, we won't add header row + _headers = null + } + } - const escape = (val: unknown) => { - if (val === null || typeof val === 'undefined') return ''; - const str = String(val); - const needsQuoting = str.includes(delimiter) || str.includes('\n') || str.includes('\r') || str.includes('"'); - if (!needsQuoting) return str; - return '"' + str.replace(/"/g, '""') + '"'; - }; + const escape = (val: unknown) => { + if (val === null || typeof val === 'undefined') return '' + const str = String(val) + const needsQuoting = str.includes(delimiter) || str.includes('\n') || str.includes('\r') || str.includes('"') + if (!needsQuoting) return str + return '"' + str.replace(/"/g, '""') + '"' + } - const rows: string[] = data.map((row) => { - if (Array.isArray(row)) { - return row.map(escape).join(delimiter); - } - // object row - map using headers if available, otherwise use object values - if (_headers && Array.isArray(_headers)) { - return _headers.map(h => escape((row as Record)[h])).join(delimiter); - } - return Object.values(row).map(escape).join(delimiter); - }); + const rows: string[] = data.map((row) => { + if (Array.isArray(row)) { + return row.map(escape).join(delimiter) + } + // object row - map using headers if available, otherwise use object values + if (_headers && Array.isArray(_headers)) { + return _headers.map((h) => escape((row as Record)[h])).join(delimiter) + } + return Object.values(row).map(escape).join(delimiter) + }) - const headerRow = _headers ? _headers.join(delimiter) : null; - const csvString = (addBOM ? '\uFEFF' : '') + [headerRow, ...rows].filter(Boolean).join('\r\n'); + const headerRow = _headers ? _headers.join(delimiter) : null + const csvString = (addBOM ? '\uFEFF' : '') + [headerRow, ...rows].filter(Boolean).join('\r\n') - const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.setAttribute('download', filename); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.setAttribute('download', filename) + document.body.appendChild(link) + link.click() + document.body.removeChild(link) }