# Phase 37: Admin — Global Item Management - Context **Gathered:** 2026-04-19 **Status:** Ready for planning ## Phase Boundary Build the `/admin/items` list and `/admin/items/$id` edit page, wired into the Phase 36 admin shell. Admins can browse all global catalog items, open and edit any item's full field set, and delete items with an impact-aware confirmation. No new capabilities beyond browse/edit/delete. ## Implementation Decisions ### List View - **D-01:** Data table layout — dense rows with sortable columns. No card grid. No existing table component; implement with plain Tailwind-styled HTML table. - **D-02:** Columns: Brand + Model | Category | Weight | Price | Tags (chip or count) | Owner Count | Actions. - **D-03:** Server-side infinite scroll pagination — load next page as admin scrolls down. No explicit next/prev buttons. Consistent with IntersectionObserver scroll detection. 50 items per page is a sensible default. ### Edit Workflow - **D-04:** Dedicated edit page at `/admin/items/$itemId` — full-page form, bookmarkable. Consistent with the item/catalog detail page pattern already in the app. - **D-05:** Delete lives on the edit page only (not on the list). Admin must open an item to delete it. ### Brand / Manufacturer Field - **D-06:** Dropdown of existing manufacturers (fetched from `/api/manufacturers`). Admin picks from the list — no free-text brand creation in this phase. Prevents accidental duplicate manufacturers. ### Delete Confirmation - **D-07:** Impact-aware confirmation dialog: show item name + owner count before confirming. Example: "Delete Salsa Woodsmoke 700? 3 users have this in their collection. This cannot be undone." Owner count already available from `getGlobalItemWithOwnerCount`. ### Claude's Discretion - Exact column sort order and default sort (brand asc is reasonable) - Whether tags column shows chips or a count badge when many tags exist - Form field layout on the edit page (single column vs. two-column grid for compact fields) - How the infinite scroll trigger is implemented (IntersectionObserver sentinel div) - Styling of the delete button (destructive red, positioned at bottom of edit form) ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Existing Service Layer - `src/server/services/global-item.service.ts` — `searchGlobalItems`, `getGlobalItemWithOwnerCount`, `upsertGlobalItem`; DELETE function needs to be added here - `src/server/routes/global-items.ts` — existing GET/POST endpoints (public); admin DELETE will go through `/api/admin/items` route ### Admin Infrastructure (Phase 36) - `src/server/routes/admin.ts` — admin route stub; extend with item sub-routes here - `src/server/middleware/auth.ts` — `requireAdmin` middleware already in place - `src/client/routes/admin.tsx` — admin shell layout with ``; "Items" nav item needs to be enabled (remove `cursor-not-allowed`, add active link) ### Client Routes - `src/client/routes/admin/index.tsx` — admin index placeholder; stays as-is - TanStack Router file-based routing: create `admin/items.tsx` (list) and `admin/items/$itemId.tsx` (edit) ### Database Schema - `src/db/schema.ts` — `globalItems`, `manufacturers`, `tags`, `globalItemTags` tables ### Existing Reusable Hooks / Patterns - `src/client/hooks/` — React Query hooks pattern; add `useAdminGlobalItems` (infinite query) and `useAdminGlobalItem` - `src/client/lib/api.ts` — `apiGet`, `apiDelete`, `apiPut` fetch wrappers - `src/server/routes/manufacturers.ts` (or similar) — GET /api/manufacturers for the brand dropdown ### Requirements - `.planning/REQUIREMENTS.md` — ADMN-02, ADMN-03, ADMN-04 ## Existing Code Insights ### Reusable Assets - `getGlobalItemWithOwnerCount` service function: already returns `ownerCount` — reuse for delete confirmation and edit page header - `upsertGlobalItem` service: handles create and update via `onConflictDoUpdate`; admin edit will call this directly (PUT /api/admin/items/:id) - `ImageUpload` component (`src/client/components/`) — reuse for image field on edit page - `useFormatters()` hook — reuse for weight/price display in the table - LucideIcon `"package"` already in the admin sidebar "Items" entry — just enable it ### Established Patterns - Infinite scroll: Phase 26 discovery feed used cursor pagination + React Query's `useInfiniteQuery` — same pattern here - Edit forms: item/catalog detail pages use controlled form state with `apiPut` - Destructive confirmation: confirm modals exist in the app (setup delete, etc.) — reuse the same modal pattern - React Query invalidation: mutations call `queryClient.invalidateQueries` with relevant query keys ### Integration Points - `src/client/routes/admin.tsx`: Change "Items" sidebar entry from `
` to `` with active styling - `src/server/index.ts`: Register `adminItemRoutes` under `/api/admin/items` - `src/server/routes/admin.ts`: Mount item sub-router ## Specific Ideas - Table row click → navigate to `/admin/items/$itemId` - Edit page has a back link "← Items" to return to the list - Delete button at bottom of edit form, styled destructive red, triggers impact-aware modal - Infinite scroll sentinel: a `
` at bottom of table; IntersectionObserver triggers next page fetch ## Deferred Ideas - Manufacturer creation from within the admin panel — admin can only pick existing manufacturers in this phase; creating new ones is out of scope - Bulk delete / bulk edit from the list — admin-only single-item workflow for now - Column sorting — mentioned as a potential feature but not in ADMN-02/03/04 success criteria; Claude's discretion whether to include basic sort --- *Phase: 37-admin-global-item-management* *Context gathered: 2026-04-19*