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
This commit is contained in:
@@ -87,21 +87,58 @@ export function CategoryFilterCombobox({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a parent category
|
||||||
|
const isParentCategory = parentCategories.some((p) => p.id === newValue);
|
||||||
|
const childCategories = isParentCategory
|
||||||
|
? childrenByParent[newValue] || []
|
||||||
|
: [];
|
||||||
|
|
||||||
// Category selection - toggle
|
// Category selection - toggle
|
||||||
let newSelection: string[];
|
let newSelection: string[];
|
||||||
|
|
||||||
if (isAll || isUncategorized) {
|
if (isAll || isUncategorized) {
|
||||||
// Start fresh with just this category
|
// Start fresh with this category and its children (if parent)
|
||||||
newSelection = [newValue];
|
if (isParentCategory && childCategories.length > 0) {
|
||||||
|
newSelection = [
|
||||||
|
newValue,
|
||||||
|
...childCategories.map((child) => child.id),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
newSelection = [newValue];
|
||||||
|
}
|
||||||
} else if (value.includes(newValue)) {
|
} else if (value.includes(newValue)) {
|
||||||
// Remove category
|
// Remove category and its children (if parent)
|
||||||
newSelection = value.filter((v) => v !== newValue);
|
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) {
|
if (newSelection.length === 0) {
|
||||||
newSelection = ["all"];
|
newSelection = ["all"];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add category
|
// Add category and its children (if parent)
|
||||||
newSelection = [...value, newValue];
|
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);
|
onChange(newSelection);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
useDuplicateIds,
|
useDuplicateIds,
|
||||||
} from "@/lib/hooks";
|
} from "@/lib/hooks";
|
||||||
import type { TransactionsPaginatedParams } from "@/services/banking.service";
|
import type { TransactionsPaginatedParams } from "@/services/banking.service";
|
||||||
|
import type { Category } from "@/lib/types";
|
||||||
|
|
||||||
type SortField = "date" | "amount" | "description";
|
type SortField = "date" | "amount" | "description";
|
||||||
type SortOrder = "asc" | "desc";
|
type SortOrder = "asc" | "desc";
|
||||||
@@ -74,15 +75,35 @@ export function useTransactionsPage() {
|
|||||||
const categoryIdsParam = searchParams.get("categoryIds");
|
const categoryIdsParam = searchParams.get("categoryIds");
|
||||||
const includeUncategorizedParam = searchParams.get("includeUncategorized");
|
const includeUncategorizedParam = searchParams.get("includeUncategorized");
|
||||||
|
|
||||||
if (categoryIdsParam) {
|
if (categoryIdsParam && metadata) {
|
||||||
const categoryIds = categoryIdsParam.split(",");
|
const categoryIds = categoryIdsParam.split(",");
|
||||||
setSelectedCategories(categoryIds);
|
|
||||||
|
// Expand parent categories to include their children
|
||||||
|
const expandedCategoryIds = new Set<string>(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);
|
setPage(0);
|
||||||
} else if (includeUncategorizedParam === "true") {
|
} else if (includeUncategorizedParam === "true") {
|
||||||
setSelectedCategories(["uncategorized"]);
|
setSelectedCategories(["uncategorized"]);
|
||||||
setPage(0);
|
setPage(0);
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams, metadata]);
|
||||||
|
|
||||||
// Calculate start date based on period
|
// Calculate start date based on period
|
||||||
const startDate = useMemo(() => {
|
const startDate = useMemo(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user