"use client"; import { useMemo } from "react"; import { useTransactions } from "@/lib/hooks"; import { useBankingMetadata } from "@/lib/hooks"; import type { TransactionsPaginatedParams } from "@/services/banking.service"; import type { Account } from "@/lib/types"; interface BalanceChartDataPoint { date: string; [key: string]: string | number; } interface UseTransactionsBalanceChartParams { selectedAccounts: string[]; selectedCategories: string[]; period: "1month" | "3months" | "6months" | "12months" | "custom" | "all"; customStartDate?: Date; customEndDate?: Date; showReconciled: string; searchQuery: string; } export function useTransactionsBalanceChart({ selectedAccounts, selectedCategories, period, customStartDate, customEndDate, showReconciled, searchQuery, }: UseTransactionsBalanceChartParams) { 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) const chartParams = useMemo(() => { const params: TransactionsPaginatedParams = { limit: 10000, // Large limit to get all transactions offset: 0, sortField: "date", sortOrder: "asc", // Ascending for balance calculation }; 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; } 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, selectedAccounts, selectedCategories, searchQuery, showReconciled, period, ]); // Build params for fetching transactions before startDate (for initial balance) const beforeStartDateParams = useMemo(() => { if (period === "all" || !startDate) { return { limit: 0, offset: 0 }; // Don't fetch if no start date } const params: TransactionsPaginatedParams = { limit: 10000, offset: 0, sortField: "date", sortOrder: "asc", endDate: new Date(startDate.getTime() - 1).toISOString().split("T")[0], // Day before startDate }; if (!selectedAccounts.includes("all")) { params.accountIds = selectedAccounts; } 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, selectedAccounts, selectedCategories, searchQuery, showReconciled, period, ]); // Fetch transactions before startDate for initial balance calculation const { data: beforeStartDateData } = useTransactions( beforeStartDateParams, !!metadata && period !== "all" && !!startDate ); // Fetch all filtered transactions for chart const { data: transactionsData, isLoading } = useTransactions( chartParams, !!metadata ); // Calculate balance chart data const chartData = useMemo(() => { if (!transactionsData || !metadata) { return { aggregatedData: [], perAccountData: [], }; } const transactions = transactionsData.transactions; const accounts = metadata.accounts; // Sort transactions by date const sortedTransactions = [...transactions].sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ); // Calculate starting balance: initialBalance + transactions before startDate let runningBalance = 0; const accountsToUse = selectedAccounts.includes("all") ? accounts : accounts.filter((acc: Account) => selectedAccounts.includes(acc.id)); // Start with initial balances runningBalance = accountsToUse.reduce( (sum: number, acc: Account) => sum + (acc.initialBalance || 0), 0 ); // Add transactions before startDate if we have them if (beforeStartDateData?.transactions) { const beforeStartTransactions = beforeStartDateData.transactions.filter( (t) => { const transactionDate = new Date(t.date); return transactionDate < startDate; } ); beforeStartTransactions.forEach((t) => { runningBalance += t.amount; }); } const aggregatedBalanceByDate = new Map(); // Calculate balance evolution sortedTransactions.forEach((t) => { runningBalance += t.amount; aggregatedBalanceByDate.set(t.date, runningBalance); }); const aggregatedBalanceData: BalanceChartDataPoint[] = Array.from( aggregatedBalanceByDate.entries() ).map(([date, balance]) => ({ date: new Date(date).toLocaleDateString("fr-FR", { day: "2-digit", month: "short", year: "numeric", }), solde: Math.round(balance), })); // Per account balance calculation const accountBalances = new Map>(); accounts.forEach((account: Account) => { accountBalances.set(account.id, new Map()); }); // Calculate running balance per account const accountRunningBalances = new Map(); accounts.forEach((account: Account) => { accountRunningBalances.set(account.id, account.initialBalance || 0); }); // Add transactions before startDate per account if (beforeStartDateData?.transactions) { const beforeStartTransactions = beforeStartDateData.transactions.filter( (t) => { const transactionDate = new Date(t.date); return transactionDate < startDate; } ); beforeStartTransactions.forEach((t) => { const currentBalance = accountRunningBalances.get(t.accountId) || 0; accountRunningBalances.set(t.accountId, currentBalance + t.amount); }); } // Filter transactions by account if needed const transactionsForAccounts = selectedAccounts.includes("all") ? sortedTransactions : sortedTransactions.filter((t) => selectedAccounts.includes(t.accountId) ); transactionsForAccounts.forEach((t) => { const currentBalance = accountRunningBalances.get(t.accountId) || 0; const newBalance = currentBalance + t.amount; accountRunningBalances.set(t.accountId, newBalance); const accountDates = accountBalances.get(t.accountId); if (accountDates) { accountDates.set(t.date, newBalance); } }); // Merge all dates and create data points const allDates = new Set(); accountBalances.forEach((dates) => { dates.forEach((_, date) => allDates.add(date)); }); const sortedDates = Array.from(allDates).sort(); const lastBalances = new Map(); accounts.forEach((account: Account) => { // Start with initial balance + transactions before startDate let accountStartingBalance = account.initialBalance || 0; if (beforeStartDateData?.transactions) { const beforeStartTransactions = beforeStartDateData.transactions.filter( (t) => { const transactionDate = new Date(t.date); return transactionDate < startDate && t.accountId === account.id; } ); beforeStartTransactions.forEach((t) => { accountStartingBalance += t.amount; }); } lastBalances.set(account.id, accountStartingBalance); }); const perAccountBalanceData: BalanceChartDataPoint[] = sortedDates.map( (date) => { const point: BalanceChartDataPoint = { date: new Date(date).toLocaleDateString("fr-FR", { day: "2-digit", month: "short", year: "numeric", }), }; accounts.forEach((account: Account) => { const accountDates = accountBalances.get(account.id); if (accountDates?.has(date)) { lastBalances.set(account.id, accountDates.get(date)!); } point[account.id] = Math.round(lastBalances.get(account.id) || 0); }); return point; } ); return { aggregatedData: aggregatedBalanceData, perAccountData: perAccountBalanceData, }; }, [ transactionsData, beforeStartDateData, metadata, selectedAccounts, startDate, ]); return { aggregatedData: chartData.aggregatedData, perAccountData: chartData.perAccountData, accounts: metadata?.accounts || [], isLoading, }; }