implemenation on the bot

This commit is contained in:
2026-08-19 17:08:45 +05:30
parent bdb21766f2
commit 37ca2352e2
52 changed files with 7074 additions and 3568 deletions

View File

@@ -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
}))}