File size: 5,067 Bytes
78b3b2b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | 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;
}
// Parse CSV
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; |