"use client" import { formatCurrency, formatPeriodLabel } from "@/lib/utils" import { DetailedTimeSeriesData } from "@/models/stats" import { addDays, endOfMonth, format, startOfMonth } from "date-fns" import { useRouter } from "next/navigation" import { useEffect, useRef, useState } from "react" import { IncomeExpenceGraphTooltip } from "./income-expense-graph-tooltip" interface IncomeExpenseGraphProps { data: DetailedTimeSeriesData[] defaultCurrency: string } export function IncomeExpenseGraph({ data, defaultCurrency }: IncomeExpenseGraphProps) { const router = useRouter() const scrollContainerRef = useRef(null) const [tooltip, setTooltip] = useState<{ data: DetailedTimeSeriesData | null position: { x: number; y: number } visible: boolean }>({ data: null, position: { x: 0, y: 0 }, visible: false, }) // Auto-scroll to the right to show latest data useEffect(() => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollLeft = scrollContainerRef.current.scrollWidth } }, [data]) const handleBarHover = (item: DetailedTimeSeriesData, event: React.MouseEvent) => { const rect = event.currentTarget.getBoundingClientRect() const containerRect = scrollContainerRef.current?.getBoundingClientRect() setTooltip({ data: item, position: { x: rect.left + rect.width / 2, y: containerRect ? containerRect.top + containerRect.height / 2 : rect.top, }, visible: true, }) } const handleBarLeave = () => { setTooltip((prev) => ({ ...prev, visible: false })) } const handleBarClick = (item: DetailedTimeSeriesData, type: "income" | "expense") => { // Calculate date range for the period const isDailyPeriod = item.period.includes("-") && item.period.split("-").length === 3 let dateFrom: string let dateTo: string if (isDailyPeriod) { // Daily period: use the exact date, add 1 day to dateTo const date = new Date(item.period) dateFrom = item.period // YYYY-MM-DD format dateTo = format(addDays(date, 1), "yyyy-MM-dd") } else { // Monthly period: use first and last day of the month, add 1 day to dateTo const [year, month] = item.period.split("-") const monthDate = new Date(parseInt(year), parseInt(month) - 1, 1) dateFrom = format(startOfMonth(monthDate), "yyyy-MM-dd") dateTo = format(addDays(endOfMonth(monthDate), 1), "yyyy-MM-dd") } // Build URL parameters const params = new URLSearchParams({ type, dateFrom, dateTo, }) // Navigate to transactions page with filters router.push(`/transactions?${params.toString()}`) } if (!data.length) { return (
No data available for the selected period
) } const maxIncome = Math.max(...data.map((d) => d.income)) const maxExpense = Math.max(...data.map((d) => d.expenses)) const maxValue = Math.max(maxIncome, maxExpense) if (maxValue === 0) { return (
No transactions found for the selected period
) } return (
{/* Chart container with horizontal scroll */}
{/* Income section (top half) */}
{data.map((item, index) => { const incomeHeight = maxValue > 0 ? (item.income / maxValue) * 100 : 0 return (
handleBarHover(item, e)} onMouseLeave={handleBarLeave} onClick={() => item.income > 0 && handleBarClick(item, "income")} > {/* Period label above income bars */}
{formatPeriodLabel(item.period, item.date)}
{item.income > 0 && ( <> {/* Income amount label */}
{formatCurrency(item.income, defaultCurrency)}
{/* Income bar growing upward from bottom */}
)}
) })}
{/* X-axis line (center) */}
{/* Expense section (bottom half) */}
{data.map((item, index) => { const expenseHeight = maxValue > 0 ? (item.expenses / maxValue) * 100 : 0 return (
handleBarHover(item, e)} onMouseLeave={handleBarLeave} onClick={() => item.expenses > 0 && handleBarClick(item, "expense")} > {item.expenses > 0 && ( <> {/* Expense bar growing downward from top */}
{/* Expense amount label */}
{formatCurrency(item.expenses, defaultCurrency)}
)}
) })}
{/* Tooltip */}
) }