updats on the dispatch page

This commit is contained in:
2026-09-10 17:57:00 +05:30
parent 79a8f2b234
commit 8b2c4daef8
34 changed files with 6929 additions and 1059 deletions

View File

@@ -75,22 +75,55 @@ export const statusColor = (map: Record<string, string>, status: string | undefi
map[(status ?? '').trim().toLowerCase()] ?? 'var(--color-ink-3)';
/**
* The status tabs, as the old console groups them.
* The status tabs — one set per kind of row, because they are different ladders.
*
* Tabs rather than a dropdown: there are six, an operator switches between them
* constantly during a shift, and a dropdown hides the counts. Each carries how
* many rows it holds, which is half the reason to look.
* ── Why not one shared set ──────────────────────────────────────────────────
*
* An order and a delivery move through different lifecycles, and a single strip
* shown over both leaves half its tabs permanently at zero. Measured across
* every tenant on 2026-09-10:
*
* orders delivered 4240 · pending 876 · cancelled 827 · created 788
* deliveries delivered 1528 · pending 209 · cancelled 146 · picked 42 ·
* rejected 30 · active 22 · skipped 11 · accepted 6 · arrived 3
*
* So an order is never "arrived" or "picked", and a delivery is never
* "created". The previous six-tab set was shown over both and had the worse
* problem: "processing" swallowed accepted, picked, active and arrived into one
* bucket, so a dispatcher could not tell a rider who had reached the shop from
* one already riding, and "cancelled" quietly included skipped.
*
* ── Every status has exactly one tab ────────────────────────────────────────
*
* `rejected` is on the delivery strip even though it is easy to forget: 30 live
* rows carry it, and a status with no tab appears under "All" and under nothing
* else, so an operator working through the tabs never sees those rows at all.
* That is the failure this list is checked against.
*/
export const STATUS_TABS = [
export const ORDER_STATUS_TABS = [
{ key: 'all', label: 'All' },
{ key: 'created', label: 'Created' },
{ key: 'pending', label: 'Pending' },
{ key: 'processing', label: 'Processing' },
{ key: 'delivered', label: 'Delivered' },
{ key: 'cancelled', label: 'Cancelled' },
] as const;
export type StatusKey = (typeof STATUS_TABS)[number]['key'];
export const DELIVERY_STATUS_TABS = [
{ key: 'all', label: 'All' },
{ key: 'pending', label: 'Pending' },
{ key: 'accepted', label: 'Accepted' },
{ key: 'arrived', label: 'Arrived' },
{ key: 'picked', label: 'Picked' },
{ key: 'active', label: 'Active' },
{ key: 'skipped', label: 'Skipped' },
{ key: 'rejected', label: 'Rejected' },
{ key: 'delivered', label: 'Delivered' },
{ key: 'cancelled', label: 'Cancelled' },
] as const;
export type StatusKey =
| (typeof ORDER_STATUS_TABS)[number]['key']
| (typeof DELIVERY_STATUS_TABS)[number]['key'];
/**
* Does a row belong under this tab?
@@ -98,8 +131,10 @@ export type StatusKey = (typeof STATUS_TABS)[number]['key'];
* Substring matching, lowercased. Fiesta stores status as free text and the
* casing is inconsistent between writers, so an exact match is how a
* "Delivered" row silently stops counting the day someone writes "delivered".
* "processing" also catches confirmed, preparing and ready — everything between
* accepted and out the door, which is what an operator means by the word.
*
* Each status now lands in exactly ONE tab. The order of these cases matters
* where words overlap: `undelivered` must not count as delivered, and
* `out for delivery` is a delivery in progress rather than a completed one.
*/
export function matchesStatus(key: StatusKey, status: string | undefined): boolean {
if (key === 'all') return true;
@@ -110,30 +145,32 @@ export function matchesStatus(key: StatusKey, status: string | undefined): boole
return s.includes('created') || s.includes('new');
case 'pending':
return s.includes('pending');
case 'processing':
return (
s.includes('process') ||
s.includes('confirm') ||
s.includes('prepar') ||
s.includes('ready') ||
s.includes('accept') ||
s.includes('picked') ||
s.includes('active') ||
s.includes('arrived') ||
s.includes('out for')
);
case 'accepted':
// Not "accept" alone — that would also catch "unaccepted" if it appears.
return s.includes('accepted');
case 'arrived':
return s.includes('arrived');
case 'picked':
return s.includes('picked') || s.includes('pickup');
case 'active':
// Everything between collecting and dropping: the platform writes
// `active`, and `out for delivery` means the same thing.
return s.includes('active') || s.includes('out for') || s.includes('transit');
case 'skipped':
// Its own status and its own tab. The rider reached the address and
// nobody was in — which is not a cancellation, and used to be filed as
// one.
return s.includes('skip');
case 'rejected':
return s.includes('reject') || s.includes('declin');
case 'delivered':
return s.includes('deliver') && !s.includes('undeliver') && !s.includes('out for');
case 'cancelled':
// `skipped` lives here too. It is its own delivery status — the rider
// reached the address and nobody was in — and it keeps its own orange
// chip, but it has no tab of its own in this six-tab strip. Without this
// line a skipped job appears under "All" and under nothing else, so an
// operator working through the tabs never sees it.
return s.includes('cancel') || s.includes('skip');
return s.includes('cancel');
}
}
/**
* The money on an order row, in the order the old console reads it.
*