'use client' import { useState, useEffect, useCallback } from 'react' import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Download, Loader2 } from 'lucide-react' /** * Converts a camelCase or snake_case column name to Title Case. * e.g. "projectTitle" -> "Project Title", "ai_meetsCriteria" -> "Ai Meets Criteria" */ function formatColumnName(col: string): string { // Replace underscores with spaces let result = col.replace(/_/g, ' ') // Insert space before uppercase letters (camelCase -> spaced) result = result.replace(/([a-z])([A-Z])/g, '$1 $2') // Capitalize first letter of each word return result .split(' ') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ') } type ExportData = { data: Record[] columns: string[] } type CsvExportDialogProps = { open: boolean onOpenChange: (open: boolean) => void exportData: ExportData | undefined isLoading: boolean filename: string onRequestData: () => Promise } export function CsvExportDialog({ open, onOpenChange, exportData, isLoading, filename, onRequestData, }: CsvExportDialogProps) { const [selectedColumns, setSelectedColumns] = useState>(new Set()) const [dataLoaded, setDataLoaded] = useState(false) // When dialog opens, fetch data if not already loaded useEffect(() => { if (open && !dataLoaded) { onRequestData().then((result) => { if (result?.columns) { setSelectedColumns(new Set(result.columns)) } setDataLoaded(true) }) } }, [open, dataLoaded, onRequestData]) // Sync selected columns when export data changes useEffect(() => { if (exportData?.columns) { setSelectedColumns(new Set(exportData.columns)) } }, [exportData]) // Reset state when dialog closes useEffect(() => { if (!open) { setDataLoaded(false) } }, [open]) const toggleColumn = (col: string, checked: boolean) => { const next = new Set(selectedColumns) if (checked) { next.add(col) } else { next.delete(col) } setSelectedColumns(next) } const toggleAll = () => { if (!exportData) return if (selectedColumns.size === exportData.columns.length) { setSelectedColumns(new Set()) } else { setSelectedColumns(new Set(exportData.columns)) } } const handleDownload = () => { if (!exportData) return const columnsArray = exportData.columns.filter((col) => selectedColumns.has(col)) // Build CSV header with formatted names const csvHeader = columnsArray.map((col) => { const formatted = formatColumnName(col) // Escape quotes in header if (formatted.includes(',') || formatted.includes('"')) { return `"${formatted.replace(/"/g, '""')}"` } return formatted }) const csvContent = [ csvHeader.join(','), ...exportData.data.map((row) => columnsArray .map((col) => { const value = row[col] if (value === null || value === undefined) return '' const str = String(value) if (str.includes(',') || str.includes('"') || str.includes('\n')) { return `"${str.replace(/"/g, '""')}"` } return str }) .join(',') ), ].join('\n') const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = `${filename}-${new Date().toISOString().split('T')[0]}.csv` document.body.appendChild(link) link.click() document.body.removeChild(link) // Delay revoking to ensure download starts before URL is invalidated setTimeout(() => URL.revokeObjectURL(url), 1000) onOpenChange(false) } const allSelected = exportData ? selectedColumns.size === exportData.columns.length : false const noneSelected = selectedColumns.size === 0 return ( Export CSV Select which columns to include in the export {isLoading ? (
Loading data...
) : exportData ? (
{exportData.columns.map((col) => (
toggleColumn(col, !!checked)} />
))}

{exportData.data.length} row{exportData.data.length !== 1 ? 's' : ''} will be exported

) : (

No data available for export.

)}
) }