|
|
import React, { useMemo, useState } from 'react'; |
|
|
import { parseCsv } from '../../utils/index.ts'; |
|
|
|
|
|
import type { CsvRow } from '../../types/index.ts'; |
|
|
|
|
|
interface UploadCsvModalProps { |
|
|
isOpen: boolean; |
|
|
onClose: () => void; |
|
|
onAnalyze: (file: File) => Promise<void>; |
|
|
isAnalyzing: boolean; |
|
|
} |
|
|
|
|
|
const UploadCsvModal = ({ |
|
|
isOpen, |
|
|
onClose, |
|
|
onAnalyze, |
|
|
isAnalyzing |
|
|
}: UploadCsvModalProps) => { |
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null); |
|
|
const [rows, setRows] = useState<CsvRow[]>([]); |
|
|
const [error, setError] = useState<string | null>(null); |
|
|
|
|
|
const fileName = useMemo( |
|
|
() => selectedFile?.name ?? 'Select a CSV file with arguments', |
|
|
[selectedFile] |
|
|
); |
|
|
|
|
|
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => { |
|
|
const file = event.target.files?.[0] ?? null; |
|
|
setSelectedFile(file); |
|
|
setError(null); |
|
|
setRows([]); |
|
|
|
|
|
if (!file) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
const content = await file.text(); |
|
|
const parsed = parseCsv(content); |
|
|
|
|
|
setRows(parsed.rows); |
|
|
|
|
|
if (parsed.rows.length === 0) { |
|
|
setError('No data found in the CSV file.'); |
|
|
} |
|
|
} catch (err) { |
|
|
setError( |
|
|
err instanceof Error ? err.message : 'Unable to read the CSV file.' |
|
|
); |
|
|
setRows([]); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleAnalyze = async () => { |
|
|
if (!selectedFile) { |
|
|
setError('Please choose a CSV file to analyze.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (rows.length === 0) { |
|
|
setError('No data found in the CSV file.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
await onAnalyze(selectedFile); |
|
|
}; |
|
|
|
|
|
if (!isOpen) return null; |
|
|
|
|
|
return ( |
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 dark:bg-black/70"> |
|
|
<div |
|
|
className="relative w-full max-w-2xl max-h-[90vh] overflow-hidden rounded-xl border border-slate-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 shadow-xl" |
|
|
onClick={(e) => e.stopPropagation()} |
|
|
> |
|
|
<div className="flex items-center justify-between border-b border-slate-200 dark:border-zinc-700 px-6 py-4"> |
|
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-white"> |
|
|
Analyze New Arguments |
|
|
</h2> |
|
|
<button |
|
|
onClick={onClose} |
|
|
className="text-slate-500 dark:text-zinc-400 hover:text-slate-700 dark:hover:text-zinc-200 transition-colors" |
|
|
aria-label="Close" |
|
|
> |
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> |
|
|
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" /> |
|
|
</svg> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div className="p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-140px)]"> |
|
|
<div className="flex flex-col gap-3"> |
|
|
<label className="text-sm font-medium text-slate-700 dark:text-zinc-300"> |
|
|
Upload a CSV file |
|
|
</label> |
|
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-3"> |
|
|
<label className="flex w-full max-w-xl cursor-pointer items-center justify-between rounded-full border border-slate-300 dark:border-zinc-600 bg-slate-50 dark:bg-zinc-800 px-5 py-3 transition hover:border-slate-400 dark:hover:border-zinc-500"> |
|
|
<span className="truncate text-sm font-semibold text-slate-500 dark:text-zinc-400"> |
|
|
{fileName} |
|
|
</span> |
|
|
<input |
|
|
type="file" |
|
|
accept=".csv,text/csv" |
|
|
className="hidden" |
|
|
onChange={handleFileChange} |
|
|
/> |
|
|
</label> |
|
|
|
|
|
<div className="flex w-full justify-center"> |
|
|
<button |
|
|
type="button" |
|
|
onClick={handleAnalyze} |
|
|
disabled={!selectedFile || rows.length === 0 || isAnalyzing} |
|
|
className="rounded-md bg-blue-600 dark:bg-blue-500 px-4 py-2 text-sm font-semibold text-white transition hover:bg-blue-700 dark:hover:bg-blue-600 disabled:cursor-not-allowed disabled:opacity-60" |
|
|
> |
|
|
{isAnalyzing ? 'Analyzing...' : 'Analyze'} |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>} |
|
|
</div> |
|
|
|
|
|
{isAnalyzing && ( |
|
|
<div className="flex items-center justify-center py-8"> |
|
|
<div className="flex flex-col items-center"> |
|
|
<div className="h-10 w-10 animate-spin rounded-full border-4 border-blue-600 border-t-transparent"></div> |
|
|
<p className="mt-3 text-sm text-slate-600 dark:text-zinc-400">Analyzing arguments...</p> |
|
|
</div> |
|
|
</div> |
|
|
)} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default UploadCsvModal; |