feat: implement enhanced transaction filtering capabilities with support for account and category filters, improving data visibility and user interaction
This commit is contained in:
@@ -33,25 +33,55 @@ export default function StatisticsPage() {
|
|||||||
const [selectedAccounts, setSelectedAccounts] = useState<string[]>(["all"]);
|
const [selectedAccounts, setSelectedAccounts] = useState<string[]>(["all"]);
|
||||||
const [selectedCategories, setSelectedCategories] = useState<string[]>(["all"]);
|
const [selectedCategories, setSelectedCategories] = useState<string[]>(["all"]);
|
||||||
|
|
||||||
const stats = useMemo(() => {
|
// Get start date based on period
|
||||||
if (!data) return null;
|
const startDate = useMemo(() => {
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let startDate: Date;
|
|
||||||
|
|
||||||
switch (period) {
|
switch (period) {
|
||||||
case "3months":
|
case "3months":
|
||||||
startDate = new Date(now.getFullYear(), now.getMonth() - 3, 1);
|
return new Date(now.getFullYear(), now.getMonth() - 3, 1);
|
||||||
break;
|
|
||||||
case "6months":
|
case "6months":
|
||||||
startDate = new Date(now.getFullYear(), now.getMonth() - 6, 1);
|
return new Date(now.getFullYear(), now.getMonth() - 6, 1);
|
||||||
break;
|
|
||||||
case "12months":
|
case "12months":
|
||||||
startDate = new Date(now.getFullYear(), now.getMonth() - 12, 1);
|
return new Date(now.getFullYear(), now.getMonth() - 12, 1);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
startDate = new Date(0);
|
return new Date(0);
|
||||||
}
|
}
|
||||||
|
}, [period]);
|
||||||
|
|
||||||
|
// Transactions filtered for account filter (by categories, period - not accounts)
|
||||||
|
const transactionsForAccountFilter = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
|
||||||
|
return data.transactions.filter(
|
||||||
|
(t) => new Date(t.date) >= startDate
|
||||||
|
).filter((t) => {
|
||||||
|
if (!selectedCategories.includes("all")) {
|
||||||
|
if (selectedCategories.includes("uncategorized")) {
|
||||||
|
return !t.categoryId;
|
||||||
|
} else {
|
||||||
|
return t.categoryId && selectedCategories.includes(t.categoryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [data, startDate, selectedCategories]);
|
||||||
|
|
||||||
|
// Transactions filtered for category filter (by accounts, period - not categories)
|
||||||
|
const transactionsForCategoryFilter = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
|
||||||
|
return data.transactions.filter(
|
||||||
|
(t) => new Date(t.date) >= startDate
|
||||||
|
).filter((t) => {
|
||||||
|
if (!selectedAccounts.includes("all")) {
|
||||||
|
return selectedAccounts.includes(t.accountId);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [data, startDate, selectedAccounts]);
|
||||||
|
|
||||||
|
const stats = useMemo(() => {
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
let transactions = data.transactions.filter(
|
let transactions = data.transactions.filter(
|
||||||
(t) => new Date(t.date) >= startDate
|
(t) => new Date(t.date) >= startDate
|
||||||
@@ -226,7 +256,7 @@ export default function StatisticsPage() {
|
|||||||
perAccountBalanceData,
|
perAccountBalanceData,
|
||||||
transactionCount: transactions.length,
|
transactionCount: transactions.length,
|
||||||
};
|
};
|
||||||
}, [data, period, selectedAccounts, selectedCategories]);
|
}, [data, startDate, selectedAccounts, selectedCategories]);
|
||||||
|
|
||||||
const formatCurrency = (amount: number) => {
|
const formatCurrency = (amount: number) => {
|
||||||
return new Intl.NumberFormat("fr-FR", {
|
return new Intl.NumberFormat("fr-FR", {
|
||||||
@@ -255,6 +285,7 @@ export default function StatisticsPage() {
|
|||||||
value={selectedAccounts}
|
value={selectedAccounts}
|
||||||
onChange={setSelectedAccounts}
|
onChange={setSelectedAccounts}
|
||||||
className="w-[200px]"
|
className="w-[200px]"
|
||||||
|
filteredTransactions={transactionsForAccountFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CategoryFilterCombobox
|
<CategoryFilterCombobox
|
||||||
@@ -262,6 +293,7 @@ export default function StatisticsPage() {
|
|||||||
value={selectedCategories}
|
value={selectedCategories}
|
||||||
onChange={setSelectedCategories}
|
onChange={setSelectedCategories}
|
||||||
className="w-[220px]"
|
className="w-[220px]"
|
||||||
|
filteredTransactions={transactionsForCategoryFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -46,6 +46,72 @@ export default function TransactionsPage() {
|
|||||||
const [ruleDialogOpen, setRuleDialogOpen] = useState(false);
|
const [ruleDialogOpen, setRuleDialogOpen] = useState(false);
|
||||||
const [ruleTransaction, setRuleTransaction] = useState<Transaction | null>(null);
|
const [ruleTransaction, setRuleTransaction] = useState<Transaction | null>(null);
|
||||||
|
|
||||||
|
// Transactions filtered for account filter (by categories, search, reconciled - not accounts)
|
||||||
|
const transactionsForAccountFilter = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
|
||||||
|
let transactions = [...data.transactions];
|
||||||
|
|
||||||
|
if (searchQuery) {
|
||||||
|
const query = searchQuery.toLowerCase();
|
||||||
|
transactions = transactions.filter(
|
||||||
|
(t) =>
|
||||||
|
t.description.toLowerCase().includes(query) ||
|
||||||
|
t.memo?.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedCategories.includes("all")) {
|
||||||
|
if (selectedCategories.includes("uncategorized")) {
|
||||||
|
transactions = transactions.filter((t) => !t.categoryId);
|
||||||
|
} else {
|
||||||
|
transactions = transactions.filter(
|
||||||
|
(t) => t.categoryId && selectedCategories.includes(t.categoryId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showReconciled !== "all") {
|
||||||
|
const isReconciled = showReconciled === "reconciled";
|
||||||
|
transactions = transactions.filter(
|
||||||
|
(t) => t.isReconciled === isReconciled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
}, [data, searchQuery, selectedCategories, showReconciled]);
|
||||||
|
|
||||||
|
// Transactions filtered for category filter (by accounts, search, reconciled - not categories)
|
||||||
|
const transactionsForCategoryFilter = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
|
||||||
|
let transactions = [...data.transactions];
|
||||||
|
|
||||||
|
if (searchQuery) {
|
||||||
|
const query = searchQuery.toLowerCase();
|
||||||
|
transactions = transactions.filter(
|
||||||
|
(t) =>
|
||||||
|
t.description.toLowerCase().includes(query) ||
|
||||||
|
t.memo?.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedAccounts.includes("all")) {
|
||||||
|
transactions = transactions.filter(
|
||||||
|
(t) => selectedAccounts.includes(t.accountId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showReconciled !== "all") {
|
||||||
|
const isReconciled = showReconciled === "reconciled";
|
||||||
|
transactions = transactions.filter(
|
||||||
|
(t) => t.isReconciled === isReconciled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
}, [data, searchQuery, selectedAccounts, showReconciled]);
|
||||||
|
|
||||||
const filteredTransactions = useMemo(() => {
|
const filteredTransactions = useMemo(() => {
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
|
|
||||||
@@ -385,6 +451,8 @@ export default function TransactionsPage() {
|
|||||||
accounts={data.accounts}
|
accounts={data.accounts}
|
||||||
folders={data.folders}
|
folders={data.folders}
|
||||||
categories={data.categories}
|
categories={data.categories}
|
||||||
|
transactionsForAccountFilter={transactionsForAccountFilter}
|
||||||
|
transactionsForCategoryFilter={transactionsForCategoryFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TransactionBulkActions
|
<TransactionBulkActions
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { CategoryFilterCombobox } from "@/components/ui/category-filter-combobox
|
|||||||
import { AccountFilterCombobox } from "@/components/ui/account-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, Wallet } from "lucide-react";
|
import { Search, X, Filter, Wallet } from "lucide-react";
|
||||||
import type { Account, Category, Folder } from "@/lib/types";
|
import type { Account, Category, Folder, Transaction } from "@/lib/types";
|
||||||
|
|
||||||
interface TransactionFiltersProps {
|
interface TransactionFiltersProps {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
@@ -28,6 +28,8 @@ interface TransactionFiltersProps {
|
|||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
folders: Folder[];
|
folders: Folder[];
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
|
transactionsForAccountFilter?: Transaction[]; // Filtered by categories, search, reconciled (not accounts)
|
||||||
|
transactionsForCategoryFilter?: Transaction[]; // Filtered by accounts, search, reconciled (not categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TransactionFilters({
|
export function TransactionFilters({
|
||||||
@@ -42,6 +44,8 @@ export function TransactionFilters({
|
|||||||
accounts,
|
accounts,
|
||||||
folders,
|
folders,
|
||||||
categories,
|
categories,
|
||||||
|
transactionsForAccountFilter,
|
||||||
|
transactionsForCategoryFilter,
|
||||||
}: TransactionFiltersProps) {
|
}: TransactionFiltersProps) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -65,6 +69,7 @@ export function TransactionFilters({
|
|||||||
value={selectedAccounts}
|
value={selectedAccounts}
|
||||||
onChange={onAccountsChange}
|
onChange={onAccountsChange}
|
||||||
className="w-[200px]"
|
className="w-[200px]"
|
||||||
|
filteredTransactions={transactionsForAccountFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CategoryFilterCombobox
|
<CategoryFilterCombobox
|
||||||
@@ -72,6 +77,7 @@ export function TransactionFilters({
|
|||||||
value={selectedCategories}
|
value={selectedCategories}
|
||||||
onChange={onCategoriesChange}
|
onChange={onCategoriesChange}
|
||||||
className="w-[220px]"
|
className="w-[220px]"
|
||||||
|
filteredTransactions={transactionsForCategoryFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select value={showReconciled} onValueChange={onReconciledChange}>
|
<Select value={showReconciled} onValueChange={onReconciledChange}>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import { CategoryIcon } from "@/components/ui/category-icon";
|
import { CategoryIcon } from "@/components/ui/category-icon";
|
||||||
import { ChevronsUpDown, Check, Wallet, X } from "lucide-react";
|
import { ChevronsUpDown, Check, Wallet, X } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { Account, Folder } from "@/lib/types";
|
import type { Account, Folder, Transaction } from "@/lib/types";
|
||||||
|
|
||||||
interface AccountFilterComboboxProps {
|
interface AccountFilterComboboxProps {
|
||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
@@ -26,6 +26,7 @@ interface AccountFilterComboboxProps {
|
|||||||
value: string[]; // ["all"] | [accountId, accountId, ...]
|
value: string[]; // ["all"] | [accountId, accountId, ...]
|
||||||
onChange: (value: string[]) => void;
|
onChange: (value: string[]) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
filteredTransactions?: Transaction[]; // Transactions filtered by other filters (categories, period, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AccountFilterCombobox({
|
export function AccountFilterCombobox({
|
||||||
@@ -34,9 +35,31 @@ export function AccountFilterCombobox({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
|
filteredTransactions,
|
||||||
}: AccountFilterComboboxProps) {
|
}: AccountFilterComboboxProps) {
|
||||||
const [open, setOpen] = useState(false);
|
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
|
// Get root folders (folders without parent) - same as folders/page.tsx
|
||||||
const rootFolders = useMemo(
|
const rootFolders = useMemo(
|
||||||
() => folders.filter((f) => f.parentId === null),
|
() => folders.filter((f) => f.parentId === null),
|
||||||
@@ -166,7 +189,9 @@ export function AccountFilterCombobox({
|
|||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
|
||||||
{/* Accounts in this folder */}
|
{/* Accounts in this folder */}
|
||||||
{folderAccounts.map((account) => (
|
{folderAccounts.map((account) => {
|
||||||
|
const total = accountTotals[account.id];
|
||||||
|
return (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={account.id}
|
key={account.id}
|
||||||
value={`${currentPath} ${account.name}`}
|
value={`${currentPath} ${account.name}`}
|
||||||
@@ -175,6 +200,11 @@ export function AccountFilterCombobox({
|
|||||||
>
|
>
|
||||||
<Wallet className="h-4 w-4 text-muted-foreground" />
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
||||||
<span>{account.name}</span>
|
<span>{account.name}</span>
|
||||||
|
{total !== undefined && (
|
||||||
|
<span className="text-xs text-muted-foreground ml-1">
|
||||||
|
({formatCurrency(total)})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
@@ -182,7 +212,8 @@ export function AccountFilterCombobox({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{/* Child folders - recursive */}
|
{/* Child folders - recursive */}
|
||||||
{childFoldersList.map((childFolder) =>
|
{childFoldersList.map((childFolder) =>
|
||||||
@@ -251,6 +282,13 @@ export function AccountFilterCombobox({
|
|||||||
<CommandItem value="all" onSelect={() => handleSelect("all")}>
|
<CommandItem value="all" onSelect={() => handleSelect("all")}>
|
||||||
<Wallet className="h-4 w-4 text-muted-foreground" />
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
||||||
<span>Tous les comptes</span>
|
<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
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
@@ -266,7 +304,9 @@ export function AccountFilterCombobox({
|
|||||||
|
|
||||||
{orphanAccounts.length > 0 && (
|
{orphanAccounts.length > 0 && (
|
||||||
<CommandGroup heading="Sans dossier">
|
<CommandGroup heading="Sans dossier">
|
||||||
{orphanAccounts.map((account) => (
|
{orphanAccounts.map((account) => {
|
||||||
|
const total = accountTotals[account.id];
|
||||||
|
return (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={account.id}
|
key={account.id}
|
||||||
value={`sans-dossier ${account.name}`}
|
value={`sans-dossier ${account.name}`}
|
||||||
@@ -274,6 +314,11 @@ export function AccountFilterCombobox({
|
|||||||
>
|
>
|
||||||
<Wallet className="h-4 w-4 text-muted-foreground" />
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
||||||
<span>{account.name}</span>
|
<span>{account.name}</span>
|
||||||
|
{total !== undefined && (
|
||||||
|
<span className="text-xs text-muted-foreground ml-1">
|
||||||
|
({formatCurrency(total)})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
@@ -281,7 +326,8 @@ export function AccountFilterCombobox({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
)}
|
)}
|
||||||
</CommandList>
|
</CommandList>
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ import {
|
|||||||
import { CategoryIcon } from "@/components/ui/category-icon";
|
import { CategoryIcon } from "@/components/ui/category-icon";
|
||||||
import { ChevronsUpDown, Check, Tags, CircleSlash, X } from "lucide-react";
|
import { ChevronsUpDown, Check, Tags, CircleSlash, X } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { Category } from "@/lib/types";
|
import type { Category, Transaction } from "@/lib/types";
|
||||||
|
|
||||||
interface CategoryFilterComboboxProps {
|
interface CategoryFilterComboboxProps {
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
value: string[]; // ["all"] | ["uncategorized"] | [categoryId, categoryId, ...]
|
value: string[]; // ["all"] | ["uncategorized"] | [categoryId, categoryId, ...]
|
||||||
onChange: (value: string[]) => void;
|
onChange: (value: string[]) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
filteredTransactions?: Transaction[]; // Transactions filtered by other filters (accounts, period, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CategoryFilterCombobox({
|
export function CategoryFilterCombobox({
|
||||||
@@ -32,9 +33,23 @@ export function CategoryFilterCombobox({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
|
filteredTransactions,
|
||||||
}: CategoryFilterComboboxProps) {
|
}: CategoryFilterComboboxProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
// Calculate transaction counts per category based on filtered transactions
|
||||||
|
const categoryCounts = useMemo(() => {
|
||||||
|
if (!filteredTransactions) return {};
|
||||||
|
|
||||||
|
const counts: Record<string, number> = {};
|
||||||
|
filteredTransactions.forEach((t) => {
|
||||||
|
const catId = t.categoryId || "uncategorized";
|
||||||
|
counts[catId] = (counts[catId] || 0) + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return counts;
|
||||||
|
}, [filteredTransactions]);
|
||||||
|
|
||||||
// Organize categories by parent
|
// Organize categories by parent
|
||||||
const { parentCategories, childrenByParent } = useMemo(() => {
|
const { parentCategories, childrenByParent } = useMemo(() => {
|
||||||
const parents = categories.filter((c) => c.parentId === null);
|
const parents = categories.filter((c) => c.parentId === null);
|
||||||
@@ -178,6 +193,11 @@ export function CategoryFilterCombobox({
|
|||||||
<CommandItem value="all" onSelect={() => handleSelect("all")}>
|
<CommandItem value="all" onSelect={() => handleSelect("all")}>
|
||||||
<Tags className="h-4 w-4 text-muted-foreground" />
|
<Tags className="h-4 w-4 text-muted-foreground" />
|
||||||
<span>Toutes catégories</span>
|
<span>Toutes catégories</span>
|
||||||
|
{filteredTransactions && (
|
||||||
|
<span className="text-xs text-muted-foreground ml-1">
|
||||||
|
({filteredTransactions.length})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
@@ -191,6 +211,11 @@ export function CategoryFilterCombobox({
|
|||||||
>
|
>
|
||||||
<CircleSlash className="h-4 w-4 text-muted-foreground" />
|
<CircleSlash className="h-4 w-4 text-muted-foreground" />
|
||||||
<span>Non catégorisé</span>
|
<span>Non catégorisé</span>
|
||||||
|
{categoryCounts["uncategorized"] !== undefined && (
|
||||||
|
<span className="text-xs text-muted-foreground ml-1">
|
||||||
|
({categoryCounts["uncategorized"]})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
@@ -212,6 +237,11 @@ export function CategoryFilterCombobox({
|
|||||||
size={16}
|
size={16}
|
||||||
/>
|
/>
|
||||||
<span className="font-medium">{parent.name}</span>
|
<span className="font-medium">{parent.name}</span>
|
||||||
|
{categoryCounts[parent.id] !== undefined && (
|
||||||
|
<span className="text-xs text-muted-foreground ml-1">
|
||||||
|
({categoryCounts[parent.id]})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
@@ -232,6 +262,11 @@ export function CategoryFilterCombobox({
|
|||||||
size={16}
|
size={16}
|
||||||
/>
|
/>
|
||||||
<span>{child.name}</span>
|
<span>{child.name}</span>
|
||||||
|
{categoryCounts[child.id] !== undefined && (
|
||||||
|
<span className="text-xs text-muted-foreground ml-1">
|
||||||
|
({categoryCounts[child.id]})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
|
|||||||
Reference in New Issue
Block a user