import { useState, useCallback, useMemo, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Hash, Table, BarChart3 } from "lucide-react"; import { useReports } from "../hooks/useReports"; import { PageHelp } from "../components/shared/PageHelp"; import type { ReportTab, CategoryBreakdownItem, DashboardPeriod, ImportSource } from "../shared/types"; import { getAllSources } from "../services/importSourceService"; import PeriodSelector from "../components/dashboard/PeriodSelector"; import MonthlyTrendsChart from "../components/reports/MonthlyTrendsChart"; import MonthlyTrendsTable from "../components/reports/MonthlyTrendsTable"; import CategoryBarChart from "../components/reports/CategoryBarChart"; import CategoryTable from "../components/reports/CategoryTable"; import CategoryOverTimeChart from "../components/reports/CategoryOverTimeChart"; import CategoryOverTimeTable from "../components/reports/CategoryOverTimeTable"; import BudgetVsActualTable from "../components/reports/BudgetVsActualTable"; import DynamicReport from "../components/reports/DynamicReport"; import ReportFilterPanel from "../components/reports/ReportFilterPanel"; import TransactionDetailModal from "../components/shared/TransactionDetailModal"; const TABS: ReportTab[] = ["trends", "byCategory", "overTime", "budgetVsActual", "dynamic"]; function computeDateRange( period: DashboardPeriod, customDateFrom?: string, customDateTo?: string, ): { dateFrom?: string; dateTo?: string } { if (period === "all") return {}; if (period === "custom" && customDateFrom && customDateTo) { return { dateFrom: customDateFrom, dateTo: customDateTo }; } const now = new Date(); const year = now.getFullYear(); const month = now.getMonth(); const day = now.getDate(); const dateTo = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`; let from: Date; switch (period) { case "month": from = new Date(year, month, 1); break; case "3months": from = new Date(year, month - 2, 1); break; case "6months": from = new Date(year, month - 5, 1); break; case "year": from = new Date(year, 0, 1); break; case "12months": from = new Date(year, month - 11, 1); break; default: from = new Date(year, month, 1); break; } const dateFrom = `${from.getFullYear()}-${String(from.getMonth() + 1).padStart(2, "0")}-${String(from.getDate()).padStart(2, "0")}`; return { dateFrom, dateTo }; } export default function ReportsPage() { const { t, i18n } = useTranslation(); const { state, setTab, setPeriod, setCustomDates, setBudgetMonth, setPivotConfig, setSourceId } = useReports(); const [sources, setSources] = useState([]); useEffect(() => { getAllSources().then(setSources); }, []); const [hiddenCategories, setHiddenCategories] = useState>(new Set()); const [detailModal, setDetailModal] = useState(null); const [showAmounts, setShowAmounts] = useState(() => localStorage.getItem("reports-show-amounts") === "true"); const [viewMode, setViewMode] = useState<"chart" | "table">(() => (localStorage.getItem("reports-view-mode") as "chart" | "table") || "chart" ); const toggleHidden = useCallback((name: string) => { setHiddenCategories((prev) => { const next = new Set(prev); if (next.has(name)) next.delete(name); else next.add(name); return next; }); }, []); const showAll = useCallback(() => setHiddenCategories(new Set()), []); const viewDetails = useCallback((item: CategoryBreakdownItem) => { setDetailModal(item); }, []); const { dateFrom, dateTo } = computeDateRange(state.period, state.customDateFrom, state.customDateTo); const filterCategories = useMemo(() => { if (state.tab === "byCategory") { return state.categorySpending.map((c) => ({ name: c.category_name, color: c.category_color })); } if (state.tab === "overTime") { return state.categoryOverTime.categories.map((name) => ({ name, color: state.categoryOverTime.colors[name] || "#9ca3af", })); } return []; }, [state.tab, state.categorySpending, state.categoryOverTime]); const monthOptions = useMemo(() => { const now = new Date(); const currentMonth = now.getMonth(); const currentYear = now.getFullYear(); return Array.from({ length: 24 }, (_, i) => { const d = new Date(currentYear, currentMonth - i, 1); const y = d.getFullYear(); const m = d.getMonth() + 1; const label = new Intl.DateTimeFormat(i18n.language, { month: "long", year: "numeric" }).format(d); return { key: `${y}-${m}`, value: `${y}-${m}`, label: label.charAt(0).toUpperCase() + label.slice(1) }; }); }, [i18n.language]); const hasCategories = ["byCategory", "overTime"].includes(state.tab) && filterCategories.length > 0; const showFilterPanel = hasCategories || (state.tab === "trends" && sources.length > 1); return (
{state.tab === "budgetVsActual" ? (

{t("reports.bva.titlePrefix")}

) : (

{t("reports.title")}

)}
{state.tab !== "budgetVsActual" && ( )}
{TABS.map((tab) => ( ))} {["trends", "byCategory", "overTime"].includes(state.tab) && ( <>
{([ { mode: "chart" as const, icon: , label: t("reports.pivot.viewChart") }, { mode: "table" as const, icon: , label: t("reports.pivot.viewTable") }, ]).map(({ mode, icon, label }) => ( ))} {viewMode === "chart" && ( <>
)} )}
{state.error && (
{state.error}
)}
{state.tab === "trends" && ( viewMode === "chart" ? ( ) : ( ) )} {state.tab === "byCategory" && ( viewMode === "chart" ? ( ) : ( ) )} {state.tab === "overTime" && ( viewMode === "chart" ? ( ) : ( ) )} {state.tab === "budgetVsActual" && ( )} {state.tab === "dynamic" && ( )}
{showFilterPanel && ( )}
{detailModal && ( setDetailModal(null)} /> )} ); }