Files
setrip/lib/csv.ts
T

68 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* CSV helpers untuk admin export. Simple string-building — bukan streaming —
* karena admin export jarang lebih dari 10k row di MVP.
*
* Escape rule (RFC 4180):
* - Field yang berisi koma, quote, CR, atau LF → bungkus quote, escape quote
* internal dengan dobel quote.
* - Field lain biarkan apa adanya.
*/
/** Escape satu cell sesuai aturan RFC 4180. */
export function escapeCsvCell(value: unknown): string {
if (value == null) return "";
const str = String(value);
if (/[",\r\n]/.test(str)) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
}
/**
* Bangun string CSV lengkap dari headers + rows. Pakai CRLF (RFC 4180) supaya
* Excel di Windows happy.
*/
export function buildCsv(headers: string[], rows: unknown[][]): string {
const lines = [headers.map(escapeCsvCell).join(",")];
for (const row of rows) {
lines.push(row.map(escapeCsvCell).join(","));
}
return lines.join("\r\n") + "\r\n";
}
/**
* Bikin Response CSV siap pakai dari Next route handler.
* BOM ditambahkan supaya Excel auto-detect UTF-8 untuk karakter non-ASCII
* (mis. nama Indonesia dengan diakritik).
*/
export function csvResponse(filename: string, csv: string): Response {
const bom = "";
return new Response(bom + csv, {
status: 200,
headers: {
"Content-Type": "text/csv; charset=utf-8",
"Content-Disposition": `attachment; filename="${filename}"`,
"Cache-Control": "no-store",
},
});
}
/** Format ISO untuk CSV (UTC, sortable). */
export function csvDate(d: Date | null | undefined): string {
if (!d) return "";
return d.toISOString();
}
/** Tanggal Jakarta yang readable di Excel. */
export function csvDateJakarta(d: Date | null | undefined): string {
if (!d) return "";
return d.toLocaleString("id-ID", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
timeZone: "Asia/Jakarta",
});
}