Files
Analysis/frontend/src/features/uploader/components/FileUploader.tsx
2026-01-11 22:56:02 +01:00

113 lines
4.0 KiB
TypeScript

"use client";
import React, { useCallback, useState } from "react";
import { useGridStore } from "@/store/use-grid-store";
import { parseArrowStream } from "@/lib/arrow-client";
import { Upload, FileSpreadsheet, Loader2, AlertCircle } from "lucide-react";
import { cn } from "@/lib/utils";
import { getApiUrl } from "@/lib/api-config";
export function FileUploader() {
const { setLoading, setData, setError, isLoading } = useGridStore();
const [isDragging, setIsDragging] = useState(false);
const handleFile = async (file: File) => {
console.log("=== UPLOAD START ===", file.name, file.size);
setLoading(true);
const formData = new FormData();
formData.append("file", file);
try {
console.log("Fetching:", getApiUrl("/upload"));
const response = await fetch(getApiUrl("/upload"), {
method: "POST",
body: formData,
});
console.log("Response status:", response.status);
if (!response.ok) {
if (response.status === 404) throw new Error("Backend non trouvé (404). Vérifiez l'URL.");
if (response.status === 500) throw new Error("Erreur interne du serveur.");
throw new Error(await response.text());
}
const metadataStr = response.headers.get("X-Column-Metadata");
console.log("Metadata:", metadataStr);
const metadata = metadataStr ? JSON.parse(metadataStr) : [];
console.log("Parsing Arrow...");
const data = await parseArrowStream(response);
console.log("Data parsed, rows:", data.length);
setData(data, metadata);
console.log("=== UPLOAD SUCCESS ===");
} catch (err: any) {
console.error("Upload Error:", err);
// Check for network error (Failed to fetch)
if (err.message === "Failed to fetch") {
setError("Impossible de contacter le serveur. Le backend est-il lancé sur le port 8000 ?");
} else {
setError(err.message || "Échec de l'upload");
}
} finally {
setLoading(false);
}
};
const onDragOver = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(true);
};
const onDragLeave = () => setIsDragging(false);
const onDrop = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files?.[0];
if (file) handleFile(file);
};
return (
<div
className={cn(
"relative group flex items-center gap-6 p-6 rounded-2xl border-2 border-dashed transition-all duration-300 cursor-pointer overflow-hidden",
isDragging
? "border-indigo-500 bg-indigo-50/50 scale-[1.02] shadow-xl shadow-indigo-100"
: "border-slate-200 bg-white hover:border-indigo-300 hover:shadow-md"
)}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
>
<input
type="file"
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
accept=".xlsx,.xls,.csv"
onChange={(e) => e.target.files?.[0] && handleFile(e.target.files[0])}
disabled={isLoading}
/>
<div className={cn(
"p-4 rounded-xl transition-colors",
isDragging ? "bg-indigo-100 text-indigo-600" : "bg-slate-100 text-slate-500 group-hover:bg-indigo-50 group-hover:text-indigo-600"
)}>
{isLoading ? <Loader2 className="w-8 h-8 animate-spin" /> : <Upload className="w-8 h-8" />}
</div>
<div className="flex-1 space-y-1">
<h3 className="text-lg font-bold text-slate-900 group-hover:text-indigo-700 transition-colors">
{isLoading ? "Traitement en cours..." : "Déposez votre fichier ici"}
</h3>
<p className="text-sm text-slate-500">
Excel (.xlsx) ou CSV. <span className="text-xs bg-slate-100 px-2 py-0.5 rounded text-slate-400">Max 50MB</span>
</p>
</div>
<div className="hidden sm:flex items-center gap-2 text-xs font-medium text-slate-400 bg-slate-50 px-3 py-1.5 rounded-lg border border-slate-100">
<FileSpreadsheet className="w-4 h-4" />
<span>Auto-Detect</span>
</div>
</div>
);
}