@@ -5,9 +5,14 @@ import { useEffect, useState } from "react"
55import { Alert , AlertDescription , AlertTitle } from "~/components/ui/alert"
66import { Button } from "~/components/ui/button"
77
8- import { TableProps } from "./table "
8+ import { type Payment , type Project , type User } from "~/server/db/types "
99
10- export default function ExportButton ( { data } : TableProps ) {
10+ export interface ExportButtonProps {
11+ data : Array < Project | Payment | User >
12+ label : string
13+ }
14+
15+ export default function ExportButton ( { data, label } : ExportButtonProps ) {
1116 const [ open , setOpen ] = useState ( false )
1217 useEffect ( ( ) => {
1318 if ( ! open ) return
@@ -17,19 +22,44 @@ export default function ExportButton({ data }: TableProps) {
1722 const downloadCSV = ( ) => {
1823 if ( ! data || data . length === 0 ) {
1924 setOpen ( true )
20- console . log ( "open" )
2125 return
2226 }
2327
24- const headers = Object . keys ( data [ 0 ] as Record < string , unknown > ) . join ( "," )
25- const rows = data . map ( ( row ) => Object . values ( row ) . join ( "," ) ) . join ( "\n" )
26- const csv = headers + "\n" + rows
28+ const headers = Object . keys ( data [ 0 ] as Record < string , unknown > )
29+ const escapeCSV = ( value : unknown ) : string => {
30+ if ( value == null ) return ""
31+
32+ let str : string
33+ if ( typeof value === "object" ) {
34+ if ( value instanceof Date ) {
35+ str = value . toISOString ( )
36+ } else {
37+ // Stringify JSON objects/arrays
38+ str = JSON . stringify ( value )
39+ }
40+ } else {
41+ str = String ( value )
42+ }
43+
44+ // Escape CSV: wrap in quotes if contains comma, quote, or newline
45+ // and escape internal quotes by doubling them
46+ if ( / [ " , \n \r ] / . test ( str ) ) {
47+ return `"${ str . replace ( / " / g, '""' ) } "`
48+ }
49+ return str
50+ }
51+
52+ const rows = data
53+ . map ( ( row ) => headers . map ( ( key ) => escapeCSV ( ( row as Record < string , unknown > ) [ key ] ) ) . join ( "," ) )
54+ . join ( "\n" )
55+
56+ const csv = headers . join ( "," ) + "\n" + rows
2757 const blob = new Blob ( [ csv ] , { type : "text/csv" } )
2858 const url = window . URL . createObjectURL ( blob )
2959
3060 const a = document . createElement ( "a" )
3161 a . setAttribute ( "href" , url )
32- a . setAttribute ( "download" , "output .csv" )
62+ a . setAttribute ( "download" , ` ${ label } .csv` )
3363 a . click ( )
3464 window . URL . revokeObjectURL ( url )
3565 setOpen ( true )
@@ -38,7 +68,7 @@ export default function ExportButton({ data }: TableProps) {
3868 return (
3969 < div className = "relative inline-block" >
4070 < Button onClick = { downloadCSV } variant = { "outline" } >
41- Export CSV
71+ Export { label }
4272 </ Button >
4373 { open && ( ! data || data . length === 0 ) && (
4474 < Alert variant = "destructive" className = "fixed top-30 left-1/2 -translate-x-1/2 z-50 max-w-sm" >
@@ -49,7 +79,7 @@ export default function ExportButton({ data }: TableProps) {
4979 { open && data && data . length > 0 && (
5080 < Alert variant = "default" className = "fixed top-30 left-1/2 -translate-x-1/2 z-50 max-w-sm" >
5181 < AlertTitle > Export Successful</ AlertTitle >
52- < AlertDescription > Your data has been exported as `output .csv`.</ AlertDescription >
82+ < AlertDescription > Your data has been exported as { ` ${ label } .csv`} .</ AlertDescription >
5383 </ Alert >
5484 ) }
5585 </ div >
0 commit comments