Files
SimpleFinanceDash/.planning/milestones/v1.0-phases/03-collapsible-dashboard-sections/03-02-PLAN.md

13 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
03-collapsible-dashboard-sections 02 execute 2
03-01
src/pages/DashboardPage.tsx
src/components/dashboard/DashboardSkeleton.tsx
false
UI-DASH-01
UI-COLLAPSE-01
truths artifacts key_links
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
path provides contains
src/pages/DashboardPage.tsx groupedSections useMemo, openSections state, CollapsibleSections rendering groupedSections
path provides contains
src/components/dashboard/DashboardSkeleton.tsx Skeleton placeholders for collapsible sections area Skeleton
from to via pattern
src/pages/DashboardPage.tsx src/components/dashboard/CollapsibleSections.tsx renders CollapsibleSections with grouped data and open state CollapsibleSections.*groups.*openSections
from to via pattern
src/pages/DashboardPage.tsx useBudgetDetail items groupedSections useMemo derives groups from items 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.

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

interface GroupData {
  type: CategoryType
  label: string
  items: BudgetItem[]
  budgeted: number
  actual: number
}

interface CollapsibleSectionsProps {
  groups: GroupData[]
  currency: string
  openSections: Record<string, boolean>
  onToggleSection: (type: string, open: boolean) => void
  t: (key: string, opts?: Record<string, unknown>) => string
}

export function CollapsibleSections(props: CollapsibleSectionsProps): JSX.Element

From src/pages/DashboardPage.tsx (current state after Plan 01):

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

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

const CATEGORY_TYPES_ALL: CategoryType[] = [
  "income",
  "bill",
  "variable_expense",
  "debt",
  "saving",
  "investment",
]

3. Add isOverBudget helper function at module level (near constants):

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.

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<typeof g> => g !== null),
  [items, t]
)

5. Add openSections state and reset effect (after groupedSections, before early returns):

const [openSections, setOpenSections] = useState<Record<string, boolean>>(() =>
  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):

const handleToggleSection = (type: string, open: boolean) => {
  setOpenSections((prev) => ({ ...prev, [type]: open }))
}

7. Update the JSX layout in DashboardContent:

Insert CollapsibleSections between the chart grid </div> and the QuickAdd <div>:

{/* Collapsible category sections */}
{groupedSections.length > 0 && (
  <CollapsibleSections
    groups={groupedSections}
    currency={currency}
    openSections={openSections}
    onToggleSection={handleToggleSection}
    t={t}
  />
)}

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:

{/* Collapsible sections skeleton */}
<div className="space-y-3">
  {[1, 2, 3].map((i) => (
    <div key={i} className="flex items-center gap-3 rounded-md border-l-4 border-muted bg-card px-4 py-3">
      <Skeleton className="size-4" />
      <Skeleton className="h-4 w-32" />
      <div className="ml-auto flex items-center gap-2">
        <Skeleton className="h-5 w-24 rounded-full" />
        <Skeleton className="h-5 w-24 rounded-full" />
        <Skeleton className="h-4 w-16" />
      </div>
    </div>
  ))}
</div>

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

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/03-collapsible-dashboard-sections/03-02-SUMMARY.md`