Archive roadmap, requirements, and phase directories to milestones/. Evolve PROJECT.md with validated requirements and key decisions. Reorganize ROADMAP.md with milestone grouping. Delete REQUIREMENTS.md (fresh for next milestone).
363 lines
17 KiB
Markdown
363 lines
17 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</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/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
|
|
|
|
<interfaces>
|
|
<!-- From Plan 01 (backend, must exist before this plan runs) -->
|
|
|
|
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<typeof createSetupSchema>;
|
|
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 }
|
|
|
|
<!-- Existing hooks patterns -->
|
|
|
|
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<T>(url: string): Promise<T>
|
|
export function apiPost<T>(url: string, body: unknown): Promise<T>
|
|
export function apiPut<T>(url: string, body: unknown): Promise<T>
|
|
export function apiDelete<T>(url: string): Promise<T>
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Navigation restructure, TotalsBar refactor, and setup hooks</name>
|
|
<files>
|
|
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
|
|
</files>
|
|
<action>
|
|
**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 `<Link to={linkTo}>` (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 `<Link to={to}>` 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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && npx tsc --noEmit 2>&1 | head -30</automated>
|
|
</verify>
|
|
<done>
|
|
- 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
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Setup list page, detail page, and item picker</name>
|
|
<files>
|
|
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
|
|
</files>
|
|
<action>
|
|
**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 `<Link to="/setups/$setupId" params={{ setupId: String(id) }}>`
|
|
- 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).
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && npx tsc --noEmit 2>&1 | head -30</automated>
|
|
</verify>
|
|
<done>
|
|
- 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
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
```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)
|
|
```
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- 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
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-setups-and-dashboard/03-02-SUMMARY.md`
|
|
</output>
|