diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index bfdcfa8..6f19b13 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -120,11 +120,11 @@ Plans: 1. A user can save a one-off expense category (with an icon) to their quick-add library from the budget item add flow 2. When adding a one-off item, the user can browse their quick-add library and select a saved category — the item populates with that category and icon 3. The quick-add library management page lets the user add, edit, and remove saved categories -**Plans**: TBD +**Plans:** 2 plans Plans: -- [ ] 07-01: Quick-add library backend — DB table, API endpoints for saved categories -- [ ] 07-02: Quick-add library frontend — management page and picker UI in add-item flow +- [ ] 07-01-PLAN.md — DB migration, QuickAddItem model, CRUD queries, REST handlers at /api/quick-add +- [ ] 07-02-PLAN.md — QuickAddPage management UI, QuickAddPicker in dashboard, sidebar nav and routing #### Phase 8: Layout and Density Rethink **Goal**: The dashboard looks and feels like a beautifully designed spreadsheet — flat containers, tight rows, no wasted chrome — and maximizes the data visible on a desktop screen without scrolling @@ -153,6 +153,6 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 | 3. Interaction Quality and Completeness | v1.0 | 4/4 | Complete | 2026-03-12 | | 4. Chart Polish and Bug Fixes | v1.0 | 2/2 | Complete | 2026-03-12 | | 5. Template Data Model and API | v1.1 | 2/2 | Complete | 2026-03-12 | -| 6. Template Frontend and Workflow Replacement | 2/2 | Complete | 2026-03-12 | - | +| 6. Template Frontend and Workflow Replacement | v1.1 | 2/2 | Complete | 2026-03-12 | | 7. Quick-Add Library | v1.1 | 0/2 | Not started | - | | 8. Layout and Density Rethink | v1.1 | 0/2 | Not started | - | diff --git a/.planning/phases/07-quick-add-library/07-01-PLAN.md b/.planning/phases/07-quick-add-library/07-01-PLAN.md new file mode 100644 index 0000000..2d6bda3 --- /dev/null +++ b/.planning/phases/07-quick-add-library/07-01-PLAN.md @@ -0,0 +1,217 @@ +--- +phase: 07-quick-add-library +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - backend/migrations/003_quick_add_library.sql + - backend/internal/models/models.go + - backend/internal/db/queries.go + - backend/internal/api/handlers.go + - backend/internal/api/router.go +autonomous: true +requirements: [QADD-01, QADD-03] + +must_haves: + truths: + - "A quick_add_items table exists with user_id, name, icon, and sort_order columns" + - "API returns a list of quick-add items for the authenticated user" + - "API can create, update, and delete quick-add items" + - "Quick-add items are user-scoped — one user cannot see another's items" + artifacts: + - path: "backend/migrations/003_quick_add_library.sql" + provides: "quick_add_items table DDL" + contains: "CREATE TABLE quick_add_items" + - path: "backend/internal/models/models.go" + provides: "QuickAddItem Go struct" + contains: "QuickAddItem" + - path: "backend/internal/db/queries.go" + provides: "CRUD query functions for quick-add items" + exports: ["ListQuickAddItems", "CreateQuickAddItem", "UpdateQuickAddItem", "DeleteQuickAddItem"] + - path: "backend/internal/api/handlers.go" + provides: "HTTP handlers for quick-add CRUD" + contains: "ListQuickAddItems" + - path: "backend/internal/api/router.go" + provides: "Route registrations under /api/quick-add" + contains: "/api/quick-add" + key_links: + - from: "backend/internal/api/router.go" + to: "backend/internal/api/handlers.go" + via: "handler method references" + pattern: "h\\..*QuickAdd" + - from: "backend/internal/api/handlers.go" + to: "backend/internal/db/queries.go" + via: "query function calls" + pattern: "q\\..*QuickAdd" +--- + + +Create the backend data model and REST API for the quick-add library feature. + +Purpose: Users need a persistent library of saved one-off expense categories (name + icon) that they can reuse across months. This plan creates the database table, Go model, query functions, and HTTP endpoints. + +Output: Migration file, QuickAddItem model, CRUD queries, REST handlers at /api/quick-add + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +@backend/internal/models/models.go +@backend/internal/db/queries.go +@backend/internal/api/handlers.go +@backend/internal/api/router.go +@backend/migrations/002_templates.sql + + + + +From backend/internal/models/models.go: +```go +// Category struct pattern — QuickAddItem should follow same shape +type Category struct { + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"user_id"` + Name string `json:"name"` + Type CategoryType `json:"type"` + Icon string `json:"icon"` + SortOrder int `json:"sort_order"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} +``` + +From backend/internal/api/router.go: +```go +// Route group pattern — quick-add follows same structure +r.Route("/api/template", func(r chi.Router) { + r.Get("/", h.GetTemplate) + r.Put("/", h.UpdateTemplateName) + r.Post("/items", h.CreateTemplateItem) + // ... +}) +``` + +From backend/internal/api/handlers.go: +```go +// Handler struct — all handlers are methods on this +type Handlers struct { + queries *db.Queries + sessionSecret string +} + +// Helper functions available: writeJSON, writeError, auth.UserIDFromContext +``` + + + + + + + Task 1: Migration, model, and query functions + backend/migrations/003_quick_add_library.sql, backend/internal/models/models.go, backend/internal/db/queries.go + +1. Create migration `backend/migrations/003_quick_add_library.sql`: + ```sql + CREATE TABLE quick_add_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + icon VARCHAR(50) NOT NULL DEFAULT '', + sort_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() + ); + CREATE INDEX idx_quick_add_items_user ON quick_add_items(user_id); + ``` + The table stores saved one-off category presets (name + icon). No FK to categories — these are independent presets the user can pick from when adding a one-off budget item. + +2. Add `QuickAddItem` struct to `backend/internal/models/models.go`: + ```go + type QuickAddItem struct { + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"user_id"` + Name string `json:"name"` + Icon string `json:"icon"` + SortOrder int `json:"sort_order"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + ``` + +3. Add four query functions to `backend/internal/db/queries.go` (append after template section): + + - `ListQuickAddItems(ctx, userID) ([]QuickAddItem, error)` — SELECT ordered by sort_order, returns empty slice (not nil) when none exist + - `CreateQuickAddItem(ctx, userID, name, icon string) (*QuickAddItem, error)` — INSERT with sort_order = (SELECT COALESCE(MAX(sort_order),0)+1), RETURNING all columns + - `UpdateQuickAddItem(ctx, id, userID, name, icon string, sortOrder int) (*QuickAddItem, error)` — UPDATE with WHERE id=$1 AND user_id=$2, return error if no rows affected + - `DeleteQuickAddItem(ctx, id, userID) error` — DELETE with WHERE id=$1 AND user_id=$2 + + Follow existing query patterns: use context parameter, userID scoping in WHERE clause, fmt.Errorf wrapping, pgx row scanning. + + + cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/backend && go vet ./... + + QuickAddItem struct compiles, all four query functions compile, migration file exists with CREATE TABLE statement + + + + Task 2: HTTP handlers and route registration + backend/internal/api/handlers.go, backend/internal/api/router.go + +1. Add four handler methods to `backend/internal/api/handlers.go`: + + - `ListQuickAddItems(w, r)` — GET, extracts userID from context, calls queries.ListQuickAddItems, returns JSON array (200) + - `CreateQuickAddItem(w, r)` — POST, accepts JSON `{name: string, icon: string}`, validates name is non-empty (400 if missing), calls queries.CreateQuickAddItem, returns created item (201) + - `UpdateQuickAddItem(w, r)` — PUT, extracts itemId from chi URL param, accepts JSON `{name: string, icon: string, sort_order: int}`, validates name non-empty, calls queries.UpdateQuickAddItem, returns updated item (200). Return 404 if no rows affected. + - `DeleteQuickAddItem(w, r)` — DELETE, extracts itemId from chi URL param, calls queries.DeleteQuickAddItem, returns 204 + + Follow existing handler patterns: + - Use `auth.UserIDFromContext(r.Context())` for userID + - Use `chi.URLParam(r, "itemId")` for path params + - Use `writeJSON(w, status, data)` and `writeError(w, status, message)` + - Parse UUID with `uuid.Parse()`, return 400 on invalid + +2. Register routes in `backend/internal/api/router.go` inside the authenticated group (after the template route block): + ```go + r.Route("/api/quick-add", func(r chi.Router) { + r.Get("/", h.ListQuickAddItems) + r.Post("/", h.CreateQuickAddItem) + r.Put("/{itemId}", h.UpdateQuickAddItem) + r.Delete("/{itemId}", h.DeleteQuickAddItem) + }) + ``` + + + cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/backend && go vet ./... && go build ./cmd/server + + All four handlers compile, routes registered under /api/quick-add, go build succeeds with no errors + + + + + +- `go vet ./...` passes with no issues +- `go build ./cmd/server` produces binary without errors +- Migration file 003 exists and has valid SQL +- QuickAddItem struct has json tags matching API contract +- All handlers use userID scoping (no cross-user data leak) + + + +- Backend compiles and builds successfully +- Migration creates quick_add_items table with correct schema +- Four REST endpoints exist: GET/POST /api/quick-add, PUT/DELETE /api/quick-add/{itemId} +- All endpoints require authentication (inside authenticated route group) +- All queries scope by user_id + + + +After completion, create `.planning/phases/07-quick-add-library/07-01-SUMMARY.md` + diff --git a/.planning/phases/07-quick-add-library/07-02-PLAN.md b/.planning/phases/07-quick-add-library/07-02-PLAN.md new file mode 100644 index 0000000..3513415 --- /dev/null +++ b/.planning/phases/07-quick-add-library/07-02-PLAN.md @@ -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" +--- + + +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 + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + +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 + + +From frontend/src/lib/api.ts: +```typescript +export const budgetItems = { + create: (budgetId: string, data: Partial) => + request(`/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(null) + const [categories, setCategories] = useState([]) + 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 }, +] +``` + + + + + + + Task 1: API client, hook, management page, routing, and i18n + 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 + +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('/quick-add'), + create: (data: { name: string; icon: string }) => + request('/quick-add', { method: 'POST', body: JSON.stringify(data) }), + update: (id: string, data: { name: string; icon: string; sort_order: number }) => + request(`/quick-add/${id}`, { method: 'PUT', body: JSON.stringify(data) }), + delete: (id: string) => + request(`/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: `} />` + 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" + } + ``` + + + cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun run build + + 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. + + + + Task 2: Quick-add picker in dashboard for one-off budget items + frontend/src/components/QuickAddPicker.tsx, frontend/src/pages/DashboardPage.tsx + +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 && ( + selectBudget(current.id)} + /> + )} + ``` + - The `onItemAdded` callback re-fetches the current budget to show the new item + + + cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun run build + + 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. + + + + Task 3: Verify complete quick-add library feature + + 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 + + + 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" + + All quick-add library features work end-to-end: management page CRUD, picker creates one-off budget items, sidebar nav accessible + + + + + +- `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 + + + +- 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 + + + +After completion, create `.planning/phases/07-quick-add-library/07-02-SUMMARY.md` +