From 8b62cd8385b5ffdfa54f626ba844c758d693b2da Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 21 Dec 2025 08:29:10 +0100 Subject: [PATCH] feat: add account and category filter functionality to TransactionsPage; implement hooks for filtering transactions based on selected accounts and categories --- app/transactions/page.tsx | 29 ++++- hooks/use-transactions-for-account-filter.ts | 109 ++++++++++++++++++ hooks/use-transactions-for-category-filter.ts | 105 +++++++++++++++++ 3 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 hooks/use-transactions-for-account-filter.ts create mode 100644 hooks/use-transactions-for-category-filter.ts diff --git a/app/transactions/page.tsx b/app/transactions/page.tsx index cdc5eff..08e35f4 100644 --- a/app/transactions/page.tsx +++ b/app/transactions/page.tsx @@ -27,6 +27,8 @@ import { useTransactionsPage } from "@/hooks/use-transactions-page"; import { useTransactionMutations } from "@/hooks/use-transaction-mutations"; import { useTransactionRules } from "@/hooks/use-transaction-rules"; import { useTransactionsChartData } from "@/hooks/use-transactions-chart-data"; +import { useTransactionsForAccountFilter } from "@/hooks/use-transactions-for-account-filter"; +import { useTransactionsForCategoryFilter } from "@/hooks/use-transactions-for-category-filter"; import { useLocalStorage } from "@/hooks/use-local-storage"; export default function TransactionsPage() { @@ -113,6 +115,28 @@ export default function TransactionsPage() { searchQuery, }); + // Transactions for account filter (filtered by categories, period, search, reconciled - NOT by accounts) + const { transactions: transactionsForAccountFilter } = + useTransactionsForAccountFilter({ + selectedCategories, + period, + customStartDate, + customEndDate, + showReconciled, + searchQuery, + }); + + // Transactions for category filter (filtered by accounts, period, search, reconciled - NOT by categories) + const { transactions: transactionsForCategoryFilter } = + useTransactionsForCategoryFilter({ + selectedAccounts, + period, + customStartDate, + customEndDate, + showReconciled, + searchQuery, + }); + const invalidateAll = useCallback(() => { invalidateTransactions(); queryClient.invalidateQueries({ queryKey: ["banking-metadata"] }); @@ -157,11 +181,6 @@ export default function TransactionsPage() { true ); - // For filter comboboxes, we'll use empty arrays for now - // They can be enhanced later with separate queries if needed - const transactionsForAccountFilter: never[] = []; - const transactionsForCategoryFilter: never[] = []; - // Early return for loading state - prevents sidebar flash if (isLoadingMetadata || !metadata) { return ( diff --git a/hooks/use-transactions-for-account-filter.ts b/hooks/use-transactions-for-account-filter.ts new file mode 100644 index 0000000..98e63c8 --- /dev/null +++ b/hooks/use-transactions-for-account-filter.ts @@ -0,0 +1,109 @@ +"use client"; + +import { useMemo } from "react"; +import { useTransactions } from "@/lib/hooks"; +import { useBankingMetadata } from "@/lib/hooks"; +import type { TransactionsPaginatedParams } from "@/services/banking.service"; + +interface UseTransactionsForAccountFilterParams { + selectedCategories: string[]; + period: "1month" | "3months" | "6months" | "12months" | "custom" | "all"; + customStartDate?: Date; + customEndDate?: Date; + showReconciled: string; + searchQuery: string; +} + +/** + * Hook to fetch transactions filtered by categories, period, search, reconciled + * but NOT by accounts. Used for displaying account totals in account filter. + */ +export function useTransactionsForAccountFilter({ + selectedCategories, + period, + customStartDate, + customEndDate, + showReconciled, + searchQuery, +}: UseTransactionsForAccountFilterParams) { + const { data: metadata } = useBankingMetadata(); + + // Calculate start date based on period + const startDate = useMemo(() => { + const now = new Date(); + switch (period) { + case "1month": + return new Date(now.getFullYear(), now.getMonth() - 1, 1); + case "3months": + return new Date(now.getFullYear(), now.getMonth() - 3, 1); + case "6months": + return new Date(now.getFullYear(), now.getMonth() - 6, 1); + case "12months": + return new Date(now.getFullYear(), now.getMonth() - 12, 1); + case "custom": + return customStartDate || new Date(0); + default: + return new Date(0); + } + }, [period, customStartDate]); + + // Calculate end date (only for custom period) + const endDate = useMemo(() => { + if (period === "custom" && customEndDate) { + return customEndDate; + } + return undefined; + }, [period, customEndDate]); + + // Build params for fetching all transactions (no pagination, no account filter) + const filterParams = useMemo(() => { + const params: TransactionsPaginatedParams = { + limit: 10000, // Large limit to get all transactions + offset: 0, + sortField: "date", + sortOrder: "asc", + }; + + if (startDate && period !== "all") { + params.startDate = startDate.toISOString().split("T")[0]; + } + if (endDate) { + params.endDate = endDate.toISOString().split("T")[0]; + } + // NOTE: We intentionally don't filter by accounts here + if (!selectedCategories.includes("all")) { + if (selectedCategories.includes("uncategorized")) { + params.includeUncategorized = true; + } else { + params.categoryIds = selectedCategories; + } + } + if (searchQuery) { + params.search = searchQuery; + } + if (showReconciled !== "all") { + params.isReconciled = showReconciled === "reconciled"; + } + + return params; + }, [ + startDate, + endDate, + selectedCategories, + searchQuery, + showReconciled, + period, + ]); + + // Fetch all filtered transactions (without account filter) + const { data: transactionsData, isLoading } = useTransactions( + filterParams, + !!metadata + ); + + return { + transactions: transactionsData?.transactions || [], + isLoading, + }; +} + diff --git a/hooks/use-transactions-for-category-filter.ts b/hooks/use-transactions-for-category-filter.ts new file mode 100644 index 0000000..712e231 --- /dev/null +++ b/hooks/use-transactions-for-category-filter.ts @@ -0,0 +1,105 @@ +"use client"; + +import { useMemo } from "react"; +import { useTransactions } from "@/lib/hooks"; +import { useBankingMetadata } from "@/lib/hooks"; +import type { TransactionsPaginatedParams } from "@/services/banking.service"; + +interface UseTransactionsForCategoryFilterParams { + selectedAccounts: string[]; + period: "1month" | "3months" | "6months" | "12months" | "custom" | "all"; + customStartDate?: Date; + customEndDate?: Date; + showReconciled: string; + searchQuery: string; +} + +/** + * Hook to fetch transactions filtered by accounts, period, search, reconciled + * but NOT by categories. Used for displaying transaction counts in category filter. + */ +export function useTransactionsForCategoryFilter({ + selectedAccounts, + period, + customStartDate, + customEndDate, + showReconciled, + searchQuery, +}: UseTransactionsForCategoryFilterParams) { + const { data: metadata } = useBankingMetadata(); + + // Calculate start date based on period + const startDate = useMemo(() => { + const now = new Date(); + switch (period) { + case "1month": + return new Date(now.getFullYear(), now.getMonth() - 1, 1); + case "3months": + return new Date(now.getFullYear(), now.getMonth() - 3, 1); + case "6months": + return new Date(now.getFullYear(), now.getMonth() - 6, 1); + case "12months": + return new Date(now.getFullYear(), now.getMonth() - 12, 1); + case "custom": + return customStartDate || new Date(0); + default: + return new Date(0); + } + }, [period, customStartDate]); + + // Calculate end date (only for custom period) + const endDate = useMemo(() => { + if (period === "custom" && customEndDate) { + return customEndDate; + } + return undefined; + }, [period, customEndDate]); + + // Build params for fetching all transactions (no pagination, no category filter) + const filterParams = useMemo(() => { + const params: TransactionsPaginatedParams = { + limit: 10000, // Large limit to get all transactions + offset: 0, + sortField: "date", + sortOrder: "asc", + }; + + if (startDate && period !== "all") { + params.startDate = startDate.toISOString().split("T")[0]; + } + if (endDate) { + params.endDate = endDate.toISOString().split("T")[0]; + } + if (!selectedAccounts.includes("all")) { + params.accountIds = selectedAccounts; + } + // NOTE: We intentionally don't filter by categories here + if (searchQuery) { + params.search = searchQuery; + } + if (showReconciled !== "all") { + params.isReconciled = showReconciled === "reconciled"; + } + + return params; + }, [ + startDate, + endDate, + selectedAccounts, + searchQuery, + showReconciled, + period, + ]); + + // Fetch all filtered transactions (without category filter) + const { data: transactionsData, isLoading } = useTransactions( + filterParams, + !!metadata + ); + + return { + transactions: transactionsData?.transactions || [], + isLoading, + }; +} +