--- phase: 03-setups-and-dashboard plan: 02 type: execute wave: 2 depends_on: ["03-01"] files_modified: - src/client/routes/index.tsx - src/client/routes/collection/index.tsx - src/client/routes/setups/index.tsx - src/client/routes/setups/$setupId.tsx - src/client/routes/__root.tsx - src/client/components/TotalsBar.tsx - src/client/components/DashboardCard.tsx - src/client/components/SetupCard.tsx - src/client/components/ItemPicker.tsx - src/client/components/ItemCard.tsx - src/client/hooks/useSetups.ts - src/client/hooks/useItems.ts - src/client/stores/uiStore.ts autonomous: true requirements: - SETP-01 - SETP-02 - SETP-03 - DASH-01 must_haves: truths: - "User sees dashboard at / with three summary cards (Collection, Planning, Setups)" - "User can navigate to /collection and see the existing gear/planning tabs" - "User can create a named setup from the setups list page" - "User can add/remove collection items to a setup via checklist picker" - "User can see total weight and cost for a setup in the sticky bar" - "GearBox title in TotalsBar links back to dashboard from all sub-pages" artifacts: - path: "src/client/routes/index.tsx" provides: "Dashboard page with three summary cards" contains: "DashboardCard" - path: "src/client/routes/collection/index.tsx" provides: "Gear + Planning tabs (moved from old index.tsx)" contains: "CollectionView" - path: "src/client/routes/setups/index.tsx" provides: "Setup list with create form" contains: "createFileRoute" - path: "src/client/routes/setups/$setupId.tsx" provides: "Setup detail with item cards and totals" contains: "ItemPicker" - path: "src/client/components/TotalsBar.tsx" provides: "Route-aware totals bar with optional stats and linkable title" contains: "linkTo" - path: "src/client/components/DashboardCard.tsx" provides: "Dashboard summary card component" contains: "DashboardCard" - path: "src/client/components/ItemPicker.tsx" provides: "Checklist picker in SlideOutPanel for selecting items" contains: "ItemPicker" - path: "src/client/hooks/useSetups.ts" provides: "TanStack Query hooks for setup CRUD" exports: ["useSetups", "useSetup", "useCreateSetup", "useDeleteSetup", "useSyncSetupItems", "useRemoveSetupItem"] key_links: - from: "src/client/routes/index.tsx" to: "src/client/hooks/useSetups.ts" via: "useSetups() for setup count" pattern: "useSetups" - from: "src/client/routes/setups/$setupId.tsx" to: "/api/setups/:id" via: "useSetup() hook" pattern: "useSetup" - from: "src/client/routes/__root.tsx" to: "src/client/components/TotalsBar.tsx" via: "route-aware props" pattern: "TotalsBar" - from: "src/client/components/ItemPicker.tsx" to: "src/client/hooks/useSetups.ts" via: "useSyncSetupItems mutation" pattern: "useSyncSetupItems" --- Build the complete frontend: restructure navigation (move gear/planning to /collection, create dashboard at /), build setup list and detail pages with item picker, make TotalsBar route-aware, and create the dashboard home page. Purpose: Delivers the user-facing features for setups and dashboard, completing all v1 requirements. Output: Working dashboard, setup CRUD UI, and item picker -- all wired to the backend from Plan 01. @/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/phases/03-setups-and-dashboard/03-CONTEXT.md @.planning/phases/03-setups-and-dashboard/03-RESEARCH.md @.planning/phases/03-setups-and-dashboard/03-01-SUMMARY.md @src/client/routes/__root.tsx @src/client/routes/index.tsx @src/client/components/TotalsBar.tsx @src/client/components/ItemCard.tsx @src/client/components/CategoryHeader.tsx @src/client/components/ThreadCard.tsx @src/client/components/SlideOutPanel.tsx @src/client/hooks/useItems.ts @src/client/hooks/useThreads.ts @src/client/hooks/useTotals.ts @src/client/stores/uiStore.ts @src/client/lib/api.ts From src/shared/schemas.ts (added by Plan 01): ```typescript export const createSetupSchema = z.object({ name: z.string().min(1, "Setup name is required"), }); export const updateSetupSchema = z.object({ name: z.string().min(1).optional(), }); export const syncSetupItemsSchema = z.object({ itemIds: z.array(z.number().int().positive()), }); ``` From src/shared/types.ts (added by Plan 01): ```typescript export type CreateSetup = z.infer; export type Setup = typeof setups.$inferSelect; export type SetupItem = typeof setupItems.$inferSelect; ``` API endpoints from Plan 01: - GET /api/setups -> SetupListItem[] (with itemCount, totalWeight, totalCost) - GET /api/setups/:id -> SetupWithItems (setup + items array with category info) - POST /api/setups -> Setup - PUT /api/setups/:id -> Setup - DELETE /api/setups/:id -> { success: boolean } - PUT /api/setups/:id/items -> { success: boolean } (body: { itemIds: number[] }) - DELETE /api/setups/:id/items/:itemId -> { success: boolean } From src/client/hooks/useThreads.ts: ```typescript export function useThreads(includeResolved = false) { return useQuery({ queryKey: ["threads", includeResolved], queryFn: ... }); } export function useCreateThread() { const qc = useQueryClient(); return useMutation({ mutationFn: ..., onSuccess: () => qc.invalidateQueries({ queryKey: ["threads"] }) }); } ``` From src/client/lib/api.ts: ```typescript export function apiGet(url: string): Promise export function apiPost(url: string, body: unknown): Promise export function apiPut(url: string, body: unknown): Promise export function apiDelete(url: string): Promise ``` Task 1: Navigation restructure, TotalsBar refactor, and setup hooks src/client/components/TotalsBar.tsx, src/client/routes/index.tsx, src/client/routes/collection/index.tsx, src/client/routes/__root.tsx, src/client/hooks/useSetups.ts, src/client/hooks/useItems.ts, src/client/components/DashboardCard.tsx, src/client/stores/uiStore.ts **1. Refactor TotalsBar to accept optional props (per CONTEXT.md decisions):** - Add props: `title?: string`, `stats?: Array<{label: string, value: string}>`, `linkTo?: string` - When no `stats` prop: show title only (for dashboard) - When `stats` provided: render them instead of fetching global totals internally - When `linkTo` provided: wrap title in `` (per decision: GearBox title always links to /) - Default behavior (no props): fetch global totals with useTotals() and display as before (backward compatible for collection page) - Dashboard passes no linkTo (already on dashboard). All other pages pass `linkTo="/"` **2. Move current index.tsx content to collection/index.tsx:** - Create `src/client/routes/collection/index.tsx` - Move the entire HomePage, CollectionView, and PlanningView content from current `index.tsx` - Update route: `createFileRoute("/collection/")` with same `validateSearch` for tab param - Update handleTabChange to navigate to `/collection` instead of `/` - The TotalsBar in __root.tsx will automatically show global stats on this page (default behavior) **3. Rewrite index.tsx as Dashboard (per CONTEXT.md decisions):** - Three equal-width cards (grid-cols-1 md:grid-cols-3 gap-6) - Collection card: shows item count, total weight, total cost. Links to `/collection`. Empty state shows "Get started" - Planning card: shows active thread count. Links to `/collection?tab=planning` - Setups card: shows setup count. Links to `/setups` - Use `useTotals()` for collection stats, `useThreads(false)` for active threads, `useSetups()` for setup count - "GearBox" title only in TotalsBar (no stats on dashboard) -- pass no stats prop - Clean layout: max-w-7xl, centered, lots of whitespace **4. Create DashboardCard.tsx component:** - Props: `to: string`, `title: string`, `icon: ReactNode`, `stats: Array<{label: string, value: string}>`, `emptyText?: string` - Card with hover shadow transition, rounded-xl, padding - Wraps in `` for navigation - Shows icon, title, stats list, and optional empty state text **5. Create useSetups.ts hooks (follows useThreads.ts pattern exactly):** - `useSetups()`: queryKey ["setups"], fetches GET /api/setups - `useSetup(setupId: number | null)`: queryKey ["setups", setupId], enabled when setupId != null - `useCreateSetup()`: POST /api/setups, invalidates ["setups"] - `useUpdateSetup(setupId: number)`: PUT /api/setups/:id, invalidates ["setups"] - `useDeleteSetup()`: DELETE /api/setups/:id, invalidates ["setups"] - `useSyncSetupItems(setupId: number)`: PUT /api/setups/:id/items, invalidates ["setups"] - `useRemoveSetupItem(setupId: number)`: DELETE /api/setups/:id/items/:itemId, invalidates ["setups"] - Define response types inline: `SetupListItem` (with itemCount, totalWeight, totalCost) and `SetupWithItems` (with items array including category info) **6. Update __root.tsx:** - Pass route-aware props to TotalsBar based on current route matching - On dashboard (`/`): no stats, no linkTo - On collection (`/collection`): default behavior (TotalsBar fetches its own stats), linkTo="/" - On thread detail: linkTo="/" (keep current behavior) - On setups: linkTo="/" - On setup detail: TotalsBar with setup-specific title and stats (will be handled by setup detail page passing context) - Update FAB visibility: only show on `/collection` route when gear tab is active (not on dashboard, not on setups). Match `/collection` route instead of just hiding on thread pages - Update ResolveDialog onResolved navigation: change from `{ to: "/", search: { tab: "planning" } }` to `{ to: "/collection", search: { tab: "planning" } }` **7. Add setup-related UI state to uiStore.ts:** - Add `itemPickerOpen: boolean` state - Add `openItemPicker()` and `closeItemPicker()` actions - Add `confirmDeleteSetupId: number | null` state with open/close actions **8. Update useItems invalidation (Pitfall 1 from research):** - In `useUpdateItem` and `useDeleteItem` mutation `onSuccess`, also invalidate `["setups"]` query key - This ensures setup totals update when a collection item's weight/price changes or item is deleted IMPORTANT: After creating route files, the TanStack Router plugin will auto-regenerate `routeTree.gen.ts`. Restart the dev server if needed. cd /home/jean-luc-makiola/Development/projects/GearBox && npx tsc --noEmit 2>&1 | head -30 - Dashboard renders at / with three summary cards showing real data - Collection view with gear/planning tabs works at /collection - GearBox title links back to / from all sub-pages - TotalsBar shows contextual stats per page (title-only on dashboard, global on collection) - FAB only appears on /collection gear tab - Thread resolution redirects to /collection?tab=planning - Setup query/mutation hooks are functional Task 2: Setup list page, detail page, and item picker src/client/routes/setups/index.tsx, src/client/routes/setups/$setupId.tsx, src/client/components/SetupCard.tsx, src/client/components/ItemPicker.tsx, src/client/components/ItemCard.tsx **1. Create SetupCard.tsx (reference ThreadCard.tsx pattern):** - Props: `id: number`, `name: string`, `itemCount: number`, `totalWeight: number`, `totalCost: number` - Card with rounded-xl, shadow-sm, hover:shadow-md transition - Shows setup name, item count pill, formatted weight and cost - Wraps in `` - Use `formatWeight` and `formatPrice` from existing `lib/formatters` **2. Create setups list page (src/client/routes/setups/index.tsx):** - Route: `createFileRoute("/setups/")` - Inline name input + "Create" button at top (same pattern as thread creation in PlanningView) - Uses `useSetups()` and `useCreateSetup()` hooks - Grid layout: grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 - Each setup rendered as SetupCard - Empty state: icon + "No setups yet" message + "Create one to plan your loadout" - Loading skeleton: 2 placeholder cards **3. Create ItemPicker.tsx (checklist in SlideOutPanel, per CONTEXT.md decisions):** - Props: `setupId: number`, `currentItemIds: number[]`, `isOpen: boolean`, `onClose: () => void` - Renders inside a SlideOutPanel with title "Select Items" - Fetches all collection items via `useItems()` - Groups items by category with emoji headers (same grouping as CollectionView) - Each item is a checkbox row: `[x] emoji ItemName (weight, price)` - Pre-checks items already in the setup (from `currentItemIds`) - Local state tracks toggled item IDs - "Done" button at bottom calls `useSyncSetupItems(setupId)` with selected IDs, then closes - Scrollable list for large collections (max-h with overflow-y-auto) - "Cancel" closes without saving **4. Create setup detail page (src/client/routes/setups/$setupId.tsx):** - Route: `createFileRoute("/setups/$setupId")` - Uses `useSetup(setupId)` to fetch setup with items - Sticky TotalsBar override: pass setup name as title, setup-specific stats (item count, total weight, total cost) - Compute totals client-side from items array (per research recommendation) - Render a local TotalsBar-like sticky bar at top of the page with setup name + stats - "Add Items" button opens ItemPicker via SlideOutPanel - "Delete Setup" button with ConfirmDialog confirmation - Item cards grouped by category using CategoryHeader + ItemCard (same visual as collection) - Each ItemCard gets a small x remove button overlay (per CONTEXT.md: non-destructive, no confirmation) - Per-category subtotals in CategoryHeader (weight/cost within this setup) - Empty state when no items: "No items in this setup" + "Add Items" button - On successful delete, navigate to `/setups` **5. Modify ItemCard.tsx to support remove mode:** - Add optional prop: `onRemove?: () => void` - When `onRemove` provided, show a small x icon button in top-right corner of card - x button calls `onRemove` on click (stops propagation to prevent edit panel opening) - Subtle styling: small, semi-transparent, visible on hover or always visible but muted - Does NOT change existing behavior when `onRemove` is not provided IMPORTANT: Use `useRemoveSetupItem(setupId)` for the x button on cards. Use `useSyncSetupItems(setupId)` for the checklist picker "Done" action. These are separate mutations for separate UX patterns (per research: batch sync vs single remove). cd /home/jean-luc-makiola/Development/projects/GearBox && npx tsc --noEmit 2>&1 | head -30 - Setup list page at /setups shows all setups with name, item count, weight, cost - User can create a new setup via inline form - Setup detail page shows items grouped by category with per-category subtotals - Item picker opens in SlideOutPanel with category-grouped checkboxes - Selecting items and clicking "Done" syncs items to setup - x button on item cards removes item from setup without confirmation - Delete setup button with confirmation dialog works - All existing TypeScript compilation passes ```bash # TypeScript compilation npx tsc --noEmit # All tests pass (backend + existing) bun test # Dev server starts without errors # (manual: bun run dev, check no console errors) ``` - Dashboard at / shows three summary cards with real data - Collection at /collection has gear + planning tabs (same as before, different URL) - Setups list at /setups shows setup cards with totals - Setup detail at /setups/:id shows items grouped by category with totals - Item picker allows adding/removing items via checklist - GearBox title links back to dashboard from all pages - TotalsBar shows contextual stats per page - All internal links updated (thread resolution, FAB visibility) - TypeScript compiles, all tests pass After completion, create `.planning/phases/03-setups-and-dashboard/03-02-SUMMARY.md`