docs(07-quick-add-library): create phase plan
This commit is contained in:
334
.planning/phases/07-quick-add-library/07-02-PLAN.md
Normal file
334
.planning/phases/07-quick-add-library/07-02-PLAN.md
Normal file
@@ -0,0 +1,334 @@
|
||||
---
|
||||
phase: 07-quick-add-library
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["07-01"]
|
||||
files_modified:
|
||||
- frontend/src/lib/api.ts
|
||||
- frontend/src/hooks/useQuickAdd.ts
|
||||
- frontend/src/pages/QuickAddPage.tsx
|
||||
- frontend/src/components/QuickAddPicker.tsx
|
||||
- frontend/src/components/AppLayout.tsx
|
||||
- frontend/src/App.tsx
|
||||
- frontend/src/i18n/en.json
|
||||
- frontend/src/i18n/de.json
|
||||
autonomous: false
|
||||
requirements: [QADD-01, QADD-02, QADD-03]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "User can view, add, edit, and remove saved categories on the quick-add library page"
|
||||
- "User can browse their quick-add library when adding a one-off item to a budget"
|
||||
- "Selecting a quick-add item creates a one-off budget item with that name and icon"
|
||||
- "Quick-add library page is accessible from sidebar navigation"
|
||||
artifacts:
|
||||
- path: "frontend/src/lib/api.ts"
|
||||
provides: "QuickAddItem type and quickAdd API namespace"
|
||||
contains: "quickAdd"
|
||||
- path: "frontend/src/hooks/useQuickAdd.ts"
|
||||
provides: "useQuickAdd hook with CRUD operations"
|
||||
exports: ["useQuickAdd"]
|
||||
- path: "frontend/src/pages/QuickAddPage.tsx"
|
||||
provides: "Management page for quick-add library"
|
||||
min_lines: 50
|
||||
- path: "frontend/src/components/QuickAddPicker.tsx"
|
||||
provides: "Picker component for selecting quick-add items when adding one-off budget items"
|
||||
min_lines: 30
|
||||
- path: "frontend/src/components/AppLayout.tsx"
|
||||
provides: "Sidebar nav item for quick-add library"
|
||||
contains: "quick-add"
|
||||
- path: "frontend/src/App.tsx"
|
||||
provides: "Route for /quick-add"
|
||||
contains: "QuickAddPage"
|
||||
key_links:
|
||||
- from: "frontend/src/hooks/useQuickAdd.ts"
|
||||
to: "frontend/src/lib/api.ts"
|
||||
via: "quickAdd namespace import"
|
||||
pattern: "import.*quickAdd.*api"
|
||||
- from: "frontend/src/pages/QuickAddPage.tsx"
|
||||
to: "frontend/src/hooks/useQuickAdd.ts"
|
||||
via: "useQuickAdd hook call"
|
||||
pattern: "useQuickAdd\\(\\)"
|
||||
- from: "frontend/src/components/QuickAddPicker.tsx"
|
||||
to: "frontend/src/lib/api.ts"
|
||||
via: "quickAdd.list and budgetItems.create"
|
||||
pattern: "quickAdd|budgetItems"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the frontend for the quick-add library: a management page for CRUD operations, a picker component for the budget item add flow, and all routing/navigation wiring.
|
||||
|
||||
Purpose: Users need a page to manage their saved one-off categories and a way to insert them as budget items with one click when viewing a budget.
|
||||
|
||||
Output: QuickAddPage, QuickAddPicker component, useQuickAdd hook, API client additions, routing and i18n
|
||||
</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/07-quick-add-library/07-01-SUMMARY.md
|
||||
|
||||
@frontend/src/lib/api.ts
|
||||
@frontend/src/hooks/useTemplate.ts
|
||||
@frontend/src/pages/TemplatePage.tsx
|
||||
@frontend/src/pages/DashboardPage.tsx
|
||||
@frontend/src/components/AppLayout.tsx
|
||||
@frontend/src/App.tsx
|
||||
@frontend/src/i18n/en.json
|
||||
@frontend/src/i18n/de.json
|
||||
|
||||
<interfaces>
|
||||
<!-- From Plan 01 — API contract the frontend consumes -->
|
||||
|
||||
QuickAddItem JSON shape (GET /api/quick-add returns array of these):
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"name": "string",
|
||||
"icon": "string",
|
||||
"sort_order": 0,
|
||||
"created_at": "timestamp",
|
||||
"updated_at": "timestamp"
|
||||
}
|
||||
```
|
||||
|
||||
Endpoints:
|
||||
- GET /api/quick-add — list all quick-add items
|
||||
- POST /api/quick-add — create {name, icon}
|
||||
- PUT /api/quick-add/{itemId} — update {name, icon, sort_order}
|
||||
- DELETE /api/quick-add/{itemId} — remove
|
||||
|
||||
<!-- Existing budget item creation pattern -->
|
||||
From frontend/src/lib/api.ts:
|
||||
```typescript
|
||||
export const budgetItems = {
|
||||
create: (budgetId: string, data: Partial<BudgetItem>) =>
|
||||
request<BudgetItem>(`/budgets/${budgetId}/items`, { method: 'POST', body: JSON.stringify(data) }),
|
||||
}
|
||||
```
|
||||
|
||||
From frontend/src/hooks/useTemplate.ts (hook pattern to follow):
|
||||
```typescript
|
||||
export function useTemplate() {
|
||||
const [template, setTemplate] = useState<TemplateDetail | null>(null)
|
||||
const [categories, setCategories] = useState<Category[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
// ... CRUD functions that call API and refresh state
|
||||
return { template, categories, loading, addItem, removeItem, moveItem }
|
||||
}
|
||||
```
|
||||
|
||||
From frontend/src/components/AppLayout.tsx (nav items pattern):
|
||||
```typescript
|
||||
const navItems = [
|
||||
{ path: '/', label: t('nav.dashboard'), icon: LayoutDashboard },
|
||||
{ path: '/categories', label: t('nav.categories'), icon: Tags },
|
||||
{ path: '/template', label: t('nav.template'), icon: FileText },
|
||||
{ path: '/settings', label: t('nav.settings'), icon: Settings },
|
||||
]
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: API client, hook, management page, routing, and i18n</name>
|
||||
<files>frontend/src/lib/api.ts, frontend/src/hooks/useQuickAdd.ts, frontend/src/pages/QuickAddPage.tsx, frontend/src/components/AppLayout.tsx, frontend/src/App.tsx, frontend/src/i18n/en.json, frontend/src/i18n/de.json</files>
|
||||
<action>
|
||||
1. **API client** (`frontend/src/lib/api.ts`):
|
||||
Add QuickAddItem interface after the TemplateDetail interface:
|
||||
```typescript
|
||||
export interface QuickAddItem {
|
||||
id: string
|
||||
user_id: string
|
||||
name: string
|
||||
icon: string
|
||||
sort_order: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
```
|
||||
Add quickAdd namespace after the template namespace:
|
||||
```typescript
|
||||
export const quickAdd = {
|
||||
list: () => request<QuickAddItem[]>('/quick-add'),
|
||||
create: (data: { name: string; icon: string }) =>
|
||||
request<QuickAddItem>('/quick-add', { method: 'POST', body: JSON.stringify(data) }),
|
||||
update: (id: string, data: { name: string; icon: string; sort_order: number }) =>
|
||||
request<QuickAddItem>(`/quick-add/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (id: string) =>
|
||||
request<void>(`/quick-add/${id}`, { method: 'DELETE' }),
|
||||
}
|
||||
```
|
||||
|
||||
2. **Hook** (`frontend/src/hooks/useQuickAdd.ts`):
|
||||
Create `useQuickAdd()` hook following the useTemplate pattern:
|
||||
- State: `items: QuickAddItem[]`, `loading: boolean`
|
||||
- On mount: fetch via `quickAdd.list()`, set items (default to empty array)
|
||||
- `addItem(name, icon)`: calls `quickAdd.create()`, refreshes list
|
||||
- `updateItem(id, name, icon, sortOrder)`: calls `quickAdd.update()`, refreshes list
|
||||
- `removeItem(id)`: calls `quickAdd.delete()`, refreshes list
|
||||
- Return: `{ items, loading, addItem, updateItem, removeItem }`
|
||||
|
||||
3. **Management page** (`frontend/src/pages/QuickAddPage.tsx`):
|
||||
Follow the TemplatePage pattern (pastel gradient header, table layout):
|
||||
- Header with gradient: `bg-gradient-to-r from-amber-50 to-orange-50` (warm tone for one-offs, distinct from template's violet)
|
||||
- Title from i18n: `t('quickAdd.title')`
|
||||
- Add form row at top: text input for name, text input for icon (emoji or short string), Add button
|
||||
- Table with columns: Name, Icon, Actions (Edit pencil button, Delete trash button)
|
||||
- Edit mode: clicking edit turns row into inline inputs, Save/Cancel buttons
|
||||
- Delete: immediate delete (no confirmation needed for library items — they are presets, not budget data)
|
||||
- Empty state: use EmptyState component with Zap icon, heading "No saved items", subtext "Save your frequently-used one-off categories here for quick access."
|
||||
|
||||
4. **Sidebar nav** (`frontend/src/components/AppLayout.tsx`):
|
||||
Add nav item after template: `{ path: '/quick-add', label: t('nav.quickAdd'), icon: Zap }`
|
||||
Import `Zap` from `lucide-react`.
|
||||
|
||||
5. **Route** (`frontend/src/App.tsx`):
|
||||
Add route: `<Route path="/quick-add" element={<QuickAddPage />} />`
|
||||
Import QuickAddPage.
|
||||
|
||||
6. **i18n** — add keys to both en.json and de.json:
|
||||
English (en.json):
|
||||
```json
|
||||
"nav": { ... "quickAdd": "Quick Add" },
|
||||
"quickAdd": {
|
||||
"title": "Quick-Add Library",
|
||||
"name": "Name",
|
||||
"icon": "Icon",
|
||||
"addItem": "Add Item",
|
||||
"noItems": "No saved items",
|
||||
"noItemsHint": "Save your frequently-used one-off categories here for quick access.",
|
||||
"editItem": "Edit",
|
||||
"deleteItem": "Remove",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
```
|
||||
German (de.json): translate equivalently:
|
||||
```json
|
||||
"nav": { ... "quickAdd": "Schnellzugriff" },
|
||||
"quickAdd": {
|
||||
"title": "Schnellzugriff-Bibliothek",
|
||||
"name": "Name",
|
||||
"icon": "Symbol",
|
||||
"addItem": "Hinzufuegen",
|
||||
"noItems": "Keine gespeicherten Eintraege",
|
||||
"noItemsHint": "Speichere hier haeufig genutzte Einmal-Kategorien fuer schnellen Zugriff.",
|
||||
"editItem": "Bearbeiten",
|
||||
"deleteItem": "Entfernen",
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen"
|
||||
}
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun run build</automated>
|
||||
</verify>
|
||||
<done>QuickAddPage renders with add form, table, and empty state. Sidebar shows "Quick Add" nav item. Route /quick-add works. All i18n keys present in both languages. Build succeeds.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Quick-add picker in dashboard for one-off budget items</name>
|
||||
<files>frontend/src/components/QuickAddPicker.tsx, frontend/src/pages/DashboardPage.tsx</files>
|
||||
<action>
|
||||
1. **QuickAddPicker component** (`frontend/src/components/QuickAddPicker.tsx`):
|
||||
A dropdown/popover that lets users add a one-off item to the current budget from their quick-add library.
|
||||
|
||||
Props:
|
||||
```typescript
|
||||
interface Props {
|
||||
budgetId: string
|
||||
onItemAdded: () => void // callback to refresh budget after adding
|
||||
}
|
||||
```
|
||||
|
||||
Implementation:
|
||||
- On mount, fetch quick-add items via `quickAdd.list()` (direct API call, not hook — this is a lightweight picker, not a full CRUD page)
|
||||
- Render a Popover (from shadcn/ui) with trigger button: icon `Zap` + text "Quick Add" (from i18n `quickAdd.addOneOff`)
|
||||
- Inside popover: list of quick-add items, each as a clickable row showing icon + name
|
||||
- On click: create a budget item via `budgetItems.create(budgetId, { category_id: null, item_tier: 'one_off', notes: item.name })` — Since quick-add items are independent presets (not linked to categories), the picker creates a one-off budget item. The backend CreateBudgetItem handler must accept this. **However**, looking at the existing BudgetItem model, category_id is required (UUID NOT NULL in DB). So instead:
|
||||
- The picker should first check if a category with the same name exists for the user. If not, create one via `categories.create({ name: item.name, type: 'variable_expense', icon: item.icon })`, then create the budget item with that category_id and `item_tier: 'one_off'`.
|
||||
- Alternatively (simpler): show the quick-add library items, and on click, find or create a matching category, then call `budgetItems.create(budgetId, { category_id, item_tier: 'one_off' })`.
|
||||
- After creation: call `onItemAdded()` to refresh, close popover
|
||||
- If quick-add library is empty: show a small message "No saved items" with a link to /quick-add
|
||||
- Add loading spinner on the clicked item while creating
|
||||
|
||||
Add i18n keys:
|
||||
- en.json: `"quickAdd": { ... "addOneOff": "Quick Add", "emptyPicker": "No saved items", "goToLibrary": "Manage library" }`
|
||||
- de.json: equivalent translations
|
||||
|
||||
2. **Wire into DashboardPage** (`frontend/src/pages/DashboardPage.tsx`):
|
||||
- Import QuickAddPicker
|
||||
- Import `categories as categoriesApi` from api.ts
|
||||
- Add QuickAddPicker next to the budget selector (after the "Create Budget" button), only visible when a budget is selected:
|
||||
```tsx
|
||||
{current && (
|
||||
<QuickAddPicker
|
||||
budgetId={current.id}
|
||||
onItemAdded={() => selectBudget(current.id)}
|
||||
/>
|
||||
)}
|
||||
```
|
||||
- The `onItemAdded` callback re-fetches the current budget to show the new item
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun run build</automated>
|
||||
</verify>
|
||||
<done>QuickAddPicker renders in dashboard toolbar. Clicking a quick-add item creates a one-off budget item (finding or creating the category first). Popover closes after add. Empty library shows link to management page. Build succeeds.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 3: Verify complete quick-add library feature</name>
|
||||
<action>
|
||||
Human verifies the complete quick-add library feature:
|
||||
1. Management page at /quick-add with add/edit/remove for saved one-off categories
|
||||
2. Quick-add picker button in dashboard toolbar that creates one-off budget items from saved library
|
||||
3. Sidebar navigation includes Quick Add link
|
||||
</action>
|
||||
<verify>
|
||||
Steps to verify:
|
||||
1. Start the app: `docker compose up --build`
|
||||
2. Navigate to /quick-add from sidebar — verify empty state shows
|
||||
3. Add 2-3 items (e.g., "Pharmacy" with pill emoji, "Haircut" with scissors emoji)
|
||||
4. Verify items appear in the table with edit/delete actions
|
||||
5. Edit one item name — verify it updates
|
||||
6. Delete one item — verify it disappears
|
||||
7. Go to Dashboard, select a budget
|
||||
8. Click "Quick Add" button in toolbar — verify popover shows your saved items
|
||||
9. Click one item — verify a new one-off budget item appears in the budget
|
||||
10. Verify the new item shows with item_tier badge "one-off"
|
||||
</verify>
|
||||
<done>All quick-add library features work end-to-end: management page CRUD, picker creates one-off budget items, sidebar nav accessible</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `bun run build` succeeds with no TypeScript errors
|
||||
- QuickAddPage renders management UI with CRUD operations
|
||||
- QuickAddPicker creates one-off budget items from library
|
||||
- Sidebar shows Quick Add navigation item
|
||||
- Route /quick-add loads the management page
|
||||
- All i18n keys present in en.json and de.json
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- User can add, edit, and remove items from the quick-add library page (QADD-03)
|
||||
- User can save a one-off category with icon to their library (QADD-01)
|
||||
- User can browse and select from library when adding one-off items to a budget (QADD-02)
|
||||
- Selected quick-add item creates a one-off budget item in the current budget
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/07-quick-add-library/07-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user