feat: implement enhanced transaction filtering capabilities with support for account and category filters, improving data visibility and user interaction

This commit is contained in:
Julien Froidefond
2025-11-29 19:19:21 +01:00
parent 921ee4a5f0
commit 5195f4adad
5 changed files with 234 additions and 47 deletions

View File

@@ -18,7 +18,7 @@ import {
import { CategoryIcon } from "@/components/ui/category-icon";
import { ChevronsUpDown, Check, Wallet, X } from "lucide-react";
import { cn } from "@/lib/utils";
import type { Account, Folder } from "@/lib/types";
import type { Account, Folder, Transaction } from "@/lib/types";
interface AccountFilterComboboxProps {
accounts: Account[];
@@ -26,6 +26,7 @@ interface AccountFilterComboboxProps {
value: string[]; // ["all"] | [accountId, accountId, ...]
onChange: (value: string[]) => void;
className?: string;
filteredTransactions?: Transaction[]; // Transactions filtered by other filters (categories, period, etc.)
}
export function AccountFilterCombobox({
@@ -34,9 +35,31 @@ export function AccountFilterCombobox({
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<string, number> = {};
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),
@@ -166,23 +189,31 @@ export function AccountFilterCombobox({
</CommandItem>
{/* Accounts in this folder */}
{folderAccounts.map((account) => (
<CommandItem
key={account.id}
value={`${currentPath} ${account.name}`}
onSelect={() => handleSelect(account.id)}
style={{ paddingLeft: `${paddingLeft + 16}px` }}
>
<Wallet className="h-4 w-4 text-muted-foreground" />
<span>{account.name}</span>
<Check
className={cn(
"ml-auto h-4 w-4",
value.includes(account.id) ? "opacity-100" : "opacity-0"
{folderAccounts.map((account) => {
const total = accountTotals[account.id];
return (
<CommandItem
key={account.id}
value={`${currentPath} ${account.name}`}
onSelect={() => handleSelect(account.id)}
style={{ paddingLeft: `${paddingLeft + 16}px` }}
>
<Wallet className="h-4 w-4 text-muted-foreground" />
<span>{account.name}</span>
{total !== undefined && (
<span className="text-xs text-muted-foreground ml-1">
({formatCurrency(total)})
</span>
)}
/>
</CommandItem>
))}
<Check
className={cn(
"ml-auto h-4 w-4",
value.includes(account.id) ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
);
})}
{/* Child folders - recursive */}
{childFoldersList.map((childFolder) =>
@@ -251,6 +282,13 @@ export function AccountFilterCombobox({
<CommandItem value="all" onSelect={() => handleSelect("all")}>
<Wallet className="h-4 w-4 text-muted-foreground" />
<span>Tous les comptes</span>
{filteredTransactions && (
<span className="text-xs text-muted-foreground ml-1">
({formatCurrency(
filteredTransactions.reduce((sum, t) => sum + t.amount, 0)
)})
</span>
)}
<Check
className={cn(
"ml-auto h-4 w-4",
@@ -266,22 +304,30 @@ export function AccountFilterCombobox({
{orphanAccounts.length > 0 && (
<CommandGroup heading="Sans dossier">
{orphanAccounts.map((account) => (
<CommandItem
key={account.id}
value={`sans-dossier ${account.name}`}
onSelect={() => handleSelect(account.id)}
>
<Wallet className="h-4 w-4 text-muted-foreground" />
<span>{account.name}</span>
<Check
className={cn(
"ml-auto h-4 w-4",
value.includes(account.id) ? "opacity-100" : "opacity-0"
{orphanAccounts.map((account) => {
const total = accountTotals[account.id];
return (
<CommandItem
key={account.id}
value={`sans-dossier ${account.name}`}
onSelect={() => handleSelect(account.id)}
>
<Wallet className="h-4 w-4 text-muted-foreground" />
<span>{account.name}</span>
{total !== undefined && (
<span className="text-xs text-muted-foreground ml-1">
({formatCurrency(total)})
</span>
)}
/>
</CommandItem>
))}
<Check
className={cn(
"ml-auto h-4 w-4",
value.includes(account.id) ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
);
})}
</CommandGroup>
)}
</CommandList>