--- phase: 03-collapsible-dashboard-sections plan: 01 type: execute wave: 1 depends_on: [] files_modified: - src/index.css - src/i18n/en.json - src/i18n/de.json - src/components/dashboard/StatCard.tsx - src/components/dashboard/SummaryStrip.tsx - src/pages/DashboardPage.tsx - src/components/dashboard/CategorySection.tsx - src/components/dashboard/CollapsibleSections.tsx autonomous: true requirements: - UI-DASH-01 - UI-COLLAPSE-01 must_haves: truths: - "Balance card shows 'Includes $X carryover' subtitle when carryover is non-zero" - "Balance card has no subtitle when carryover is zero" - "Negative carryover displays with red styling" - "CategorySection renders with left border accent, chevron, label, badges, and difference" - "CollapsibleSections renders an ordered list of CategorySection components" - "Collapsible animation tokens are defined in CSS" artifacts: - path: "src/index.css" provides: "Collapsible animation keyframes and tokens" contains: "collapsible-open" - path: "src/i18n/en.json" provides: "Section and carryover i18n keys" contains: "carryoverIncludes" - path: "src/i18n/de.json" provides: "German section and carryover i18n keys" contains: "carryoverIncludes" - path: "src/components/dashboard/StatCard.tsx" provides: "Optional subtitle prop for carryover display" contains: "subtitle" - path: "src/components/dashboard/SummaryStrip.tsx" provides: "Carryover subtitle threading to balance StatCard" contains: "carryoverSubtitle" - path: "src/pages/DashboardPage.tsx" provides: "Carryover subtitle computed and passed to SummaryStrip" contains: "carryoverSubtitle" - path: "src/components/dashboard/CategorySection.tsx" provides: "Collapsible section with header badges and line-item table" exports: ["CategorySection"] - path: "src/components/dashboard/CollapsibleSections.tsx" provides: "Container rendering ordered CategorySection list" exports: ["CollapsibleSections"] key_links: - from: "src/pages/DashboardPage.tsx" to: "src/components/dashboard/SummaryStrip.tsx" via: "carryoverSubtitle prop on balance object" pattern: "carryoverSubtitle.*formatCurrency.*carryover" - from: "src/components/dashboard/SummaryStrip.tsx" to: "src/components/dashboard/StatCard.tsx" via: "subtitle prop" pattern: "subtitle.*carryoverSubtitle" - from: "src/components/dashboard/CollapsibleSections.tsx" to: "src/components/dashboard/CategorySection.tsx" via: "renders CategorySection per group" pattern: "CategorySection" --- Build the carryover display, CSS animation tokens, i18n keys, and the two new collapsible section components (CategorySection + CollapsibleSections) as pure presentational building blocks. Purpose: Establish all the foundational pieces that Plan 02 will wire into DashboardContent. Carryover display is self-contained and ships immediately. Section components are built and tested in isolation. Output: StatCard with subtitle, SummaryStrip with carryover, CSS animation tokens, i18n keys, CategorySection component, CollapsibleSections component. @/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 @src/pages/DashboardPage.tsx @src/components/dashboard/StatCard.tsx @src/components/dashboard/SummaryStrip.tsx @src/components/ui/collapsible.tsx @src/components/ui/table.tsx @src/components/ui/badge.tsx @src/lib/palette.ts @src/lib/types.ts @src/lib/format.ts @src/index.css @src/i18n/en.json @src/i18n/de.json From src/lib/types.ts: ```typescript export type CategoryType = "income" | "bill" | "variable_expense" | "debt" | "saving" | "investment" export interface Budget { id: string; carryover_amount: number; currency: string; /* ... */ } export interface BudgetItem { id: string; budgeted_amount: number; actual_amount: number; category?: Category; /* ... */ } export interface Category { id: string; name: string; type: CategoryType; /* ... */ } ``` From src/lib/palette.ts: ```typescript export const categoryColors: Record // e.g. { income: "var(--color-income)" } export const categoryLabels: Record ``` From src/lib/format.ts: ```typescript export function formatCurrency(amount: number, currency?: string, locale?: string): string ``` From src/components/ui/collapsible.tsx: ```typescript export { Collapsible, CollapsibleTrigger, CollapsibleContent } ``` From src/components/dashboard/StatCard.tsx (current): ```typescript interface StatCardProps { title: string value: string valueClassName?: string variance?: { amount: string; direction: "up" | "down" | "neutral"; label: string } } ``` From src/components/dashboard/SummaryStrip.tsx (current): ```typescript interface SummaryStripProps { income: { value: string; budgeted: string } expenses: { value: string; budgeted: string } balance: { value: string; isPositive: boolean } t: (key: string) => string } ``` Task 1: Add CSS animation tokens, i18n keys, and carryover display src/index.css, src/i18n/en.json, src/i18n/de.json, src/components/dashboard/StatCard.tsx, src/components/dashboard/SummaryStrip.tsx, src/pages/DashboardPage.tsx **1. CSS animation tokens (src/index.css):** Add to the existing `@theme inline` block, after the `--radius` line: ```css /* Collapsible animation */ --animate-collapsible-open: collapsible-open 200ms ease-out; --animate-collapsible-close: collapsible-close 200ms ease-out; ``` Add after the `@layer base` block: ```css @keyframes collapsible-open { from { height: 0; overflow: hidden; } to { height: var(--radix-collapsible-content-height); overflow: hidden; } } @keyframes collapsible-close { from { height: var(--radix-collapsible-content-height); overflow: hidden; } to { height: 0; overflow: hidden; } } ``` **2. i18n keys (src/i18n/en.json):** Add under the `"dashboard"` object: ```json "sections": { "itemName": "Item", "groupTotal": "{{label}} Total" }, "carryoverIncludes": "Includes {{amount}} carryover" ``` **3. i18n keys (src/i18n/de.json):** Add under the `"dashboard"` object: ```json "sections": { "itemName": "Posten", "groupTotal": "{{label}} Gesamt" }, "carryoverIncludes": "Inkl. {{amount}} Übertrag" ``` **4. StatCard subtitle prop (src/components/dashboard/StatCard.tsx):** Add two optional props to `StatCardProps`: ```typescript subtitle?: string // e.g. "Includes EUR 150.00 carryover" subtitleClassName?: string // e.g. "text-over-budget" for negative carryover ``` Add to the destructured props. Render below the value `

` and before the variance block: ```tsx {subtitle && (

{subtitle}

)} ``` `cn` is already imported from `@/lib/utils`. **5. SummaryStrip carryover prop (src/components/dashboard/SummaryStrip.tsx):** Extend the `balance` prop type: ```typescript balance: { value: string isPositive: boolean carryoverSubtitle?: string // NEW carryoverIsNegative?: boolean // NEW } ``` Pass to the balance `StatCard`: ```tsx ``` **6. DashboardContent carryover pass-through (src/pages/DashboardPage.tsx):** In the `DashboardContent` function, after the `availableBalance` computation (line ~125) and before the `return`, compute the carryover subtitle: ```typescript const carryover = budget.carryover_amount const carryoverSubtitle = carryover !== 0 ? t("dashboard.carryoverIncludes", { amount: formatCurrency(Math.abs(carryover), currency) }) : undefined const carryoverIsNegative = carryover < 0 ``` Update the `SummaryStrip` balance prop: ```tsx balance={{ value: formatCurrency(availableBalance, currency), isPositive: availableBalance >= 0, carryoverSubtitle, carryoverIsNegative, }} ``` Note: The `t` function used in DashboardContent is from `useTranslation()` — it already supports interpolation.
cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run lint && bun run build - StatCard accepts optional subtitle/subtitleClassName props and renders subtitle text below value - SummaryStrip accepts carryoverSubtitle/carryoverIsNegative on balance and passes to StatCard - DashboardContent computes carryover subtitle from budget.carryover_amount and passes to SummaryStrip - CSS animation tokens for collapsible-open/close defined in index.css - i18n keys for sections and carryover added to both en.json and de.json
Task 2: Build CategorySection and CollapsibleSections components src/components/dashboard/CategorySection.tsx, src/components/dashboard/CollapsibleSections.tsx **1. Create src/components/dashboard/CategorySection.tsx:** A pure presentational component. Accepts pre-computed group data, controlled open/onOpenChange, and `t()` for i18n. ```typescript interface CategorySectionProps { type: CategoryType label: string items: BudgetItem[] budgeted: number actual: number currency: string open: boolean onOpenChange: (open: boolean) => void t: (key: string, opts?: Record) => string } ``` Implementation details: - Import `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` from `@/components/ui/collapsible` - Import `Table`, `TableBody`, `TableCell`, `TableFooter`, `TableHead`, `TableHeader`, `TableRow` from `@/components/ui/table` - Import `Badge` from `@/components/ui/badge` - Import `ChevronRight` from `lucide-react` - Import `categoryColors` from `@/lib/palette` - Import `formatCurrency` from `@/lib/format` - Import `cn` from `@/lib/utils` - Import `CategoryType`, `BudgetItem` from `@/lib/types` **Header (CollapsibleTrigger):** - `` - Trigger is a `