--- phase: 03-dashboard-intake-ui plan: "03" subsystem: ui tags: [react, typescript, tanstack-query, tanstack-router, zustand, tailwind, clickhouse-design, inventory] # Dependency graph requires: - web/src/store/ui.ts (useUIStore — viewMode, setViewMode) - web/src/components/ui/badge.tsx (Badge with status variants) - web/src/components/ui/button.tsx (Button with forest/ghost/outline variants) - web/src/components/ui/card.tsx (Card, CardHeader, CardTitle, CardContent, CardFooter) - internal/api/handlers.InventoryItemResponse (JSON shape from GET /api/inventory) provides: - web/src/lib/api.ts (InventoryItem type, fetchInventory, fetchInventoryItem) - web/src/hooks/useInventory.ts (useInventory, useInventoryItem TanStack Query hooks) - web/src/components/layout/AppShell.tsx (TopBar + main content wrapper) - web/src/components/layout/TopBar.tsx (sticky nav with HWLab branding) - web/src/components/inventory/StatusBadge.tsx (catalog_status color-coded badge) - web/src/components/inventory/ItemCard.tsx (grid card component) - web/src/components/inventory/ItemRow.tsx (list-mode row component) - web/src/components/inventory/FilterBar.tsx (search + status filter + view toggle) - web/src/pages/DashboardPage.tsx (/ route — inventory grid/list) - web/src/pages/ItemDetailPage.tsx (/item/$id route — full detail view) affects: - web/src/router.tsx (indexRoute + itemRoute updated to lazy-load real pages) - 03-04, 03-05 (intake + scan pages reuse AppShell and TopBar) # Tech tracking tech-stack: added: [] patterns: - TanStack Query useQuery with typed fetchJSON wrapper (no axios) - Zustand viewMode persists grid/list toggle across navigation within session - lazy() + Suspense for route-level code splitting (DashboardPage + ItemDetailPage split into separate chunks) - client-side filter via useMemo — no server-side filtering for <200 items - label-upper CSS class applied to uppercase tracked labels key-files: created: - web/src/lib/api.ts - web/src/hooks/useInventory.ts - web/src/components/inventory/StatusBadge.tsx - web/src/components/inventory/ItemCard.tsx - web/src/components/inventory/ItemRow.tsx - web/src/components/layout/TopBar.tsx - web/src/components/layout/AppShell.tsx - web/src/components/inventory/FilterBar.tsx - web/src/pages/DashboardPage.tsx - web/src/pages/ItemDetailPage.tsx modified: - web/src/router.tsx key-decisions: - id: DASH-01 summary: "lazy() + Suspense for DashboardPage and ItemDetailPage — creates separate JS chunks (9.17KB + 4.10KB) keeping initial bundle lean" - id: DASH-02 summary: "Client-side filtering via useMemo on items array — acceptable for <=200 item limit from API; no server-side filter params needed" - id: DASH-03 summary: "fetchJSON generic wraps raw fetch with typed error handling — no axios dependency, consistent with project WHAT NOT TO USE list" # Metrics duration: 12min completed: 2026-04-10 --- # Phase 3 Plan 03: Dashboard + Item Detail Pages Summary **Inventory dashboard and item detail views wired to GET /api/inventory with ClickHouse design (volt/canvas/charcoal), grid/list toggle via Zustand, client-side search+filter, and mobile-responsive two-column detail layout** ## Performance - **Duration:** ~12 min - **Completed:** 2026-04-10 - **Tasks:** 2 - **Files created:** 10 - **Files modified:** 1 ## Accomplishments - `web/src/lib/api.ts` — typed `fetchInventory` / `fetchInventoryItem` using native fetch with error unwrapping; exports `InventoryItem` interface matching backend JSON shape - `web/src/hooks/useInventory.ts` — `useInventory()` and `useInventoryItem(id)` TanStack Query hooks with proper query keys for cache invalidation - `AppShell` + `TopBar` — sticky dark header with volt "HWLab" brand, forest-green "Add Item" button, outline "Scan" button; main content area with 7xl max-width - `StatusBadge` — maps `catalog_status` string to 6 color-coded Badge variants (indexed=green, draft=gray, needs_research=yellow, researched=blue, complete=forest, destructive=red) - `ItemCard` — grid card with aspect-video photo (or Package placeholder icon), volt HW ID, item name, StatusBadge, ai_notes preview (2-line clamp), hover volt border, View in NetBox link - `ItemRow` — list-mode row with 4px status color indicator bar, HW ID, name, badge, ai_notes (hidden on mobile), hover reveal NetBox link - `FilterBar` — search input with icon, catalog_status dropdown, item count label, grid/list toggle (Zustand viewMode) - `DashboardPage` — full inventory view with loading/error/empty states, responsive grid (1→2→3→4→5 cols by breakpoint), list view in border container - `ItemDetailPage` — back nav, header with HW ID + name + status + NetBox action, two-column lg (photos left, fields right) single-column mobile, ai_notes card, test_data pretty-printed JSON code block - `router.tsx` updated — DashboardPage and ItemDetailPage lazy-loaded via `lazy()` + `Suspense` with Spinner fallback; intake/scan stubs preserved ## Task Commits 1. **Task 1: API client, hooks, layout, components** — `1867846` 2. **Task 2: Pages, FilterBar, router wiring** — `19c2bb7` ## Files Created/Modified | File | Purpose | |------|---------| | `web/src/lib/api.ts` | Typed fetch wrappers + InventoryItem interface | | `web/src/hooks/useInventory.ts` | TanStack Query hooks | | `web/src/components/inventory/StatusBadge.tsx` | Status → Badge color mapping | | `web/src/components/inventory/ItemCard.tsx` | Grid card (photo, HW ID, name, status, action) | | `web/src/components/inventory/ItemRow.tsx` | List-mode row with status color bar | | `web/src/components/layout/TopBar.tsx` | Sticky app header with navigation | | `web/src/components/layout/AppShell.tsx` | TopBar + main content wrapper | | `web/src/components/inventory/FilterBar.tsx` | Search + status filter + view toggle | | `web/src/pages/DashboardPage.tsx` | / route — inventory grid/list with filters | | `web/src/pages/ItemDetailPage.tsx` | /item/$id route — detail view, mobile responsive | | `web/src/router.tsx` | Lazy-loaded real pages replacing stubs | ## Deviations from Plan None — plan executed exactly as written. ## Known Stubs None — all data flows from `useInventory` / `useInventoryItem` hooks which call the real backend API. No hardcoded or mock data in any component. Empty state and loading state are functional UI states, not stubs. ## Threat Surface Coverage No new network endpoints, auth paths, or trust boundary changes introduced — this plan is purely frontend components consuming the existing GET /api/inventory endpoints established in Plan 03-02. ## Self-Check Files created: - web/src/lib/api.ts: FOUND - web/src/hooks/useInventory.ts: FOUND - web/src/components/inventory/StatusBadge.tsx: FOUND - web/src/components/inventory/ItemCard.tsx: FOUND - web/src/components/inventory/ItemRow.tsx: FOUND - web/src/components/layout/TopBar.tsx: FOUND - web/src/components/layout/AppShell.tsx: FOUND - web/src/components/inventory/FilterBar.tsx: FOUND - web/src/pages/DashboardPage.tsx: FOUND - web/src/pages/ItemDetailPage.tsx: FOUND Commits: - 1867846: feat(03-03): API client, TanStack Query hooks, layout shell, inventory item components - 19c2bb7: feat(03-03): DashboardPage, ItemDetailPage, FilterBar, and router wiring `npm run build`: PASS (1717 modules, 0 TypeScript errors, dist/assets/ written) ## Self-Check: PASSED