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,
|
||||
DashboardSummary,
|
||||
CategoryBreakdownItem,
|
||||
RecentTransaction,
|
||||
CategoryOverTimeData,
|
||||
BudgetVsActualRow,
|
||||
} from "../shared/types";
|
||||
import {
|
||||
getDashboardSummary,
|
||||
getExpensesByCategory,
|
||||
getRecentTransactions,
|
||||
} from "../services/dashboardService";
|
||||
import { getCategoryOverTime } from "../services/reportService";
|
||||
import { getBudgetVsActualData } from "../services/budgetService";
|
||||
|
||||
interface DashboardState {
|
||||
summary: DashboardSummary;
|
||||
categoryBreakdown: CategoryBreakdownItem[];
|
||||
recentTransactions: RecentTransaction[];
|
||||
categoryOverTime: CategoryOverTimeData;
|
||||
budgetVsActual: BudgetVsActualRow[];
|
||||
period: DashboardPeriod;
|
||||
customDateFrom: string;
|
||||
customDateTo: string;
|
||||
|
|
@ -30,7 +33,8 @@ type DashboardAction =
|
|||
payload: {
|
||||
summary: DashboardSummary;
|
||||
categoryBreakdown: CategoryBreakdownItem[];
|
||||
recentTransactions: RecentTransaction[];
|
||||
categoryOverTime: CategoryOverTimeData;
|
||||
budgetVsActual: BudgetVsActualRow[];
|
||||
};
|
||||
}
|
||||
| { type: "SET_PERIOD"; payload: DashboardPeriod }
|
||||
|
|
@ -38,14 +42,15 @@ type DashboardAction =
|
|||
|
||||
const now = new Date();
|
||||
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 = {
|
||||
summary: { totalCount: 0, totalAmount: 0, incomeTotal: 0, expenseTotal: 0 },
|
||||
categoryBreakdown: [],
|
||||
recentTransactions: [],
|
||||
period: "month",
|
||||
customDateFrom: monthStartStr,
|
||||
categoryOverTime: { categories: [], data: [], colors: {}, categoryIds: {} },
|
||||
budgetVsActual: [],
|
||||
period: "year",
|
||||
customDateFrom: yearStartStr,
|
||||
customDateTo: todayStr,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
|
@ -62,7 +67,8 @@ function reducer(state: DashboardState, action: DashboardAction): DashboardState
|
|||
...state,
|
||||
summary: action.payload.summary,
|
||||
categoryBreakdown: action.payload.categoryBreakdown,
|
||||
recentTransactions: action.payload.recentTransactions,
|
||||
categoryOverTime: action.payload.categoryOverTime,
|
||||
budgetVsActual: action.payload.budgetVsActual,
|
||||
isLoading: false,
|
||||
};
|
||||
case "SET_PERIOD":
|
||||
|
|
@ -129,14 +135,17 @@ export function useDashboard() {
|
|||
|
||||
try {
|
||||
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),
|
||||
getExpensesByCategory(dateFrom, dateTo),
|
||||
getRecentTransactions(10),
|
||||
getCategoryOverTime(dateFrom, dateTo),
|
||||
getBudgetVsActualData(currentYear, currentMonth),
|
||||
]);
|
||||
|
||||
if (fetchId !== fetchIdRef.current) return;
|
||||
dispatch({ type: "SET_DATA", payload: { summary, categoryBreakdown, recentTransactions } });
|
||||
dispatch({ type: "SET_DATA", payload: { summary, categoryBreakdown, categoryOverTime, budgetVsActual } });
|
||||
} catch (e) {
|
||||
if (fetchId !== fetchIdRef.current) return;
|
||||
dispatch({
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
"noData": "No data available. Start by importing your bank statements.",
|
||||
"expensesByCategory": "Expenses by Category",
|
||||
"recentTransactions": "Recent Transactions",
|
||||
"budgetVsActual": "Budget vs Actual",
|
||||
"expensesOverTime": "Expenses Over Time",
|
||||
"period": {
|
||||
"month": "This month",
|
||||
"3months": "3 months",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
"noData": "Aucune donnée disponible. Commencez par importer vos relevés bancaires.",
|
||||
"expensesByCategory": "Dépenses par catégorie",
|
||||
"recentTransactions": "Transactions récentes",
|
||||
"budgetVsActual": "Budget vs Réel",
|
||||
"expensesOverTime": "Dépenses dans le temps",
|
||||
"period": {
|
||||
"month": "Ce mois",
|
||||
"3months": "3 mois",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { useDashboard } from "../hooks/useDashboard";
|
|||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
||||
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 type { CategoryBreakdownItem, DashboardPeriod } from "../shared/types";
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ function computeDateRange(
|
|||
export default function DashboardPage() {
|
||||
const { t } = useTranslation();
|
||||
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 [detailModal, setDetailModal] = useState<CategoryBreakdownItem | null>(null);
|
||||
|
|
@ -108,7 +109,7 @@ export default function DashboardPage() {
|
|||
/>
|
||||
</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) => (
|
||||
<div
|
||||
key={card.labelKey}
|
||||
|
|
@ -125,7 +126,7 @@ export default function DashboardPage() {
|
|||
))}
|
||||
</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
|
||||
data={categoryBreakdown}
|
||||
hiddenCategories={hiddenCategories}
|
||||
|
|
@ -133,7 +134,21 @@ export default function DashboardPage() {
|
|||
onShowAll={showAll}
|
||||
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>
|
||||
|
||||
{detailModal && (
|
||||
|
|
|
|||
Loading…
Reference in a new issue