diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx
new file mode 100644
index 0000000..6f8c97b
--- /dev/null
+++ b/src/pages/DashboardPage.tsx
@@ -0,0 +1,294 @@
+import { Link } from "react-router-dom"
+import { useTranslation } from "react-i18next"
+import {
+ PieChart,
+ Pie,
+ Cell,
+ ResponsiveContainer,
+ Tooltip,
+} from "recharts"
+import { useBudgets, useBudgetDetail } from "@/hooks/useBudgets"
+import type { CategoryType } from "@/lib/types"
+import { categoryColors } from "@/lib/palette"
+import { formatCurrency } from "@/lib/format"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { PageShell } from "@/components/shared/PageShell"
+import { SummaryStrip } from "@/components/dashboard/SummaryStrip"
+import { DashboardSkeleton } from "@/components/dashboard/DashboardSkeleton"
+import QuickAddPicker from "@/components/QuickAddPicker"
+
+// ---------------------------------------------------------------------------
+// Constants
+// ---------------------------------------------------------------------------
+
+const EXPENSE_TYPES: CategoryType[] = [
+ "bill",
+ "variable_expense",
+ "debt",
+ "saving",
+ "investment",
+]
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+/**
+ * Returns the ISO date string for the first day of the given month.
+ * e.g. currentMonthStart(2026, 3) => "2026-03-01"
+ */
+function currentMonthStart(year: number, month: number): string {
+ return `${year}-${String(month).padStart(2, "0")}-01`
+}
+
+// ---------------------------------------------------------------------------
+// Dashboard inner — rendered once a budget id is known
+// ---------------------------------------------------------------------------
+
+function DashboardContent({ budgetId }: { budgetId: string }) {
+ const { t } = useTranslation()
+ const { budget, items, loading } = useBudgetDetail(budgetId)
+
+ if (loading) return
+ if (!budget) return null
+
+ const currency = budget.currency
+
+ // ------------------------------------------------------------------
+ // Derived totals
+ // ------------------------------------------------------------------
+
+ const totalIncome = items
+ .filter((i) => i.category?.type === "income")
+ .reduce((sum, i) => sum + i.actual_amount, 0)
+
+ const totalExpenses = items
+ .filter((i) => i.category?.type !== "income")
+ .reduce((sum, i) => sum + i.actual_amount, 0)
+
+ const availableBalance = totalIncome - totalExpenses + budget.carryover_amount
+
+ const budgetedIncome = items
+ .filter((i) => i.category?.type === "income")
+ .reduce((sum, i) => sum + i.budgeted_amount, 0)
+
+ const budgetedExpenses = items
+ .filter((i) => i.category?.type !== "income")
+ .reduce((sum, i) => sum + i.budgeted_amount, 0)
+
+ // ------------------------------------------------------------------
+ // Pie chart data — actual spending grouped by category type (non-income)
+ // ------------------------------------------------------------------
+
+ const pieData = EXPENSE_TYPES.map((type) => {
+ const total = items
+ .filter((i) => i.category?.type === type)
+ .reduce((sum, i) => sum + i.actual_amount, 0)
+ return { name: t(`categories.types.${type}`), value: total, type }
+ }).filter((d) => d.value > 0)
+
+ // ------------------------------------------------------------------
+ // Category progress rows — non-income types with at least one item
+ // ------------------------------------------------------------------
+
+ const progressGroups = EXPENSE_TYPES.map((type) => {
+ const groupItems = items.filter((i) => i.category?.type === type)
+ if (groupItems.length === 0) return null
+
+ const budgeted = groupItems.reduce((sum, i) => sum + i.budgeted_amount, 0)
+ const actual = groupItems.reduce((sum, i) => sum + i.actual_amount, 0)
+ const pct = budgeted > 0 ? Math.round((actual / budgeted) * 100) : 0
+ const overBudget = actual > budgeted
+
+ return { type, budgeted, actual, pct, overBudget }
+ }).filter(Boolean)
+
+ return (
+
+ {/* Quick Add button */}
+
+
+
+
+ {/* Summary cards */}
+
= 0,
+ }}
+ t={t}
+ />
+
+ {/* Expense breakdown chart + category progress */}
+
+ {/* Pie chart */}
+ {pieData.length > 0 && (
+
+
+
+ {t("dashboard.expenseBreakdown")}
+
+
+
+
+
+
+ {pieData.map((entry) => (
+ |
+ ))}
+
+
+ formatCurrency(Number(value), currency)
+ }
+ />
+
+
+
+ {/* Legend */}
+
+ {pieData.map((entry) => (
+ -
+
+ {entry.name}
+
+ {formatCurrency(entry.value, currency)}
+
+
+ ))}
+
+
+
+ )}
+
+ {/* Category progress */}
+ {progressGroups.length > 0 && (
+
+
+
+ {t("dashboard.expenseBreakdown")}
+
+
+
+
+
+
+ )}
+
+
+ )
+}
+
+// ---------------------------------------------------------------------------
+// DashboardPage
+// ---------------------------------------------------------------------------
+
+export default function DashboardPage() {
+ const { t } = useTranslation()
+ const { budgets, loading } = useBudgets()
+
+ // Find budget whose start_date falls in the current calendar month
+ const now = new Date()
+ const year = now.getFullYear()
+ const month = now.getMonth() + 1
+ const monthPrefix = currentMonthStart(year, month).slice(0, 7) // "YYYY-MM"
+
+ const currentBudget = budgets.find((b) =>
+ b.start_date.startsWith(monthPrefix)
+ )
+
+ if (loading) return (
+
+
+
+ )
+
+ return (
+
+ {!currentBudget ? (
+ /* No budget for this month */
+
+
{t("dashboard.noBudget")}
+
+ {t("budgets.newBudget")}
+
+
+ ) : (
+
+ )}
+
+ )
+}