import { useState, useEffect, useRef } from 'react' import { api } from '../api' function KeyList({ keys, label, color }) { if (!keys || keys.length === 0) return null return (
{label} ({keys.length})
{keys.map((k, i) => (
{typeof k === 'object' && k !== null ? Object.entries(k).map(([field, val]) => `${field}: ${val}`).join(' · ') : k}
))}
) } function LogRow({ entry, selected, onToggle }) { const [expanded, setExpanded] = useState(false) const info = entry.info || {} const insertedKeys = info.inserted_keys || [] const excludedKeys = info.excluded_keys || [] const hasKeys = insertedKeys.length > 0 || excludedKeys.length > 0 return ( <> {entry.id} {new Date(entry.imported_at).toLocaleString()} {entry.records_imported} {entry.records_duplicate} {hasKeys && ( )} {expanded && ( )} ) } export default function Import({ source }) { const [stats, setStats] = useState(null) const [log, setLog] = useState([]) const [result, setResult] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [dragOver, setDragOver] = useState(false) const [selected, setSelected] = useState(new Set()) const fileRef = useRef() useEffect(() => { if (!source) return api.getStats(source).then(setStats).catch(() => {}) api.getImportLog(source).then(setLog).catch(() => {}) setSelected(new Set()) }, [source]) async function handleImport(file) { if (!file || !source) return setLoading(true) setError('') setResult(null) try { const res = await api.importCSV(source, file) setResult(res) api.getStats(source).then(setStats) api.getImportLog(source).then(setLog) } catch (err) { setError(err.message) } finally { setLoading(false) } } async function handleTransform() { if (!source) return setLoading(true) try { const res = await api.transform(source) setResult(res) api.getStats(source).then(setStats) } catch (err) { setError(err.message) } finally { setLoading(false) } } function toggleSelect(id) { setSelected(prev => { const next = new Set(prev) next.has(id) ? next.delete(id) : next.add(id) return next }) } async function handleDeleteSelected() { if (selected.size === 0) return const plural = selected.size === 1 ? 'import' : 'imports' if (!confirm(`Delete ${selected.size} ${plural}? This will permanently remove all records from those batches.`)) return setLoading(true) try { await Promise.all([...selected].map(id => api.deleteImport(source, id))) const [newLog, newStats] = await Promise.all([api.getImportLog(source), api.getStats(source)]) setLog(newLog) setStats(newStats) setSelected(new Set()) } catch (err) { setError(err.message) } finally { setLoading(false) } } async function handleReprocess() { if (!confirm('Reprocess all records? This will clear and reapply all transformation rules.')) return setLoading(true) setResult(null) try { const res = await api.reprocess(source) setResult(res) api.getStats(source).then(setStats) } catch (err) { setError(err.message) } finally { setLoading(false) } } if (!source) return
Select a source first.
return (

Import — {source}

{/* Stats */} {stats && (
{[ { label: 'Total records', value: stats.total_records }, { label: 'Transformed', value: stats.transformed_records }, { label: 'Pending', value: stats.pending_records }, ].map(({ label, value }) => (
{value}
{label}
))}
)} {/* Drop zone */}
{ e.preventDefault(); setDragOver(true) }} onDragLeave={() => setDragOver(false)} onDrop={e => { e.preventDefault(); setDragOver(false); handleImport(e.dataTransfer.files[0]) }} onClick={() => fileRef.current?.click()} > handleImport(e.target.files[0])} /> {loading ?

Importing…

:

Drop a CSV file here, or click to browse

}
{error &&

{error}

} {result && (
{result.imported !== undefined ? ( <> {result.imported} imported · {result.duplicates} duplicates skipped {result.transform && ( <> · {result.transform.transformed} transformed )} ) : ( {result.transformed} records transformed )}
)} {/* Action buttons */}
{stats && Number(stats.pending_records) > 0 && ( )} {stats && Number(stats.total_records) > 0 && ( )}
{/* Import log */} {log.length > 0 && (

Import history

{selected.size > 0 && ( )}
{log.map(entry => ( toggleSelect(entry.id)} /> ))}
ID Date Imported Duplicates
)}
) }