/** * Global Table Import/Export Utility * Handles CSV/JSON export and import for any table-based page * * Usage: * ```typescript * import { useTableImportExport } from '../../utils/table-import-export'; * * const { handleExport, handleImport } = useTableImportExport({ * endpoint: '/v1/planner/keywords/export/', * filename: 'keywords', * columns: columns, // Column config from TablePageTemplate * filters: filterValues, // Current filter values * }); * ``` */ import { API_BASE_URL } from '../services/api'; export interface ExportConfig { endpoint: string; filename: string; format?: 'csv' | 'json'; columns?: Array<{ key: string; label: string }>; filters?: Record; } export interface ImportConfig { endpoint: string; acceptedFormats?: string[]; maxFileSize?: number; queryParams?: Record; // Additional query params (e.g., site_id, sector_id) onSuccess?: (result: any) => void; onError?: (error: Error) => void; } /** * Build export URL with filters as query parameters */ export const buildExportUrl = (config: ExportConfig): string => { const params = new URLSearchParams(); // Add filters as query params if (config.filters) { Object.entries(config.filters).forEach(([key, value]) => { if (value !== '' && value !== null && value !== undefined) { params.append(key, String(value)); } }); } const queryString = params.toString(); const endpoint = config.endpoint.endsWith('/') ? config.endpoint : `${config.endpoint}/`; const fullUrl = `${API_BASE_URL}${endpoint}${queryString ? `?${queryString}` : ''}`; return fullUrl; }; /** * Export table data to CSV or JSON */ export const exportTableData = async ( config: ExportConfig, onProgress?: (progress: string) => void, onError?: (error: Error) => void ): Promise => { const format = config.format || 'csv'; const url = buildExportUrl(config); onProgress?.(`Exporting ${format.toUpperCase()}...`); try { const response = await fetch(url, { method: 'GET', credentials: 'include', }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Export failed: ${response.statusText} - ${errorText}`); } const blob = await response.blob(); const downloadUrl = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = downloadUrl; link.download = `${config.filename}.${format}`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(downloadUrl); onProgress?.(`Export successful: ${config.filename}.${format}`); } catch (error) { const err = error instanceof Error ? error : new Error('Export failed'); onError?.(err); throw err; } }; /** * Import table data from file */ export const importTableData = async ( file: File, config: ImportConfig, onProgress?: (progress: string) => void, onError?: (error: Error) => void ): Promise => { // Validate file format const acceptedFormats = config.acceptedFormats || ['.csv']; const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase(); if (!acceptedFormats.includes(fileExtension)) { const error = new Error( `Invalid file format. Accepted formats: ${acceptedFormats.join(', ')}` ); onError?.(error); throw error; } // Validate file size const maxSize = config.maxFileSize || 5 * 1024 * 1024; // 5MB default if (file.size > maxSize) { const error = new Error(`File size exceeds ${maxSize / 1024 / 1024}MB limit`); onError?.(error); throw error; } onProgress?.(`Importing ${file.name}...`); const formData = new FormData(); formData.append('file', file); try { const endpoint = config.endpoint.endsWith('/') ? config.endpoint : `${config.endpoint}/`; // Build query string from queryParams const params = new URLSearchParams(); if (config.queryParams) { Object.entries(config.queryParams).forEach(([key, value]) => { if (value !== '' && value !== null && value !== undefined) { params.append(key, String(value)); } }); } const queryString = params.toString(); const url = `${API_BASE_URL}${endpoint}${queryString ? `?${queryString}` : ''}`; const response = await fetch(url, { method: 'POST', body: formData, credentials: 'include', }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Import failed: ${response.statusText} - ${errorText}`); } const result = await response.json(); onProgress?.(`Import successful: ${result.imported || 0} rows imported`); config.onSuccess?.(result); return result; } catch (error) { const err = error instanceof Error ? error : new Error('Import failed'); onError?.(err); throw err; } }; /** * React hook for table import/export functionality */ export const useTableImportExport = (exportConfig: ExportConfig) => { const handleExport = async ( format: 'csv' | 'json' = 'csv', onProgress?: (progress: string) => void, onError?: (error: Error) => void ) => { await exportTableData( { ...exportConfig, format }, onProgress, onError ); }; const handleImport = async ( file: File, importConfig: ImportConfig, onProgress?: (progress: string) => void, onError?: (error: Error) => void ) => { return await importTableData(file, importConfig, onProgress, onError); }; return { handleExport, handleImport, }; }; /** * Generate CSV from table data (client-side fallback) * Used when backend doesn't provide export endpoint */ export const generateCSVFromTable = ( data: any[], columns: Array<{ key: string; label: string }>, filename: string = 'export' ): void => { // Create header row const headers = columns.map((col) => col.label).join(','); // Create data rows const rows = data.map((row) => columns .map((col) => { const value = row[col.key]; // Escape commas and quotes in CSV const stringValue = value?.toString() || ''; if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) { return `"${stringValue.replace(/"/g, '""')}"`; } return stringValue; }) .join(',') ); const csvContent = [headers, ...rows].join('\n'); // Create download const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const downloadUrl = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = downloadUrl; link.download = `${filename}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(downloadUrl); };