import { useState, useRef } from 'react'; import { api } from '@/lib/api'; import type { ImportPreview, ImportResult } from '@/types'; import Modal from '@/components/Modal'; import LoadingSpinner from '@/components/LoadingSpinner'; import { Upload, FileSpreadsheet, CheckCircle2, AlertCircle, ArrowRight, X } from 'lucide-react'; const CLIENT_FIELDS = [ { value: '', label: '-- Skip --' }, { value: 'firstName', label: 'First Name' }, { value: 'lastName', label: 'Last Name' }, { value: 'email', label: 'Email' }, { value: 'phone', label: 'Phone' }, { value: 'company', label: 'Company' }, { value: 'role', label: 'Role/Title' }, { value: 'industry', label: 'Industry' }, { value: 'street', label: 'Street' }, { value: 'city', label: 'City' }, { value: 'state', label: 'State' }, { value: 'zip', label: 'ZIP Code' }, { value: 'birthday', label: 'Birthday' }, { value: 'anniversary', label: 'Anniversary' }, { value: 'notes', label: 'Notes' }, { value: 'tags', label: 'Tags (comma-separated)' }, { value: 'interests', label: 'Interests (comma-separated)' }, ]; interface CSVImportModalProps { isOpen: boolean; onClose: () => void; onComplete: () => void; } type Step = 'upload' | 'mapping' | 'importing' | 'results'; export default function CSVImportModal({ isOpen, onClose, onComplete }: CSVImportModalProps) { const [step, setStep] = useState('upload'); const [file, setFile] = useState(null); const [preview, setPreview] = useState(null); const [mapping, setMapping] = useState>({}); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const fileRef = useRef(null); const reset = () => { setStep('upload'); setFile(null); setPreview(null); setMapping({}); setResult(null); setError(null); setLoading(false); }; const handleClose = () => { reset(); onClose(); }; const handleFileSelect = async (e: React.ChangeEvent) => { const f = e.target.files?.[0]; if (!f) return; setFile(f); setError(null); setLoading(true); try { const previewData = await api.importPreview(f); setPreview(previewData); setMapping(previewData.mapping); setStep('mapping'); } catch (err: any) { setError(err.message || 'Failed to parse CSV'); } finally { setLoading(false); } }; const updateMapping = (colIndex: number, field: string) => { setMapping(prev => { const next = { ...prev }; if (field === '') { delete next[colIndex]; } else { next[colIndex] = field; } return next; }); }; const hasRequiredFields = () => { const values = Object.values(mapping); return values.includes('firstName') && values.includes('lastName'); }; const handleImport = async () => { if (!file || !hasRequiredFields()) return; setStep('importing'); setLoading(true); setError(null); try { const importResult = await api.importClients(file, mapping); setResult(importResult); setStep('results'); if (importResult.imported > 0) { onComplete(); } } catch (err: any) { setError(err.message || 'Import failed'); setStep('mapping'); } finally { setLoading(false); } }; return (
{/* Step: Upload */} {step === 'upload' && (
fileRef.current?.click()} className="border-2 border-dashed border-slate-300 rounded-xl p-8 text-center cursor-pointer hover:border-blue-400 hover:bg-blue-50/50 transition-all" >

Click to select a CSV file

Must include at least First Name and Last Name columns

{loading && (
Parsing CSV...
)} {error && (
{error}
)}
)} {/* Step: Column Mapping */} {step === 'mapping' && preview && (
{preview.totalRows} rows found in {file?.name}
{/* Column mapping */}

Map columns to client fields:

{preview.headers.map((header, index) => (
{header}
))}
{/* Preview table */} {preview.sampleRows.length > 0 && (

Preview (first {preview.sampleRows.length} rows):

{preview.headers.map((h, i) => ( ))} {preview.sampleRows.map((row, ri) => ( {row.map((cell, ci) => ( ))} ))}
{mapping[i] ? ( {CLIENT_FIELDS.find(f => f.value === mapping[i])?.label || mapping[i]} ) : ( {h} )}
{cell || '—'}
)} {!hasRequiredFields() && (
You must map both "First Name" and "Last Name" columns
)} {error && (
{error}
)}
)} {/* Step: Importing */} {step === 'importing' && (

Importing clients...

This may take a moment for large files

)} {/* Step: Results */} {step === 'results' && result && (

Import Complete

Successfully imported {result.imported} client{result.imported !== 1 ? 's' : ''} {result.skipped > 0 && `, ${result.skipped} skipped`}

{result.errors.length > 0 && (

Issues ({result.errors.length}):

{result.errors.map((err, i) => (

{err}

))}
)}
)}
); }