"use client" import type React from "react" import { useState, useCallback } from "react" import { useDropzone } from "react-dropzone" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Progress } from "@/components/ui/progress" import { Upload, FileText, CheckCircle2, AlertCircle, Loader2 } from "lucide-react" import { parseOFX } from "@/lib/ofx-parser" import { loadData, addAccount, updateAccount, addTransactions, generateId, autoCategorize } from "@/lib/store-db" import type { OFXAccount, Account, Transaction, Folder, BankingData } from "@/lib/types" import { cn } from "@/lib/utils" interface OFXImportDialogProps { children: React.ReactNode onImportComplete?: () => void } type ImportStep = "upload" | "configure" | "importing" | "success" | "error" interface ImportResult { fileName: string accountName: string transactionsImported: number isNew: boolean error?: string } export function OFXImportDialog({ children, onImportComplete }: OFXImportDialogProps) { const [open, setOpen] = useState(false) const [step, setStep] = useState("upload") // Single file mode const [parsedData, setParsedData] = useState(null) const [accountName, setAccountName] = useState("") const [selectedFolder, setSelectedFolder] = useState("folder-root") const [folders, setFolders] = useState([]) const [existingAccountId, setExistingAccountId] = useState(null) // Multi-file mode const [importResults, setImportResults] = useState([]) const [importProgress, setImportProgress] = useState(0) const [totalFiles, setTotalFiles] = useState(0) const [error, setError] = useState(null) // Import a single OFX file directly (for multi-file mode) const importOFXDirect = async ( parsed: OFXAccount, fileName: string, data: BankingData ): Promise => { try { // Check if account already exists const existing = data.accounts.find( (a) => a.accountNumber === parsed.accountId && a.bankId === parsed.bankId ) let accountId: string let accountName: string let isNew = false if (existing) { accountId = existing.id accountName = existing.name await updateAccount({ ...existing, balance: parsed.balance, lastImport: new Date().toISOString(), }) } else { isNew = true accountName = `Compte ${parsed.accountId.slice(-4)}` const newAccount = await addAccount({ name: accountName, bankId: parsed.bankId, accountNumber: parsed.accountId, type: parsed.accountType as Account["type"], folderId: "folder-root", balance: parsed.balance, currency: parsed.currency, lastImport: new Date().toISOString(), }) accountId = newAccount.id } // Add transactions with auto-categorization const existingFitIds = new Set( data.transactions.filter((t) => t.accountId === accountId).map((t) => t.fitId) ) const newTransactions: Transaction[] = parsed.transactions .filter((t) => !existingFitIds.has(t.fitId)) .map((t) => ({ id: generateId(), accountId, date: t.date, amount: t.amount, description: t.name, type: t.amount >= 0 ? "CREDIT" : "DEBIT", categoryId: autoCategorize(t.name + " " + (t.memo || ""), data.categories), isReconciled: false, fitId: t.fitId, memo: t.memo, checkNum: t.checkNum, })) if (newTransactions.length > 0) { await addTransactions(newTransactions) } return { fileName, accountName, transactionsImported: newTransactions.length, isNew, } } catch (err) { return { fileName, accountName: "Erreur", transactionsImported: 0, isNew: false, error: err instanceof Error ? err.message : "Erreur inconnue", } } } const onDrop = useCallback(async (acceptedFiles: File[]) => { if (acceptedFiles.length === 0) return // Multi-file mode: import directly if (acceptedFiles.length > 1) { setStep("importing") setTotalFiles(acceptedFiles.length) setImportProgress(0) setImportResults([]) const data = await loadData() const results: ImportResult[] = [] for (let i = 0; i < acceptedFiles.length; i++) { const file = acceptedFiles[i] const content = await file.text() const parsed = parseOFX(content) if (parsed) { // Reload data after each import to get updated accounts/transactions const freshData = i === 0 ? data : await loadData() const result = await importOFXDirect(parsed, file.name, freshData) results.push(result) } else { results.push({ fileName: file.name, accountName: "Erreur", transactionsImported: 0, isNew: false, error: "Format OFX invalide", }) } setImportProgress(((i + 1) / acceptedFiles.length) * 100) setImportResults([...results]) } setStep("success") onImportComplete?.() return } // Single file mode: show configuration const file = acceptedFiles[0] const content = await file.text() const parsed = parseOFX(content) if (parsed) { setParsedData(parsed) setAccountName(`Compte ${parsed.accountId.slice(-4)}`) try { const data = await loadData() setFolders(data.folders) const existing = data.accounts.find( (a) => a.accountNumber === parsed.accountId && a.bankId === parsed.bankId ) if (existing) { setExistingAccountId(existing.id) setAccountName(existing.name) setSelectedFolder(existing.folderId || "folder-root") } setStep("configure") } catch (err) { console.error("Error loading data:", err) setError("Erreur lors du chargement des données") setStep("error") } } else { setError("Impossible de lire le fichier OFX. Vérifiez le format.") setStep("error") } }, [onImportComplete]) const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { "application/x-ofx": [".ofx"], "application/vnd.intu.qfx": [".qfx"], "text/plain": [".ofx", ".qfx"], }, // No maxFiles limit - accept multiple files }) const handleImport = async () => { if (!parsedData) return try { setStep("importing") const data = await loadData() let accountId: string if (existingAccountId) { accountId = existingAccountId const existingAccount = data.accounts.find((a) => a.id === existingAccountId) if (existingAccount) { await updateAccount({ ...existingAccount, name: accountName, folderId: selectedFolder, balance: parsedData.balance, lastImport: new Date().toISOString(), }) } } else { const newAccount = await addAccount({ name: accountName, bankId: parsedData.bankId, accountNumber: parsedData.accountId, type: parsedData.accountType as Account["type"], folderId: selectedFolder, balance: parsedData.balance, currency: parsedData.currency, lastImport: new Date().toISOString(), }) accountId = newAccount.id } const existingFitIds = new Set( data.transactions.filter((t) => t.accountId === accountId).map((t) => t.fitId) ) const newTransactions: Transaction[] = parsedData.transactions .filter((t) => !existingFitIds.has(t.fitId)) .map((t) => ({ id: generateId(), accountId, date: t.date, amount: t.amount, description: t.name, type: t.amount >= 0 ? "CREDIT" : "DEBIT", categoryId: autoCategorize(t.name + " " + (t.memo || ""), data.categories), isReconciled: false, fitId: t.fitId, memo: t.memo, checkNum: t.checkNum, })) if (newTransactions.length > 0) { await addTransactions(newTransactions) } setImportResults([{ fileName: "Import", accountName, transactionsImported: newTransactions.length, isNew: !existingAccountId, }]) setStep("success") onImportComplete?.() } catch (err) { console.error("Error importing:", err) setError("Erreur lors de l'import") setStep("error") } } const handleClose = () => { setOpen(false) setTimeout(() => { setStep("upload") setParsedData(null) setAccountName("") setSelectedFolder("folder-root") setExistingAccountId(null) setError(null) setImportResults([]) setImportProgress(0) setTotalFiles(0) }, 200) } const totalTransactions = importResults.reduce((sum, r) => sum + r.transactionsImported, 0) const successCount = importResults.filter((r) => !r.error).length const errorCount = importResults.filter((r) => r.error).length return ( { if (!o) handleClose() else setOpen(true) }} > {children} {step === "upload" && "Importer des fichiers OFX"} {step === "configure" && "Configurer le compte"} {step === "importing" && "Import en cours..."} {step === "success" && "Import terminé"} {step === "error" && "Erreur d'import"} {step === "upload" && "Glissez-déposez vos fichiers OFX ou cliquez pour sélectionner"} {step === "configure" && "Vérifiez les informations du compte avant l'import"} {step === "importing" && `Import de ${totalFiles} fichier${totalFiles > 1 ? "s" : ""}...`} {step === "success" && ( importResults.length > 1 ? `${successCount} fichier${successCount > 1 ? "s" : ""} importé${successCount > 1 ? "s" : ""}, ${totalTransactions} transactions` : `${totalTransactions} nouvelles transactions importées` )} {step === "error" && error} {step === "upload" && (

{isDragActive ? "Déposez les fichiers ici..." : "Fichiers .ofx ou .qfx acceptés"}

Un fichier = configuration manuelle • Plusieurs fichiers = import direct

)} {step === "configure" && parsedData && (

{parsedData.transactions.length} transactions

Solde:{" "} {new Intl.NumberFormat("fr-FR", { style: "currency", currency: parsedData.currency }).format( parsedData.balance, )}

setAccountName(e.target.value)} placeholder="Ex: Compte courant BNP" />
{existingAccountId && (

Ce compte existe déjà. Les nouvelles transactions seront ajoutées.

)}
)} {step === "importing" && (
Import en cours...
{totalFiles > 1 && ( )} {importResults.length > 0 && (
{importResults.map((result, i) => (
{result.error ? ( ) : ( )} {result.fileName} {!result.error && ( +{result.transactionsImported} )}
))}
)}
)} {step === "success" && (
{importResults.length > 1 && (
{importResults.map((result, i) => (
{result.error ? ( ) : ( )}

{result.accountName}

{result.fileName}

{result.error ? ( {result.error} ) : ( {result.isNew ? "Nouveau" : "Mis à jour"} • +{result.transactionsImported} )}
))}
)} {errorCount > 0 && (

{errorCount} fichier{errorCount > 1 ? "s" : ""} en erreur

)}
)} {step === "error" && (
)}
) }