diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 6cd6611..8edb3e9 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -64,10 +64,11 @@ Plans: 2. Expanding a section reveals a line-item table with individual budget items, and collapsing it hides the table with a smooth CSS animation (no layout shift in charts above) 3. Toggling sections rapidly does not produce `ResizeObserver loop` console errors or visible chart resize jank 4. Carryover amount is visible on the dashboard balance card when the budget has a non-zero carryover -**Plans**: TBD +**Plans**: 2 plans Plans: -- [ ] 03-01: TBD +- [ ] 03-01-PLAN.md — Build carryover display, CSS animation tokens, i18n keys, CategorySection and CollapsibleSections components +- [ ] 03-02-PLAN.md — Wire collapsible sections into DashboardContent with smart defaults, update skeleton, verify ### Phase 4: Full-App Design Consistency **Goal**: Apply the design system established in Phases 1-3 to every page in the app, delivering a consistent visual experience across all navigation paths @@ -135,5 +136,5 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 |-------|----------------|--------|-----------| | 1. Design Foundation and Primitives | 2/2 | Complete | 2026-03-16 | | 2. Dashboard Charts and Layout | 3/3 | Complete | 2026-03-16 | -| 3. Collapsible Dashboard Sections | 0/TBD | Not started | - | +| 3. Collapsible Dashboard Sections | 0/2 | Planning complete | - | | 4. Full-App Design Consistency | 0/TBD | Not started | - | diff --git a/.planning/phases/03-collapsible-dashboard-sections/03-01-PLAN.md b/.planning/phases/03-collapsible-dashboard-sections/03-01-PLAN.md new file mode 100644 index 0000000..3849e48 --- /dev/null +++ b/.planning/phases/03-collapsible-dashboard-sections/03-01-PLAN.md @@ -0,0 +1,422 @@ +--- +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 `