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 |
|
|
false |
|
|
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 }
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:
- All existing useMemo hooks (totalIncome, etc.)
- groupedSections useMemo (new)
- openSections useState (new)
- openSections useEffect (new)
- Early returns (loading, !budget)
- 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:
- SummaryStrip
- Chart grid (3-column)
- CollapsibleSections (new)
- 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
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
-
Run
cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run devand open http://localhost:5173 -
Collapsible sections visible: Navigate to a month with budget items. Verify collapsible sections appear below the chart grid and above the QuickAdd button.
-
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)
-
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.
-
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.
-
Line-item table: Expanded sections show a 4-column table: Item Name, Budgeted, Actual, Difference. Footer row with bold group totals.
-
Empty groups hidden: If a category type has zero budget items, it should not appear at all.
-
Month navigation reset: Expand/collapse some sections, then navigate to a different month. Smart defaults should recalculate.
-
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. -
Rapid toggle: Toggle sections open/closed rapidly 10+ times. Check browser console (F12) for "ResizeObserver loop" errors.
-
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
<success_criteria>
- All 4 ROADMAP success criteria for Phase 3 are met:
- Category groups render as collapsible sections with color-accented headers, budgeted/actual totals, and difference
- Expanding reveals line-item table, collapsing hides it with smooth animation, no chart jank
- Rapid toggling produces no ResizeObserver loop errors
- Carryover amount visible on balance card when non-zero
- Human verification checkpoint passes </success_criteria>