A hidden, installable PWA: one link, opened on a phone, showing where every order is. New / Processing / Completed, with a tap-to-preview popup and the invoice/line detail Gary needs. Read-only, and NOT in the sidebar — the route lives outside `(app)`, so `<Sidebar />` never renders there. The preview popup is reused, not rebuilt: the service emits `UniversalHit`s and the page hands them to the existing `SearchPreviewDialog`, so the manager gets the desk's quick-overview verbatim. Also fixes the /orders Completed tab, which was matching ZERO orders. `delivered_at` is stamped on only 2 of 12,957 delivered lines and the business stopped writing `delivered` on 2026-08-14 (the live pipeline now ends at `invoiced` / `checked_in`), so the old filter never matched. "Settled" is now defined once — `SETTLED_STATUSES` + `SETTLED_DATE_FIELDS` — and dated per status: `invoiced_date` for invoiced (the only properly stamped field, 2,199 lines), `delivered_at` for delivered, `updated` only where no dedicated column exists. `delivery_note` and `checked_in` move into `completed` as instructed, and the standalone `post_processing` tab is retired (a DN is not a stage of its own). `invoiced` stays in `collected` as well, so invoiced orders don't vanish from the workflow tabs. Completed tab: 0 -> 341 orders over 30 days, 112 over 7.
4.6 KiB
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 <Sidebar /> 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 UniversalHits — 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=<order no|customer|company|rep>
&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.