import { useState, useMemo } from 'react'; import { useTransactions, useRates, useWallets, useExchanges, useLoans } from '../../hooks/queries'; import { useAppStore } from '../../store/useAppStore'; import { startOfMonth, endOfMonth, eachDayOfInterval, format, subDays, isSameDay, startOfDay } from 'date-fns'; export function useAnalyticsStats() { const { data: transactions = [], isLoading: txLoading } = useTransactions(); const { data: wallets = [], isLoading: wlLoading } = useWallets(); const { data: exchanges = [], isLoading: exLoading } = useExchanges(); const { data: loans = [], isLoading: lnLoading } = useLoans(); const { data: rates, isLoading: ratesLoading } = useRates(); const mainCurrency = useAppStore(s => s.mainCurrency); // New State for Year/Month Selector const [selectedYear, setSelectedYear] = useState(() => new Date().getFullYear()); const [selectedMonth, setSelectedMonth] = useState(() => new Date().getMonth() + 1); // 1-12 const exchangeRates = rates || { 'USD': 1, 'IQD': 1500, 'RMB': 7.2 }; const toMain = (amount: number, currency: string) => { const rate = exchangeRates[currency] || 1; return (amount / rate) * (exchangeRates[mainCurrency] || 1); }; const stats = useMemo(() => { // Filter interval based on selectedYear/Month let start: Date; let end: Date; if (selectedMonth !== null) { start = startOfMonth(new Date(selectedYear, selectedMonth - 1)); end = endOfMonth(new Date(selectedYear, selectedMonth - 1)); } else { start = new Date(selectedYear, 0, 1); end = new Date(selectedYear, 11, 31, 23, 59, 59); } let periodIncome = 0; let periodExpense = 0; let totalDeposits = 0; let totalWithdrawals = 0; const categories: Record = {}; const incomeCategories: Record = {}; const dailyData: Record = {}; const monthlyData: Record = {}; // Initialize daily data const days = eachDayOfInterval({ start, end }); days.forEach(day => { const dateStr = format(day, 'yyyy-MM-dd'); dailyData[dateStr] = { date: dateStr, income: 0, expense: 0, balance: 0 }; }); const filteredTxs = transactions.filter((t: any) => { const txDate = new Date(t.date); return txDate >= start && txDate <= end; }); filteredTxs.forEach((tx: any) => { const amountInMain = toMain(tx.amount, tx.currency); const dateStr = tx.date.split('T')[0]; const monthKey = dateStr.substring(0, 7); // YYYY-MM if (!monthlyData[monthKey]) { monthlyData[monthKey] = { month: monthKey, income: 0, expense: 0, balance: 0 }; } if (tx.type === 'income') { periodIncome += amountInMain; totalDeposits += amountInMain; if (dailyData[dateStr]) dailyData[dateStr].income += amountInMain; monthlyData[monthKey].income += amountInMain; const cat = tx.category || 'Other'; if (!incomeCategories[cat]) incomeCategories[cat] = { total: 0, count: 0 }; incomeCategories[cat].total += amountInMain; incomeCategories[cat].count += 1; } else if (tx.type === 'expense') { periodExpense += amountInMain; totalWithdrawals += amountInMain; if (dailyData[dateStr]) dailyData[dateStr].expense += amountInMain; monthlyData[monthKey].expense += amountInMain; const cat = tx.category || 'Other'; if (!categories[cat]) categories[cat] = { total: 0, count: 0 }; categories[cat].total += amountInMain; categories[cat].count += 1; } }); // 1. Advanced Loan Metrics (Global) let totalLent = 0; let totalBorrowed = 0; loans.forEach((l: any) => { const amountInMain = toMain(l.amount, l.currency); const paidInMain = toMain(l.paid || 0, l.currency); const remaining = amountInMain - paidInMain; if (l.type === 'borrowed_from_me') { totalLent += remaining; } else { totalBorrowed += remaining; } }); // 2. Spending Deltas (Today vs Yesterday) const today = startOfDay(new Date()); const yesterday = subDays(today, 1); const todaySpend = transactions .filter((t: any) => t.type === 'expense' && isSameDay(new Date(t.date), today)) .reduce((acc, t) => acc + toMain(t.amount, t.currency), 0); const yesterdaySpend = transactions .filter((t: any) => t.type === 'expense' && isSameDay(new Date(t.date), yesterday)) .reduce((acc, t) => acc + toMain(t.amount, t.currency), 0); const dailySpendChange = yesterdaySpend > 0 ? ((todaySpend - yesterdaySpend) / yesterdaySpend) * 100 : todaySpend > 0 ? 100 : 0; // 3. Asset & Net Worth Calculation const cBalances: Record = { 'USD': 0, 'IQD': 0, 'RMB': 0 }; wallets.forEach((w: any) => { const txBal = transactions.reduce((acc: number, tx: any) => { let effectiveAmount = tx.amount; if (tx.currency !== w.currency) { const txRate = exchangeRates[tx.currency] || 1; const walletRate = exchangeRates[w.currency] || 1; effectiveAmount = (tx.amount / txRate) * walletRate; } if (tx.type === 'income' && tx.wallet_id === w.id) return acc + effectiveAmount; if (tx.type === 'expense' && tx.wallet_id === w.id) return acc - effectiveAmount; if (tx.type === 'transfer') { if (tx.wallet_id === w.id) return acc - effectiveAmount; if (tx.to_wallet_id === w.id) return acc + effectiveAmount; } return acc; }, 0); const exBal = exchanges.reduce((acc: number, ex: any) => { let bal = 0; if (ex.from_wallet_id === w.id) bal -= ex.from_amount; if (ex.to_wallet_id === w.id) bal += ex.to_amount; return acc + bal; }, 0); cBalances[w.currency] = (cBalances[w.currency] || 0) + (txBal + exBal); }); const liquidAssets = Object.entries(cBalances).reduce((acc, [curr, amt]) => { const rate = exchangeRates[curr] || 1; return acc + (amt / rate * (exchangeRates[mainCurrency] || 1)); }, 0); const netWorth = liquidAssets + totalLent - totalBorrowed; // Cumulative Net Worth Trend (Historical) const netCashFlowSinceStart = transactions.reduce((acc, tx) => { if (new Date(tx.date) < start) return acc; // Simplified: only trend within selected period const amt = toMain(tx.amount, tx.currency); if (tx.type === 'income') return acc + amt; if (tx.type === 'expense') return acc - amt; return acc; }, 0); const startingNetWorth = liquidAssets - netCashFlowSinceStart; let cumulative = startingNetWorth; const cumulativeHistory = Object.values(dailyData).map(d => { cumulative += d.income - d.expense; return { date: d.date, balance: cumulative }; }); // 4. Advanced Risk & Efficiency Metrics const numDays = days.length || 1; const avgDailySpend = periodExpense / numDays; // Sharpe Ratio Calculation (Daily Volatility of Balances) const dailyReturns = []; for (let i = 1; i < cumulativeHistory.length; i++) { const prev = cumulativeHistory[i-1].balance; const curr = cumulativeHistory[i].balance; if (prev !== 0) dailyReturns.push((curr - prev) / Math.abs(prev)); } const avgReturn = dailyReturns.length > 0 ? dailyReturns.reduce((a, b) => a + b, 0) / dailyReturns.length : 0; const stdDev = dailyReturns.length > 0 ? Math.sqrt(dailyReturns.reduce((a, b) => a + Math.pow(b - avgReturn, 2), 0) / dailyReturns.length) : 0; const sharpeRatio = stdDev !== 0 ? (avgReturn / stdDev) * Math.sqrt(365) : 0; // Annualized // Max Drawdown let peak = -Infinity; let maxDrawdown = 0; cumulativeHistory.forEach(d => { if (d.balance > peak) peak = d.balance; const drawdown = peak !== 0 ? (peak - d.balance) / peak : 0; if (drawdown > maxDrawdown) maxDrawdown = drawdown; }); // Velocity of Money (Total Flow / Avg Balance) const avgBalance = cumulativeHistory.reduce((a, b) => a + b.balance, 0) / cumulativeHistory.length || 1; const velocity = (periodIncome + periodExpense) / avgBalance; // 5. 50/30/20 Analysis const RULES = { needs: 0, wants: 0, savings: 0 }; const NEEDS_CATS = ['market', 'bills', 'health', 'transport', 'tax', 'rent', 'utilities', 'bills']; Object.entries(categories).forEach(([name, data]) => { if (NEEDS_CATS.includes(name.toLowerCase())) RULES.needs += data.total; else RULES.wants += data.total; }); RULES.savings = Math.max(0, periodIncome - periodExpense); const totalRuleBase = (RULES.needs + RULES.wants + RULES.savings) || 1; const ruleAnalysis = { needs: (RULES.needs / totalRuleBase) * 100, wants: (RULES.wants / totalRuleBase) * 100, savings: (RULES.savings / totalRuleBase) * 100 }; // 6. Trend Projection (Linear Regression: y = mx + b) const n = cumulativeHistory.length; let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; cumulativeHistory.forEach((d, i) => { sumX += i; sumY += d.balance; sumXY += i * d.balance; sumX2 += i * i; }); const m = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX) || 0; const b = (sumY - m * sumX) / n; const projectionDays = selectedMonth ? (endOfMonth(new Date(selectedYear, selectedMonth - 1)).getDate()) : 365; const projectedBalance = m * projectionDays + b; // 7. Spending Heatmap Data (Day of Week vs Hour) const heatmap: Record = {}; transactions.forEach((tx: any) => { if (tx.type !== 'expense') return; const d = new Date(tx.date); if (d < start || d > end) return; const key = `${d.getDay()}-${d.getHours()}`; heatmap[key] = (heatmap[key] || 0) + toMain(tx.amount, tx.currency); }); // 8. Formatting Return Data const savingsRate = periodIncome > 0 ? ((periodIncome - periodExpense) / periodIncome) * 100 : 0; const debtRatio = (liquidAssets + totalLent) > 0 ? (totalBorrowed / (liquidAssets + totalLent)) * 100 : 0; const expenseRatio = periodIncome > 0 ? (periodExpense / periodIncome) * 100 : 0; // Top spending categories const totalExpenseForPct = periodExpense || 1; const spendingByCategory = Object.entries(categories) .map(([name, data]) => ({ name, value: data.total, count: data.count, pct: (data.total / totalExpenseForPct) * 100 })) .sort((a, b) => b.value - a.value); const incomeBreakdown = Object.entries(incomeCategories) .map(([name, data]) => ({ name, value: data.total, count: data.count, pct: (data.total / (periodIncome || 1)) * 100 })) .sort((a, b) => b.value - a.value); const comparisonTableData = selectedMonth ? Object.values(dailyData).sort((a, b) => b.date.localeCompare(a.date)).map(d => ({ date: d.date, income: d.income, expense: d.expense, balance: d.income - d.expense })) : Object.values(monthlyData).sort((a, b) => b.month.localeCompare(a.month)).map(m => ({ date: m.month, income: m.income, expense: m.expense, balance: m.income - m.expense })); const largestTransaction = filteredTxs.length > 0 ? filteredTxs.reduce((prev: any, curr: any) => (toMain(curr.amount, curr.currency) > toMain(prev.amount, prev.currency) ? curr : prev)) : null; return { periodIncome, periodExpense, netCashFlow: periodIncome - periodExpense, totalDeposits, totalWithdrawals, liquidAssets, totalLent, totalBorrowed, netWorth, spendingByCategory, incomeBreakdown, avgDailySpend, savingsRate, debtRatio, expenseRatio, todaySpend, dailySpendChange, cumulativeHistory, comparisonTableData, sharpeRatio, maxDrawdown, velocity, ruleAnalysis, projectedBalance, heatmap, trendLine: { m, b }, largestTransaction: largestTransaction ? { ...largestTransaction, mainAmount: toMain(largestTransaction.amount, largestTransaction.currency) } : null, currencyDistribution: Object.entries(cBalances).map(([name, value]) => { const rate = exchangeRates[name] || 1; return { name, value: value / rate * (exchangeRates[mainCurrency] || 1), originalValue: value }; }).filter(d => d.value !== 0), }; }, [transactions, wallets, exchanges, loans, rates, mainCurrency, selectedYear, selectedMonth, exchangeRates]); // Available Years const availableYears = useMemo(() => { const currentYear = new Date().getFullYear(); return [currentYear, currentYear - 1, currentYear - 2]; }, []); return { isLoaded: !txLoading && !wlLoading && !exLoading && !lnLoading && !ratesLoading, mainCurrency, selectedYear, setSelectedYear, selectedMonth, setSelectedMonth, availableYears, ...stats }; }