Compare commits
2 commits
e1192beca3
...
0bbbcc541b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bbbcc541b | ||
|
|
861d78eca2 |
10 changed files with 88 additions and 60 deletions
|
|
@ -387,12 +387,12 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
|
||||||
</tr>
|
</tr>
|
||||||
{reorderRows(group, subtotalsOnTop).map((row) => renderRow(row))}
|
{reorderRows(group, subtotalsOnTop).map((row) => renderRow(row))}
|
||||||
<tr className="bg-[var(--muted)]/40 border-b border-[var(--border)]">
|
<tr className="bg-[var(--muted)]/40 border-b border-[var(--border)]">
|
||||||
<td className="py-2 px-3 sticky left-0 bg-[var(--muted)]/40 z-10 text-xs font-semibold">
|
<td className="py-2.5 px-3 sticky left-0 bg-[var(--muted)]/40 z-10 text-sm font-semibold">
|
||||||
{t(typeTotalKeys[type])}
|
{t(typeTotalKeys[type])}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-2 text-right text-xs font-semibold">{formatSigned(sectionAnnualTotal)}</td>
|
<td className="py-2.5 px-2 text-right text-sm font-semibold">{formatSigned(sectionAnnualTotal)}</td>
|
||||||
{sectionMonthTotals.map((total, mIdx) => (
|
{sectionMonthTotals.map((total, mIdx) => (
|
||||||
<td key={mIdx} className="py-2 px-2 text-right text-xs font-semibold">
|
<td key={mIdx} className="py-2.5 px-2 text-right text-sm font-semibold">
|
||||||
{formatSigned(total)}
|
{formatSigned(total)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|
@ -401,11 +401,11 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{/* Totals row */}
|
{/* Totals row */}
|
||||||
<tr className="bg-[var(--muted)] font-semibold">
|
<tr className="bg-[var(--muted)] font-bold border-t-2 border-[var(--border)]">
|
||||||
<td className="py-2.5 px-3 sticky left-0 bg-[var(--muted)] z-10 text-xs">{t("common.total")}</td>
|
<td className="py-3 px-3 sticky left-0 bg-[var(--muted)] z-10 text-sm">{t("common.total")}</td>
|
||||||
<td className="py-2.5 px-2 text-right text-xs">{formatSigned(annualTotal)}</td>
|
<td className="py-3 px-2 text-right text-sm">{formatSigned(annualTotal)}</td>
|
||||||
{monthTotals.map((total, mIdx) => (
|
{monthTotals.map((total, mIdx) => (
|
||||||
<td key={mIdx} className="py-2.5 px-2 text-right text-xs">
|
<td key={mIdx} className="py-3 px-2 text-right text-sm">
|
||||||
{formatSigned(total)}
|
{formatSigned(total)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -256,26 +256,26 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<tr className="border-b border-[var(--border)] bg-[var(--muted)]/40 font-semibold">
|
<tr className="border-b border-[var(--border)] bg-[var(--muted)]/40 font-semibold text-sm">
|
||||||
<td className="px-3 py-2 text-xs">{t(typeTotalKeys[section.type])}</td>
|
<td className="px-3 py-2.5">{t(typeTotalKeys[section.type])}</td>
|
||||||
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50">
|
<td className="text-right px-3 py-2.5 border-l border-[var(--border)]/50">
|
||||||
{cadFormatter(sectionTotals.monthActual)}
|
{cadFormatter(sectionTotals.monthActual)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right px-3 py-2">{cadFormatter(sectionTotals.monthBudget)}</td>
|
<td className="text-right px-3 py-2.5">{cadFormatter(sectionTotals.monthBudget)}</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.monthVariation)}`}>
|
<td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.monthVariation)}`}>
|
||||||
{cadFormatter(sectionTotals.monthVariation)}
|
{cadFormatter(sectionTotals.monthVariation)}
|
||||||
</td>
|
</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.monthVariation)}`}>
|
<td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.monthVariation)}`}>
|
||||||
{pctFormatter(sectionMonthPct)}
|
{pctFormatter(sectionMonthPct)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50">
|
<td className="text-right px-3 py-2.5 border-l border-[var(--border)]/50">
|
||||||
{cadFormatter(sectionTotals.ytdActual)}
|
{cadFormatter(sectionTotals.ytdActual)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right px-3 py-2">{cadFormatter(sectionTotals.ytdBudget)}</td>
|
<td className="text-right px-3 py-2.5">{cadFormatter(sectionTotals.ytdBudget)}</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.ytdVariation)}`}>
|
<td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.ytdVariation)}`}>
|
||||||
{cadFormatter(sectionTotals.ytdVariation)}
|
{cadFormatter(sectionTotals.ytdVariation)}
|
||||||
</td>
|
</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.ytdVariation)}`}>
|
<td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.ytdVariation)}`}>
|
||||||
{pctFormatter(sectionYtdPct)}
|
{pctFormatter(sectionYtdPct)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -283,26 +283,26 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{/* Grand totals */}
|
{/* Grand totals */}
|
||||||
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20">
|
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
|
||||||
<td className="px-3 py-2">{t("common.total")}</td>
|
<td className="px-3 py-3">{t("common.total")}</td>
|
||||||
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50">
|
<td className="text-right px-3 py-3 border-l border-[var(--border)]/50">
|
||||||
{cadFormatter(totals.monthActual)}
|
{cadFormatter(totals.monthActual)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right px-3 py-2">{cadFormatter(totals.monthBudget)}</td>
|
<td className="text-right px-3 py-3">{cadFormatter(totals.monthBudget)}</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(totals.monthVariation)}`}>
|
<td className={`text-right px-3 py-3 ${variationColor(totals.monthVariation)}`}>
|
||||||
{cadFormatter(totals.monthVariation)}
|
{cadFormatter(totals.monthVariation)}
|
||||||
</td>
|
</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(totals.monthVariation)}`}>
|
<td className={`text-right px-3 py-3 ${variationColor(totals.monthVariation)}`}>
|
||||||
{pctFormatter(totalMonthPct)}
|
{pctFormatter(totalMonthPct)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50">
|
<td className="text-right px-3 py-3 border-l border-[var(--border)]/50">
|
||||||
{cadFormatter(totals.ytdActual)}
|
{cadFormatter(totals.ytdActual)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right px-3 py-2">{cadFormatter(totals.ytdBudget)}</td>
|
<td className="text-right px-3 py-3">{cadFormatter(totals.ytdBudget)}</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(totals.ytdVariation)}`}>
|
<td className={`text-right px-3 py-3 ${variationColor(totals.ytdVariation)}`}>
|
||||||
{cadFormatter(totals.ytdVariation)}
|
{cadFormatter(totals.ytdVariation)}
|
||||||
</td>
|
</td>
|
||||||
<td className={`text-right px-3 py-2 ${variationColor(totals.ytdVariation)}`}>
|
<td className={`text-right px-3 py-3 ${variationColor(totals.ytdVariation)}`}>
|
||||||
{pctFormatter(totalYtdPct)}
|
{pctFormatter(totalYtdPct)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@ export default function CategoryOverTimeTable({ data, hiddenCategories }: Catego
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20">
|
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
|
||||||
<td className="px-3 py-2 sticky left-0 bg-[var(--muted)]/20 z-10">{t("common.total")}</td>
|
<td className="px-3 py-3 sticky left-0 bg-[var(--muted)]/20 z-10">{t("common.total")}</td>
|
||||||
{months.map((month) => {
|
{months.map((month) => {
|
||||||
const monthData = data.data.find((d) => d.month === month);
|
const monthData = data.data.find((d) => d.month === month);
|
||||||
const monthTotal = visibleCategories.reduce(
|
const monthTotal = visibleCategories.reduce(
|
||||||
|
|
@ -89,12 +89,12 @@ export default function CategoryOverTimeTable({ data, hiddenCategories }: Catego
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<td key={month} className="text-right px-3 py-2">
|
<td key={month} className="text-right px-3 py-3">
|
||||||
{cadFormatter(monthTotal)}
|
{cadFormatter(monthTotal)}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50">
|
<td className="text-right px-3 py-3 border-l border-[var(--border)]/50">
|
||||||
{cadFormatter(
|
{cadFormatter(
|
||||||
visibleCategories.reduce(
|
visibleCategories.reduce(
|
||||||
(sum, cat) => sum + data.data.reduce((s, d) => s + ((d as Record<string, unknown>)[cat] as number || 0), 0),
|
(sum, cat) => sum + data.data.reduce((s, d) => s + ((d as Record<string, unknown>)[cat] as number || 0), 0),
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,10 @@ export default function CategoryTable({ data, hiddenCategories }: CategoryTableP
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20">
|
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
|
||||||
<td className="px-3 py-2">{t("common.total")}</td>
|
<td className="px-3 py-3">{t("common.total")}</td>
|
||||||
<td className="text-right px-3 py-2">{cadFormatter(grandTotal)}</td>
|
<td className="text-right px-3 py-3">{cadFormatter(grandTotal)}</td>
|
||||||
<td className="text-right px-3 py-2 text-[var(--muted-foreground)]">100%</td>
|
<td className="text-right px-3 py-3 text-[var(--muted-foreground)]">100%</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -192,13 +192,13 @@ export default function DynamicReportTable({ config, result }: DynamicReportTabl
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
{/* Grand total */}
|
{/* Grand total */}
|
||||||
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20">
|
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
|
||||||
<td colSpan={rowDims.length || 1} className="px-3 py-2">
|
<td colSpan={rowDims.length || 1} className="px-3 py-3">
|
||||||
{t("reports.pivot.total")}
|
{t("reports.pivot.total")}
|
||||||
</td>
|
</td>
|
||||||
{colValues.map((colVal) =>
|
{colValues.map((colVal) =>
|
||||||
measures.map((m) => (
|
measures.map((m) => (
|
||||||
<td key={`total-${colVal}-${m}`} className="text-right px-3 py-2 border-l border-[var(--border)]/50">
|
<td key={`total-${colVal}-${m}`} className="text-right px-3 py-3 border-l border-[var(--border)]/50">
|
||||||
{cadFormatter(grandTotals[colVal]?.[m] || 0)}
|
{cadFormatter(grandTotals[colVal]?.[m] || 0)}
|
||||||
</td>
|
</td>
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,11 @@ export default function MonthlyTrendsTable({ data }: MonthlyTrendsTableProps) {
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20">
|
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
|
||||||
<td className="px-3 py-2">{t("common.total")}</td>
|
<td className="px-3 py-3">{t("common.total")}</td>
|
||||||
<td className="text-right px-3 py-2 text-[var(--positive)]">{cadFormatter(totals.income)}</td>
|
<td className="text-right px-3 py-3 text-[var(--positive)]">{cadFormatter(totals.income)}</td>
|
||||||
<td className="text-right px-3 py-2 text-[var(--negative)]">{cadFormatter(totals.expenses)}</td>
|
<td className="text-right px-3 py-3 text-[var(--negative)]">{cadFormatter(totals.expenses)}</td>
|
||||||
<td className={`text-right px-3 py-2 ${totals.income - totals.expenses >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"}`}>
|
<td className={`text-right px-3 py-3 ${totals.income - totals.expenses >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"}`}>
|
||||||
{cadFormatter(totals.income - totals.expenses)}
|
{cadFormatter(totals.income - totals.expenses)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,21 @@ import type {
|
||||||
DashboardPeriod,
|
DashboardPeriod,
|
||||||
DashboardSummary,
|
DashboardSummary,
|
||||||
CategoryBreakdownItem,
|
CategoryBreakdownItem,
|
||||||
RecentTransaction,
|
CategoryOverTimeData,
|
||||||
|
BudgetVsActualRow,
|
||||||
} from "../shared/types";
|
} from "../shared/types";
|
||||||
import {
|
import {
|
||||||
getDashboardSummary,
|
getDashboardSummary,
|
||||||
getExpensesByCategory,
|
getExpensesByCategory,
|
||||||
getRecentTransactions,
|
|
||||||
} from "../services/dashboardService";
|
} from "../services/dashboardService";
|
||||||
|
import { getCategoryOverTime } from "../services/reportService";
|
||||||
|
import { getBudgetVsActualData } from "../services/budgetService";
|
||||||
|
|
||||||
interface DashboardState {
|
interface DashboardState {
|
||||||
summary: DashboardSummary;
|
summary: DashboardSummary;
|
||||||
categoryBreakdown: CategoryBreakdownItem[];
|
categoryBreakdown: CategoryBreakdownItem[];
|
||||||
recentTransactions: RecentTransaction[];
|
categoryOverTime: CategoryOverTimeData;
|
||||||
|
budgetVsActual: BudgetVsActualRow[];
|
||||||
period: DashboardPeriod;
|
period: DashboardPeriod;
|
||||||
customDateFrom: string;
|
customDateFrom: string;
|
||||||
customDateTo: string;
|
customDateTo: string;
|
||||||
|
|
@ -30,7 +33,8 @@ type DashboardAction =
|
||||||
payload: {
|
payload: {
|
||||||
summary: DashboardSummary;
|
summary: DashboardSummary;
|
||||||
categoryBreakdown: CategoryBreakdownItem[];
|
categoryBreakdown: CategoryBreakdownItem[];
|
||||||
recentTransactions: RecentTransaction[];
|
categoryOverTime: CategoryOverTimeData;
|
||||||
|
budgetVsActual: BudgetVsActualRow[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| { type: "SET_PERIOD"; payload: DashboardPeriod }
|
| { type: "SET_PERIOD"; payload: DashboardPeriod }
|
||||||
|
|
@ -38,14 +42,15 @@ type DashboardAction =
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
||||||
const monthStartStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`;
|
const yearStartStr = `${now.getFullYear()}-01-01`;
|
||||||
|
|
||||||
const initialState: DashboardState = {
|
const initialState: DashboardState = {
|
||||||
summary: { totalCount: 0, totalAmount: 0, incomeTotal: 0, expenseTotal: 0 },
|
summary: { totalCount: 0, totalAmount: 0, incomeTotal: 0, expenseTotal: 0 },
|
||||||
categoryBreakdown: [],
|
categoryBreakdown: [],
|
||||||
recentTransactions: [],
|
categoryOverTime: { categories: [], data: [], colors: {}, categoryIds: {} },
|
||||||
period: "month",
|
budgetVsActual: [],
|
||||||
customDateFrom: monthStartStr,
|
period: "year",
|
||||||
|
customDateFrom: yearStartStr,
|
||||||
customDateTo: todayStr,
|
customDateTo: todayStr,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
@ -62,7 +67,8 @@ function reducer(state: DashboardState, action: DashboardAction): DashboardState
|
||||||
...state,
|
...state,
|
||||||
summary: action.payload.summary,
|
summary: action.payload.summary,
|
||||||
categoryBreakdown: action.payload.categoryBreakdown,
|
categoryBreakdown: action.payload.categoryBreakdown,
|
||||||
recentTransactions: action.payload.recentTransactions,
|
categoryOverTime: action.payload.categoryOverTime,
|
||||||
|
budgetVsActual: action.payload.budgetVsActual,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
case "SET_PERIOD":
|
case "SET_PERIOD":
|
||||||
|
|
@ -129,14 +135,17 @@ export function useDashboard() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { dateFrom, dateTo } = computeDateRange(period, customFrom, customTo);
|
const { dateFrom, dateTo } = computeDateRange(period, customFrom, customTo);
|
||||||
const [summary, categoryBreakdown, recentTransactions] = await Promise.all([
|
const currentMonth = new Date().getMonth() + 1;
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const [summary, categoryBreakdown, categoryOverTime, budgetVsActual] = await Promise.all([
|
||||||
getDashboardSummary(dateFrom, dateTo),
|
getDashboardSummary(dateFrom, dateTo),
|
||||||
getExpensesByCategory(dateFrom, dateTo),
|
getExpensesByCategory(dateFrom, dateTo),
|
||||||
getRecentTransactions(10),
|
getCategoryOverTime(dateFrom, dateTo),
|
||||||
|
getBudgetVsActualData(currentYear, currentMonth),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (fetchId !== fetchIdRef.current) return;
|
if (fetchId !== fetchIdRef.current) return;
|
||||||
dispatch({ type: "SET_DATA", payload: { summary, categoryBreakdown, recentTransactions } });
|
dispatch({ type: "SET_DATA", payload: { summary, categoryBreakdown, categoryOverTime, budgetVsActual } });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (fetchId !== fetchIdRef.current) return;
|
if (fetchId !== fetchIdRef.current) return;
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@
|
||||||
"noData": "No data available. Start by importing your bank statements.",
|
"noData": "No data available. Start by importing your bank statements.",
|
||||||
"expensesByCategory": "Expenses by Category",
|
"expensesByCategory": "Expenses by Category",
|
||||||
"recentTransactions": "Recent Transactions",
|
"recentTransactions": "Recent Transactions",
|
||||||
|
"budgetVsActual": "Budget vs Actual",
|
||||||
|
"expensesOverTime": "Expenses Over Time",
|
||||||
"period": {
|
"period": {
|
||||||
"month": "This month",
|
"month": "This month",
|
||||||
"3months": "3 months",
|
"3months": "3 months",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@
|
||||||
"noData": "Aucune donnée disponible. Commencez par importer vos relevés bancaires.",
|
"noData": "Aucune donnée disponible. Commencez par importer vos relevés bancaires.",
|
||||||
"expensesByCategory": "Dépenses par catégorie",
|
"expensesByCategory": "Dépenses par catégorie",
|
||||||
"recentTransactions": "Transactions récentes",
|
"recentTransactions": "Transactions récentes",
|
||||||
|
"budgetVsActual": "Budget vs Réel",
|
||||||
|
"expensesOverTime": "Dépenses dans le temps",
|
||||||
"period": {
|
"period": {
|
||||||
"month": "Ce mois",
|
"month": "Ce mois",
|
||||||
"3months": "3 mois",
|
"3months": "3 mois",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import { useDashboard } from "../hooks/useDashboard";
|
||||||
import { PageHelp } from "../components/shared/PageHelp";
|
import { PageHelp } from "../components/shared/PageHelp";
|
||||||
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
||||||
import CategoryPieChart from "../components/dashboard/CategoryPieChart";
|
import CategoryPieChart from "../components/dashboard/CategoryPieChart";
|
||||||
import RecentTransactionsList from "../components/dashboard/RecentTransactionsList";
|
import CategoryOverTimeChart from "../components/reports/CategoryOverTimeChart";
|
||||||
|
import BudgetVsActualTable from "../components/reports/BudgetVsActualTable";
|
||||||
import TransactionDetailModal from "../components/shared/TransactionDetailModal";
|
import TransactionDetailModal from "../components/shared/TransactionDetailModal";
|
||||||
import type { CategoryBreakdownItem, DashboardPeriod } from "../shared/types";
|
import type { CategoryBreakdownItem, DashboardPeriod } from "../shared/types";
|
||||||
|
|
||||||
|
|
@ -41,7 +42,7 @@ function computeDateRange(
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { state, setPeriod, setCustomDates } = useDashboard();
|
const { state, setPeriod, setCustomDates } = useDashboard();
|
||||||
const { summary, categoryBreakdown, recentTransactions, period, isLoading } = state;
|
const { summary, categoryBreakdown, categoryOverTime, budgetVsActual, period, isLoading } = state;
|
||||||
|
|
||||||
const [hiddenCategories, setHiddenCategories] = useState<Set<string>>(new Set());
|
const [hiddenCategories, setHiddenCategories] = useState<Set<string>>(new Set());
|
||||||
const [detailModal, setDetailModal] = useState<CategoryBreakdownItem | null>(null);
|
const [detailModal, setDetailModal] = useState<CategoryBreakdownItem | null>(null);
|
||||||
|
|
@ -108,7 +109,7 @@ export default function DashboardPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
{cards.map((card) => (
|
{cards.map((card) => (
|
||||||
<div
|
<div
|
||||||
key={card.labelKey}
|
key={card.labelKey}
|
||||||
|
|
@ -125,7 +126,7 @@ export default function DashboardPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
||||||
<CategoryPieChart
|
<CategoryPieChart
|
||||||
data={categoryBreakdown}
|
data={categoryBreakdown}
|
||||||
hiddenCategories={hiddenCategories}
|
hiddenCategories={hiddenCategories}
|
||||||
|
|
@ -133,7 +134,21 @@ export default function DashboardPage() {
|
||||||
onShowAll={showAll}
|
onShowAll={showAll}
|
||||||
onViewDetails={viewDetails}
|
onViewDetails={viewDetails}
|
||||||
/>
|
/>
|
||||||
<RecentTransactionsList transactions={recentTransactions} />
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold mb-3">{t("dashboard.budgetVsActual")}</h2>
|
||||||
|
<BudgetVsActualTable data={budgetVsActual} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-3">{t("dashboard.expensesOverTime")}</h2>
|
||||||
|
<CategoryOverTimeChart
|
||||||
|
data={categoryOverTime}
|
||||||
|
hiddenCategories={hiddenCategories}
|
||||||
|
onToggleHidden={toggleHidden}
|
||||||
|
onShowAll={showAll}
|
||||||
|
onViewDetails={viewDetails}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{detailModal && (
|
{detailModal && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue