import { useMemo } from "react"; import { useFinanceData } from "@/context/FinanceDataContext"; import { useMoney } from "@/hooks/useMoney"; import { cn } from "@/lib/utils"; import { Progress } from "@/components/ui/progress"; import { TrendingUp, ArrowRight } from "lucide-react"; import { Link } from "react-router-dom"; import { monthKey } from "@/lib/format"; import { projectMonthEnd } from "@/lib/budgets"; /** * Projected end-of-month balance. * * The previous version fetched budgets once on mount (so it never reflected an * edit), ignored income entirely, and used `max(totalBudget, actualSpend)` with * no sense of how far through the month it was, on the 2nd it projected a full * month of spending, on the 28th almost none. It now extrapolates real spend at * the current daily rate and counts income still expected. */ export function ForecastWidget() { const { accounts, transactions } = useFinanceData({ summary: true }); const { money } = useMoney(); const currentBalance = useMemo( () => accounts .filter((a) => a.type === "checking" || a.type === "savings") .reduce((s, a) => s + a.balance, 0), [accounts] ); const p = useMemo( () => projectMonthEnd(transactions, currentBalance, monthKey(new Date())), [transactions, currentBalance] ); const isPositive = p.projectedBalance >= 0; const hasData = currentBalance !== 0 || p.spentToDate > 0; const spendRatio = currentBalance > 0 ? Math.min((p.projectedSpend / currentBalance) * 100, 100) : 0; const daysLeft = p.daysInMonth - p.daysElapsed; if (!hasData) { return (

Connect your bank to see a forecast

); } return (

Projected end of month

{money(p.projectedBalance)}

{daysLeft > 0 ? `${daysLeft} days left ยท ` : ""} {money(p.spentToDate)} spent so far

div]:transition-all [&>div]:duration-700 [&>div]:ease-out", spendRatio > 85 ? "[&>div]:bg-chart-red" : spendRatio > 60 ? "[&>div]:bg-chart-yellow" : "[&>div]:bg-chart-green" )} />
Balance {money(currentBalance)} Projected spend {money(p.projectedSpend)}
View forecast
); }