# Manager orders view (`/manager`) A hidden, installable PWA for the managers: one link, opened on a phone, showing where every order is — **New / Processing / Completed**. It is deliberately not in the sidebar. The route lives in the `(mobile)` route group, outside `(app)`, so `` never renders on it and there is no nav entry anywhere. ## Where things live | Piece | File | |---|---| | Page (route group) | `src/app/(mobile)/manager/` — `layout.tsx`, `auth-gate.tsx`, `page.tsx`, `manager-orders-view.tsx` | | Read model | `src/lib/server/manager-orders-service.ts` | | API route | `src/app/api/v1/manager-orders/route.ts` | | PWA manifest | `public/manifest-manager.json` | | Service worker | `public/sw-manager.js` | | SW registration | `src/components/service-worker-registration.tsx` (adds `/manager` → `/sw-manager.js`) | | Bucket + settled-status contract | `src/lib/item-status-ui.ts` | | Shared window filter | `settledWindowFilter` in `src/lib/server/orders-service.ts` | ## The three buckets `PIPELINE_GROUP_ITEM_STATUSES` in `src/lib/item-status-ui.ts` is a coarse view of the desk's eight workflow tabs. Both surfaces read the same status lists, so they can never disagree about what a status means. | Bucket | Statuses | Orders (live, 2026-10-26) | |---|---|---| | **New** | `pending`, `approval_needed`, `approval_rejected` | 8 | | **Processing** | `processed`, `po_generated`, `collection_pool`, `assigned`, `ready_delivery`, `collected`, `stock_verified`, `picking`, `ready_invoice`, `stock_booked`, `with_driver` | 79 | | **Completed** | the settled set, windowed to 7 / 14 / 30 days | 65 / 112 / 200 | An order lands in a bucket when **at least one of its lines** is in that bucket. That is why Processing holds 79 orders for only 206 open lines: 45 of those orders have only ONE line left, and 40 have other lines already invoiced or collected. `cancelled` and `removed` are deliberately outside the pipeline (they live on /orders/archive). ## The Completed window — read before changing it There is no single "finished at" column. `SETTLED_STATUSES` + `SETTLED_DATE_FIELDS` in `src/lib/item-status-ui.ts` encode which field dates each settled status: | Status | Dated on | Why | |---|---|---| | `delivered` | `delivered_at` | populated on only 2 of 12,957 lines; the rest are legacy rows whose `updated` is the MIGRATION timestamp, so an un-dated delivered row is ARCHIVED, not recent | | `invoiced` | `invoiced_date` | the only properly stamped settlement date in the schema (2,199 lines) | | `completed`, `checked_in`, `delivery_note` | `updated` | no dedicated column exists, so `updated` is the only signal | `primary` and `fallback` are mutually exclusive. Giving a status both would resurface archived rows every time an old row was touched. Before this change the Completed tab matched **0** orders: the filter only looked at `delivered` + `completed` dated on `delivered_at` / `updated`, and the business stopped writing `delivered` on 2026-08-14 (the live pipeline now ends at `invoiced` / `checked_in`). It now matches 341 orders over 30 days, 112 over 7. `settledWindowFilter(days)` is shared with `/orders` (30 days) — only the window length differs, so the two surfaces never disagree. ## The preview popup is reused, not rebuilt The service emits `UniversalHit`s — the same shape the universal search returns — and the page hands them to the existing `SearchPreviewDialog` (`src/app/(app)/search/search-preview.tsx`). So the manager gets the desk's quick-overview verbatim: status chips, stats, invoice refs, delivery-note numbers, the line list and the lifecycle timeline, plus a link out to the full order page. **Read-only.** Nothing on the phone can change an order's state. ## API ``` GET /api/v1/manager-orders?group=new|processing|completed &window=5|7|14|30 &search= &page=1&perPage=20 ``` Auth is the PB browser session in the `Authorization` header — the same `assertBrowserSession` pattern as `/api/v1/boss-dashboard`. The response carries `counts` (distinct orders per bucket), `total`, `totalPages`, the page of `orders` and `viewerName`. ## Installing it on a phone Open the link on the phone → browser menu → **Add to Home Screen**. It installs as a standalone app ("WFDASH — Orders") with its own icon, launched in portrait with no browser chrome. iOS needs the `apple-mobile-web-app-capable` meta, which the layout sets (Next 16 only emits the standardised name, which iOS ignores — see the note in `(mobile)/m/layout.tsx`). The service worker precaches the shell and never caches `/api/`, so a stale order count can never be shown.