feat(5): phase 5 planning complete - 3 plans, 18 tasks covering quick adjust, search, exports

This commit is contained in:
2026-04-22 17:36:36 +03:00
parent bc890faa5d
commit ae3ca2cbee
3 changed files with 369 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
---
plan: 5-PLAN-01
feature: Quick Quantity Adjustment
status: ready
estimated_tasks: 5
total_lines: ~450
---
# Phase 5 Plan 01: Quick Quantity Adjustment
## Overview
Implement hybrid quantity adjustment UI combining persistent +/- buttons with tap-to-edit on number display. Eliminates modal friction for check-in/check-out workflows by allowing both button-based increment/decrement AND direct number input via tap-to-edit inline.
## Tasks
### Task 1: Refactor QuantityDisplay Component (UI Layer)
- **File:** frontend/components/inventory/QuantityDisplay.tsx
- **Component:** `QuantityDisplay(itemId: string, currentQuantity: number, onQuantityChange: (newQty: number) => Promise<void>) → JSX.Element`
- **Lines:** ~120
- **Description:** Create editable quantity display with tap-to-edit mode. Render number as pressable text that toggles edit mode; show inline input field with +/- buttons flanking the number.
- **Acceptance Criteria:**
- [ ] Normal state: displays quantity as tappable text
- [ ] Tap triggers edit mode: input field + +/- buttons visible
- [ ] +/- buttons increment/decrement in-field value without API call (optimistic UI)
- [ ] Blur or Enter key commits change to backend via `onQuantityChange`
- [ ] Escape key cancels edit without changes
- [ ] Input only accepts positive integers
- [ ] Accessibility: proper ARIA labels on buttons, input has focus indicators
- [ ] Unit tests pass (Vitest)
### Task 2: Create useQuantityAdjustment Hook
- **File:** frontend/hooks/useQuantityAdjustment.ts
- **Hook:** `useQuantityAdjustment(itemId: string, initialQuantity: number) → { quantity: number; isLoading: boolean; error: string | null; adjustQuantity: (delta: number | absolute: number) => Promise<void>; resetError: () => void }`
- **Lines:** ~80
- **Description:** Custom hook managing quantity state, API calls, optimistic updates, and error handling. Supports both delta (increment/decrement) and absolute (direct input) adjustments.
- **Acceptance Criteria:**
- [ ] Optimistic UI: state updates immediately; reverts on API failure
- [ ] API call to PATCH /items/{itemId} with new quantity
- [ ] Handles network errors gracefully with user-facing message
- [ ] Validates quantity >= 0
- [ ] Debounces rapid successive calls (100ms)
- [ ] Unit tests cover success, failure, validation scenarios
### Task 3: Update Inventory Page Main UI (Integration)
- **File:** frontend/app/inventory/page.tsx
- **Component:** Update inventory item list rendering section
- **Lines:** ~60
- **Description:** Integrate new QuantityDisplay component into main inventory grid/list. Replace or augment existing quantity display with hybrid tap-to-edit UI.
- **Acceptance Criteria:**
- [ ] Each item row displays new QuantityDisplay component
- [ ] Quantity changes persist immediately (no modal needed)
- [ ] Layout remains responsive on mobile/desktop
- [ ] Spinner shows during API call
- [ ] Error toast appears on failure
- [ ] No modal opens for quantity adjustment
- [ ] Integration tests confirm full workflow
### Task 4: Backend Endpoint Enhancement (PATCH /items/{itemId})
- **File:** backend/routers/items.py
- **Endpoint:** `PATCH /items/{itemId}` with body `{ quantity: int }`
- **Function:** `update_item_quantity(itemId: str, body: UpdateQuantityRequest, auth: User) → ItemResponse`
- **Lines:** ~40
- **Description:** Ensure endpoint handles direct quantity updates (no modal validation needed). Create audit log entry for quantity change.
- **Acceptance Criteria:**
- [ ] Accepts POST/PATCH with `{ quantity: int }`
- [ ] Validates quantity >= 0
- [ ] Creates AuditLog entry with old_qty → new_qty delta
- [ ] Returns updated Item with new quantity
- [ ] Authorization: users can adjust inventory they have access to
- [ ] Unit tests confirm audit logging
### Task 5: Integration & E2E Tests
- **File:** frontend/tests/inventory/quick-adjust.test.ts
- **Test:** Full workflow: tap number → edit → +/- button → commit
- **Lines:** ~150
- **Description:** End-to-end test of quantity adjustment workflow without modal. Verify UI state transitions, API calls, error handling.
- **Acceptance Criteria:**
- [ ] Test case: tap number displays edit mode UI
- [ ] Test case: +/- buttons change input value (no API yet)
- [ ] Test case: Enter key commits, calls API, updates UI
- [ ] Test case: Error from API shows toast, reverts quantity
- [ ] Test case: Escape cancels without API call
- [ ] All assertions pass (Vitest)
- [ ] Backend integration test: PATCH /items/{itemId} creates audit log
## Dependencies
- Task 1 (UI) must complete before Task 3 (integration)
- Task 2 (hook) must complete before Task 1 (UI needs the hook)
- Task 4 (backend endpoint) can run in parallel with Tasks 1-2
- Task 5 (tests) depends on Tasks 1-4
## Testing Strategy
- **Unit tests:** QuantityDisplay component (Vitest), useQuantityAdjustment hook (Vitest)
- **Integration tests:** Inventory page with QuantityDisplay (Vitest)
- **Backend tests:** PATCH endpoint audit logging (Pytest)
- **E2E:** Manual verification on mobile device (tap number, edit, +/-, commit)
## Blockers & Workarounds
- **Mobile tap detection:** Ensure click/tap handlers work consistently on iOS/Android. Use `onClick` + `onTouchEnd` for maximum compatibility.
- **Input validation:** Client-side validation prevents invalid input; backend validation is final authority.
- **Concurrent edits:** If user edits quantity twice rapidly, second edit overwrites first. Acceptable per Phase 5 scope (single-user offline scenario).

View File

@@ -0,0 +1,121 @@
---
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.

View File

@@ -0,0 +1,147 @@
---
plan: 5-PLAN-03
feature: Export/Reports (Admin Dashboard)
status: ready
estimated_tasks: 7
total_lines: ~600
---
# Phase 5 Plan 03: Export/Reports (Admin Dashboard)
## Overview
Implement inventory snapshot and audit trail exports in CSV and Excel (.xlsx) formats for admins. Manual trigger via button in Admin Dashboard. Filenames include timestamps. Support future field additions without code changes.
## Tasks
### Task 1: Backend Export Service (Core Logic)
- **File:** backend/services/export_service.py
- **Classes:** `ExportService`, `InventorySnapshotExporter`, `AuditTrailExporter`
- **Functions:**
- `InventorySnapshotExporter.to_csv(items: List[Item], timestamp: str) → str`
- `InventorySnapshotExporter.to_excel(items: List[Item], timestamp: str) → bytes`
- `AuditTrailExporter.to_csv(logs: List[AuditLog], timestamp: str) → str`
- `AuditTrailExporter.to_excel(logs: List[AuditLog], timestamp: str) → bytes`
- **Lines:** ~200
- **Description:** Export service handling CSV/Excel generation. Uses Python `csv` and `openpyxl` libraries. Returns file content ready for download. Timestamps included in both filenames and file content.
- **Acceptance Criteria:**
- [ ] InventorySnapshotExporter: exports all item fields (ID, Name, PN, Barcode, Category, Qty, Description, Notes, Box Label, Created, Modified, etc.)
- [ ] AuditTrailExporter: exports all audit fields (ID, Item ID, Item Name, Action, Old Value, New Value, User, Timestamp, etc.)
- [ ] CSV format: proper quoting/escaping, UTF-8 encoding, comma-separated
- [ ] Excel format: .xlsx with headers, proper column widths, data types preserved
- [ ] Both formats include timestamp in header/metadata
- [ ] Filename format: `inventory_snapshot_2026-04-22.csv`, `audit_trail_2026-04-22.xlsx`, etc.
- [ ] Handles empty datasets (no items → empty file with headers)
- [ ] Unit tests: CSV generation, Excel generation, timestamp formatting
### Task 2: Backend Export Endpoints (API)
- **File:** backend/routers/admin/exports.py (new router)
- **Endpoints:**
- `POST /admin/exports/inventory-snapshot?format={csv|xlsx}` → file download
- `POST /admin/exports/audit-trail?format={csv|xlsx}` → file download
- **Functions:**
- `export_inventory_snapshot(format: str, auth: AdminUser) → FileResponse`
- `export_audit_trail(format: str, auth: AdminUser) → FileResponse`
- **Lines:** ~80
- **Description:** REST endpoints for triggering exports. Return file as download response with proper MIME type and filename.
- **Acceptance Criteria:**
- [ ] Both endpoints require admin authorization (`auth.get_current_admin`)
- [ ] Query param `format` accepts "csv" or "xlsx" (case-insensitive)
- [ ] Returns file with correct MIME type (text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)
- [ ] HTTP header sets filename with timestamp
- [ ] Endpoint audits the export action (log who exported, when, format)
- [ ] Error handling: invalid format → 400 Bad Request
- [ ] Unit tests: both formats, authorization checks, error cases
### Task 3: Frontend Admin Export UI Component
- **File:** frontend/components/admin/ExportPanel.tsx
- **Component:** `ExportPanel() → JSX.Element`
- **Lines:** ~180
- **Description:** Dedicated panel in Admin Dashboard with export buttons. Two sections: Inventory Snapshot and Audit Trail. Each has CSV/Excel buttons. Loading spinners, success/error toasts.
- **Acceptance Criteria:**
- [ ] Two main sections: "Inventory Snapshot" and "Audit Trail"
- [ ] Each section has "Export as CSV" and "Export as Excel" buttons
- [ ] Button labels clearly indicate format
- [ ] Loading spinner during export (prevents double-click)
- [ ] Success toast: "Snapshot exported as CSV" with filename
- [ ] Error toast: "Export failed: {error message}"
- [ ] Buttons disabled while export in progress
- [ ] Mobile-responsive button layout
- [ ] Accessibility: ARIA labels on buttons, proper semantic HTML
### Task 4: Frontend Export Hook
- **File:** frontend/hooks/useExport.ts
- **Hook:** `useExport() → { exportSnapshot: (format: 'csv' | 'xlsx') => Promise<void>; exportAuditTrail: (format: 'csv' | 'xlsx') => Promise<void>; isLoading: boolean; error: string | null }`
- **Lines:** ~120
- **Description:** Custom hook managing export API calls, loading states, error handling, and file download triggering.
- **Acceptance Criteria:**
- [ ] Calls POST endpoints with correct format parameter
- [ ] Handles blob response and triggers browser download
- [ ] Filename extracted from HTTP response header (Content-Disposition)
- [ ] Error states propagated to caller
- [ ] Loading state managed properly (prevents concurrent calls)
- [ ] Unit tests: successful export, error handling, filename extraction
### Task 5: Integrate ExportPanel into Admin Dashboard
- **File:** frontend/app/admin/page.tsx
- **Component:** Update admin page layout
- **Lines:** ~40
- **Description:** Add ExportPanel to Admin Dashboard. Include it in the main layout alongside other admin sections (settings, user management, etc.).
- **Acceptance Criteria:**
- [ ] ExportPanel renders in Admin Dashboard
- [ ] Visually separated from other admin sections (e.g., card/section styling)
- [ ] No layout conflicts with existing admin UI
- [ ] Responsive on mobile/desktop
- [ ] Only visible to admins (via auth check in component or page)
### Task 6: Dependency Management & Configuration
- **File:** backend/requirements.txt
- **Update:** Add openpyxl library
- **Lines:** ~5
- **Description:** Ensure openpyxl (for .xlsx generation) is in requirements.txt with version constraint.
- **Acceptance Criteria:**
- [ ] openpyxl>=3.10.0 added to requirements.txt
- [ ] Python `csv` module (stdlib) is available (no extra install needed)
- [ ] All tests can import and use both libraries
### Task 7: Integration & E2E Tests
- **File:** frontend/tests/admin/exports.test.ts + backend/tests/test_exports.py
- **Tests:** Full export workflow for both formats and both report types
- **Lines:** ~250
- **Description:** End-to-end tests confirming exports work, files are generated correctly, and contain expected data.
- **Acceptance Criteria:**
- [ ] Test: export inventory snapshot as CSV, verify file content
- [ ] Test: export inventory snapshot as Excel, verify file is valid .xlsx
- [ ] Test: export audit trail as CSV, verify headers and data
- [ ] Test: export audit trail as Excel, verify structure and data types
- [ ] Test: exports include timestamp in filename
- [ ] Test: unauthorized user cannot export (403 Forbidden)
- [ ] Test: invalid format param returns 400 Bad Request
- [ ] Backend test: CSV generation logic (proper escaping, encoding)
- [ ] Backend test: Excel generation logic (valid .xlsx structure)
- [ ] Frontend test: button click triggers download
- [ ] Frontend test: loading spinner appears during export
- [ ] Frontend test: success/error toasts appear
- [ ] All assertions pass (Vitest + Pytest)
## Dependencies
- Task 1 (export service) must complete before Tasks 2-3
- Task 2 (backend endpoints) depends on Task 1
- Task 4 (hook) depends on Task 2 (endpoints exist)
- Task 3 (UI component) and Task 4 (hook) can run in parallel
- Task 5 (integration) depends on Tasks 3-4
- Task 6 (dependencies) can run in parallel with all other tasks
- Task 7 (tests) depends on Tasks 1-5
## Testing Strategy
- **Unit tests:** ExportService CSV/Excel generation (Pytest), useExport hook (Vitest)
- **Integration tests:** Full export workflow from Admin Dashboard (Vitest + mocked API)
- **Backend integration tests:** Endpoints with real database, authorization checks (Pytest)
- **File validation tests:** Verify exported CSV is valid (can parse), Excel is valid .xlsx (can open)
- **E2E:** Manual verification: click export button, file downloads, verify content
## Blockers & Workarounds
- **File download handling:** Browser file downloads work via blob response + `<a href="blob:...">` trick. Ensure Content-Disposition header is set correctly.
- **Large datasets:** If inventory grows to 10k+ items, export may be slow. Defer pagination/streaming to Phase 6+ (for now, accept latency).
- **Excel generation:** openpyxl can be memory-intensive. For Phase 5, assume datasets < 50k rows. Monitor performance in production.
- **Timestamp format:** Use consistent ISO 8601 format (YYYY-MM-DD) in filenames and file headers. Document in PROJECT_ARCHITECTURE.md.
- **Future fields:** Design exporters to dynamically include all item/audit fields (use `__dict__` or similar) so adding new fields doesn't require code changes.