/** * @deprecated — legacy monolithic reports hook. Kept during the refonte * (Issues #70 → #76) so the pre-existing 4 tabs on `/reports` keep working * while the new per-domain hooks (useHighlights / useTrends / useCompare / * useCategoryZoom) are wired up. Will be removed in Issue #76 once every * report migrates to its own route. */ import { useReducer, useCallback, useEffect, useRef } from "react"; import type { ReportTab, MonthlyTrendItem, CategoryBreakdownItem, CategoryOverTimeData, BudgetVsActualRow, } from "../shared/types"; import { getMonthlyTrends, getCategoryOverTime } from "../services/reportService"; import { getExpensesByCategory } from "../services/dashboardService"; import { getBudgetVsActualData } from "../services/budgetService"; import { useReportsPeriod } from "./useReportsPeriod"; export type CategoryTypeFilter = "expense" | "income" | "transfer" | null; interface ReportsState { tab: ReportTab; sourceId: number | null; categoryType: CategoryTypeFilter; monthlyTrends: MonthlyTrendItem[]; categorySpending: CategoryBreakdownItem[]; categoryOverTime: CategoryOverTimeData; budgetYear: number; budgetMonth: number; budgetVsActual: BudgetVsActualRow[]; isLoading: boolean; error: string | null; } type ReportsAction = | { type: "SET_TAB"; payload: ReportTab } | { type: "SET_LOADING"; payload: boolean } | { type: "SET_ERROR"; payload: string | null } | { type: "SET_MONTHLY_TRENDS"; payload: MonthlyTrendItem[] } | { type: "SET_CATEGORY_SPENDING"; payload: CategoryBreakdownItem[] } | { type: "SET_CATEGORY_OVER_TIME"; payload: CategoryOverTimeData } | { type: "SET_BUDGET_MONTH"; payload: { year: number; month: number } } | { type: "SET_BUDGET_VS_ACTUAL"; payload: BudgetVsActualRow[] } | { type: "SET_SOURCE_ID"; payload: number | null } | { type: "SET_CATEGORY_TYPE"; payload: CategoryTypeFilter }; const now = new Date(); const initialState: ReportsState = { tab: "trends", sourceId: null, categoryType: "expense", monthlyTrends: [], categorySpending: [], categoryOverTime: { categories: [], data: [], colors: {}, categoryIds: {} }, budgetYear: now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear(), budgetMonth: now.getMonth() === 0 ? 12 : now.getMonth(), budgetVsActual: [], isLoading: false, error: null, }; function reducer(state: ReportsState, action: ReportsAction): ReportsState { switch (action.type) { case "SET_TAB": return { ...state, tab: action.payload }; case "SET_LOADING": return { ...state, isLoading: action.payload }; case "SET_ERROR": return { ...state, error: action.payload, isLoading: false }; case "SET_MONTHLY_TRENDS": return { ...state, monthlyTrends: action.payload, isLoading: false }; case "SET_CATEGORY_SPENDING": return { ...state, categorySpending: action.payload, isLoading: false }; case "SET_CATEGORY_OVER_TIME": return { ...state, categoryOverTime: action.payload, isLoading: false }; case "SET_BUDGET_MONTH": return { ...state, budgetYear: action.payload.year, budgetMonth: action.payload.month }; case "SET_BUDGET_VS_ACTUAL": return { ...state, budgetVsActual: action.payload, isLoading: false }; case "SET_SOURCE_ID": return { ...state, sourceId: action.payload }; case "SET_CATEGORY_TYPE": return { ...state, categoryType: action.payload }; default: return state; } } /** @deprecated — see module-level comment. */ export function useReports() { const { from, to, period, setPeriod, setCustomDates } = useReportsPeriod(); const [innerState, dispatch] = useReducer(reducer, initialState); const fetchIdRef = useRef(0); const fetchData = useCallback( async ( tab: ReportTab, dateFrom: string, dateTo: string, budgetYear: number, budgetMonth: number, srcId: number | null, catType: CategoryTypeFilter, ) => { const fetchId = ++fetchIdRef.current; dispatch({ type: "SET_LOADING", payload: true }); dispatch({ type: "SET_ERROR", payload: null }); try { switch (tab) { case "trends": { const data = await getMonthlyTrends(dateFrom, dateTo, srcId ?? undefined); if (fetchId !== fetchIdRef.current) return; dispatch({ type: "SET_MONTHLY_TRENDS", payload: data }); break; } case "byCategory": { const data = await getExpensesByCategory(dateFrom, dateTo, srcId ?? undefined); if (fetchId !== fetchIdRef.current) return; dispatch({ type: "SET_CATEGORY_SPENDING", payload: data }); break; } case "overTime": { const data = await getCategoryOverTime(dateFrom, dateTo, undefined, srcId ?? undefined, catType ?? undefined); if (fetchId !== fetchIdRef.current) return; dispatch({ type: "SET_CATEGORY_OVER_TIME", payload: data }); break; } case "budgetVsActual": { const data = await getBudgetVsActualData(budgetYear, budgetMonth); if (fetchId !== fetchIdRef.current) return; dispatch({ type: "SET_BUDGET_VS_ACTUAL", payload: data }); break; } } } catch (e) { if (fetchId !== fetchIdRef.current) return; dispatch({ type: "SET_ERROR", payload: e instanceof Error ? e.message : String(e), }); } }, [], ); useEffect(() => { fetchData( innerState.tab, from, to, innerState.budgetYear, innerState.budgetMonth, innerState.sourceId, innerState.categoryType, ); }, [fetchData, innerState.tab, from, to, innerState.budgetYear, innerState.budgetMonth, innerState.sourceId, innerState.categoryType]); const setTab = useCallback((tab: ReportTab) => { dispatch({ type: "SET_TAB", payload: tab }); }, []); const setBudgetMonth = useCallback((year: number, month: number) => { dispatch({ type: "SET_BUDGET_MONTH", payload: { year, month } }); }, []); const setSourceId = useCallback((id: number | null) => { dispatch({ type: "SET_SOURCE_ID", payload: id }); }, []); const setCategoryType = useCallback((catType: CategoryTypeFilter) => { dispatch({ type: "SET_CATEGORY_TYPE", payload: catType }); }, []); const state = { ...innerState, period, customDateFrom: from, customDateTo: to, }; return { state, setTab, setPeriod, setCustomDates, setBudgetMonth, setSourceId, setCategoryType }; }