implemenation on the bot
This commit is contained in:
@@ -7,6 +7,7 @@ dayjs.extend(utc);
|
||||
import { notifyMiler, getConsignmentLogs } from 'pages/api/doormileApi';
|
||||
import { kalmanSmoothGps, polylineLengthKm } from '../dispatch/dispatchShared';
|
||||
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||
import { BATCHES as SHARED_BATCHES, batchRangeLabel } from 'utils/batchBucket';
|
||||
import {
|
||||
MdOutlineDateRange,
|
||||
MdDirectionsBike,
|
||||
@@ -111,6 +112,10 @@ const BRAND = DT.brand;
|
||||
// the canonical status registry (themes/dt/status.js) key to the
|
||||
// `currentStatus` key the queries use AND to the `batchCounts` key (so the
|
||||
// chip count for the tab is one lookup).
|
||||
// 'other' is NOT in this list — it is appended at render time and only when
|
||||
// rows actually land in it, so the strip doesn't carry a permanently-empty tab.
|
||||
const KNOWN_TAB_STATUSES = ['pending', 'accepted', 'arrived', 'picked', 'active', 'skipped', 'delivered', 'cancelled', 'canceled'];
|
||||
|
||||
const STATUS_TABS = [
|
||||
{ status: 'pending', countKey: 'uncoveredLength' },
|
||||
{ status: 'accepted', countKey: 'assignedLength' },
|
||||
@@ -134,19 +139,26 @@ const KPI_META = [
|
||||
// segments the day there sees the same buckets here. Hours are 24h, half-open
|
||||
// [startHour, endHour) — a delivery at exactly endHour falls into the *next*
|
||||
// batch (or none, if the gap isn't covered).
|
||||
// Windows and ranges come from utils/batchBucket.js — the same source Dispatch
|
||||
// derives its own from. They were three hand-maintained copies of numbers that
|
||||
// must agree; a row bucketed one way here and another on Dispatch is the same
|
||||
// bug twice. Only the presentation (colour, icon) is local.
|
||||
const BATCH_PRESENTATION = {
|
||||
morning: { color: '#0ea5e9', iconKey: 'morning' },
|
||||
afternoon: { color: '#f59e0b', iconKey: 'afternoon' },
|
||||
evening: { color: '#6366f1', iconKey: 'evening' }
|
||||
};
|
||||
|
||||
const BATCH_OPTIONS = [
|
||||
{ id: 'all', label: 'All Batches', range: 'Across the day', color: '#7c3aed', iconKey: 'all' },
|
||||
{ id: 'morning', label: 'Morning Batch', range: '12 AM to 8 AM', color: '#0ea5e9', iconKey: 'morning', startHour: 0, endHour: 8 },
|
||||
{
|
||||
id: 'afternoon',
|
||||
label: 'Afternoon Batch',
|
||||
range: '9 AM to 12:30 PM',
|
||||
color: '#f59e0b',
|
||||
iconKey: 'afternoon',
|
||||
startHour: 9,
|
||||
endHour: 12.5
|
||||
},
|
||||
{ id: 'evening', label: 'Evening Batch', range: '4 PM to 7 PM', color: '#6366f1', iconKey: 'evening', startHour: 16, endHour: 19 }
|
||||
...SHARED_BATCHES.map((b) => ({
|
||||
id: b.id,
|
||||
label: b.label,
|
||||
range: batchRangeLabel(b),
|
||||
startHour: b.startHour,
|
||||
endHour: b.endHour,
|
||||
...BATCH_PRESENTATION[b.id]
|
||||
}))
|
||||
];
|
||||
|
||||
// Auto-pick the batch matching the operator's LOCAL wall-clock hour so the
|
||||
@@ -571,7 +583,14 @@ const Deliveries = () => {
|
||||
isLoading: countSourceIsLoading,
|
||||
refetch: countSourceRefetch
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ['fetchdeliveries-batchcounts', appId, userid, 'all', startdate, enddate, 200, '', tenantid, locationid, riderid],
|
||||
// The trailing 'activity' is the date basis (api.js reads queryKey[11]):
|
||||
// keep a row whose LAST UPDATE falls in the window, not only one created in
|
||||
// it. Scoping a delivery board by creation date hid every order booked
|
||||
// yesterday and picked or delivered today — which is why each tab past
|
||||
// Accepted read 0 while the day's fresh orders filled Pending and Accepted.
|
||||
// The other three callers of fetchDeliveries deliberately keep the old
|
||||
// created-only basis.
|
||||
queryKey: ['fetchdeliveries-batchcounts', appId, userid, 'all', startdate, enddate, 200, '', tenantid, locationid, riderid, 'activity'],
|
||||
queryFn: fetchDeliveries,
|
||||
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined
|
||||
});
|
||||
@@ -618,7 +637,9 @@ const Deliveries = () => {
|
||||
return countSourceRows.filter((r) => {
|
||||
if (selectedBatch !== 'all' && getRowBatchId(r) !== selectedBatch) return false;
|
||||
const s = String(r.orderstatus || '').toLowerCase();
|
||||
if (wantStatus && s !== wantStatus) return false;
|
||||
if (wantStatus === 'other') {
|
||||
if (KNOWN_TAB_STATUSES.includes(s)) return false;
|
||||
} else if (wantStatus && s !== wantStatus) return false;
|
||||
if (q) {
|
||||
const hay = [
|
||||
r.deliverycustomer,
|
||||
@@ -817,8 +838,8 @@ const Deliveries = () => {
|
||||
{
|
||||
id: 'orderLocation',
|
||||
accessorFn: (row) => row.orderid,
|
||||
size: 220,
|
||||
minSize: 160,
|
||||
size: 240,
|
||||
minSize: 180,
|
||||
enableSorting: false,
|
||||
meta: { label: 'Order / Location' },
|
||||
header: () => (
|
||||
@@ -826,46 +847,43 @@ const Deliveries = () => {
|
||||
Order / Location
|
||||
</Text>
|
||||
),
|
||||
// Order and delivery are stacked, not side by side. They used to be two
|
||||
// VStacks inside an HStack, and flex items default to `min-width: auto`
|
||||
// — so the order id (`DM-BK-BC87D8B0-30828`, unbreakable and wider than
|
||||
// half the column) refused to shrink and crushed the delivery stack to
|
||||
// zero width. A zero-width cell doesn't hide its text, it wraps it one
|
||||
// character per line, which is what rendered as a vertical strip of
|
||||
// letters. Two 20-character ids never fitted a 220px column; labelling
|
||||
// them and giving each its own line is what actually fixes it, and the
|
||||
// bare "-" separator between them conveyed nothing.
|
||||
cell: ({ row }) => {
|
||||
const r = row.original;
|
||||
// express-console-api.md: Doormile timestamps are IST wall-clock with
|
||||
// no timezone marker. A Z-suffixed response reinterpreted as local
|
||||
// time shifts by 5:30 — showing the wrong time and sometimes the
|
||||
// wrong day. parseDoormileTimestamp strips that, matching
|
||||
// getRowBatchId above.
|
||||
const ordered = parseDoormileTimestamp(r.orderdate);
|
||||
const delivered = parseDoormileTimestamp(r.deliverydate);
|
||||
return (
|
||||
<VStack gap={0} padding={0}>
|
||||
<VStack gap={0} padding={0} minWidth={0}>
|
||||
<Tooltip content="Location Name-Suburb">
|
||||
<Text maxLines={1}>{r.locationsuburb ? `${r.locationname}-(${r.locationsuburb})` : r.locationname}</Text>
|
||||
</Tooltip>
|
||||
<HStack gap={3}>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Tooltip content="Order Id">
|
||||
<Text maxLines={1}>{r.orderid}</Text>
|
||||
</Tooltip>
|
||||
{/* express-console-api.md: Doormile timestamps are IST
|
||||
wall-clock with no timezone marker — bare dayjs() was
|
||||
still being used here despite this comment saying
|
||||
otherwise (the fix was never actually applied below,
|
||||
only claimed) — a Z-suffixed response reinterprets as
|
||||
local time and shifts 5:30, showing the wrong time
|
||||
(and sometimes the wrong day). parseDoormileTimestamp
|
||||
strips that, matching getRowBatchId above. */}
|
||||
<Tooltip content="Ordered date">
|
||||
<Text type="supporting">{parseDoormileTimestamp(r.orderdate).format('DD/MM/YYYY')}</Text>
|
||||
</Tooltip>
|
||||
<Text type="supporting" color="secondary">
|
||||
{parseDoormileTimestamp(r.orderdate).format('hh:mm A')}
|
||||
</Text>
|
||||
</VStack>
|
||||
-
|
||||
<VStack gap={0} padding={0}>
|
||||
<Tooltip content="Delivery Id">
|
||||
<Text maxLines={1}>{r.deliveryid}</Text>
|
||||
</Tooltip>
|
||||
<Tooltip content="Delivery date">
|
||||
<Text type="supporting">{parseDoormileTimestamp(r.deliverydate).format('DD/MM/YYYY')}</Text>
|
||||
</Tooltip>
|
||||
<Text type="supporting" color="secondary">
|
||||
{parseDoormileTimestamp(r.deliverydate).format('hh:mm A')}
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
|
||||
<Tooltip content="Order Id">
|
||||
<Text maxLines={1}>{r.orderid}</Text>
|
||||
</Tooltip>
|
||||
<Text type="supporting" color="secondary">
|
||||
Ordered {ordered.format('DD/MM/YYYY · hh:mm A')}
|
||||
</Text>
|
||||
|
||||
<Tooltip content="Delivery Id">
|
||||
<Text maxLines={1}>{r.deliveryid}</Text>
|
||||
</Tooltip>
|
||||
<Text type="supporting" color="secondary">
|
||||
Delivery {delivered.format('DD/MM/YYYY · hh:mm A')}
|
||||
</Text>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
@@ -1140,6 +1158,16 @@ const Deliveries = () => {
|
||||
|
||||
const colCount = table.getVisibleLeafColumns().length;
|
||||
|
||||
// The distinct statuses that matched no tab, named so the fix is obvious.
|
||||
const unmappedStatuses = useMemo(() => {
|
||||
const seen = new Set();
|
||||
countSourceRows.forEach((r) => {
|
||||
const s = String(r.orderstatus || '').toLowerCase();
|
||||
if (s && !KNOWN_TAB_STATUSES.includes(s)) seen.add(s);
|
||||
});
|
||||
return [...seen];
|
||||
}, [countSourceRows]);
|
||||
|
||||
// Counts per status, scoped to the selected batch. Keys mirror the legacy
|
||||
// *Length keys returned by fetchCountAPI so the JSX swap-in is mechanical
|
||||
// (countData?.uncoveredLength → batchCounts.uncoveredLength).
|
||||
@@ -1147,6 +1175,7 @@ const Deliveries = () => {
|
||||
const c = {
|
||||
uncoveredLength: 0,
|
||||
assignedLength: 0,
|
||||
otherLength: 0,
|
||||
arrivedLength: 0,
|
||||
pickedLength: 0,
|
||||
activeLength: 0,
|
||||
@@ -1184,7 +1213,12 @@ const Deliveries = () => {
|
||||
case 'canceled':
|
||||
c.cancelLength += 1;
|
||||
break;
|
||||
// Every status this page knows has a case above. Anything else — a
|
||||
// backend enum nobody has mapped yet — used to fall through here AND
|
||||
// fail every tab's equality filter, so the row was counted nowhere and
|
||||
// displayed nowhere: silent data loss, not an empty state.
|
||||
default:
|
||||
c.otherLength += 1;
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -1540,7 +1574,15 @@ const Deliveries = () => {
|
||||
const idx = STATUS_TABS.findIndex((t) => t.status === status);
|
||||
if (idx !== -1) handleChangetab(idx);
|
||||
}}
|
||||
tabs={STATUS_TABS.map((t) => ({ status: t.status, count: batchCounts[t.countKey] ?? 0 }))}
|
||||
tabs={[
|
||||
...STATUS_TABS.map((t) => ({ status: t.status, count: batchCounts[t.countKey] ?? 0 })),
|
||||
// Only ever rendered when a status arrived that this page has
|
||||
// no tab for. Its presence is the signal that the mapping in
|
||||
// api.js needs extending — the rows are reachable meanwhile.
|
||||
...(batchCounts.otherLength > 0
|
||||
? [{ status: 'other', label: `Other (${unmappedStatuses.join(', ')})`, count: batchCounts.otherLength }]
|
||||
: [])
|
||||
]}
|
||||
size="sm"
|
||||
hasDivider={false}
|
||||
/>
|
||||
@@ -1781,7 +1823,13 @@ const Deliveries = () => {
|
||||
key={header.id}
|
||||
scope="col"
|
||||
aria-sort={sorted === 'asc' ? 'ascending' : sorted === 'desc' ? 'descending' : canSort ? 'none' : undefined}
|
||||
style={{ width: header.getSize(), minWidth: header.column.columnDef.minSize, maxWidth: header.column.columnDef.maxSize || header.getSize(), textAlign: align, position: 'relative' }}
|
||||
style={{
|
||||
width: header.getSize(),
|
||||
minWidth: header.column.columnDef.minSize,
|
||||
maxWidth: header.column.columnDef.maxSize || header.getSize(),
|
||||
textAlign: align,
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<HStack
|
||||
gap={1}
|
||||
@@ -2113,7 +2161,13 @@ const Deliveries = () => {
|
||||
/>
|
||||
<Selector
|
||||
label="Status"
|
||||
options={['pending', 'accepted', 'started', 'arrived', 'delivered', 'cancelled'].map((s) => ({
|
||||
// 'picked' has to be here even though nothing sets it from
|
||||
// this dialog: the dialog opens with the row's CURRENT status
|
||||
// selected, and Converted_To_Consignment now classifies as
|
||||
// picked — an option list without it would open blank on
|
||||
// exactly the rows this dialog is reachable from (it needs a
|
||||
// consignmentid, which is what being picked means).
|
||||
options={['pending', 'accepted', 'started', 'arrived', 'picked', 'delivered', 'cancelled'].map((s) => ({
|
||||
value: s,
|
||||
label: getStatusMeta(s).label
|
||||
}))}
|
||||
|
||||
Reference in New Issue
Block a user