"use client"; import { useState } from "react"; import { DndContext, DragEndEvent, DragOverlay, DragStartEvent, PointerSensor, useSensor, useSensors, closestCenter, useDroppable, } from "@dnd-kit/core"; import { PageLayout, LoadingState, PageHeader } from "@/components/layout"; import { AccountCard, AccountEditDialog, AccountBulkActions, } from "@/components/accounts"; import { FolderEditDialog } from "@/components/folders"; import { useBankingMetadata, useAccountsWithStats } from "@/lib/hooks"; import { useQueryClient } from "@tanstack/react-query"; import { updateAccount, deleteAccount, addFolder, updateFolder, deleteFolder, } from "@/lib/store-db"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Building2, Folder, Plus, List, LayoutGrid } from "lucide-react"; import type { Account, Folder as FolderType } from "@/lib/types"; import { cn } from "@/lib/utils"; import { getAccountBalance } from "@/lib/account-utils"; // Composant wrapper pour les zones de drop des dossiers function FolderDropZone({ folderId, children, }: { folderId: string; children: React.ReactNode; }) { const { setNodeRef, isOver } = useDroppable({ id: `folder-${folderId}`, }); return (
{children}
); } export default function AccountsPage() { const queryClient = useQueryClient(); const { data: metadata, isLoading: isLoadingMetadata } = useBankingMetadata(); const { data: accountsWithStats, isLoading: isLoadingAccounts } = useAccountsWithStats(); // refresh function is not used directly, invalidations are done inline const refreshSilent = async () => { await queryClient.invalidateQueries({ queryKey: ["accounts-with-stats"] }); await queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); }; const [editingAccount, setEditingAccount] = useState(null); const [isDialogOpen, setIsDialogOpen] = useState(false); const [selectedAccounts, setSelectedAccounts] = useState>( new Set(), ); const [formData, setFormData] = useState({ name: "", type: "CHECKING" as Account["type"], folderId: "folder-root", externalUrl: "", initialBalance: 0, }); // Folder management state const [isFolderDialogOpen, setIsFolderDialogOpen] = useState(false); const [editingFolder, setEditingFolder] = useState(null); const [folderFormData, setFolderFormData] = useState({ name: "", parentId: "folder-root" as string | null, color: "#6366f1", }); const [activeId, setActiveId] = useState(null); const [isCompactView, setIsCompactView] = useState(false); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), ); if ( isLoadingMetadata || !metadata || isLoadingAccounts || !accountsWithStats ) { return ; } // Convert accountsWithStats to regular accounts for compatibility const accounts = accountsWithStats.map( ({ transactionCount: _transactionCount, ...account }) => account, ); const formatCurrency = (amount: number) => { return new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", }).format(amount); }; const handleEdit = (account: Account) => { setEditingAccount(account); setFormData({ name: account.name, type: account.type, folderId: account.folderId || "folder-root", externalUrl: account.externalUrl || "", initialBalance: account.initialBalance || 0, }); setIsDialogOpen(true); }; const handleSave = async () => { if (!editingAccount) return; try { const updatedAccount = { ...editingAccount, name: formData.name, type: formData.type, folderId: formData.folderId, externalUrl: formData.externalUrl || null, initialBalance: formData.initialBalance, }; await updateAccount(updatedAccount); queryClient.invalidateQueries({ queryKey: ["accounts-with-stats"] }); queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); setIsDialogOpen(false); setEditingAccount(null); } catch (error) { console.error("Error updating account:", error); alert("Erreur lors de la mise à jour du compte"); } }; const handleDelete = async (accountId: string) => { if (!confirm("Supprimer ce compte et toutes ses transactions ?")) return; try { await deleteAccount(accountId); queryClient.invalidateQueries({ queryKey: ["accounts-with-stats"] }); queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); } catch (error) { console.error("Error deleting account:", error); alert("Erreur lors de la suppression du compte"); } }; const handleBulkDelete = async () => { const count = selectedAccounts.size; if ( !confirm( `Supprimer ${count} compte${count > 1 ? "s" : ""} et toutes leurs transactions ?`, ) ) return; try { const ids = Array.from(selectedAccounts); const response = await fetch( `/api/banking/accounts?ids=${ids.join(",")}`, { method: "DELETE", }, ); if (!response.ok) { throw new Error("Failed to delete accounts"); } setSelectedAccounts(new Set()); queryClient.invalidateQueries({ queryKey: ["accounts-with-stats"] }); queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); } catch (error) { console.error("Error deleting accounts:", error); alert("Erreur lors de la suppression des comptes"); } }; const toggleSelectAccount = (accountId: string, selected: boolean) => { const newSelected = new Set(selectedAccounts); if (selected) { newSelected.add(accountId); } else { newSelected.delete(accountId); } setSelectedAccounts(newSelected); }; // Folder management handlers const handleNewFolder = () => { setEditingFolder(null); setFolderFormData({ name: "", parentId: "folder-root", color: "#6366f1" }); setIsFolderDialogOpen(true); }; const handleEditFolder = (folder: FolderType) => { setEditingFolder(folder); setFolderFormData({ name: folder.name, parentId: folder.parentId || "folder-root", color: folder.color, }); setIsFolderDialogOpen(true); }; const handleSaveFolder = async () => { const parentId = folderFormData.parentId === "folder-root" ? null : folderFormData.parentId; try { if (editingFolder) { await updateFolder({ ...editingFolder, name: folderFormData.name, parentId, color: folderFormData.color, }); } else { await addFolder({ name: folderFormData.name, parentId, color: folderFormData.color, icon: "folder", }); } queryClient.invalidateQueries({ queryKey: ["accounts-with-stats"] }); queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); setIsFolderDialogOpen(false); } catch (error) { console.error("Error saving folder:", error); alert("Erreur lors de la sauvegarde du dossier"); } }; const handleDeleteFolder = async (folderId: string) => { if ( !confirm( "Supprimer ce dossier ? Les comptes seront déplacés à la racine.", ) ) return; try { await deleteFolder(folderId); queryClient.invalidateQueries({ queryKey: ["accounts-with-stats"] }); queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); } catch (error) { console.error("Error deleting folder:", error); alert("Erreur lors de la suppression du dossier"); } }; // Drag and drop handlers const handleDragStart = (event: DragStartEvent) => { setActiveId(event.active.id as string); }; const handleDragEnd = async (event: DragEndEvent) => { const { active, over } = event; setActiveId(null); if (!over || active.id === over.id || !accountsWithStats) return; const activeId = active.id as string; const overId = over.id as string; // Déplacer un compte vers un dossier if (activeId.startsWith("account-")) { const accountId = activeId.replace("account-", ""); let targetFolderId: string | null = null; if (overId.startsWith("folder-")) { const folderId = overId.replace("folder-", ""); targetFolderId = folderId === "root" ? null : folderId; } else if (overId.startsWith("account-")) { // Déplacer vers le dossier du compte cible const targetAccountId = overId.replace("account-", ""); const targetAccount = accountsWithStats.find( (a) => a.id === targetAccountId, ); if (targetAccount) { targetFolderId = targetAccount.folderId; } } if (targetFolderId !== undefined) { const account = accountsWithStats.find((a) => a.id === accountId); if (!account) return; // Optimistic update : mettre à jour immédiatement l'interface const updatedAccount = { ...account, folderId: targetFolderId, }; // Update cache directly queryClient.setQueryData( ["accounts-with-stats"], (old: Array | undefined) => { if (!old) return old; return old.map((a) => (a.id === accountId ? updatedAccount : a)); }, ); // Faire la requête en arrière-plan try { await updateAccount(updatedAccount); // Refresh silencieux pour synchroniser avec le serveur sans loader await refreshSilent(); } catch (error) { console.error("Error moving account:", error); // Rollback en cas d'erreur - refresh data queryClient.invalidateQueries({ queryKey: ["accounts-with-stats"] }); queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); alert("Erreur lors du déplacement du compte"); } } } }; const getTransactionCount = (accountId: string) => { const account = accountsWithStats.find((a) => a.id === accountId); return account?.transactionCount || 0; }; const totalBalance = accounts.reduce( (sum, a) => sum + getAccountBalance(a), 0, ); // Grouper les comptes par folder const accountsByFolder = accounts.reduce( (acc, account) => { const folderId = account.folderId || "no-folder"; if (!acc[folderId]) { acc[folderId] = []; } acc[folderId].push(account); return acc; }, {} as Record, ); // Obtenir les folders racine (sans parent) et les trier par nom const rootFolders = metadata.folders .filter((f: FolderType) => !f.parentId) .sort((a: FolderType, b: FolderType) => a.name.localeCompare(b.name)); return ( } rightContent={

Solde total

= 0 ? "text-emerald-600" : "text-red-600", )} > {formatCurrency(totalBalance)}

} /> {accounts.length === 0 ? (

Aucun compte

Importez un fichier OFX depuis le tableau de bord pour ajouter votre premier compte.

) : ( <>
{/* Afficher d'abord les comptes sans dossier */} {accountsByFolder["no-folder"] && accountsByFolder["no-folder"].length > 0 && (

Sans dossier

({accountsByFolder["no-folder"].length}) sum + getAccountBalance(a), 0, ) >= 0 ? "text-emerald-600" : "text-red-600", )} > {formatCurrency( accountsByFolder["no-folder"].reduce( (sum, a) => sum + getAccountBalance(a), 0, ), )}
{accountsByFolder["no-folder"].map((account) => { const folder = metadata.folders.find( (f: FolderType) => f.id === account.folderId, ); return ( ); })}
)} {/* Afficher les comptes groupés par folder */} {rootFolders.map((folder: FolderType) => { const folderAccounts = accountsByFolder[folder.id] || []; const folderBalance = folderAccounts.reduce( (sum, a) => sum + getAccountBalance(a), 0, ); return (

{folder.name}

({folderAccounts.length}) {folderAccounts.length > 0 && ( = 0 ? "text-emerald-600" : "text-red-600", )} > {formatCurrency(folderBalance)} )}
{folderAccounts.length > 0 ? (
{folderAccounts.map((account) => { const accountFolder = metadata.folders.find( (f: FolderType) => f.id === account.folderId, ); return ( ); })}
) : (

Aucun compte dans ce dossier

Glissez-déposez un compte ici pour l'ajouter

)}
); })}
{activeId ? (
{activeId.startsWith("account-") ? ( {accounts.find( (a) => a.id === activeId.replace("account-", ""), )?.name || ""} ) : null}
) : null}
)}
); }