Files
SimpleFinanceDash/.planning/milestones/v1.0-phases/02-dashboard-charts-and-layout/02-03-PLAN.md

14 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-dashboard-charts-and-layout 03 execute 2
02-01
02-02
src/pages/DashboardPage.tsx
src/components/dashboard/DashboardSkeleton.tsx
true
UI-DASH-01
UI-BAR-01
UI-HBAR-01
UI-DONUT-01
truths artifacts key_links
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
path provides exports min_lines
src/pages/DashboardPage.tsx Refactored dashboard with URL month nav and 3-column chart grid
default
80
path provides exports
src/components/dashboard/DashboardSkeleton.tsx Updated skeleton matching 3-column chart layout
DashboardSkeleton
from to via pattern
src/pages/DashboardPage.tsx src/hooks/useMonthParam.ts useMonthParam hook for month state useMonthParam
from to via pattern
src/pages/DashboardPage.tsx src/components/dashboard/MonthNavigator.tsx MonthNavigator in PageShell action slot MonthNavigator
from to via pattern
src/pages/DashboardPage.tsx src/components/dashboard/charts/ExpenseDonutChart.tsx import and render in chart grid ExpenseDonutChart
from to via pattern
src/pages/DashboardPage.tsx src/components/dashboard/charts/IncomeBarChart.tsx import and render in chart grid IncomeBarChart
from to via pattern
src/pages/DashboardPage.tsx src/components/dashboard/charts/SpendBarChart.tsx import and render in chart grid SpendBarChart
from to via pattern
src/pages/DashboardPage.tsx src/hooks/useBudgets.ts useBudgets for budget list + useBudgetDetail for selected budget useBudgets.*useBudgetDetail
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.

<execution_context> @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md </execution_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

From src/hooks/useMonthParam.ts (Plan 01):

export function useMonthParam(): {
  month: string          // "YYYY-MM"
  setMonth: (newMonth: string) => void
  navigateMonth: (delta: number) => void
}

From src/components/dashboard/MonthNavigator.tsx (Plan 01):

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):

export function ChartEmptyState({ message, className }: { message: string; className?: string }): JSX.Element

From src/components/dashboard/charts/ExpenseDonutChart.tsx (Plan 02):

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):

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):

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:

export function PageShell({ title, description, action, children }: PageShellProps): JSX.Element
// action slot renders top-right, ideal for MonthNavigator

From src/hooks/useBudgets.ts:

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:

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; ... }
Task 1: Refactor DashboardPage with month navigation and 3-column chart grid src/pages/DashboardPage.tsx 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):

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):
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):
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. cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build 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.

Task 2: Update DashboardSkeleton for 3-column chart layout src/components/dashboard/DashboardSkeleton.tsx 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 cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build DashboardSkeleton mirrors the new 3-column chart layout with 3 skeleton chart cards. Build passes.
- `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

<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>
After completion, create `.planning/phases/02-dashboard-charts-and-layout/02-03-SUMMARY.md`