import * as XLSX from 'xlsx' import { ColumnConfig } from '../types' export type ExportFormat = 'csv' | 'excel' export interface ExportOptions { filename: string format: ExportFormat includeHeaders?: boolean dateFormat?: string } /** * Extracts the value from a row using column configuration */ function getColumnValue(row: TData, column: ColumnConfig): any { if (column.accessorFn) { return column.accessorFn(row) } if (column.accessorKey) { const keys = column.accessorKey.split('.') let value: any = row for (const key of keys) { value = value?.[key] } return value } return '' } /** * Formats a value for export (handles dates, objects, arrays) */ function formatValueForExport(value: any): string { if (value == null) { return '' } if (value instanceof Date) { return value.toISOString() } if (typeof value === 'object') { return JSON.stringify(value) } if (typeof value === 'boolean') { return value ? 'Yes' : 'No' } return String(value) } /** * Gets the header text for a column */ function getColumnHeader(column: ColumnConfig): string { if (typeof column.header === 'string') { return column.header } return column.id } /** * Prepares data for export based on visible columns */ function prepareExportData( data: TData[], columns: ColumnConfig[], includeHeaders: boolean = true ): string[][] { const rows: string[][] = [] // Add headers if (includeHeaders) { rows.push(columns.map(getColumnHeader)) } // Add data rows for (const row of data) { const rowData: string[] = [] for (const column of columns) { const value = getColumnValue(row, column) rowData.push(formatValueForExport(value)) } rows.push(rowData) } return rows } /** * Converts array data to CSV string */ function arrayToCSV(data: string[][]): string { return data .map(row => row .map(cell => { // Escape quotes and wrap in quotes if contains comma, quote, or newline const needsQuotes = /[,"\n\r]/.test(cell) if (needsQuotes) { return `"${cell.replace(/"/g, '""')}"` } return cell }) .join(',') ) .join('\n') } /** * Triggers a download of the given content */ function downloadFile(content: string | Blob, filename: string, mimeType: string): void { const blob = content instanceof Blob ? content : new Blob([content], { type: mimeType }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } /** * Exports data to CSV format * @param data Array of data to export * @param columns Visible columns configuration * @param filename Name of the file to download (without extension) * @param includeHeaders Whether to include header row (default: true) */ export function exportToCSV( data: TData[], columns: ColumnConfig[], filename: string, includeHeaders: boolean = true ): void { if (!data || data.length === 0) { throw new Error('No data to export') } if (!columns || columns.length === 0) { throw new Error('No columns specified for export') } const exportData = prepareExportData(data, columns, includeHeaders) const csv = arrayToCSV(exportData) downloadFile(csv, `${filename}.csv`, 'text/csv;charset=utf-8;') } /** * Exports data to Excel format (XLSX) * @param data Array of data to export * @param columns Visible columns configuration * @param filename Name of the file to download (without extension) * @param includeHeaders Whether to include header row (default: true) */ export function exportToExcel( data: TData[], columns: ColumnConfig[], filename: string, includeHeaders: boolean = true ): void { if (!data || data.length === 0) { throw new Error('No data to export') } if (!columns || columns.length === 0) { throw new Error('No columns specified for export') } const exportData = prepareExportData(data, columns, includeHeaders) // Create workbook and worksheet const ws = XLSX.utils.aoa_to_sheet(exportData) const wb = XLSX.utils.book_new() XLSX.utils.book_append_sheet(wb, ws, 'Data') // Auto-size columns const columnWidths = columns.map((column, idx) => { const header = getColumnHeader(column) const maxWidth = exportData.reduce((max, row) => { const cellValue = row[idx] || '' return Math.max(max, cellValue.length) }, header.length) return { wch: Math.min(maxWidth + 2, 50) } // Max width of 50 chars }) ws['!cols'] = columnWidths // Generate Excel file and trigger download const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }) const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) downloadFile(blob, `${filename}.xlsx`, blob.type) } /** * Generic export function that routes to CSV or Excel based on format * @param data Array of data to export * @param columns Visible columns configuration * @param options Export options including format and filename */ export function exportData( data: TData[], columns: ColumnConfig[], options: ExportOptions ): void { const { format, filename, includeHeaders = true } = options switch (format) { case 'csv': exportToCSV(data, columns, filename, includeHeaders) break case 'excel': exportToExcel(data, columns, filename, includeHeaders) break default: throw new Error(`Unsupported export format: ${format}`) } } /** * Helper to generate a filename with timestamp */ export function generateExportFilename(baseFilename: string): string { const now = new Date() const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, -5) return `${baseFilename}_${timestamp}` }