import React, { useState, useEffect, useMemo } from 'react'; import { Box, Text, useInput } from 'ink'; import TextInput from 'ink-text-input'; import SelectInput from 'ink-select-input'; import Spinner from 'ink-spinner'; import { getExportService, ExportOptions, ExportResult, ExportTemplate, BatchExportJob } from '../services/export-service.js'; export interface ExportPanelProps { data: T[]; title?: string; defaultFields?: string[]; visible?: boolean; onClose?: () => void; onExportComplete?: (result: ExportResult) => void; } interface ExportPanelState { mode: 'format' | 'options' | 'templates' | 'batch' | 'progress'; selectedFormat: string; filename: string; selectedFields: string[]; includeHeaders: boolean; timestamp: boolean; chartType: string; chartTitle: string; selectedTemplate: string; isExporting: boolean; exportProgress: number; lastResult?: ExportResult; batchJob?: BatchExportJob; } export function ExportPanel({ data, title = 'Data Export', defaultFields, visible = true, onClose, onExportComplete }: ExportPanelProps) { const [state, setState] = useState({ mode: 'format', selectedFormat: 'csv', filename: '', selectedFields: defaultFields || [], includeHeaders: true, timestamp: true, chartType: 'line', chartTitle: 'Data Chart', selectedTemplate: '', isExporting: false, exportProgress: 0 }); const exportService = getExportService(); const templates = useMemo(() => exportService.getTemplates(), []); const stats = useMemo(() => exportService.getExportStats(), []); // Available fields from data const availableFields = useMemo(() => { if (data.length === 0) return []; const firstItem = data[0] as any; return Object.keys(firstItem).filter(key => typeof firstItem[key] !== 'object'); }, [data]); // Initialize selected fields if not provided useEffect(() => { if (state.selectedFields.length === 0 && availableFields.length > 0) { setState(prev => ({ ...prev, selectedFields: availableFields.slice(0, 10) })); } }, [availableFields, state.selectedFields.length]); // Handle keyboard input useInput((input, key) => { if (!visible) return; if (state.isExporting) { // Only allow escape during export if (key.escape) { setState(prev => ({ ...prev, isExporting: false, mode: 'format' })); } return; } if (key.escape) { onClose?.(); } else if (key.tab) { // Cycle through modes const modes: ExportPanelState['mode'][] = ['format', 'options', 'templates', 'batch']; const currentIndex = modes.indexOf(state.mode); const nextIndex = (currentIndex + 1) % modes.length; setState(prev => ({ ...prev, mode: modes[nextIndex] })); } else if (key.return && state.mode === 'format') { // Start export performExport(); } else if (input === 's' && state.mode === 'templates') { // Save current settings as template saveAsTemplate(); } else if (input === 'b' && state.mode === 'batch') { // Create batch job createBatchJob(); } }); const performExport = async () => { setState(prev => ({ ...prev, isExporting: true, exportProgress: 0 })); try { const options: ExportOptions = { format: state.selectedFormat as any, filename: state.filename || undefined, fields: state.selectedFields.length > 0 ? state.selectedFields : undefined, includeHeaders: state.includeHeaders, timestamp: state.timestamp, chartType: state.selectedFormat === 'chart' ? state.chartType as any : undefined, chartOptions: state.selectedFormat === 'chart' ? { title: state.chartTitle, xAxis: state.selectedFields[0], yAxis: state.selectedFields[1], width: 800, height: 400, showGrid: true, format: 'html' } : undefined }; const result = await exportService.exportData(data, options); setState(prev => ({ ...prev, isExporting: false, lastResult: result, mode: 'progress' })); onExportComplete?.(result); } catch (error) { setState(prev => ({ ...prev, isExporting: false, lastResult: { success: false, filename: '', filepath: '', size: 0, recordCount: 0, duration: 0, error: error instanceof Error ? error.message : String(error) } })); } }; const saveAsTemplate = () => { const options: ExportOptions = { format: state.selectedFormat as any, fields: state.selectedFields.length > 0 ? state.selectedFields : undefined, includeHeaders: state.includeHeaders, timestamp: state.timestamp, chartType: state.selectedFormat === 'chart' ? state.chartType as any : undefined }; const templateId = exportService.createTemplate( `Template ${new Date().toLocaleTimeString()}`, options, `Auto-saved template for ${state.selectedFormat} export` ); // Could show notification here }; const createBatchJob = () => { // Create a sample batch job with multiple formats const formats = ['csv', 'json', 'xml']; const exports = formats.map(format => ({ data, options: { format: format as any, fields: state.selectedFields, includeHeaders: state.includeHeaders, timestamp: state.timestamp } })); const jobId = exportService.createBatchJob(`Batch Export ${new Date().toLocaleTimeString()}`, exports); // Execute the batch job exportService.executeBatchJob(jobId).then(job => { setState(prev => ({ ...prev, batchJob: job })); }); }; if (!visible) return null; return ( {/* Header */} 📤 {title} {state.mode.toUpperCase()} | {data.length} records | Tab: Switch | Esc: Close {/* Content based on mode */} {state.isExporting ? ( ) : ( <> {state.mode === 'format' && ( setState(prev => ({ ...prev, selectedFormat: format }))} filename={state.filename} onFilenameChange={(filename) => setState(prev => ({ ...prev, filename }))} chartType={state.chartType} onChartTypeChange={(chartType) => setState(prev => ({ ...prev, chartType }))} chartTitle={state.chartTitle} onChartTitleChange={(chartTitle) => setState(prev => ({ ...prev, chartTitle }))} /> )} {state.mode === 'options' && ( setState(prev => ({ ...prev, selectedFields: fields }))} includeHeaders={state.includeHeaders} onIncludeHeadersChange={(include) => setState(prev => ({ ...prev, includeHeaders: include }))} timestamp={state.timestamp} onTimestampChange={(timestamp) => setState(prev => ({ ...prev, timestamp }))} /> )} {state.mode === 'templates' && ( { setState(prev => ({ ...prev, selectedFormat: template.options.format, selectedFields: template.options.fields || [], includeHeaders: template.options.includeHeaders !== false, timestamp: template.options.timestamp !== false, chartType: template.options.chartType || 'line', selectedTemplate: template.id })); }} /> )} {state.mode === 'batch' && ( )} {state.mode === 'progress' && state.lastResult && ( )} )} {/* Footer */} Actions: [Enter] Export • [S] Save Template • [B] Batch Export • [Tab] Switch Mode ); } // Format Selection Panel interface FormatSelectionPanelProps { selectedFormat: string; onFormatChange: (format: string) => void; filename: string; onFilenameChange: (filename: string) => void; chartType: string; onChartTypeChange: (type: string) => void; chartTitle: string; onChartTitleChange: (title: string) => void; } function FormatSelectionPanel({ selectedFormat, onFormatChange, filename, onFilenameChange, chartType, onChartTypeChange, chartTitle, onChartTitleChange }: FormatSelectionPanelProps) { const formats = [ { label: '📊 CSV - Comma Separated Values', value: 'csv' }, { label: '📋 JSON - JavaScript Object Notation', value: 'json' }, { label: '📄 XML - Extensible Markup Language', value: 'xml' }, { label: '📝 YAML - YAML Ain\'t Markup Language', value: 'yaml' }, { label: '📈 Chart - Visual Chart', value: 'chart' } ]; const chartTypes = [ { label: '📈 Line Chart', value: 'line' }, { label: '📊 Bar Chart', value: 'bar' }, { label: '🔴 Scatter Plot', value: 'scatter' } ]; return ( Export Format: {formats.map(format => ( {selectedFormat === format.value ? '► ' : ' '}{format.label} ))} Filename (optional): {filename || 'auto-generated'} {selectedFormat === 'chart' && ( Chart Options: Type: {chartType} Title: {chartTitle} )} Use options panel to configure fields and settings ); } // Options Panel interface OptionsPanelProps { availableFields: string[]; selectedFields: string[]; onFieldsChange: (fields: string[]) => void; includeHeaders: boolean; onIncludeHeadersChange: (include: boolean) => void; timestamp: boolean; onTimestampChange: (timestamp: boolean) => void; } function OptionsPanel({ availableFields, selectedFields, onFieldsChange, includeHeaders, onIncludeHeadersChange, timestamp, onTimestampChange }: OptionsPanelProps) { return ( Export Options: Include Headers: {includeHeaders ? '✓ Yes' : '✗ No'} Add Timestamp: {timestamp ? '✓ Yes' : '✗ No'} Selected Fields ({selectedFields.length}): {selectedFields.slice(0, 8).map(field => ( • {field} ))} {selectedFields.length > 8 && ( ... and {selectedFields.length - 8} more )} Available Fields ({availableFields.length}): {availableFields.slice(0, 5).map(field => ( {selectedFields.includes(field) ? '✓' : '○'} {field} ))} {availableFields.length > 5 && ( ... and {availableFields.length - 5} more )} ); } // Templates Panel interface TemplatesPanelProps { templates: ExportTemplate[]; onLoadTemplate: (template: ExportTemplate) => void; } function TemplatesPanel({ templates, onLoadTemplate }: TemplatesPanelProps) { if (templates.length === 0) { return ( No saved templates Configure export settings and press 'S' to save as template ); } return ( Saved Templates ({templates.length}): {templates.slice(0, 5).map(template => ( {template.name} Format: {template.options.format} • Used: {template.useCount} times Last used: {template.lastUsed.toLocaleDateString()} {template.description && ( {template.description} )} ))} Click templates to load (interactive mode coming soon) ); } // Batch Panel interface BatchPanelProps { stats: any; batchJob?: BatchExportJob; } function BatchPanel({ stats, batchJob }: BatchPanelProps) { return ( Batch Export: Total Exports: {stats.totalExports} Completed Jobs: {stats.completedJobs} Total Size: {(stats.totalSize / 1024).toFixed(1)} KB {batchJob && ( Current Batch Job: Status: {batchJob.status} Progress: {batchJob.progress.toFixed(1)}% Results: {batchJob.results.length} )} Press 'B' to create batch job with multiple formats ); } // Export Progress Panel interface ExportProgressPanelProps { progress: number; format: string; } function ExportProgressPanel({ progress, format }: ExportProgressPanelProps) { return ( Exporting to {format.toUpperCase()}... Progress: {progress.toFixed(1)}% Press Esc to cancel ); } // Result Panel interface ResultPanelProps { result: ExportResult; } function ResultPanel({ result }: ResultPanelProps) { return ( {result.success ? '✅ Export Successful!' : '❌ Export Failed'} {result.success ? ( File: {result.filename} Size: {(result.size / 1024).toFixed(1)} KB Records: {result.recordCount} Duration: {result.duration}ms Path: {result.filepath} ) : ( Error: {result.error} )} ); } export default ExportPanel;