Initial commit
This commit is contained in:
143
src/features/store-admin/orderStatus.ts
Normal file
143
src/features/store-admin/orderStatus.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Order and delivery status, taken verbatim from the old console.
|
||||
*
|
||||
* Two ladders, not one, and that is the important part. An order walks
|
||||
* created → pending → processing → confirmed/ready → delivered, while its
|
||||
* delivery job walks pending → accepted → arrived → picked → active →
|
||||
* delivered. Colouring a delivery with the order map paints "picked" and
|
||||
* "arrived" grey, which is exactly the middle of the journey where an operator
|
||||
* most needs to see progress.
|
||||
*
|
||||
* Values are the old console's `consoleUi.tsx` hex codes, unchanged, so a row
|
||||
* that was amber there is amber here.
|
||||
*/
|
||||
|
||||
/** The order lifecycle. */
|
||||
export const ORDER_STATUS: Record<string, string> = {
|
||||
created: '#0ea5e9',
|
||||
pending: '#f59e0b',
|
||||
processing: '#0ea5e9',
|
||||
modified: '#06b6d4',
|
||||
confirmed: '#10b981',
|
||||
accepted: '#6366f1',
|
||||
ready: '#6366f1',
|
||||
delivered: '#10b981',
|
||||
cancelled: '#ef4444',
|
||||
};
|
||||
|
||||
/** The delivery lifecycle. Different words, different colours. */
|
||||
export const DELIVERY_STATUS: Record<string, string> = {
|
||||
pending: '#f59e0b',
|
||||
accepted: '#6366f1',
|
||||
arrived: '#06b6d4',
|
||||
picked: '#8b5cf6',
|
||||
active: '#14b8a6',
|
||||
skipped: '#f97316',
|
||||
delivered: '#10b981',
|
||||
cancelled: '#ef4444',
|
||||
};
|
||||
|
||||
/** Unknown statuses fall to meta grey rather than to a colour that means something. */
|
||||
export const statusColor = (map: Record<string, string>, status: string | undefined): string =>
|
||||
map[(status ?? '').trim().toLowerCase()] ?? 'var(--color-ink-3)';
|
||||
|
||||
/**
|
||||
* The status tabs, as the old console groups them.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export const 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'];
|
||||
|
||||
/**
|
||||
* Does a row belong under this tab?
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export function matchesStatus(key: StatusKey, status: string | undefined): boolean {
|
||||
if (key === 'all') return true;
|
||||
const s = (status ?? '').trim().toLowerCase();
|
||||
if (!s) return false;
|
||||
switch (key) {
|
||||
case 'created':
|
||||
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 '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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The money on an order row, in the order the old console reads it.
|
||||
*
|
||||
* `ordervalue` is the full figure (goods + tax + charges − promo), `orderamount`
|
||||
* is the goods alone, and `deliveryamt` is what a delivery job carries. Which
|
||||
* one is populated depends on which endpoint produced the row, so this walks
|
||||
* them rather than picking one and showing blanks half the time.
|
||||
*/
|
||||
export function orderValue(row: {
|
||||
ordervalue?: number;
|
||||
orderamount?: number;
|
||||
deliveryamt?: number;
|
||||
}): number {
|
||||
return row.ordervalue || row.orderamount || row.deliveryamt || 0;
|
||||
}
|
||||
|
||||
/** Items on an order. `quantity` first, `itemcount` as the fallback. */
|
||||
export function orderQuantity(row: { quantity?: number; itemcount?: number }): number {
|
||||
return row.quantity || row.itemcount || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* How far behind a counter bill was when it reached us.
|
||||
*
|
||||
* `billedat` is when the sale was rung; `receivedat` is when it landed here. A
|
||||
* gap means the till was offline and caught up later, and it is worth showing
|
||||
* on the row: a day's takings that all arrived at 6pm did not happen at 6pm.
|
||||
*
|
||||
* Returns null when the gap is under a minute (normal) or when either stamp is
|
||||
* missing — a bill with no `receivedat` predates the field, and calling that
|
||||
* "0 seconds late" would be a claim the data does not support.
|
||||
*/
|
||||
export function syncLagMs(bill: { billedat?: string; receivedat?: string }): number | null {
|
||||
if (!bill.billedat || !bill.receivedat) return null;
|
||||
const billed = new Date(bill.billedat).getTime();
|
||||
const received = new Date(bill.receivedat).getTime();
|
||||
if (Number.isNaN(billed) || Number.isNaN(received)) return null;
|
||||
const lag = received - billed;
|
||||
return lag >= 60_000 ? lag : null;
|
||||
}
|
||||
Reference in New Issue
Block a user