The Trends report previously opened on the global monthly chart, which has no income section — so a user's revenue (e.g. their pay) was hidden until they manually switched to the "By category" table view. Open the report on byCategory + table by default so the income section (and therefore payroll) is visible immediately. Two scoped call-site changes: - useTrends initial subView: "global" -> "byCategory" - ReportsTrendsPage viewMode fallback: readViewMode(key, "table") Both defaults are scoped to Trends. The real-vs-real compare report (distinct "reports-viewmode-compare" storage key) is unaffected, and a user's own persisted view choice still wins over the new fallback. Resolves #262 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { useReducer, useEffect, useRef, useCallback } from "react";
|
|
import type { MonthlyTrendItem, CategoryOverTimeData } from "../shared/types";
|
|
import { getMonthlyTrends, getCategoryOverTime } from "../services/reportService";
|
|
import { useReportsPeriod } from "./useReportsPeriod";
|
|
|
|
export type TrendsSubView = "global" | "byCategory";
|
|
|
|
interface State {
|
|
subView: TrendsSubView;
|
|
monthlyTrends: MonthlyTrendItem[];
|
|
categoryOverTime: CategoryOverTimeData;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
type Action =
|
|
| { type: "SET_SUBVIEW"; payload: TrendsSubView }
|
|
| { type: "SET_LOADING"; payload: boolean }
|
|
| { type: "SET_TRENDS"; payload: MonthlyTrendItem[] }
|
|
| { type: "SET_CATEGORY_OVER_TIME"; payload: CategoryOverTimeData }
|
|
| { type: "SET_ERROR"; payload: string };
|
|
|
|
const initialState: State = {
|
|
subView: "byCategory",
|
|
monthlyTrends: [],
|
|
categoryOverTime: { categories: [], data: [], colors: {}, categoryIds: {}, types: {} },
|
|
isLoading: false,
|
|
error: null,
|
|
};
|
|
|
|
function reducer(state: State, action: Action): State {
|
|
switch (action.type) {
|
|
case "SET_SUBVIEW":
|
|
return { ...state, subView: action.payload };
|
|
case "SET_LOADING":
|
|
return { ...state, isLoading: action.payload };
|
|
case "SET_TRENDS":
|
|
return { ...state, monthlyTrends: action.payload, isLoading: false, error: null };
|
|
case "SET_CATEGORY_OVER_TIME":
|
|
return { ...state, categoryOverTime: action.payload, isLoading: false, error: null };
|
|
case "SET_ERROR":
|
|
return { ...state, error: action.payload, isLoading: false };
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
export function useTrends() {
|
|
const { from, to } = useReportsPeriod();
|
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
const fetchIdRef = useRef(0);
|
|
|
|
const fetch = useCallback(async (subView: TrendsSubView, dateFrom: string, dateTo: string) => {
|
|
const id = ++fetchIdRef.current;
|
|
dispatch({ type: "SET_LOADING", payload: true });
|
|
try {
|
|
if (subView === "global") {
|
|
const data = await getMonthlyTrends(dateFrom, dateTo);
|
|
if (id !== fetchIdRef.current) return;
|
|
dispatch({ type: "SET_TRENDS", payload: data });
|
|
} else {
|
|
const data = await getCategoryOverTime(dateFrom, dateTo);
|
|
if (id !== fetchIdRef.current) return;
|
|
dispatch({ type: "SET_CATEGORY_OVER_TIME", payload: data });
|
|
}
|
|
} catch (e) {
|
|
if (id !== fetchIdRef.current) return;
|
|
dispatch({ type: "SET_ERROR", payload: e instanceof Error ? e.message : String(e) });
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetch(state.subView, from, to);
|
|
}, [fetch, state.subView, from, to]);
|
|
|
|
const setSubView = useCallback((sv: TrendsSubView) => {
|
|
dispatch({ type: "SET_SUBVIEW", payload: sv });
|
|
}, []);
|
|
|
|
return { ...state, setSubView, from, to };
|
|
}
|