WFDASH-WEBAPP/src/components/item-status-tabs.tsx
SJCoetz d33bb41a59 feat(orders): move delivered+cancelled to dedicated /orders/archive page
The 'Delivered' tab on /orders was broken \u2014 4,326 of 4,514 orders
(96%) have at least one delivered item. The old tab tried to
build a WHERE id IN (...) filter with 4,326 ids (~120 KB),
blowing past PB's ~4 KB filter cap and 400ing. This commit
moves historical orders to a dedicated page so:
  - The main /orders view is fast (22-28 active orders, not 4.5k
    historical)
  - The archive can be honest about its pagination ('Page 1 of
    217' is expected when looking for something from last month)
  - The badge counts on the main page stop being dominated by
    'Delivered (4,326)' / 'Cancelled (111)'

# Measured against the live PB

  /api/v1/orders/archive?page=1&perPage=2 \u2014 15.2s cold, 6.5 KB
  response. The 15s is the order_items.getFullList over 13,256
  items (the bottleneck; PB paginates over many round-trips).
  Per-row payload is the standard 5-6 KB for orders with deep
  expand. Subsequent pages within the same minute would be much
  faster since PB caches the order_items query.

# Service layer

- getOrdersArchiveData(params) in src/lib/server/orders-service.ts
  - Returns paginated list of orders with \u22651 item in
    (delivered, cancelled) status, plus per-status distinct
    counts for the header totals
  - Strategy: getFullList on order_items (ids + status only),
    dedupe + per-status counts in one pass, chunk the order ids
    by 100 (PB's URL/filter cap is between 4.5-6.7 KB) and fire
    parallel getFullList queries on orders with deep expand
  - Search hits order_number, company, customer, contact_name
  - Sort defaults to -created, override via the sort param

- /api/v1/orders/archive thin route: page, perPage, search, sort
  (no tab, no nocache \u2014 archive is one bucket, not a tab split)

# Page

- /orders/archive/page.tsx: card list with header counts
  (delivered / cancelled badges), search input, pagination
  footer. Click an order to open /orders/[id] in detail.

# Main /orders updates

- Delivered + Cancelled removed from the tab strip (passed
  tabKeys=['new','processing','collection_pool','collected'] to
  ItemStatusTabs)
- Counts on the main page now exclude delivered + cancelled
- 'Archive' button in the page header takes the operator to
  /orders/archive
- Safety useEffect: if activeTab is delivered/cancelled (e.g.
  stale URL or local state), reset to 'new'

# Tests

4 new tests in orders-service.test.ts:
  - Returns orders with \u22651 delivered/cancelled item, plus counts
  - Dedupe: one order with multiple matching items counts as 1
  - Search by order_number and contact_name
  - Empty result when no orders match

Full suite: 1321/1321 pass.

Co-authored-by: Orca <help@stably.ai>
2026-08-04 19:58:43 +02:00

57 lines
No EOL
1.5 KiB
TypeScript

"use client";
import {
TAB_GROUP_ORDER,
TAB_GROUP_VISUAL,
type TabGroup,
} from "@/lib/item-status-ui";
import { FilterTabs, type FilterTab } from "@/components/filter-tabs";
type Counts = Record<TabGroup, number>;
interface ItemStatusTabsProps {
active: TabGroup;
onChange: (next: TabGroup) => void;
counts: Counts;
/**
* Optional subset of tab keys to render. Defaults to the full
* `TAB_GROUP_ORDER`. The /orders page passes the active-workflow
* subset (`new`, `processing`, `collection_pool`, `collected`)
* so delivered + cancelled don't appear as zero-count tabs —
* they live on /orders/archive.
*/
tabKeys?: readonly TabGroup[];
}
/**
* /orders filter pill bar — thin wrapper around the generic
* `<FilterTabs>` that maps the `TabGroup` enum + its colour map into
* the `FilterTab` shape.
*
* Kept as a separate export so /orders callers don't have to know
* about FilterTabs. The sprint page will use FilterTabs directly with
* category data.
*/
export function ItemStatusTabs({
active,
onChange,
counts,
tabKeys,
}: ItemStatusTabsProps) {
const keys = tabKeys ?? TAB_GROUP_ORDER;
const tabs: FilterTab[] = keys.map((key) => ({
key,
label: TAB_GROUP_VISUAL[key].label,
color: TAB_GROUP_VISUAL[key].hex,
count: counts[key] ?? 0,
}));
return (
<FilterTabs
tabs={tabs}
active={active}
onChange={(k) => onChange(k as TabGroup)}
ariaLabel="Filter orders by item status"
/>
);
}