"use client"; import { useState, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { CategoryIcon } from "@/components/ui/category-icon"; import { ChevronsUpDown, Check, Wallet, X } from "lucide-react"; import { cn } from "@/lib/utils"; import type { Account, Folder, Transaction } from "@/lib/types"; import { accountTypeIcons } from "@/components/accounts/constants"; interface AccountFilterComboboxProps { accounts: Account[]; folders: Folder[]; value: string[]; // ["all"] | [accountId, accountId, ...] onChange: (value: string[]) => void; className?: string; filteredTransactions?: Transaction[]; // Transactions filtered by other filters (categories, period, etc.) } export function AccountFilterCombobox({ accounts, folders, value, onChange, className, filteredTransactions, }: AccountFilterComboboxProps) { const [open, setOpen] = useState(false); // Calculate total amount per account based on filtered transactions const accountTotals = useMemo(() => { if (!filteredTransactions) return {}; const totals: Record = {}; filteredTransactions.forEach((t) => { totals[t.accountId] = (totals[t.accountId] || 0) + t.amount; }); return totals; }, [filteredTransactions]); const formatCurrency = (amount: number) => { return new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(amount); }; // Get root folders (folders without parent) - same as folders/page.tsx const rootFolders = useMemo( () => folders.filter((f) => f.parentId === null), [folders], ); // Get child folders for a given parent - same as FolderTreeItem const getChildFolders = (parentId: string) => folders.filter((f) => f.parentId === parentId); // Get accounts in a specific folder - same as FolderTreeItem const getFolderAccounts = (folderId: string) => accounts.filter((a) => a.folderId === folderId); // Get accounts without folder const orphanAccounts = useMemo( () => accounts.filter((a) => !a.folderId), [accounts], ); const selectedAccounts = accounts.filter((a) => value.includes(a.id)); const isAll = value.includes("all") || value.length === 0; // Get all accounts in a folder and its descendants recursively const getAllAccountsInFolder = (folderId: string): Account[] => { const directAccounts = getFolderAccounts(folderId); const childFoldersList = getChildFolders(folderId); const childAccounts = childFoldersList.flatMap((cf) => getAllAccountsInFolder(cf.id), ); return [...directAccounts, ...childAccounts]; }; const handleSelect = (newValue: string) => { if (newValue === "all") { onChange(["all"]); return; } let newSelection: string[]; if (isAll) { newSelection = [newValue]; } else if (value.includes(newValue)) { newSelection = value.filter((v) => v !== newValue); if (newSelection.length === 0) { newSelection = ["all"]; } } else { newSelection = [...value, newValue]; } onChange(newSelection); }; const handleSelectFolder = (folderId: string) => { const allFolderAccounts = getAllAccountsInFolder(folderId); const allFolderAccountIds = allFolderAccounts.map((a) => a.id); if (allFolderAccountIds.length === 0) return; const allSelected = allFolderAccountIds.every((id) => value.includes(id)); if (allSelected) { const newSelection = value.filter( (v) => !allFolderAccountIds.includes(v), ); onChange(newSelection.length > 0 ? newSelection : ["all"]); } else { if (isAll) { onChange(allFolderAccountIds); } else { const newSelection = [...new Set([...value, ...allFolderAccountIds])]; onChange(newSelection); } } }; const clearSelection = () => { onChange(["all"]); }; const isFolderSelected = (folderId: string) => { const folderAccounts = getAllAccountsInFolder(folderId); if (folderAccounts.length === 0) return false; return folderAccounts.every((a) => value.includes(a.id)); }; const isFolderPartiallySelected = (folderId: string) => { const folderAccounts = getAllAccountsInFolder(folderId); if (folderAccounts.length === 0) return false; const selectedCount = folderAccounts.filter((a) => value.includes(a.id), ).length; return selectedCount > 0 && selectedCount < folderAccounts.length; }; // Recursive render function - mirrors FolderTreeItem logic const renderFolder = (folder: Folder, depth: number, parentPath: string) => { const folderAccounts = getFolderAccounts(folder.id); const childFoldersList = getChildFolders(folder.id); const currentPath = parentPath ? `${parentPath} ${folder.name}` : folder.name; const paddingLeft = depth * 16 + 8; return (
{/* Folder row */} handleSelectFolder(folder.id)} style={{ paddingLeft: `${paddingLeft}px` }} className="font-medium" > {folder.name}
{isFolderPartiallySelected(folder.id) && (
)}
{/* Accounts in this folder */} {folderAccounts.map((account) => { const total = accountTotals[account.id]; const AccountIcon = accountTypeIcons[account.type]; return ( handleSelect(account.id)} style={{ paddingLeft: `${paddingLeft + 16}px` }} className="min-w-0" > {account.name} {total !== undefined && ( ({formatCurrency(total)}) )} ); })} {/* Child folders - recursive */} {childFoldersList.map((childFolder) => renderFolder(childFolder, depth + 1, currentPath), )}
); }; return ( e.preventDefault()} > Aucun compte trouvé. handleSelect("all")}> Tous les comptes {filteredTransactions && ( ( {formatCurrency( filteredTransactions.reduce( (sum, t) => sum + t.amount, 0, ), )} ) )} {rootFolders.map((folder) => renderFolder(folder, 0, ""))} {orphanAccounts.length > 0 && ( {orphanAccounts.map((account) => { const total = accountTotals[account.id]; const AccountIcon = accountTypeIcons[account.type]; return ( handleSelect(account.id)} className="min-w-0" > {account.name} {total !== undefined && ( ({formatCurrency(total)}) )} ); })} )} ); }