updates on the deliveries page and status updates as well

This commit is contained in:
2026-08-27 10:34:48 +05:30
parent cf4f4ecd9c
commit 4bcf604539
7 changed files with 256 additions and 158 deletions

View File

@@ -33,6 +33,7 @@ import { currency, exportRows, km as formatKm, matchesQuery, orDash, useDebounce
/* `other` is not declared here — it is appended only when rows actually land
in it, so the strip never carries a permanently empty tab. */
const STATUS_TABS = [
{ value: 'all', label: 'All', countKey: 'total' },
{ value: 'pending', label: 'Pending', countKey: 'uncoveredLength' },
{ value: 'accepted', label: 'Accepted', countKey: 'assignedLength' },
{ value: 'arrived', label: 'Arrived', countKey: 'arrivedLength' },
@@ -60,26 +61,8 @@ const SETTABLE_STATUSES = [
];
export default function Deliveries() {
// Unscoped by default. `fetchDeliveries` treats a blank from/to as "no
// bound" (see queries.js — `if (!startdate && !enddate) return true`), and
// it filters on `orderdate` (the booking's creation day), never on when a
// row was last touched. Defaulting this range to today meant the Pending/
// Accepted/Arrived/Picked/Active tabs — an outstanding-work QUEUE, not a
// day's log — only showed a booking that was also *created* today.
// Reassign an order that came in yesterday and it's still correctly
// "Pending" work, but it dropped off the board the moment you did it,
// reading as "I just assigned it and it's not there." Same "state vs
// flow" distinction this app already draws elsewhere for orders. Delivered/
// Cancelled stay meaningful to scope by date — the operator narrows via
// the date inputs same as before.
const [range, setRange] = useState({ from: '', to: '' });
const [tab, setTab] = useState('pending');
// 'all' rather than the wave matching the current clock hour: a batch is a
// slice of ONE day, so "jump to the slot matching right now" only means
// something once the date range above is scoped to a single day. With the
// range unscoped by default, defaulting the batch too would silently hide
// almost everything — a booking created three days ago at 11am has no
// relationship to "the slot matching right now" on a different day.
const [tab, setTab] = useState('all');
const [batch, setBatch] = useState('all');
const [search, setSearch] = useState('');
const debouncedSearch = useDebouncedValue(search);
@@ -144,7 +127,10 @@ export default function Deliveries() {
const base = STATUS_TABS.map((entry) => ({
value: entry.value,
label: entry.label,
count: batchRows.filter((row) => String(row.orderstatus || '').toLowerCase() === entry.value).length,
count:
entry.value === 'all'
? batchRows.length
: batchRows.filter((row) => String(row.orderstatus || '').toLowerCase() === entry.value).length,
}));
/* A row whose status matches no tab used to be counted nowhere and shown
nowhere. It gets its own tab, but only when one actually exists. */
@@ -154,7 +140,9 @@ export default function Deliveries() {
const rows = useMemo(() => {
const scoped =
tab === 'other'
tab === 'all'
? batchRows
: tab === 'other'
? unmapped
: batchRows.filter((row) => {
const status = String(row.orderstatus || '').toLowerCase();
@@ -164,7 +152,17 @@ export default function Deliveries() {
return scoped.filter((row) =>
matchesQuery(
row,
['orderid', 'deliverycustomer', 'ridername', 'pickupaddress', 'deliveryaddress', 'tenantname'],
[
'orderid',
'bookingno',
'bookingid',
'consignmentid',
'deliverycustomer',
'ridername',
'pickupaddress',
'deliveryaddress',
'tenantname',
],
debouncedSearch
)
);
@@ -252,7 +250,7 @@ export default function Deliveries() {
sortable: true,
cell: (row) => (
<div className="flex flex-col items-start gap-0.5">
<StatusBadge status={row.orderstatus} dot size="sm" />
<StatusBadge status={row.consignmentstatus || row.orderstatus} dot size="sm" />
{/* Where the value came from. Without this, a row reading "Active"
while the booking still says Converted_To_Consignment looks
exactly like a bug. */}
@@ -643,7 +641,7 @@ function OrderDetailDrawer({ row, onClose }) {
<Surface variant="subtle" padding="sm" radius="lg">
<dl className="grid grid-cols-2 gap-3">
{[
['Status', row?.orderstatus ? <StatusBadge status={row.orderstatus} size="sm" /> : '—'],
['Status', row ? <StatusBadge status={row.consignmentstatus || row.orderstatus} size="sm" /> : '—'],
['Rider', orDash(row?.ridername)],
['Customer', orDash(row?.deliverycustomer)],
['Contact', orDash(row?.deliverycontactno)],

View File

@@ -54,13 +54,15 @@ An opt-in "activity" date basis was added so an order booked yesterday and still
The filter field and the bucket field have to be the same field. If carried-over work needs to be visible on the board, it needs its own bucket ("Carried over") — not a time-of-day wave it does not belong to. Decide that before reaching for the date filter again.
### Picked is empty for hyperlocal traffic, and that is correct
### Picked vs Out for Delivery & Converted_To_Consignment
`doormile-flow.md` §5: at `pickup-complete` the booking becomes a consignment, and **matching 3-digit pickup/delivery pincode prefixes go straight to `Out_for_Delivery`** (hyperlocal); everything else routes via a hub.
`doormile-flow.md` §5: at `pickup-complete` the booking becomes a consignment, and **matching 3-digit pickup/delivery pincode prefixes go straight to `Out_for_Delivery`** (hyperlocal) when the pickup collection flag is off; everything else sits at `Collected_By_Miler` (Picked) or routes via a hub (`Inwarded_At_Hub`).
So a Coimbatore-to-Coimbatore order (641xxx → 641xxx) is picked up and is *immediately* out for delivery. It never rests in a "picked" state. The Picked tab reading 0 while orders are plainly moving is expected behaviour on hyperlocal traffic, not a missing mapping.
The confusing part is downstream of that: the booking's own `status` **freezes at `Converted_To_Consignment`** the moment it is picked up. The Deliveries page joins `GET /admin/consignments` and lets the consignment's status win, so a row correctly reads **Active** while `GET /admin/bookings` still says `Converted_To_Consignment`. That looks like a bug on inspection, which is why the status badge carries a tooltip naming the consignment and its status whenever the value came from there (`statusfromconsignment` on the row).
The booking's own `status` **freezes at `Converted_To_Consignment`** the moment pickup is complete. `Converted_To_Consignment` is a handoff marker, not a display state, and is never displayed literally to the user. The Deliveries page joins `GET /admin/consignments` (and reads `consignmentstatus` attached to the booking row) to derive the live state:
- `Collected_By_Miler` → **Picked** (under Picked tab)
- `Out_for_Delivery` → **Active / Out for Delivery** (under Active tab)
- `Inwarded_At_Hub` / `At_Hub` / `Outwarded_From_Hub` → **Active** (with dedicated Inwarded/Hub badge)
- `Delivered` → **Delivered** (under Delivered tab)
### ⛔ Never bucket on `assigntime`
It is **not** an assignment time. The Doormile bookings feed has no assignment timestamp, so `api.js` maps `assigntime` to the booking's `updatedat` — its last-modified column. Any status change, parcel scan, payment or pickup-complete re-stamps it.

View File

@@ -275,11 +275,18 @@ export default function Orders() {
header: 'Status',
sortable: true,
cell: (row) => {
const rawStatus = String(row.status || '').trim().toLowerCase();
const isArrived =
String(row.status || '').toLowerCase() === 'pickup_scheduled' &&
rawStatus === 'pickup_scheduled' &&
Boolean(row.reachedat ?? row.reached_at ?? row.reachedAt ?? row.reachedtime ?? row.reached_time);
const displayStatus = isArrived ? 'arrived' : row.status;
return <StatusBadge status={displayStatus} dot size="sm" />;
if (isArrived) {
return <StatusBadge status="arrived" dot size="sm" />;
}
if (rawStatus === 'converted_to_consignment') {
const cStatus = row.consignmentstatus ?? row.consignment_status;
return <StatusBadge status={cStatus || 'picked'} dot size="sm" />;
}
return <StatusBadge status={row.status} dot size="sm" />;
},
},
],