fix: add download csv function

This commit is contained in:
riefive
2025-11-18 14:41:37 +07:00
parent a6ff2499ee
commit 7606afdac4
+72
View File
@@ -0,0 +1,72 @@
/**
* Download data as CSV file.
*
* @param headers - Array of header names. If omitted and data is array of objects, keys will be taken from first object.
* @param data - Array of rows. Each row can be either an object (key -> value) or an array of values.
* @param filename - optional file name to use for downloaded file
* @param delimiter - csv delimiter (default is comma)
* @param addBOM - add UTF-8 BOM to the file to make Excel detect UTF-8 correctly
*/
export function downloadCsv(
headers: string[] | null,
data: Array<Record<string, any> | 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 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 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<string, any>)[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 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);
}