docs(02): create phase plan — 3 plans across 2 waves for dashboard charts and layout

This commit is contained in:
2026-03-16 12:57:33 +01:00
parent e0b3194211
commit dca5b04494
4 changed files with 759 additions and 6 deletions

View File

@@ -0,0 +1,282 @@
---
phase: 02-dashboard-charts-and-layout
plan: 03
type: execute
wave: 2
depends_on: ["02-01", "02-02"]
files_modified:
- src/pages/DashboardPage.tsx
- src/components/dashboard/DashboardSkeleton.tsx
autonomous: true
requirements: [UI-DASH-01, UI-BAR-01, UI-HBAR-01, UI-DONUT-01]
must_haves:
truths:
- "Dashboard page reads month from URL search params and looks up the corresponding budget"
- "MonthNavigator appears in the PageShell action slot with a dropdown of all available budget months"
- "Dashboard displays SummaryStrip, then a 3-column chart row (donut, vertical bar, horizontal bar), then QuickAdd button"
- "Charts and cards update when user navigates to a different month"
- "When no budget exists for the selected month, an empty prompt is shown with create/generate options"
- "DashboardSkeleton mirrors the new 3-column chart layout"
artifacts:
- path: "src/pages/DashboardPage.tsx"
provides: "Refactored dashboard with URL month nav and 3-column chart grid"
exports: ["default"]
min_lines: 80
- path: "src/components/dashboard/DashboardSkeleton.tsx"
provides: "Updated skeleton matching 3-column chart layout"
exports: ["DashboardSkeleton"]
key_links:
- from: "src/pages/DashboardPage.tsx"
to: "src/hooks/useMonthParam.ts"
via: "useMonthParam hook for month state"
pattern: "useMonthParam"
- from: "src/pages/DashboardPage.tsx"
to: "src/components/dashboard/MonthNavigator.tsx"
via: "MonthNavigator in PageShell action slot"
pattern: "MonthNavigator"
- from: "src/pages/DashboardPage.tsx"
to: "src/components/dashboard/charts/ExpenseDonutChart.tsx"
via: "import and render in chart grid"
pattern: "ExpenseDonutChart"
- from: "src/pages/DashboardPage.tsx"
to: "src/components/dashboard/charts/IncomeBarChart.tsx"
via: "import and render in chart grid"
pattern: "IncomeBarChart"
- from: "src/pages/DashboardPage.tsx"
to: "src/components/dashboard/charts/SpendBarChart.tsx"
via: "import and render in chart grid"
pattern: "SpendBarChart"
- from: "src/pages/DashboardPage.tsx"
to: "src/hooks/useBudgets.ts"
via: "useBudgets for budget list + useBudgetDetail for selected budget"
pattern: "useBudgets.*useBudgetDetail"
---
<objective>
Wire all chart components, month navigation, and updated layout into the DashboardPage, and update the DashboardSkeleton to match.
Purpose: This is the integration plan that ties together the month navigation (Plan 01) and chart components (Plan 02) into the refactored dashboard. Replaces the existing flat pie chart and progress bars with the 3-column chart grid, adds URL-based month navigation, and updates the loading skeleton.
Output: Refactored DashboardPage.tsx and updated DashboardSkeleton.tsx.
</objective>
<execution_context>
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
@/home/jlmak/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-dashboard-charts-and-layout/02-CONTEXT.md
@.planning/phases/02-dashboard-charts-and-layout/02-RESEARCH.md
@.planning/phases/02-dashboard-charts-and-layout/02-01-SUMMARY.md
@.planning/phases/02-dashboard-charts-and-layout/02-02-SUMMARY.md
<interfaces>
<!-- Contracts from Plan 01 and Plan 02 that this plan consumes. -->
From src/hooks/useMonthParam.ts (Plan 01):
```typescript
export function useMonthParam(): {
month: string // "YYYY-MM"
setMonth: (newMonth: string) => void
navigateMonth: (delta: number) => void
}
```
From src/components/dashboard/MonthNavigator.tsx (Plan 01):
```typescript
interface MonthNavigatorProps {
availableMonths: string[] // "YYYY-MM"[]
t: (key: string) => string
}
export function MonthNavigator({ availableMonths, t }: MonthNavigatorProps): JSX.Element
```
From src/components/dashboard/charts/ChartEmptyState.tsx (Plan 01):
```typescript
export function ChartEmptyState({ message, className }: { message: string; className?: string }): JSX.Element
```
From src/components/dashboard/charts/ExpenseDonutChart.tsx (Plan 02):
```typescript
interface ExpenseDonutChartProps {
data: Array<{ type: string; value: number; label: string }>
totalExpenses: number
currency: string
emptyMessage: string
}
export function ExpenseDonutChart(props: ExpenseDonutChartProps): JSX.Element
```
From src/components/dashboard/charts/IncomeBarChart.tsx (Plan 02):
```typescript
interface IncomeBarChartProps {
data: Array<{ label: string; budgeted: number; actual: number }>
currency: string
emptyMessage: string
}
export function IncomeBarChart(props: IncomeBarChartProps): JSX.Element
```
From src/components/dashboard/charts/SpendBarChart.tsx (Plan 02):
```typescript
interface SpendBarChartProps {
data: Array<{ type: string; label: string; budgeted: number; actual: number }>
currency: string
emptyMessage: string
}
export function SpendBarChart(props: SpendBarChartProps): JSX.Element
```
From src/components/shared/PageShell.tsx:
```typescript
export function PageShell({ title, description, action, children }: PageShellProps): JSX.Element
// action slot renders top-right, ideal for MonthNavigator
```
From src/hooks/useBudgets.ts:
```typescript
export function useBudgets(): { budgets: Budget[]; loading: boolean; createBudget: UseMutationResult; generateFromTemplate: UseMutationResult; ... }
export function useBudgetDetail(id: string): { budget: Budget | null; items: BudgetItem[]; loading: boolean }
```
From src/lib/types.ts:
```typescript
export type CategoryType = "income" | "bill" | "variable_expense" | "debt" | "saving" | "investment"
export interface Budget { id: string; start_date: string; end_date: string; currency: string; carryover_amount: number; ... }
export interface BudgetItem { id: string; budget_id: string; category_id: string; budgeted_amount: number; actual_amount: number; category?: Category; ... }
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Refactor DashboardPage with month navigation and 3-column chart grid</name>
<files>src/pages/DashboardPage.tsx</files>
<action>
Rewrite `src/pages/DashboardPage.tsx` to replace the existing flat pie chart + progress bars with the new 3-chart layout and URL-based month navigation.
**DashboardPage (outer component):**
- Remove the hardcoded `currentMonthStart` helper and the `now`/`year`/`month`/`monthPrefix` date logic
- Import `useMonthParam` from `@/hooks/useMonthParam`
- Import `MonthNavigator` from `@/components/dashboard/MonthNavigator`
- Call `const { month } = useMonthParam()` to get the selected month as `YYYY-MM`
- Call `const { budgets, loading } = useBudgets()`
- Derive `availableMonths` from budgets: `useMemo(() => budgets.map(b => b.start_date.slice(0, 7)), [budgets])` -- array of `YYYY-MM` strings
- Find current budget: `useMemo(() => budgets.find(b => b.start_date.startsWith(month)), [budgets, month])` -- uses `startsWith` prefix matching (per Pitfall 7)
- Pass `MonthNavigator` into PageShell `action` slot: `<PageShell title={t("dashboard.title")} action={<MonthNavigator availableMonths={availableMonths} t={t} />}>`
- Loading state: show `DashboardSkeleton` inside PageShell (same as current)
- No budget for month: show empty prompt with `t("dashboard.noBudgetForMonth")` text and two buttons:
- "Create Budget" button calling `createBudget.mutate({ month: parsedMonth, year: parsedYear, currency: "EUR" })`
- "Generate from Template" button calling `generateFromTemplate.mutate({ month: parsedMonth, year: parsedYear, currency: "EUR" })`
- Parse month/year from the `month` string (split on "-")
- Per user decision: empty prompt when navigating to month with no budget, with create/generate option
- When budget exists: render `<DashboardContent budgetId={currentBudget.id} />`
**DashboardContent (inner component):**
- Keep `useBudgetDetail(budgetId)` call and the loading/null guards
- Keep existing derived totals logic (totalIncome, totalExpenses, availableBalance, budgetedIncome, budgetedExpenses)
- Memoize all derived data with `useMemo` (wrap existing reduce operations)
- **Derive pieData** (same as existing but memoized):
```typescript
const pieData = useMemo(() =>
EXPENSE_TYPES.map(type => {
const total = items.filter(i => i.category?.type === type).reduce((sum, i) => sum + i.actual_amount, 0)
return { type, value: total, label: t(`categories.types.${type}`) }
}).filter(d => d.value > 0),
[items, t])
```
- **Derive incomeBarData** (NEW):
```typescript
const incomeBarData = useMemo(() => {
const budgeted = items.filter(i => i.category?.type === "income").reduce((sum, i) => sum + i.budgeted_amount, 0)
const actual = items.filter(i => i.category?.type === "income").reduce((sum, i) => sum + i.actual_amount, 0)
if (budgeted === 0 && actual === 0) return []
return [{ label: t("categories.types.income"), budgeted, actual }]
}, [items, t])
```
- **Derive spendBarData** (NEW):
```typescript
const spendBarData = useMemo(() =>
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)
return { type, label: t(`categories.types.${type}`), budgeted, actual }
}).filter(Boolean) as Array<{ type: string; label: string; budgeted: number; actual: number }>,
[items, t])
```
- **Layout (JSX):** Replace the entire existing chart/progress section with:
1. `SummaryStrip` (same as current -- first row)
2. Chart row: `<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">` containing three `Card` wrappers:
- Card 1: `<Card><CardHeader><CardTitle className="text-base">{t("dashboard.expenseDonut")}</CardTitle></CardHeader><CardContent><ExpenseDonutChart data={pieData} totalExpenses={totalExpenses} currency={currency} emptyMessage={t("dashboard.noData")} /></CardContent></Card>`
- Card 2: `<Card><CardHeader><CardTitle className="text-base">{t("dashboard.incomeChart")}</CardTitle></CardHeader><CardContent><IncomeBarChart data={incomeBarData} currency={currency} emptyMessage={t("dashboard.noData")} /></CardContent></Card>`
- Card 3: `<Card><CardHeader><CardTitle className="text-base">{t("dashboard.spendChart")}</CardTitle></CardHeader><CardContent><SpendBarChart data={spendBarData} currency={currency} emptyMessage={t("dashboard.noData")} /></CardContent></Card>`
3. `QuickAddPicker` row (moved below charts per user decision)
- **Remove:** The old `PieChart`, `Pie`, `Cell`, `ResponsiveContainer`, `Tooltip` imports from recharts (replaced by chart components). Remove the old `progressGroups` derivation. Remove the old 2-column grid layout. Remove old inline pie chart and progress bar JSX.
- **Keep:** EXPENSE_TYPES constant (still used for data derivation), all SummaryStrip logic, QuickAddPicker integration.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build</automated>
</verify>
<done>DashboardPage uses URL search params for month selection, MonthNavigator in PageShell action slot, and a 3-column chart grid (donut, vertical bar, horizontal bar) replacing the old pie chart + progress bars. Empty month prompt shows create/generate buttons. Build passes.</done>
</task>
<task type="auto">
<name>Task 2: Update DashboardSkeleton for 3-column chart layout</name>
<files>src/components/dashboard/DashboardSkeleton.tsx</files>
<action>
Update `src/components/dashboard/DashboardSkeleton.tsx` to mirror the new dashboard layout. The skeleton must match the real layout structure to prevent layout shift on load (established pattern from Phase 1).
**Changes:**
- Keep the 3-card summary skeleton row unchanged: `<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">` with 3 `SkeletonStatCard` components
- Replace the 2-column chart skeleton with a 3-column grid: `<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">`
- Each chart skeleton card: `<Card><CardHeader><Skeleton className="h-5 w-40" /></CardHeader><CardContent><Skeleton className="h-[250px] w-full rounded-md" /></CardContent></Card>`
- Three skeleton chart cards (matching the donut, bar, bar layout)
- Keep the `SkeletonStatCard` helper component unchanged
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build</automated>
</verify>
<done>DashboardSkeleton mirrors the new 3-column chart layout with 3 skeleton chart cards. Build passes.</done>
</task>
</tasks>
<verification>
- `bun run build` passes with no type errors
- `bun run lint` passes (or pre-existing errors only)
- DashboardPage imports and renders all 3 chart components
- DashboardPage uses `useMonthParam` for month state (no `useState` for month)
- MonthNavigator placed in PageShell `action` slot
- No old recharts direct imports remain in DashboardPage (PieChart, Pie, Cell, ResponsiveContainer, Tooltip)
- No old progress bar JSX remains
- Chart grid uses `lg:grid-cols-3` responsive breakpoint
- DashboardSkeleton has 3 chart skeleton cards matching real layout
</verification>
<success_criteria>
- User can navigate between months using prev/next arrows and month dropdown
- Month is stored in URL search params (`?month=YYYY-MM`)
- Dashboard shows SummaryStrip, then 3-column chart row, then QuickAdd
- Charts and summary cards update when month changes
- Empty month shows create/generate prompt
- DashboardSkeleton mirrors new layout
- `bun run build && bun run lint` passes
</success_criteria>
<output>
After completion, create `.planning/phases/02-dashboard-charts-and-layout/02-03-SUMMARY.md`
</output>