--- phase: 03-collapsible-dashboard-sections plan: 02 type: execute wave: 2 depends_on: - 03-01 files_modified: - src/pages/DashboardPage.tsx - src/components/dashboard/DashboardSkeleton.tsx autonomous: false requirements: - UI-DASH-01 - UI-COLLAPSE-01 must_haves: truths: - "Each non-empty category group renders as a collapsible section between charts and QuickAdd" - "Over-budget sections auto-expand on load (direction-aware: spending overspent, income/savings under-earned/saved)" - "On/under-budget sections start collapsed" - "Empty category groups are hidden entirely" - "Expand/collapse state resets when navigating months" - "Toggling sections does not produce ResizeObserver loop errors or chart resize jank" - "Collapsible sections animate open/close smoothly with no flicker on mount" - "DashboardSkeleton mirrors the sections area layout" artifacts: - path: "src/pages/DashboardPage.tsx" provides: "groupedSections useMemo, openSections state, CollapsibleSections rendering" contains: "groupedSections" - path: "src/components/dashboard/DashboardSkeleton.tsx" provides: "Skeleton placeholders for collapsible sections area" contains: "Skeleton" key_links: - from: "src/pages/DashboardPage.tsx" to: "src/components/dashboard/CollapsibleSections.tsx" via: "renders CollapsibleSections with grouped data and open state" pattern: "CollapsibleSections.*groups.*openSections" - from: "src/pages/DashboardPage.tsx" to: "useBudgetDetail items" via: "groupedSections useMemo derives groups from items" pattern: "groupedSections.*useMemo.*items\\.filter" --- Wire the collapsible sections into DashboardContent with smart expand/collapse defaults, month-navigation state reset, and chart isolation. Update DashboardSkeleton. Purpose: Complete the dashboard hybrid view by integrating the CategorySection/CollapsibleSections components built in Plan 01 into the live dashboard page with all the required state management. Output: Fully functional collapsible sections on the dashboard, DashboardSkeleton updated. @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-collapsible-dashboard-sections/03-CONTEXT.md @.planning/phases/03-collapsible-dashboard-sections/03-RESEARCH.md @.planning/phases/03-collapsible-dashboard-sections/03-VALIDATION.md @.planning/phases/03-collapsible-dashboard-sections/03-01-SUMMARY.md @src/pages/DashboardPage.tsx @src/components/dashboard/DashboardSkeleton.tsx @src/components/dashboard/CollapsibleSections.tsx @src/components/dashboard/CategorySection.tsx From src/components/dashboard/CollapsibleSections.tsx (created in Plan 01): ```typescript interface GroupData { type: CategoryType label: string items: BudgetItem[] budgeted: number actual: number } interface CollapsibleSectionsProps { groups: GroupData[] currency: string openSections: Record onToggleSection: (type: string, open: boolean) => void t: (key: string, opts?: Record) => string } export function CollapsibleSections(props: CollapsibleSectionsProps): JSX.Element ``` From src/pages/DashboardPage.tsx (current state after Plan 01): ```typescript // DashboardContent receives { budgetId: string } // Uses useBudgetDetail(budgetId) -> { budget, items, loading } // Already has: totalIncome, totalExpenses, budgetedIncome, budgetedExpenses, pieData, incomeBarData, spendBarData useMemos // Already has: carryover subtitle computation from Plan 01 // Layout: SummaryStrip -> chart grid -> QuickAdd // Collapsible sections insert between chart grid and QuickAdd ``` From src/lib/types.ts: ```typescript export type CategoryType = "income" | "bill" | "variable_expense" | "debt" | "saving" | "investment" export interface BudgetItem { id: string; budgeted_amount: number; actual_amount: number; category?: Category } ``` Task 1: Wire collapsible sections into DashboardContent with smart defaults src/pages/DashboardPage.tsx, src/components/dashboard/DashboardSkeleton.tsx **1. Add imports to DashboardPage.tsx:** ```typescript import { useState, useMemo, useEffect } from "react" // add useState, useEffect import { CollapsibleSections } from "@/components/dashboard/CollapsibleSections" ``` **2. Add CATEGORY_TYPES_ALL constant (near top, alongside existing EXPENSE_TYPES):** ```typescript const CATEGORY_TYPES_ALL: CategoryType[] = [ "income", "bill", "variable_expense", "debt", "saving", "investment", ] ``` **3. Add isOverBudget helper function at module level (near constants):** ```typescript function isOverBudget(type: CategoryType, budgeted: number, actual: number): boolean { if (type === "income" || type === "saving" || type === "investment") { return actual < budgeted // under-earned / under-saved } return actual > budgeted // overspent } ``` **4. Add groupedSections useMemo in DashboardContent:** Place after the existing `spendBarData` useMemo and BEFORE the early returns (`if (loading)` / `if (!budget)`). This follows the established hooks-before-returns pattern from Phase 2. ```typescript const groupedSections = useMemo(() => CATEGORY_TYPES_ALL .map((type) => { const groupItems = items.filter((i) => i.category?.type === type) if (groupItems.length === 0) return null const budgeted = groupItems.reduce((s, i) => s + i.budgeted_amount, 0) const actual = groupItems.reduce((s, i) => s + i.actual_amount, 0) return { type, label: t(`categories.types.${type}`), items: groupItems, budgeted, actual, } }) .filter((g): g is NonNullable => g !== null), [items, t] ) ``` **5. Add openSections state and reset effect (after groupedSections, before early returns):** ```typescript const [openSections, setOpenSections] = useState>(() => Object.fromEntries( groupedSections.map((g) => [g.type, isOverBudget(g.type, g.budgeted, g.actual)]) ) ) // Reset expand state when month (budgetId) changes useEffect(() => { setOpenSections( Object.fromEntries( groupedSections.map((g) => [g.type, isOverBudget(g.type, g.budgeted, g.actual)]) ) ) }, [budgetId]) // budgetId changes on month navigation; groupedSections flows from it ``` IMPORTANT: `useState` and `useEffect` must be called before any early returns (hooks rules). The ordering in DashboardContent should be: 1. All existing useMemo hooks (totalIncome, etc.) 2. groupedSections useMemo (new) 3. openSections useState (new) 4. openSections useEffect (new) 5. Early returns (loading, !budget) 6. Computed values and JSX **6. Add handleToggleSection callback (after early returns, before JSX return):** ```typescript const handleToggleSection = (type: string, open: boolean) => { setOpenSections((prev) => ({ ...prev, [type]: open })) } ``` **7. Update the JSX layout in DashboardContent:** Insert `CollapsibleSections` between the chart grid `` and the QuickAdd `
`: ```tsx {/* Collapsible category sections */} {groupedSections.length > 0 && ( )} ``` The final DashboardContent JSX order becomes: 1. SummaryStrip 2. Chart grid (3-column) 3. CollapsibleSections (new) 4. QuickAdd button **8. Update DashboardSkeleton (src/components/dashboard/DashboardSkeleton.tsx):** Add skeleton placeholders for the collapsible sections area. After the chart grid skeleton div, add: ```tsx {/* Collapsible sections skeleton */}
{[1, 2, 3].map((i) => (
))}
``` This mirrors 3 collapsed section headers (the most common default state), matching the real CategorySection header structure. cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run lint && bun run build - DashboardContent derives groupedSections from items via useMemo (filters empty groups, computes totals) - openSections state initializes with direction-aware smart defaults (over-budget expanded, others collapsed) - openSections resets via useEffect keyed on budgetId (month navigation) - CollapsibleSections renders between chart grid and QuickAdd - All hooks declared before early returns (Rules of Hooks compliance) - DashboardSkeleton includes section header placeholders - Lint and build pass Task 2: Verify collapsible sections and carryover display none Human verification of the complete Phase 3 feature set. No code changes — this is a visual/functional verification checkpoint. **What was built across Plan 01 and Plan 02:** - Collapsible per-category sections between charts and QuickAdd - Smart expand/collapse defaults (over-budget sections auto-expand) - Carryover subtitle on balance card when non-zero - Smooth expand/collapse animation - Direction-aware difference coloring - DashboardSkeleton updated with section placeholders cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build Human approves all 11 verification checks pass Complete dashboard hybrid view with: - Collapsible per-category sections between charts and QuickAdd - Smart expand/collapse defaults (over-budget sections auto-expand) - Carryover subtitle on balance card when non-zero - Smooth expand/collapse animation - Direction-aware difference coloring 1. Run `cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run dev` and open http://localhost:5173 2. **Collapsible sections visible:** Navigate to a month with budget items. Verify collapsible sections appear below the chart grid and above the QuickAdd button. 3. **Section header design:** Each section should have: - Thick colored left border (category color) - Chevron icon on the left - Category group label (e.g., "Income", "Bills") - Two badges on the right: "Budgeted $X" and "Actual $X" - Color-coded difference (green for on-budget, red for over-budget) 4. **Smart defaults:** If any category group is over-budget (e.g., spending actual > budget), that section should be expanded on page load. On-budget sections should be collapsed. 5. **Expand/collapse animation:** Click a section header. It should expand with a smooth 200ms animation. Click again to collapse. No layout jank in the charts above. 6. **Line-item table:** Expanded sections show a 4-column table: Item Name, Budgeted, Actual, Difference. Footer row with bold group totals. 7. **Empty groups hidden:** If a category type has zero budget items, it should not appear at all. 8. **Month navigation reset:** Expand/collapse some sections, then navigate to a different month. Smart defaults should recalculate. 9. **Carryover display:** If the budget has a non-zero `carryover_amount`, the balance card should show "Includes $X carryover" in small text below the balance value. If carryover is zero, no subtitle should appear. 10. **Rapid toggle:** Toggle sections open/closed rapidly 10+ times. Check browser console (F12) for "ResizeObserver loop" errors. 11. **Chevron rotation:** When a section is expanded, the chevron should rotate 90 degrees (pointing down). When collapsed, it should point right. Type "approved" or describe any issues found - `bun run lint` passes - `bun run build` succeeds - Dashboard renders collapsible sections for all non-empty category groups - Over-budget sections auto-expand, on-budget sections start collapsed - Section headers show correct badges, left border accent, and difference - Line-item tables have 4 columns with footer totals - Carryover subtitle displays on balance card when non-zero - Expand/collapse animation is smooth, no ResizeObserver errors - Month navigation resets expand/collapse state - All 4 ROADMAP success criteria for Phase 3 are met: 1. Category groups render as collapsible sections with color-accented headers, budgeted/actual totals, and difference 2. Expanding reveals line-item table, collapsing hides it with smooth animation, no chart jank 3. Rapid toggling produces no ResizeObserver loop errors 4. Carryover amount visible on balance card when non-zero - Human verification checkpoint passes After completion, create `.planning/phases/03-collapsible-dashboard-sections/03-02-SUMMARY.md`