diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index ba58876..dbd0204 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -12,6 +12,9 @@ ### Modifié - Tableau de bord : le graphique circulaire prend 1/3 de la largeur au lieu de 1/2, donnant plus d'espace au tableau budget (#23) - Tableau de bord : les étiquettes du graphique circulaire s'affichent uniquement au survol via le tooltip (#23) +- Budget vs Réel : la colonne des catégories reste désormais fixe lors du défilement horizontal (#29) +- Budget vs Réel : titre changé pour « Budget vs Réel pour le mois de [mois] » avec un menu déroulant pour sélectionner le mois (#29) +- Budget vs Réel : le mois par défaut est maintenant le dernier mois complété au lieu du mois courant (#29) ## [0.6.3] diff --git a/CHANGELOG.md b/CHANGELOG.md index dd0c18a..d331fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ ### Changed - Dashboard: pie chart takes 1/3 width instead of 1/2, giving more space to the budget table (#23) - Dashboard: pie chart labels now shown only on hover via tooltip instead of permanent legend (#23) +- Budget vs Actual: category column now stays fixed when scrolling horizontally (#29) +- Budget vs Actual: title changed to "Budget vs Réel pour le mois de [month]" with a dropdown month selector (#29) +- Budget vs Actual: default month is now the last completed month instead of current month (#29) ## [0.6.3] diff --git a/src/components/reports/BudgetVsActualTable.tsx b/src/components/reports/BudgetVsActualTable.tsx index a7ef69f..63218a9 100644 --- a/src/components/reports/BudgetVsActualTable.tsx +++ b/src/components/reports/BudgetVsActualTable.tsx @@ -103,7 +103,7 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps) - - + @@ -173,11 +173,17 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps) - - + + @@ -236,8 +242,8 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps) ); })} {/* Grand totals */} - - + + diff --git a/src/hooks/useReports.ts b/src/hooks/useReports.ts index 16ae339..0cbfc5a 100644 --- a/src/hooks/useReports.ts +++ b/src/hooks/useReports.ts @@ -59,8 +59,8 @@ const initialState: ReportsState = { monthlyTrends: [], categorySpending: [], categoryOverTime: { categories: [], data: [], colors: {}, categoryIds: {} }, - budgetYear: now.getFullYear(), - budgetMonth: now.getMonth() + 1, + budgetYear: now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear(), + budgetMonth: now.getMonth() === 0 ? 12 : now.getMonth(), budgetVsActual: [], pivotConfig: { rows: [], columns: [], filters: {}, values: [] }, pivotResult: { rows: [], columnValues: [], dimensionLabels: {} }, @@ -224,18 +224,9 @@ export function useReports() { dispatch({ type: "SET_PERIOD", payload: period }); }, []); - const navigateBudgetMonth = useCallback((delta: -1 | 1) => { - let newMonth = state.budgetMonth + delta; - let newYear = state.budgetYear; - if (newMonth < 1) { - newMonth = 12; - newYear -= 1; - } else if (newMonth > 12) { - newMonth = 1; - newYear += 1; - } - dispatch({ type: "SET_BUDGET_MONTH", payload: { year: newYear, month: newMonth } }); - }, [state.budgetYear, state.budgetMonth]); + const setBudgetMonth = useCallback((year: number, month: number) => { + dispatch({ type: "SET_BUDGET_MONTH", payload: { year, month } }); + }, []); const setCustomDates = useCallback((dateFrom: string, dateTo: string) => { dispatch({ type: "SET_CUSTOM_DATES", payload: { dateFrom, dateTo } }); @@ -249,5 +240,5 @@ export function useReports() { dispatch({ type: "SET_SOURCE_ID", payload: id }); }, []); - return { state, setTab, setPeriod, setCustomDates, navigateBudgetMonth, setPivotConfig, setSourceId }; + return { state, setTab, setPeriod, setCustomDates, setBudgetMonth, setPivotConfig, setSourceId }; } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index b1da85f..7de1e70 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -377,7 +377,8 @@ "ytd": "Year-to-Date", "dollarVar": "$ Var", "pctVar": "% Var", - "noData": "No budget or transaction data for this period." + "noData": "No budget or transaction data for this period.", + "titlePrefix": "Budget vs Actual for" }, "dynamic": "Dynamic Report", "export": "Export", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 28abfd7..0f32f2c 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -377,7 +377,8 @@ "ytd": "Cumul annuel", "dollarVar": "$ \u00c9cart", "pctVar": "% \u00c9cart", - "noData": "Aucune donn\u00e9e de budget ou de transaction pour cette p\u00e9riode." + "noData": "Aucune donn\u00e9e de budget ou de transaction pour cette p\u00e9riode.", + "titlePrefix": "Budget vs Réel pour le mois de" }, "dynamic": "Rapport dynamique", "export": "Exporter", diff --git a/src/pages/ReportsPage.tsx b/src/pages/ReportsPage.tsx index 906a193..0102b19 100644 --- a/src/pages/ReportsPage.tsx +++ b/src/pages/ReportsPage.tsx @@ -6,7 +6,6 @@ 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 MonthNavigator from "../components/budget/MonthNavigator"; import MonthlyTrendsChart from "../components/reports/MonthlyTrendsChart"; import MonthlyTrendsTable from "../components/reports/MonthlyTrendsTable"; import CategoryBarChart from "../components/reports/CategoryBarChart"; @@ -48,8 +47,8 @@ function computeDateRange( } export default function ReportsPage() { - const { t } = useTranslation(); - const { state, setTab, setPeriod, setCustomDates, navigateBudgetMonth, setPivotConfig, setSourceId } = useReports(); + const { t, i18n } = useTranslation(); + const { state, setTab, setPeriod, setCustomDates, setBudgetMonth, setPivotConfig, setSourceId } = useReports(); const [sources, setSources] = useState([]); useEffect(() => { @@ -93,6 +92,19 @@ export default function ReportsPage() { 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); @@ -100,16 +112,30 @@ export default function ReportsPage() {
-

{t("reports.title")}

+ {state.tab === "budgetVsActual" ? ( +

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

+ ) : ( +

{t("reports.title")}

+ )}
- {state.tab === "budgetVsActual" ? ( - - ) : ( + {state.tab !== "budgetVsActual" && (
+ {t("budget.category")} @@ -158,8 +158,8 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps) const sectionYtdPct = sectionTotals.ytdBudget !== 0 ? sectionTotals.ytdVariation / Math.abs(sectionTotals.ytdBudget) : null; return ( -
+
{section.label}
+ ); })} -
{t(typeTotalKeys[section.type])}
{t(typeTotalKeys[section.type])} {cadFormatter(sectionTotals.monthActual)}
{t("common.total")}
{t("common.total")} {cadFormatter(totals.monthActual)}