263 lines
11 KiB
Markdown
263 lines
11 KiB
Markdown
---
|
|
phase: 03-interaction-quality-and-completeness
|
|
plan: 03
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["03-01", "03-00"]
|
|
files_modified:
|
|
- frontend/src/components/BillsTracker.tsx
|
|
- frontend/src/components/VariableExpenses.tsx
|
|
- frontend/src/components/DebtTracker.tsx
|
|
- frontend/src/pages/DashboardPage.tsx
|
|
autonomous: true
|
|
requirements: [IXTN-03, STATE-03]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "After saving an inline edit in BillsTracker, the entire row briefly flashes green"
|
|
- "After a failed inline edit save, the row briefly flashes red"
|
|
- "Same flash behavior works in VariableExpenses and DebtTracker"
|
|
- "Dashboard loading skeleton uses pastel-tinted backgrounds matching section colors"
|
|
- "BillsTracker, VariableExpenses, DebtTracker show tinted skeletons when budget has no items for that section"
|
|
artifacts:
|
|
- path: "frontend/src/components/BillsTracker.tsx"
|
|
provides: "Row flash state + tinted skeleton loading state"
|
|
contains: "flashRowId"
|
|
- path: "frontend/src/components/VariableExpenses.tsx"
|
|
provides: "Row flash state + tinted skeleton loading state"
|
|
contains: "flashRowId"
|
|
- path: "frontend/src/components/DebtTracker.tsx"
|
|
provides: "Row flash state + tinted skeleton loading state"
|
|
contains: "flashRowId"
|
|
- path: "frontend/src/pages/DashboardPage.tsx"
|
|
provides: "Tinted dashboard loading skeletons using palette light shades"
|
|
contains: "palette"
|
|
key_links:
|
|
- from: "frontend/src/components/BillsTracker.tsx"
|
|
to: "frontend/src/components/InlineEditCell.tsx"
|
|
via: "onSaveSuccess/onSaveError callbacks"
|
|
pattern: "onSaveSuccess.*flashRow"
|
|
- from: "frontend/src/pages/DashboardPage.tsx"
|
|
to: "frontend/src/lib/palette.ts"
|
|
via: "palette import for skeleton tinting"
|
|
pattern: "palette\\..*\\.light"
|
|
---
|
|
|
|
<objective>
|
|
Wire row-level flash feedback into all three tracker components and add pastel-tinted loading skeletons to the dashboard.
|
|
|
|
Purpose: Complete the inline edit feedback loop — users see green/red row flashes confirming save success/failure. Tinted skeletons make the loading state feel intentional and branded rather than generic.
|
|
Output: BillsTracker, VariableExpenses, DebtTracker with flash + skeleton states; DashboardPage with tinted skeletons.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/03-interaction-quality-and-completeness/03-CONTEXT.md
|
|
@.planning/phases/03-interaction-quality-and-completeness/03-RESEARCH.md
|
|
@.planning/phases/03-interaction-quality-and-completeness/03-01-SUMMARY.md
|
|
|
|
<interfaces>
|
|
<!-- InlineEditCell interface AFTER Plan 01 (with new callbacks) -->
|
|
From frontend/src/components/InlineEditCell.tsx (post Plan 01):
|
|
```typescript
|
|
interface InlineEditCellProps {
|
|
value: number
|
|
currency: string
|
|
onSave: (value: number) => Promise<void>
|
|
onSaveSuccess?: () => void
|
|
onSaveError?: () => void
|
|
className?: string
|
|
}
|
|
```
|
|
|
|
<!-- Palette light shades for skeleton tinting -->
|
|
From frontend/src/lib/palette.ts:
|
|
```typescript
|
|
export const palette = {
|
|
bill: { base: '...', light: 'oklch(0.96 0.03 250)', header: '...' },
|
|
variable_expense: { base: '...', light: 'oklch(0.97 0.04 85)', header: '...' },
|
|
debt: { base: '...', light: 'oklch(0.96 0.04 15)', header: '...' },
|
|
saving: { base: '...', light: 'oklch(0.95 0.04 280)', header: '...' },
|
|
investment: { base: '...', light: 'oklch(0.96 0.03 320)', header: '...' },
|
|
// ...
|
|
}
|
|
```
|
|
|
|
<!-- BillsTracker current structure (representative of all 3 trackers) -->
|
|
From frontend/src/components/BillsTracker.tsx:
|
|
```typescript
|
|
interface Props {
|
|
budget: BudgetDetail
|
|
onUpdate: (itemId: string, data: { actual_amount?: number; budgeted_amount?: number }) => Promise<void>
|
|
}
|
|
// Uses: <TableRow key={item.id}> containing <InlineEditCell onSave={...} />
|
|
```
|
|
|
|
<!-- Skeleton component -->
|
|
From frontend/src/components/ui/skeleton.tsx:
|
|
```typescript
|
|
// Accepts className and style props. Default bg is bg-muted.
|
|
// Override with style={{ backgroundColor: '...' }} to tint.
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Wire row flash feedback into BillsTracker, VariableExpenses, and DebtTracker</name>
|
|
<files>frontend/src/components/BillsTracker.tsx, frontend/src/components/VariableExpenses.tsx, frontend/src/components/DebtTracker.tsx</files>
|
|
<action>
|
|
Apply the same pattern to all three tracker components. Import `useState` (already imported in most) and `cn` from `@/lib/utils`.
|
|
|
|
Add flash state and helper to each component:
|
|
```typescript
|
|
const [flashRowId, setFlashRowId] = useState<string | null>(null)
|
|
const [errorRowId, setErrorRowId] = useState<string | null>(null)
|
|
|
|
const triggerFlash = (id: string, type: 'success' | 'error') => {
|
|
if (type === 'success') {
|
|
setFlashRowId(id)
|
|
setTimeout(() => setFlashRowId(null), 600)
|
|
} else {
|
|
setErrorRowId(id)
|
|
setTimeout(() => setErrorRowId(null), 600)
|
|
}
|
|
}
|
|
```
|
|
|
|
On each data `<TableRow>` (not the totals row), add inline style for the flash:
|
|
```tsx
|
|
<TableRow
|
|
key={item.id}
|
|
className="transition-colors duration-500"
|
|
style={
|
|
flashRowId === item.id
|
|
? { backgroundColor: 'color-mix(in oklch, var(--success) 20%, transparent)' }
|
|
: errorRowId === item.id
|
|
? { backgroundColor: 'color-mix(in oklch, var(--destructive) 20%, transparent)' }
|
|
: undefined
|
|
}
|
|
>
|
|
```
|
|
|
|
Use `color-mix()` inline style (not Tailwind `bg-success/20`) per research recommendation — avoids potential Tailwind class generation issues.
|
|
|
|
Pass callbacks to each `<InlineEditCell>`:
|
|
```tsx
|
|
<InlineEditCell
|
|
value={item.actual_amount}
|
|
currency={budget.currency}
|
|
onSave={(actual) => onUpdate(item.id, { actual_amount: actual })}
|
|
onSaveSuccess={() => triggerFlash(item.id, 'success')}
|
|
onSaveError={() => triggerFlash(item.id, 'error')}
|
|
className={amountColorClass({ type: 'bill', actual: item.actual_amount, budgeted: item.budgeted_amount })}
|
|
/>
|
|
```
|
|
|
|
Adjust the `type` argument in `amountColorClass` per component:
|
|
- BillsTracker: `type: 'bill'`
|
|
- VariableExpenses: `type: 'variable_expense'`
|
|
- DebtTracker: `type: 'debt'`
|
|
(These should already be correct from Phase 1 — just ensure the new onSaveSuccess/onSaveError props are added.)
|
|
|
|
**Do NOT modify** the totals row or the CardHeader — only add flash state and wire callbacks.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun vitest run src/components/BillsTracker.test.tsx && bun run build</automated>
|
|
</verify>
|
|
<done>All three tracker components have flash state, triggerFlash helper, inline style on data rows, and onSaveSuccess/onSaveError wired to InlineEditCell. Build passes.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Add pastel-tinted loading skeletons to DashboardPage and tracker sections</name>
|
|
<files>frontend/src/pages/DashboardPage.tsx, frontend/src/components/BillsTracker.tsx, frontend/src/components/VariableExpenses.tsx, frontend/src/components/DebtTracker.tsx</files>
|
|
<action>
|
|
**DashboardPage.tsx** — tint existing loading skeleton block (lines 39-47):
|
|
- Import `palette` from `@/lib/palette`
|
|
- Replace the existing generic Skeleton elements with tinted versions:
|
|
```tsx
|
|
if (loading && list.length === 0) {
|
|
return (
|
|
<div className="flex flex-col gap-4 p-6">
|
|
<Skeleton className="h-10 w-64" />
|
|
<Skeleton className="h-48 w-full rounded-lg" style={{ backgroundColor: palette.saving.light }} />
|
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
<Skeleton className="h-40 w-full rounded-lg" style={{ backgroundColor: palette.bill.light }} />
|
|
<Skeleton className="h-40 w-full rounded-lg" style={{ backgroundColor: palette.variable_expense.light }} />
|
|
</div>
|
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
<Skeleton className="h-40 w-full rounded-lg" style={{ backgroundColor: palette.debt.light }} />
|
|
<Skeleton className="h-40 w-full rounded-lg" style={{ backgroundColor: palette.investment.light }} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
Use `style` prop to override `bg-muted` without editing `ui/skeleton.tsx`.
|
|
|
|
**BillsTracker.tsx, VariableExpenses.tsx, DebtTracker.tsx** — add skeleton for empty sections:
|
|
- Import `Skeleton` from `@/components/ui/skeleton` and ensure `palette` is imported (already imported for `headerGradient`)
|
|
- After the filter (e.g., `const bills = budget.items.filter(...)`) add an early return if no items exist:
|
|
```tsx
|
|
if (bills.length === 0) {
|
|
return (
|
|
<Card>
|
|
<CardHeader style={headerGradient('bill')}>
|
|
<CardTitle>{t('dashboard.billsTracker')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-2 p-4">
|
|
{[1, 2, 3].map((i) => (
|
|
<Skeleton
|
|
key={i}
|
|
className="h-10 w-full rounded-md"
|
|
style={{ backgroundColor: palette.bill.light }}
|
|
/>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
```
|
|
Use the matching palette key per component:
|
|
- BillsTracker: `palette.bill.light`
|
|
- VariableExpenses: `palette.variable_expense.light`
|
|
- DebtTracker: `palette.debt.light`
|
|
|
|
**Note:** These skeletons show when a budget exists but has no items of that type — they serve as visual placeholders indicating the section exists. This is distinct from the DashboardPage loading skeleton (which shows before any data loads).
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun vitest run src/components/BillsTracker.test.tsx src/pages/DashboardPage.test.tsx && bun run build</automated>
|
|
</verify>
|
|
<done>Dashboard loading skeleton uses palette-tinted backgrounds per section. Each tracker shows tinted skeletons when no items of its type exist. All tests pass, build succeeds.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `cd frontend && bun vitest run` — full test suite passes
|
|
- `cd frontend && bun run build` — production build succeeds
|
|
- Row flash uses `color-mix(in oklch, var(--success/destructive) 20%, transparent)` inline style
|
|
- Dashboard skeleton uses palette.*.light inline styles
|
|
- Tracker skeletons use matching palette key for their section
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Inline edit save success produces visible green row flash (~600ms duration)
|
|
- Inline edit save failure produces visible red row flash + value revert
|
|
- Dashboard loading state shows pastel-tinted skeletons (not grey)
|
|
- Empty tracker sections show tinted skeleton placeholders matching their card header color
|
|
- No flash or skeleton interferes with existing functionality
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-interaction-quality-and-completeness/03-03-SUMMARY.md`
|
|
</output>
|