Revamp dashboard: YTD default, expenses chart, budget table
- Default period changed from month to year-to-date - Remove recent transactions section - Add expenses over time stacked bar chart (by category/month) - Add budget vs actual table (current month) - Reorganize layout: cards, pie + budget table, full-width chart Closes #15 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
861d78eca2
commit
0bbbcc541b
4 changed files with 45 additions and 17 deletions
|
|
@ -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