diff --git a/src/features/store-admin/WindowFilterSelect.tsx b/src/features/store-admin/WindowFilterSelect.tsx new file mode 100644 index 0000000..72b2ae5 --- /dev/null +++ b/src/features/store-admin/WindowFilterSelect.tsx @@ -0,0 +1,65 @@ +import { WINDOW_FILTERS, type WindowFilterKey } from './deliveryWindow'; + +/** + * Pick a delivery window to narrow by. + * + * One component, because this control now stands in two places — the dispatch + * board's view switcher and the sales page's status strip — and both sit in a + * row of tab-shaped buttons. Styled twice, the two would drift the first time + * either row was touched, and a filter that looks subtly different on two + * screens reads as two different filters. + * + * ── Shaped like a tab, deliberately not pretending to be one ──────────────── + * + * The measurements below are `.tabbar-tab`'s own — 32px tall, 13px/500 type, + * the radius derived from the same token — so it belongs in the strip rather + * than looking like a form field dropped beside it. + * + * It keeps what a tab does not: a visible border, because it stands outside the + * sunken track the tabs sit on and would otherwise float, and a chevron, + * because it opens rather than toggles. Making it indistinguishable from a tab + * would promise a click that does something it does not do. + */ +export function WindowFilterSelect({ + value, + onChange, + /** `auto` pushes it to the end of a flex row. The sales strip wants it inline. */ + align = 'inline', +}: { + value: WindowFilterKey; + onChange: (next: WindowFilterKey) => void; + align?: 'inline' | 'end'; +}) { + return ( + + ); +} diff --git a/src/features/store-admin/pages/DispatchPage.tsx b/src/features/store-admin/pages/DispatchPage.tsx index a044083..ebd091b 100644 --- a/src/features/store-admin/pages/DispatchPage.tsx +++ b/src/features/store-admin/pages/DispatchPage.tsx @@ -22,8 +22,9 @@ import { TriangleAlert, } from 'lucide-react'; import type { DeliveryRow, OrderRow, RiderInfo, TenantLocation } from '@/api/types'; -import { WINDOW_FILTERS, matchesWindow, type WindowFilterKey } from '../deliveryWindow'; +import { matchesWindow, type WindowFilterKey } from '../deliveryWindow'; import { groupByWindow } from '../dispatchModel'; +import { WindowFilterSelect } from '../WindowFilterSelect'; import { KpiCard } from '@/components/KpiCard'; import { Tab, TabBar } from '@/components/TabBar'; import { StickyRow } from '@/components/StickyRow'; @@ -275,6 +276,17 @@ export function DispatchBoard({ scope }: { scope: DispatchScope }) { own switcher instead of going through `PageHeader`, so it asks for the behaviour directly — same component, same row, same canvas. */} + {/* A row of its own, because `.page-sticky` is a COLUMN flex — without + this the filter drops onto its own line under the tabs. */} +
changeMode('profit')} /> + + {/* Beside the board switcher, not down with the status pills. + + It narrows every board — the groups, the counters above them and the + map — so it belongs with the controls that change what the whole + screen is about, rather than looking like one more status filter + over a single list. */} + +
@@ -448,29 +469,6 @@ export function DispatchBoard({ scope }: { scope: DispatchScope }) { Unassigned - {/* Narrows the whole board, not just this list — the counters - and the map follow it, because a count that describes more - work than the screen shows is worse than no count. */} - allDeliveries.filter( (row) => matchesStatus(status, row.orderstatus) && matchesWindow(windowFilter, row), ), - [allDeliveries, status], + [allDeliveries, status, windowFilter], ); const billRows = useMemo(() => { @@ -387,12 +390,20 @@ export function SalesPage() { /* Tabs left, search right, on ONE row — see `isTabsInline`. */ isTabsInline actions={ - +
+ {/* Not on the counter tab. Imported till bills are not deliveries + and can never carry a window, so the control would be one that + does nothing on a third of this page. */} + {tab === 'counter' ? null : ( + + )} + +
} tabs={ /* THREE views, not four. @@ -477,10 +488,6 @@ export function SalesPage() { counts={tabCounts} onStatus={setStatus} /> - {/* Beside the status tabs, not inside them. Both filters apply at - once, because "evening orders still pending" is the question - somebody actually has. */} - {/* Above the table, not below it and not in a drawer. Assigning is something you do WHILE reading the day's orders — glancing between the drop addresses and who is free — so the @@ -1528,39 +1535,3 @@ function WindowCell({ row }: { row: OrderRow | DeliveryRow }) { ); } -/** - * Which window to show. - * - * "No window" earns its place: it is how an operator finds orders placed - * without choosing one, which is most of them while this rolls out, and is the - * population worth watching to see the feature being used at all. - */ -function WindowFilter({ - value, - onChange, -}: { - value: WindowFilterKey; - onChange: (next: WindowFilterKey) => void; -}) { - return ( - - ); -}