--- plan: 5-PLAN-02 feature: Search & Filtering status: ready estimated_tasks: 6 total_lines: ~520 --- # Phase 5 Plan 02: Search & Filtering ## Overview Implement search modal with real-time search across all item text fields (Name, PN, Barcode, Description, Category, Notes). Results displayed as vertical list; tapping item opens quantity adjustment modal (leveraging Task 1 from Plan 01). No advanced filtering in Phase 5—keep it simple. ## Tasks ### Task 1: Backend Search Endpoint - **File:** backend/routers/items.py - **Endpoint:** `GET /items/search?q={query}` - **Function:** `search_items(query: str, auth: User) → List[ItemResponse]` - **Lines:** ~50 - **Description:** Full-text search across Name, Part Number, Barcode, Description, Category, Notes fields. Case-insensitive substring matching. Return max 50 results ordered by relevance (name match > PN > barcode > description). - **Acceptance Criteria:** - [ ] Accepts query string parameter (min 1 char, max 100 chars) - [ ] Searches all text fields (case-insensitive) - [ ] Returns max 50 results (pagination deferred to Phase 6+) - [ ] Results ordered by relevance score - [ ] Empty query returns empty list (no "show all") - [ ] Authorization: users see only items in their accessible locations - [ ] Unit tests for: exact match, partial match, multi-field, empty results ### Task 2: Create SearchModal Component (UI Layer) - **File:** frontend/components/inventory/SearchModal.tsx - **Component:** `SearchModal(isOpen: boolean; onClose: () => void; onSelectItem: (item: Item) => void) → JSX.Element` - **Lines:** ~180 - **Description:** Modal with search input field + real-time result list. Results rendered as clickable item rows. Tapping item emits `onSelectItem` and closes modal. - **Acceptance Criteria:** - [ ] Modal opens/closes via `isOpen` prop - [ ] Search input with placeholder "Search by name, PN, barcode..." - [ ] Real-time search triggered on input change (debounced 300ms) - [ ] Results displayed as vertical list below input - [ ] Each result row shows: Name, PN, Barcode, current Qty - [ ] Clicking result: emits `onSelectItem`, closes modal - [ ] Loading spinner during API call - [ ] Error message if search fails - [ ] Escape key or X button closes modal - [ ] Accessibility: proper ARIA labels, keyboard navigation (arrow keys, Enter) ### Task 3: Create useItemSearch Hook - **File:** frontend/hooks/useItemSearch.ts - **Hook:** `useItemSearch(query: string, enabled: boolean) → { results: Item[]; isLoading: boolean; error: string | null }` - **Lines:** ~100 - **Description:** Custom hook handling search API calls with debouncing and caching. Manages loading/error states. - **Acceptance Criteria:** - [ ] Debounces API calls (300ms) - [ ] Caches results per query to avoid redundant calls - [ ] Returns empty results if query < 2 chars (client-side validation) - [ ] Handles network errors gracefully - [ ] Clears cache on component unmount - [ ] Unit tests: debouncing, caching, error handling ### Task 4: Add Search Button to Inventory Page - **File:** frontend/app/inventory/page.tsx - **Component:** Update header/toolbar area - **Lines:** ~30 - **Description:** Add prominent Search button (magnifying glass icon) in inventory page header. Toggle SearchModal open/closed on button click. - **Acceptance Criteria:** - [ ] Search button visible in toolbar/header (Lucide Search icon) - [ ] Button click opens SearchModal - [ ] Modal closes on cancel or item selection - [ ] Selected item triggers quantity adjustment UI (from Plan 01) - [ ] Mobile-responsive button placement - [ ] Focus management: focus returns to Search button after modal closes ### Task 5: Quantity Adjustment Modal (Plan 01 Integration) - **File:** frontend/components/inventory/QuantityAdjustmentModal.tsx - **Component:** `QuantityAdjustmentModal(item: Item | null; isOpen: boolean; onClose: () => void) → JSX.Element` - **Lines:** ~140 - **Description:** Modal displayed when user taps search result. Allows quick +/- adjustment and commit. Reuse QuantityDisplay component from Plan 01. - **Acceptance Criteria:** - [ ] Modal shows item name, current quantity - [ ] Uses QuantityDisplay component from Plan 01 for adjustment - [ ] +/- buttons, input field, or both available - [ ] Commit button saves changes - [ ] Cancel button closes without changes - [ ] Success toast after save - [ ] Error toast on failure - [ ] Mobile responsive ### Task 6: Integration & E2E Tests - **File:** frontend/tests/inventory/search.test.ts - **Test:** Full search workflow: open search → type query → select result → adjust quantity - **Lines:** ~200 - **Description:** End-to-end test of entire search feature including backend integration. - **Acceptance Criteria:** - [ ] Test: open search modal, input appears focused - [ ] Test: type query, results update (mocked API) - [ ] Test: click result, modal opens with item details - [ ] Test: adjust quantity, save succeeds, modals close - [ ] Test: search with no results shows message - [ ] Test: search with special chars/symbols works - [ ] Backend integration test: GET /items/search endpoint - [ ] All assertions pass (Vitest) ## Dependencies - Task 1 (backend) must complete before Tasks 2-3 - Task 2 (SearchModal) and Task 3 (hook) can run in parallel - Task 4 (add button) depends on Task 2 (SearchModal exists) - Task 5 (quantity modal) depends on Plan 01 completion - Task 6 (tests) depends on all other tasks ## Testing Strategy - **Unit tests:** SearchModal component, useItemSearch hook (Vitest) - **Integration tests:** Full search workflow with mocked API (Vitest) - **Backend tests:** GET /items/search endpoint with various queries (Pytest) - **E2E:** Manual verification: search for item, results appear, select item, adjust quantity ## Blockers & Workarounds - **Real-time search performance:** Debounce aggressively (300ms+) to avoid excessive API calls. Cache results to reduce load. - **Mobile keyboard:** SearchModal input should auto-focus and show keyboard on mobile. Test on actual device. - **Empty state messaging:** If query matches 0 items, show friendly message (not blank list). - **Query length validation:** Enforce min 2 chars (client + server) to prevent broad searches.