feat(reports): adopt shared FilterPanel on the Trends page
Mounts <FilterPanel> (from #274) on ReportsTrendsPage next to the period selector, and threads accountIds from useReportsPeriod through useTrends into getMonthlyTrends/getCategoryOverTime (both already accountIds-aware since #273). The account checkbox list reuses the same getAllImportSources query as the Transactions single-source filter. Empty selection = no filter, byte-identical to pre-#275 output. Resolves #275
This commit is contained in:
parent
70b2cd2e42
commit
30682004d3
5 changed files with 97 additions and 66 deletions
|
|
@ -9,6 +9,7 @@
|
|||
- Rapports : pose les fondations d'un futur filtre de comptes pour les rapports — le hook de période partagé suit désormais aussi une sélection multi-comptes via l'URL (mémorisable comme la plage de dates, et validée contre les URL malformées ou modifiées à la main). Purement interne pour l'instant : aucun contrôle de filtre visible encore, le sélecteur de comptes et le branchement par rapport arrivent dans des issues de suivi (#272).
|
||||
- Rapports : les sept services de rapports (tendances, catégories dans le temps, comparaison réel-vs-réel, budget-vs-réel, dépenses par catégorie, et le tableau de bord Cartes) acceptent désormais un filtre multi-comptes optionnel, comparé à une liste paramétrée de sources d'import. Plomberie interne seulement — omettre le filtre partout donne des résultats identiques au byte près, et aucune page de rapport n'expose encore de contrôle de filtre ; cela arrive dans des issues de suivi (#273).
|
||||
- Rapports : pose les fondations de l'interface du filtre de comptes — un panneau de filtre partagé affiche le contrôle de période propre à chaque rapport à côté d'une sélection multiple (cases à cocher) de vos sources d'import. Purement interne pour l'instant : aucune page de rapport n'affiche encore ce panneau, cela arrive dans des issues de suivi (#274).
|
||||
- Rapports → Tendances : le panneau de filtre partagé apparaît désormais sur ce rapport, à côté du sélecteur de période. Cocher une ou plusieurs sources d'import restreint à la fois la vue mensuelle globale et le tableau de résultat par catégorie à ces sources ; ne rien cocher garde l'affichage de toutes les sources, inchangé (#275).
|
||||
|
||||
### Corrigé
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
- Reports: laid the groundwork for an upcoming account filter across the report pages — the shared period hook now also tracks a URL-backed, multi-account selection (bookmarkable like the date range, and validated against malformed/hand-edited URLs). Internal only for now: no filter control is visible yet, the account picker and per-report wiring land in follow-up issues (#272).
|
||||
- Reports: all seven report services (trends, by-category-over-time, real-vs-real compare, budget-vs-actual, expenses-by-category, and the Cartes dashboard) now accept an optional multi-account filter, matched against a parameterized list of import sources. Backend plumbing only — leaving every filter out still returns byte-identical results, and no report page exposes a filter control yet; that lands in follow-up issues (#273).
|
||||
- Reports: laid the groundwork for the account filter's UI — a shared filter panel renders each report page's own period control alongside a multi-select (checkbox list) of your import sources. Internal only for now: no report page renders this panel yet, that lands in follow-up issues (#274).
|
||||
- Reports → Trends: the shared filter panel now appears on this report, next to the period selector. Checking one or more import sources narrows both the global monthly view and the by-category result table to those sources; leaving none checked keeps showing every source, unchanged (#275).
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ Chaque hook encapsule la logique d'état via `useReducer` :
|
|||
| `useAdjustments` | Ajustements |
|
||||
| `useBudget` | Budget |
|
||||
| `useDashboard` | Métriques du tableau de bord |
|
||||
| `useReportsPeriod` | Période de reporting synchronisée via query string (bookmarkable) + filtre compte (`accountIds`, `import_sources.id`) via le paramètre `sources`, même mécanique bookmarkable ; défaut `[]` = aucun filtre (fondation #272, pas encore branché sur les services ni exposé dans une UI) |
|
||||
| `useReportsPeriod` | Période de reporting synchronisée via query string (bookmarkable) + filtre compte (`accountIds`, `import_sources.id`) via le paramètre `sources`, même mécanique bookmarkable ; défaut `[]` = aucun filtre (fondation #272). Branché sur les 7 services de rapports (#273) et exposé via `<FilterPanel>` (#274) ; adopté sur la page Tendances (#275) — reste à adopter sur Comparaison/Budget/Dashboard/Hub (#276) |
|
||||
| `useHighlights` | Panneau de faits saillants du hub rapports |
|
||||
| `useTrends` | Rapport Tendances (sous-vue flux global / par catégorie) |
|
||||
| `useCompare` | Rapport Comparables (mode `actual`/`budget`, sous-toggle MoM ↔ YoY, mois de référence explicite avec wrap-around janvier) |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useReducer, useEffect, useRef, useCallback } from "react";
|
||||
import type { MonthlyTrendItem, CategoryOverTimeData } from "../shared/types";
|
||||
import type { MonthlyTrendItem, CategoryOverTimeData, ImportSource } from "../shared/types";
|
||||
import { getMonthlyTrends, getCategoryOverTime } from "../services/reportService";
|
||||
import { getAllImportSources } from "../services/transactionService";
|
||||
import { useReportsPeriod } from "./useReportsPeriod";
|
||||
|
||||
export type TrendsSubView = "global" | "byCategory";
|
||||
|
|
@ -9,6 +10,7 @@ interface State {
|
|||
subView: TrendsSubView;
|
||||
monthlyTrends: MonthlyTrendItem[];
|
||||
categoryOverTime: CategoryOverTimeData;
|
||||
accounts: ImportSource[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
|
@ -18,12 +20,14 @@ type Action =
|
|||
| { type: "SET_LOADING"; payload: boolean }
|
||||
| { type: "SET_TRENDS"; payload: MonthlyTrendItem[] }
|
||||
| { type: "SET_CATEGORY_OVER_TIME"; payload: CategoryOverTimeData }
|
||||
| { type: "SET_ACCOUNTS"; payload: ImportSource[] }
|
||||
| { type: "SET_ERROR"; payload: string };
|
||||
|
||||
const initialState: State = {
|
||||
subView: "byCategory",
|
||||
monthlyTrends: [],
|
||||
categoryOverTime: { categories: [], data: [], colors: {}, categoryIds: {}, types: {}, tree: [] },
|
||||
accounts: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
};
|
||||
|
|
@ -38,6 +42,8 @@ function reducer(state: State, action: Action): State {
|
|||
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_ACCOUNTS":
|
||||
return { ...state, accounts: action.payload };
|
||||
case "SET_ERROR":
|
||||
return { ...state, error: action.payload, isLoading: false };
|
||||
default:
|
||||
|
|
@ -46,20 +52,21 @@ function reducer(state: State, action: Action): State {
|
|||
}
|
||||
|
||||
export function useTrends() {
|
||||
const { from, to } = useReportsPeriod();
|
||||
const { from, to, accountIds } = useReportsPeriod();
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const fetchIdRef = useRef(0);
|
||||
|
||||
const fetch = useCallback(async (subView: TrendsSubView, dateFrom: string, dateTo: string) => {
|
||||
const fetch = useCallback(
|
||||
async (subView: TrendsSubView, dateFrom: string, dateTo: string, ids: number[]) => {
|
||||
const id = ++fetchIdRef.current;
|
||||
dispatch({ type: "SET_LOADING", payload: true });
|
||||
try {
|
||||
if (subView === "global") {
|
||||
const data = await getMonthlyTrends(dateFrom, dateTo);
|
||||
const data = await getMonthlyTrends(dateFrom, dateTo, ids);
|
||||
if (id !== fetchIdRef.current) return;
|
||||
dispatch({ type: "SET_TRENDS", payload: data });
|
||||
} else {
|
||||
const data = await getCategoryOverTime(dateFrom, dateTo);
|
||||
const data = await getCategoryOverTime(dateFrom, dateTo, undefined, ids);
|
||||
if (id !== fetchIdRef.current) return;
|
||||
dispatch({ type: "SET_CATEGORY_OVER_TIME", payload: data });
|
||||
}
|
||||
|
|
@ -67,11 +74,26 @@ export function useTrends() {
|
|||
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]);
|
||||
fetch(state.subView, from, to, accountIds);
|
||||
}, [fetch, state.subView, from, to, accountIds]);
|
||||
|
||||
// Load the import-source list once, for the FilterPanel's account checkboxes
|
||||
// (same query as the Transactions single-source filter — see useTransactions.ts).
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const accounts = await getAllImportSources();
|
||||
dispatch({ type: "SET_ACCOUNTS", payload: accounts });
|
||||
} catch (e) {
|
||||
dispatch({ type: "SET_ERROR", payload: e instanceof Error ? e.message : String(e) });
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const setSubView = useCallback((sv: TrendsSubView) => {
|
||||
dispatch({ type: "SET_SUBVIEW", payload: sv });
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { Link } from "react-router-dom";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
||||
import FilterPanel from "../components/reports/FilterPanel";
|
||||
import MonthlyTrendsChart from "../components/reports/MonthlyTrendsChart";
|
||||
import MonthlyTrendsTable from "../components/reports/MonthlyTrendsTable";
|
||||
import CategoryOverTimeChart from "../components/reports/CategoryOverTimeChart";
|
||||
|
|
@ -21,8 +22,8 @@ const STORAGE_KEY = "reports-viewmode-trends";
|
|||
|
||||
export default function ReportsTrendsPage() {
|
||||
const { t } = useTranslation();
|
||||
const { period, setPeriod, from, to, setCustomDates } = useReportsPeriod();
|
||||
const { subView, setSubView, monthlyTrends, categoryOverTime, isLoading, error } = useTrends();
|
||||
const { period, setPeriod, from, to, setCustomDates, accountIds, setAccountIds } = useReportsPeriod();
|
||||
const { subView, setSubView, monthlyTrends, categoryOverTime, accounts, isLoading, error } = useTrends();
|
||||
const [viewMode, setViewMode] = useState<ViewMode>(() => readViewMode(STORAGE_KEY, "table"));
|
||||
const [chartType, setChartType] = useState<CategoryOverTimeChartType>(() => readTrendsChartType());
|
||||
const [hiddenCategories, setHiddenCategories] = useState<Set<string>>(new Set());
|
||||
|
|
@ -55,7 +56,8 @@ export default function ReportsTrendsPage() {
|
|||
<h1 className="text-2xl font-bold">{t("reports.hub.trends")}</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 flex-wrap">
|
||||
<FilterPanel
|
||||
temporalControl={
|
||||
<PeriodSelector
|
||||
value={period}
|
||||
onChange={setPeriod}
|
||||
|
|
@ -63,7 +65,13 @@ export default function ReportsTrendsPage() {
|
|||
customDateTo={to}
|
||||
onCustomDateChange={setCustomDates}
|
||||
/>
|
||||
<div className="flex gap-2 items-center flex-wrap">
|
||||
}
|
||||
accountIds={accountIds}
|
||||
onAccountIdsChange={setAccountIds}
|
||||
accounts={accounts}
|
||||
/>
|
||||
|
||||
<div className="flex gap-2 items-center justify-end flex-wrap mb-6">
|
||||
<div className="inline-flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -99,7 +107,6 @@ export default function ReportsTrendsPage() {
|
|||
)}
|
||||
<ViewModeToggle value={viewMode} onChange={setViewMode} storageKey={STORAGE_KEY} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-[var(--negative)]/10 text-[var(--negative)] rounded-xl p-4 mb-6">
|
||||
|
|
|
|||
Loading…
Reference in a new issue