From 55f0e5c625522dd9cb63121d22309481d2e19c11 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 21 Dec 2025 07:38:57 +0100 Subject: [PATCH] feat: enhance category filter functionality to include child categories in selection and deselection; update transaction page to expand selected categories based on parent-child relationships --- components/ui/category-filter-combobox.tsx | 49 +++++++++++++++++++--- hooks/use-transactions-page.ts | 27 ++++++++++-- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/components/ui/category-filter-combobox.tsx b/components/ui/category-filter-combobox.tsx index 7f7dea2..2a720bd 100644 --- a/components/ui/category-filter-combobox.tsx +++ b/components/ui/category-filter-combobox.tsx @@ -87,21 +87,58 @@ export function CategoryFilterCombobox({ return; } + // Check if this is a parent category + const isParentCategory = parentCategories.some((p) => p.id === newValue); + const childCategories = isParentCategory + ? childrenByParent[newValue] || [] + : []; + // Category selection - toggle let newSelection: string[]; if (isAll || isUncategorized) { - // Start fresh with just this category - newSelection = [newValue]; + // Start fresh with this category and its children (if parent) + if (isParentCategory && childCategories.length > 0) { + newSelection = [ + newValue, + ...childCategories.map((child) => child.id), + ]; + } else { + newSelection = [newValue]; + } } else if (value.includes(newValue)) { - // Remove category - newSelection = value.filter((v) => v !== newValue); + // Remove category and its children (if parent) + if (isParentCategory && childCategories.length > 0) { + const childIds = childCategories.map((child) => child.id); + newSelection = value.filter( + (v) => v !== newValue && !childIds.includes(v) + ); + } else { + newSelection = value.filter((v) => v !== newValue); + } if (newSelection.length === 0) { newSelection = ["all"]; } } else { - // Add category - newSelection = [...value, newValue]; + // Add category and its children (if parent) + if (isParentCategory && childCategories.length > 0) { + const childIds = childCategories.map((child) => child.id); + newSelection = [ + ...value.filter((v) => !childIds.includes(v)), // Remove any existing children + newValue, + ...childIds, + ]; + } else { + // Check if this child's parent is already selected + const category = categories.find((c) => c.id === newValue); + if (category?.parentId && value.includes(category.parentId)) { + // Parent is selected, so we're adding a child - keep parent + newSelection = [...value, newValue]; + } else { + // Regular add + newSelection = [...value, newValue]; + } + } } onChange(newSelection); diff --git a/hooks/use-transactions-page.ts b/hooks/use-transactions-page.ts index 8341d4c..df941d9 100644 --- a/hooks/use-transactions-page.ts +++ b/hooks/use-transactions-page.ts @@ -8,6 +8,7 @@ import { useDuplicateIds, } from "@/lib/hooks"; import type { TransactionsPaginatedParams } from "@/services/banking.service"; +import type { Category } from "@/lib/types"; type SortField = "date" | "amount" | "description"; type SortOrder = "asc" | "desc"; @@ -74,15 +75,35 @@ export function useTransactionsPage() { const categoryIdsParam = searchParams.get("categoryIds"); const includeUncategorizedParam = searchParams.get("includeUncategorized"); - if (categoryIdsParam) { + if (categoryIdsParam && metadata) { const categoryIds = categoryIdsParam.split(","); - setSelectedCategories(categoryIds); + + // Expand parent categories to include their children + const expandedCategoryIds = new Set(categoryIds); + + categoryIds.forEach((categoryId) => { + // Check if this is a parent category + const category = metadata.categories.find( + (c: Category) => c.id === categoryId + ); + if (category && category.parentId === null) { + // Find all children of this parent + const children = metadata.categories.filter( + (c: Category) => c.parentId === categoryId + ); + children.forEach((child: Category) => { + expandedCategoryIds.add(child.id); + }); + } + }); + + setSelectedCategories(Array.from(expandedCategoryIds)); setPage(0); } else if (includeUncategorizedParam === "true") { setSelectedCategories(["uncategorized"]); setPage(0); } - }, [searchParams]); + }, [searchParams, metadata]); // Calculate start date based on period const startDate = useMemo(() => {