import { useReducer, useCallback, useEffect, useRef } from "react"; import type { CategoryDelta } from "../shared/types"; import { getCompareMonthOverMonth, getCompareYearOverYear } from "../services/reportService"; import { useReportsPeriod } from "./useReportsPeriod"; export type CompareMode = "mom" | "yoy" | "budget"; interface State { mode: CompareMode; year: number; month: number; rows: CategoryDelta[]; isLoading: boolean; error: string | null; } type Action = | { type: "SET_MODE"; payload: CompareMode } | { type: "SET_PERIOD"; payload: { year: number; month: number } } | { type: "SET_LOADING"; payload: boolean } | { type: "SET_ROWS"; payload: CategoryDelta[] } | { type: "SET_ERROR"; payload: string }; const today = new Date(); const initialState: State = { mode: "mom", year: today.getFullYear(), month: today.getMonth() + 1, rows: [], isLoading: false, error: null, }; function reducer(state: State, action: Action): State { switch (action.type) { case "SET_MODE": return { ...state, mode: action.payload }; case "SET_PERIOD": return { ...state, year: action.payload.year, month: action.payload.month }; case "SET_LOADING": return { ...state, isLoading: action.payload }; case "SET_ROWS": return { ...state, rows: action.payload, isLoading: false, error: null }; case "SET_ERROR": return { ...state, error: action.payload, isLoading: false }; default: return state; } } export function useCompare() { const { from, to } = useReportsPeriod(); const [state, dispatch] = useReducer(reducer, initialState); const fetchIdRef = useRef(0); const fetch = useCallback(async (mode: CompareMode, year: number, month: number) => { if (mode === "budget") return; // Budget view uses BudgetVsActualTable directly const id = ++fetchIdRef.current; dispatch({ type: "SET_LOADING", payload: true }); try { const rows = mode === "mom" ? await getCompareMonthOverMonth(year, month) : await getCompareYearOverYear(year); if (id !== fetchIdRef.current) return; dispatch({ type: "SET_ROWS", payload: rows }); } catch (e) { if (id !== fetchIdRef.current) return; dispatch({ type: "SET_ERROR", payload: e instanceof Error ? e.message : String(e) }); } }, []); useEffect(() => { fetch(state.mode, state.year, state.month); }, [fetch, state.mode, state.year, state.month]); // When the URL period changes, use the `to` date to infer the target year/month. useEffect(() => { const [y, m] = to.split("-").map(Number); if (!Number.isFinite(y) || !Number.isFinite(m)) return; if (y !== state.year || m !== state.month) { dispatch({ type: "SET_PERIOD", payload: { year: y, month: m } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [to]); const setMode = useCallback((m: CompareMode) => { dispatch({ type: "SET_MODE", payload: m }); }, []); const setTargetPeriod = useCallback((year: number, month: number) => { dispatch({ type: "SET_PERIOD", payload: { year, month } }); }, []); return { ...state, setMode, setTargetPeriod, from, to }; }