Assistant — assign:
- Order step is a dropdown, not a typed booking number. Exact bookingid /
bookingno matches now win across the whole list before any substring
fallback; row order previously decided the winner, so a short numeric id
could substring-match a different booking than the one on screen.
- Rider step resolves by id or name, so answering by typing no longer
leaves __rider undefined and throws inside executeAssign.
- notified now reflects whether the push actually went out. It was
Boolean(rider.milerprofileid) — "has an id, assume they were told" — so a
notify returning 400 still reported "The rider has been notified."
- Batch assign removed. /hub/bookings/batch-assign is a hub-console
endpoint; an admin JWT is refused (403 on an empty body, so the gate is
on identity). It read as the fast path and could never run.
Assistant — repeat runs:
- Bulk create response is read at data.results, the shape the live endpoint
returns. Only the two flatter shapes were checked, so every bookingid was
discarded while the count came out right by accident.
- Repeat carries each order's previous rider and re-assigns on create, one
admin call per order, one push per rider. Confirm cards say so.
- Message ids are clock-seeded and healed on load; the counter restarted at
1 each session and overwrote itself, giving duplicate React keys.
- Successful writes invalidate the query cache, so the page behind the panel
stops serving rows cached before the write.
Date range picker:
- Fields seed from the range already in force instead of opening blank on a
filtered table.
- Start field takes max={end}; only the end had a bound, so an inverted
range could be emitted and filtered to nothing.
UI:
- Deliveries filters moved outside the table card.
- Side nav keeps one hairline; the border and box-shadow stacked into a
heavy edge.
- KPI gloss lines removed across orders, riders, tenants, customers, hubs,
vehicles, exceptions, app users, orders summary/details, riders summary
and profitability.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
113 lines
5.9 KiB
JavaScript
113 lines
5.9 KiB
JavaScript
// ============================================================================
|
|
// Canonical lifecycle-status registry.
|
|
//
|
|
// Five pages (deliveries, orders, ordersDetails, Tenants, and the dispatch
|
|
// panels) each carried their own private `STATUS_META` map. They agreed on the
|
|
// hexes but disagreed on everything else — orders.js keys off the raw
|
|
// `GET /admin/bookings` enum (`pending_pickup`, `miler_assigned`, …) while
|
|
// deliveries.js keys off api.js's generic `pending`/`accepted`/`delivered`
|
|
// mapping, so the same order rendered under two different labels depending on
|
|
// which page you were looking at.
|
|
//
|
|
// This module is the one place that knows what a status LOOKS like. It does
|
|
// not know what a status MEANS to a given page — the pending/accepted bucket
|
|
// rules stay in each page's query layer, because those genuinely differ (see
|
|
// the long comment above orders.js's STATUS_TABS).
|
|
//
|
|
// Each entry carries four renderings of the same state so a page never has to
|
|
// pick a colour by hand:
|
|
// color — hex, for the surfaces that take a raw accent (StatCard,
|
|
// AccentAvatar, chart series). Matches CLAUDE.md's status palette.
|
|
// badge — Astryx <Badge variant>. Non-semantic tinted variants on purpose:
|
|
// per Astryx's Badge guidance the solid semantic variants
|
|
// (success/warning/error) are for states that demand attention, and
|
|
// a table where every row shouts is a table where nothing does.
|
|
// `cancelled` is the one state that keeps a loud variant.
|
|
// dot — Astryx <StatusDot variant>, which only has five values, so
|
|
// several lifecycle states collapse onto `accent` here.
|
|
// icon — react-icons component (not an element) so callers size it.
|
|
// hint — a two-or-three word definition of the state, for surfaces that
|
|
// show the name and its meaning together (SegmentCard's `range`).
|
|
// Deliberately terse: it sits beside the label, not under it.
|
|
// ============================================================================
|
|
import {
|
|
MdHourglassEmpty,
|
|
MdPersonPin,
|
|
MdLocationOn,
|
|
MdInventory2,
|
|
MdRoute,
|
|
MdSkipNext,
|
|
MdCheckCircle,
|
|
MdCancel,
|
|
MdList,
|
|
MdHelpOutline
|
|
} from 'react-icons/md';
|
|
|
|
export const STATUS_META = {
|
|
all: { label: 'All', hint: 'every status', color: '#000000', badge: 'neutral', dot: 'neutral', icon: MdList },
|
|
pending: { label: 'Pending', hint: 'awaiting rider', color: '#f59e0b', badge: 'yellow', dot: 'warning', icon: MdHourglassEmpty },
|
|
accepted: { label: 'Accepted', hint: 'rider assigned', color: '#6366f1', badge: 'blue', dot: 'accent', icon: MdPersonPin },
|
|
arrived: { label: 'Arrived', hint: 'at pickup', color: '#06b6d4', badge: 'cyan', dot: 'accent', icon: MdLocationOn },
|
|
picked: { label: 'Picked', hint: 'parcel collected', color: '#8b5cf6', badge: 'purple', dot: 'accent', icon: MdInventory2 },
|
|
active: { label: 'Active', hint: 'in transit', color: '#14b8a6', badge: 'teal', dot: 'success', icon: MdRoute },
|
|
skipped: { label: 'Skipped', hint: 'attempt failed', color: '#f97316', badge: 'orange', dot: 'warning', icon: MdSkipNext },
|
|
delivered: { label: 'Delivered', hint: 'completed', color: '#10b981', badge: 'green', dot: 'success', icon: MdCheckCircle },
|
|
cancelled: { label: 'Cancelled', hint: 'not fulfilled', color: '#ef4444', badge: 'error', dot: 'error', icon: MdCancel },
|
|
inactive: { label: 'Inactive', hint: 'not in service', color: '#ef4444', badge: 'red', dot: 'error', icon: MdCancel }
|
|
};
|
|
|
|
// Raw backend enums that render as one of the states above. Kept separate from
|
|
// STATUS_META so the canonical list stays readable and so a page can still ask
|
|
// "is this a known alias?" rather than silently falling back.
|
|
//
|
|
// The booking enums come from `GET /admin/bookings` (confirmed live — see
|
|
// orders.js). `converted_to_consignment` stays `accepted` HERE and only here:
|
|
// this registry renders the badge on the Orders page, whose operator workflow
|
|
// treats everything before hand-off as assigned.
|
|
//
|
|
// The DELIVERIES page classifies the same status as `picked`, in
|
|
// `mapBookingStatusToDeliveryStatus` (api.js) — deliberately, and the two are
|
|
// not in conflict. Deliveries tracks the rider's engagement, and
|
|
// doormile-flow.md §5 is explicit that `pickup-complete` is what converts a
|
|
// booking into a consignment, so on that page the parcel is in the rider's
|
|
// hands. Orders tracks the operator's action, where it is still just assigned.
|
|
// Same booking, two questions, two answers. Don't "fix" one to match the other
|
|
// without re-reading root CLAUDE.md on why the two taxonomies exist.
|
|
export const STATUS_ALIASES = {
|
|
pending_pickup: 'pending',
|
|
pending_assignment: 'pending',
|
|
miler_assigned: 'accepted',
|
|
pickup_scheduled: 'accepted',
|
|
converted_to_consignment: 'accepted',
|
|
// `Out_for_Delivery` is a confirmed live booking status (named in
|
|
// express-console-api.md's CityGate note, and mapped to 'active' by
|
|
// api.js's BOOKING_STATUS_TO_DELIVERY_STATUS). Without this entry it fell
|
|
// through to the neutral "unknown" badge showing the raw enum string.
|
|
out_for_delivery: 'active',
|
|
in_transit: 'active',
|
|
intransit: 'active',
|
|
completed: 'delivered',
|
|
cancel: 'cancelled',
|
|
canceled: 'cancelled'
|
|
};
|
|
|
|
// Fallback for a status the backend invents that nobody has mapped yet. Renders
|
|
// as a neutral badge with the raw string as its label, so an unknown state is
|
|
// visible and debuggable rather than blank.
|
|
const unknownStatus = (status) => ({
|
|
label: String(status || 'Unknown'),
|
|
color: '#94a3b8',
|
|
badge: 'neutral',
|
|
dot: 'neutral',
|
|
icon: MdHelpOutline
|
|
});
|
|
|
|
// Resolve any status string — canonical key, backend alias, or arbitrary
|
|
// casing — to its visual meta. Always returns an object; never throws.
|
|
export function getStatusMeta(status) {
|
|
if (!status) return unknownStatus(status);
|
|
const raw = String(status).trim();
|
|
const key = raw.toLowerCase();
|
|
return STATUS_META[key] || STATUS_META[STATUS_ALIASES[raw]] || STATUS_META[STATUS_ALIASES[key]] || unknownStatus(raw);
|
|
}
|