352 lines
13 KiB
Markdown
352 lines
13 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</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/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
|
|
|
|
<interfaces>
|
|
<!-- Interfaces created by Plan 01 that this plan consumes. -->
|
|
|
|
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<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):
|
|
```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 }
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Wire collapsible sections into DashboardContent with smart defaults</name>
|
|
<files>src/pages/DashboardPage.tsx, src/components/dashboard/DashboardSkeleton.tsx</files>
|
|
<action>
|
|
**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<typeof g> => g !== null),
|
|
[items, t]
|
|
)
|
|
```
|
|
|
|
**5. Add openSections state and reset effect (after groupedSections, before early returns):**
|
|
|
|
```typescript
|
|
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):**
|
|
|
|
```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 `</div>` and the QuickAdd `<div>`:
|
|
|
|
```tsx
|
|
{/* 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:
|
|
|
|
```tsx
|
|
{/* 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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run lint && bun run build</automated>
|
|
</verify>
|
|
<done>
|
|
- 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
|
|
</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 2: Verify collapsible sections and carryover display</name>
|
|
<files>none</files>
|
|
<action>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build</automated>
|
|
</verify>
|
|
<done>Human approves all 11 verification checks pass</done>
|
|
<what-built>
|
|
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
|
|
</what-built>
|
|
<how-to-verify>
|
|
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.
|
|
</how-to-verify>
|
|
<resume-signal>Type "approved" or describe any issues found</resume-signal>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `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
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-collapsible-dashboard-sections/03-02-SUMMARY.md`
|
|
</output>
|