feat: refactor transaction filters to support multiple account selection and improve UI with new account filter component
This commit is contained in:
@@ -27,12 +27,12 @@ export default function TransactionsPage() {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const { data, isLoading, refresh, update } = useBankingData();
|
const { data, isLoading, refresh, update } = useBankingData();
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [selectedAccount, setSelectedAccount] = useState<string>("all");
|
const [selectedAccounts, setSelectedAccounts] = useState<string[]>(["all"]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const accountId = searchParams.get("accountId");
|
const accountId = searchParams.get("accountId");
|
||||||
if (accountId) {
|
if (accountId) {
|
||||||
setSelectedAccount(accountId);
|
setSelectedAccounts([accountId]);
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ export default function TransactionsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedAccount !== "all") {
|
if (!selectedAccounts.includes("all")) {
|
||||||
transactions = transactions.filter(
|
transactions = transactions.filter(
|
||||||
(t) => t.accountId === selectedAccount
|
(t) => selectedAccounts.includes(t.accountId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ export default function TransactionsPage() {
|
|||||||
}, [
|
}, [
|
||||||
data,
|
data,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
selectedAccount,
|
selectedAccounts,
|
||||||
selectedCategories,
|
selectedCategories,
|
||||||
showReconciled,
|
showReconciled,
|
||||||
sortField,
|
sortField,
|
||||||
@@ -376,13 +376,14 @@ export default function TransactionsPage() {
|
|||||||
<TransactionFilters
|
<TransactionFilters
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
onSearchChange={setSearchQuery}
|
onSearchChange={setSearchQuery}
|
||||||
selectedAccount={selectedAccount}
|
selectedAccounts={selectedAccounts}
|
||||||
onAccountChange={setSelectedAccount}
|
onAccountsChange={setSelectedAccounts}
|
||||||
selectedCategories={selectedCategories}
|
selectedCategories={selectedCategories}
|
||||||
onCategoriesChange={setSelectedCategories}
|
onCategoriesChange={setSelectedCategories}
|
||||||
showReconciled={showReconciled}
|
showReconciled={showReconciled}
|
||||||
onReconciledChange={setShowReconciled}
|
onReconciledChange={setShowReconciled}
|
||||||
accounts={data.accounts}
|
accounts={data.accounts}
|
||||||
|
folders={data.folders}
|
||||||
categories={data.categories}
|
categories={data.categories}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -11,33 +11,36 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { CategoryFilterCombobox } from "@/components/ui/category-filter-combobox";
|
import { CategoryFilterCombobox } from "@/components/ui/category-filter-combobox";
|
||||||
|
import { AccountFilterCombobox } from "@/components/ui/account-filter-combobox";
|
||||||
import { CategoryIcon } from "@/components/ui/category-icon";
|
import { CategoryIcon } from "@/components/ui/category-icon";
|
||||||
import { Search, X, Filter } from "lucide-react";
|
import { Search, X, Filter, Wallet } from "lucide-react";
|
||||||
import type { Account, Category } from "@/lib/types";
|
import type { Account, Category, Folder } from "@/lib/types";
|
||||||
|
|
||||||
interface TransactionFiltersProps {
|
interface TransactionFiltersProps {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
onSearchChange: (query: string) => void;
|
onSearchChange: (query: string) => void;
|
||||||
selectedAccount: string;
|
selectedAccounts: string[];
|
||||||
onAccountChange: (account: string) => void;
|
onAccountsChange: (accounts: string[]) => void;
|
||||||
selectedCategories: string[];
|
selectedCategories: string[];
|
||||||
onCategoriesChange: (categories: string[]) => void;
|
onCategoriesChange: (categories: string[]) => void;
|
||||||
showReconciled: string;
|
showReconciled: string;
|
||||||
onReconciledChange: (value: string) => void;
|
onReconciledChange: (value: string) => void;
|
||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
|
folders: Folder[];
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TransactionFilters({
|
export function TransactionFilters({
|
||||||
searchQuery,
|
searchQuery,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
selectedAccount,
|
selectedAccounts,
|
||||||
onAccountChange,
|
onAccountsChange,
|
||||||
selectedCategories,
|
selectedCategories,
|
||||||
onCategoriesChange,
|
onCategoriesChange,
|
||||||
showReconciled,
|
showReconciled,
|
||||||
onReconciledChange,
|
onReconciledChange,
|
||||||
accounts,
|
accounts,
|
||||||
|
folders,
|
||||||
categories,
|
categories,
|
||||||
}: TransactionFiltersProps) {
|
}: TransactionFiltersProps) {
|
||||||
return (
|
return (
|
||||||
@@ -56,19 +59,13 @@ export function TransactionFilters({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Select value={selectedAccount} onValueChange={onAccountChange}>
|
<AccountFilterCombobox
|
||||||
<SelectTrigger className="w-[180px]">
|
accounts={accounts}
|
||||||
<SelectValue placeholder="Compte" />
|
folders={folders}
|
||||||
</SelectTrigger>
|
value={selectedAccounts}
|
||||||
<SelectContent>
|
onChange={onAccountsChange}
|
||||||
<SelectItem value="all">Tous les comptes</SelectItem>
|
className="w-[200px]"
|
||||||
{accounts.map((account) => (
|
/>
|
||||||
<SelectItem key={account.id} value={account.id}>
|
|
||||||
{account.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<CategoryFilterCombobox
|
<CategoryFilterCombobox
|
||||||
categories={categories}
|
categories={categories}
|
||||||
@@ -92,8 +89,12 @@ export function TransactionFilters({
|
|||||||
<ActiveFilters
|
<ActiveFilters
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
onClearSearch={() => onSearchChange("")}
|
onClearSearch={() => onSearchChange("")}
|
||||||
selectedAccount={selectedAccount}
|
selectedAccounts={selectedAccounts}
|
||||||
onClearAccount={() => onAccountChange("all")}
|
onRemoveAccount={(id) => {
|
||||||
|
const newAccounts = selectedAccounts.filter((a) => a !== id);
|
||||||
|
onAccountsChange(newAccounts.length > 0 ? newAccounts : ["all"]);
|
||||||
|
}}
|
||||||
|
onClearAccounts={() => onAccountsChange(["all"])}
|
||||||
selectedCategories={selectedCategories}
|
selectedCategories={selectedCategories}
|
||||||
onRemoveCategory={(id) => {
|
onRemoveCategory={(id) => {
|
||||||
const newCategories = selectedCategories.filter((c) => c !== id);
|
const newCategories = selectedCategories.filter((c) => c !== id);
|
||||||
@@ -113,8 +114,9 @@ export function TransactionFilters({
|
|||||||
function ActiveFilters({
|
function ActiveFilters({
|
||||||
searchQuery,
|
searchQuery,
|
||||||
onClearSearch,
|
onClearSearch,
|
||||||
selectedAccount,
|
selectedAccounts,
|
||||||
onClearAccount,
|
onRemoveAccount,
|
||||||
|
onClearAccounts,
|
||||||
selectedCategories,
|
selectedCategories,
|
||||||
onRemoveCategory,
|
onRemoveCategory,
|
||||||
onClearCategories,
|
onClearCategories,
|
||||||
@@ -125,8 +127,9 @@ function ActiveFilters({
|
|||||||
}: {
|
}: {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
onClearSearch: () => void;
|
onClearSearch: () => void;
|
||||||
selectedAccount: string;
|
selectedAccounts: string[];
|
||||||
onClearAccount: () => void;
|
onRemoveAccount: (id: string) => void;
|
||||||
|
onClearAccounts: () => void;
|
||||||
selectedCategories: string[];
|
selectedCategories: string[];
|
||||||
onRemoveCategory: (id: string) => void;
|
onRemoveCategory: (id: string) => void;
|
||||||
onClearCategories: () => void;
|
onClearCategories: () => void;
|
||||||
@@ -136,21 +139,21 @@ function ActiveFilters({
|
|||||||
categories: Category[];
|
categories: Category[];
|
||||||
}) {
|
}) {
|
||||||
const hasSearch = searchQuery.trim() !== "";
|
const hasSearch = searchQuery.trim() !== "";
|
||||||
const hasAccount = selectedAccount !== "all";
|
const hasAccounts = !selectedAccounts.includes("all");
|
||||||
const hasCategories = !selectedCategories.includes("all");
|
const hasCategories = !selectedCategories.includes("all");
|
||||||
const hasReconciled = showReconciled !== "all";
|
const hasReconciled = showReconciled !== "all";
|
||||||
|
|
||||||
const hasActiveFilters = hasSearch || hasAccount || hasCategories || hasReconciled;
|
const hasActiveFilters = hasSearch || hasAccounts || hasCategories || hasReconciled;
|
||||||
|
|
||||||
if (!hasActiveFilters) return null;
|
if (!hasActiveFilters) return null;
|
||||||
|
|
||||||
const account = accounts.find((a) => a.id === selectedAccount);
|
const selectedAccs = accounts.filter((a) => selectedAccounts.includes(a.id));
|
||||||
const selectedCats = categories.filter((c) => selectedCategories.includes(c.id));
|
const selectedCats = categories.filter((c) => selectedCategories.includes(c.id));
|
||||||
const isUncategorized = selectedCategories.includes("uncategorized");
|
const isUncategorized = selectedCategories.includes("uncategorized");
|
||||||
|
|
||||||
const clearAll = () => {
|
const clearAll = () => {
|
||||||
onClearSearch();
|
onClearSearch();
|
||||||
onClearAccount();
|
onClearAccounts();
|
||||||
onClearCategories();
|
onClearCategories();
|
||||||
onClearReconciled();
|
onClearReconciled();
|
||||||
};
|
};
|
||||||
@@ -168,14 +171,18 @@ function ActiveFilters({
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasAccount && account && (
|
{selectedAccs.map((acc) => (
|
||||||
<Badge variant="secondary" className="gap-1 text-xs font-normal">
|
<Badge key={acc.id} variant="secondary" className="gap-1 text-xs font-normal">
|
||||||
Compte: {account.name}
|
<Wallet className="h-3 w-3" />
|
||||||
<button onClick={onClearAccount} className="ml-1 hover:text-foreground">
|
{acc.name}
|
||||||
|
<button
|
||||||
|
onClick={() => onRemoveAccount(acc.id)}
|
||||||
|
className="ml-1 hover:text-foreground"
|
||||||
|
>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
))}
|
||||||
|
|
||||||
{isUncategorized && (
|
{isUncategorized && (
|
||||||
<Badge variant="secondary" className="gap-1 text-xs font-normal">
|
<Badge variant="secondary" className="gap-1 text-xs font-normal">
|
||||||
|
|||||||
292
components/ui/account-filter-combobox.tsx
Normal file
292
components/ui/account-filter-combobox.tsx
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
"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 } from "@/lib/types";
|
||||||
|
|
||||||
|
interface AccountFilterComboboxProps {
|
||||||
|
accounts: Account[];
|
||||||
|
folders: Folder[];
|
||||||
|
value: string[]; // ["all"] | [accountId, accountId, ...]
|
||||||
|
onChange: (value: string[]) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AccountFilterCombobox({
|
||||||
|
accounts,
|
||||||
|
folders,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
}: AccountFilterComboboxProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<div key={folder.id}>
|
||||||
|
{/* Folder row */}
|
||||||
|
<CommandItem
|
||||||
|
value={`folder-${currentPath}`}
|
||||||
|
onSelect={() => handleSelectFolder(folder.id)}
|
||||||
|
style={{ paddingLeft: `${paddingLeft}px` }}
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
|
<CategoryIcon icon={folder.icon} color={folder.color} size={16} />
|
||||||
|
<span>{folder.name}</span>
|
||||||
|
<div className="ml-auto flex items-center">
|
||||||
|
{isFolderPartiallySelected(folder.id) && (
|
||||||
|
<div className="h-3 w-3 rounded-sm bg-primary/50 mr-1" />
|
||||||
|
)}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"h-4 w-4",
|
||||||
|
isFolderSelected(folder.id) ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</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"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Child folders - recursive */}
|
||||||
|
{childFoldersList.map((childFolder) =>
|
||||||
|
renderFolder(childFolder, depth + 1, currentPath)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className={cn("justify-between", className)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
|
{selectedAccounts.length === 1 ? (
|
||||||
|
<>
|
||||||
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span className="truncate">{selectedAccounts[0].name}</span>
|
||||||
|
</>
|
||||||
|
) : selectedAccounts.length > 1 ? (
|
||||||
|
<>
|
||||||
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span className="truncate">
|
||||||
|
{selectedAccounts.length} comptes
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span className="text-muted-foreground">Tous les comptes</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{!isAll && (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
className="h-4 w-4 rounded-sm hover:bg-muted flex items-center justify-center"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
clearSelection();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-[300px] p-0"
|
||||||
|
align="start"
|
||||||
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Rechercher..." />
|
||||||
|
<CommandList className="max-h-[300px]">
|
||||||
|
<CommandEmpty>Aucun compte trouvé.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem value="all" onSelect={() => handleSelect("all")}>
|
||||||
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span>Tous les comptes</span>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
isAll ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
|
||||||
|
<CommandGroup>
|
||||||
|
{rootFolders.map((folder) => renderFolder(folder, 0, ""))}
|
||||||
|
</CommandGroup>
|
||||||
|
|
||||||
|
{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"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user