updates on the fix
This commit is contained in:
@@ -5,7 +5,9 @@ import { useState, useEffect, Fragment, useRef, useMemo } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
import { notifyMiler } from 'pages/api/doormileApi';
|
||||
import { notifyMiler, getConsignmentLogs } from 'pages/api/doormileApi';
|
||||
import { kalmanSmoothGps, polylineLengthKm } from '../dispatch/dispatchShared';
|
||||
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
MdOutlineDateRange,
|
||||
@@ -213,7 +215,12 @@ const getRowBatchId = (row) => {
|
||||
// so a row that lands in Evening Batch on the dispatch page must also land
|
||||
// in Evening Batch here — otherwise an operator running in IST/PST/etc.
|
||||
// sees different totals on the two pages for the same underlying rows.
|
||||
const d = dayjs(t);
|
||||
// parseDoormileTimestamp (not bare dayjs()) strips a false trailing Z some
|
||||
// Doormile timestamps carry (see utils/doormileTimestamp.js) — without
|
||||
// this, a booking assigned "just now" could get shifted +5:30 (IST) past
|
||||
// every batch window into a gap, silently vanishing from the table even
|
||||
// though it was correctly assigned server-side.
|
||||
const d = parseDoormileTimestamp(t);
|
||||
if (!d.isValid()) return null;
|
||||
const h = d.hour() + d.minute() / 60;
|
||||
for (const b of BATCH_OPTIONS) {
|
||||
@@ -280,6 +287,58 @@ const Deliveries = () => {
|
||||
const [deliveryamount, setDeliveryamount] = useState();
|
||||
const [notes, setNotes] = useState('');
|
||||
const [currentorder, setCurrentorder] = useState({});
|
||||
|
||||
// Real Actual KMs for the Update Status dialog, computed from real GPS
|
||||
// ping history instead of leaving the field permanently blank (there is
|
||||
// no backend-stored actual-distance field — see fetchDeliveries in
|
||||
// api.js). Same sort -> Kalman-smooth -> polyline-length pipeline
|
||||
// Dispatch.js's Compare feature already uses to show a real "actual km"
|
||||
// figure, just reused here (see dispatchShared.js). Keyed off
|
||||
// consignmentid, NOT deliveryid/bookingid — GET /admin/consignments/:id/logs
|
||||
// is consignment-scoped (the same id-class mix-up already fixed in
|
||||
// updateDeliveryAPI this session).
|
||||
const { data: computedActualKm } = useQuery({
|
||||
queryKey: ['deliveryActualKm', currentorder?.consignmentid],
|
||||
queryFn: async () => {
|
||||
let rows = [];
|
||||
try {
|
||||
rows = (await getConsignmentLogs(currentorder.consignmentid)) || [];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const sorted = rows
|
||||
.map((r) => {
|
||||
const ts = r?.logdate ? dayjs(r.logdate) : null;
|
||||
return {
|
||||
lat: parseFloat(r?.latitude ?? r?.lat),
|
||||
lng: parseFloat(r?.longitude ?? r?.lng ?? r?.lon),
|
||||
logdate: r?.logdate,
|
||||
_ts: ts && ts.isValid() ? ts.valueOf() : Number.MAX_SAFE_INTEGER
|
||||
};
|
||||
})
|
||||
.filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lng))
|
||||
.sort((a, b) => a._ts - b._ts);
|
||||
// Fewer than 2 usable pings (order not yet delivered, or no telemetry
|
||||
// recorded) — a real, expected case, not an error. Leave the field
|
||||
// to its existing blank/manual-entry behavior.
|
||||
if (sorted.length < 2) return null;
|
||||
const smoothed = kalmanSmoothGps(sorted);
|
||||
const km = polylineLengthKm(smoothed.map((p) => [p.lat, p.lng]));
|
||||
return km > 0 ? km : null;
|
||||
},
|
||||
enabled: dialogopen && !!currentorder?.consignmentid,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
// Only pre-fill while the field is still empty — never overwrite an
|
||||
// operator's own edit (or an earlier pre-fill) if this query happens to
|
||||
// re-run while the dialog is already open.
|
||||
useEffect(() => {
|
||||
if (computedActualKm != null && !cumulativekms) {
|
||||
setCumulativeKms(Number(computedActualKm.toFixed(2)));
|
||||
}
|
||||
}, [computedActualKm]);
|
||||
const [deliverylat, setDeliverylat] = useState('');
|
||||
const [deliverylong, setDeliverylong] = useState('');
|
||||
const [currentStatus, setCurrentStatus] = useState('pending');
|
||||
@@ -311,7 +370,6 @@ const Deliveries = () => {
|
||||
// batch matching the current UTC hour (the operator is most likely curious
|
||||
// about "now"); never `null` since there's no longer an "All" option.
|
||||
const [selectedBatch, setSelectedBatch] = useState(detectInitialBatchId);
|
||||
const roleid = localStorage.getItem('roleid');
|
||||
|
||||
useEffect(() => {
|
||||
setTenantid(0);
|
||||
@@ -534,7 +592,21 @@ const Deliveries = () => {
|
||||
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined
|
||||
});
|
||||
|
||||
const countSourceRows = useMemo(() => (countSourceData?.pages || []).flatMap((p) => p.rows || []), [countSourceData]);
|
||||
// Newest activity first — an order just created and assigned should be the
|
||||
// very first row an operator sees, not buried wherever GET /admin/bookings
|
||||
// happens to return it (unconfirmed/unspecified server-side order). Every
|
||||
// row here has been dispatched at least once (fetchDeliveries filters to
|
||||
// that), so assigntime (booking.updatedat) is the more recent of the two
|
||||
// timestamps on a fresh assignment; orderdate (createdat) covers the rare
|
||||
// row missing it. parseDoormileTimestamp (not bare dayjs()) avoids the
|
||||
// false-Z parsing bug documented in utils/doormileTimestamp.js.
|
||||
const countSourceRows = useMemo(() => {
|
||||
const rows = (countSourceData?.pages || []).flatMap((p) => p.rows || []);
|
||||
return [...rows].sort(
|
||||
(a, b) =>
|
||||
parseDoormileTimestamp(b.assigntime || b.orderdate).valueOf() - parseDoormileTimestamp(a.assigntime || a.orderdate).valueOf()
|
||||
);
|
||||
}, [countSourceData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (countHasNext && !countIsFetchingNext) countFetchNext();
|
||||
@@ -1097,7 +1169,7 @@ const Deliveries = () => {
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
options={locationlist || []}
|
||||
getOptionLabel={(option) => `${option.locationname} (${option.suburb})` || ''}
|
||||
getOptionLabel={(option) => (option?.locationname ? `${option.locationname}${option.suburb ? ` (${option.suburb})` : ''}` : '')}
|
||||
value={locationValue}
|
||||
PaperComponent={SoftPaper}
|
||||
sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }}
|
||||
@@ -1144,7 +1216,10 @@ const Deliveries = () => {
|
||||
disabled={riderListIsLoading}
|
||||
options={ridersList}
|
||||
PaperComponent={SoftPaper}
|
||||
getOptionLabel={(option) => `${option.displayname || option.authname || ''} (${option.contactno || ''})`}
|
||||
getOptionLabel={(option) => {
|
||||
const name = option?.displayname || option?.authname || '';
|
||||
return option?.contactno ? `${name} (${option.contactno})` : name;
|
||||
}}
|
||||
sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }}
|
||||
onChange={(e, value, reason) => {
|
||||
if (reason === 'clear') setRiderid(0);
|
||||
@@ -1494,7 +1569,7 @@ const Deliveries = () => {
|
||||
<MobileFieldGrid>
|
||||
<MobileField label="Order / Location" full>
|
||||
<Typography sx={{ fontSize: 13, fontWeight: 600, color: DT.textPrimary }} noWrap>
|
||||
{`${row.locationname}-(${row.locationsuburb})`}
|
||||
{row.locationsuburb ? `${row.locationname}-(${row.locationsuburb})` : row.locationname}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
{row.orderid} · {row.deliveryid}
|
||||
@@ -1546,8 +1621,8 @@ const Deliveries = () => {
|
||||
</MobileField>
|
||||
<MobileField label="Kms · plan / act">
|
||||
<Stack direction="row" spacing={0.5} flexWrap="wrap" useFlexGap>
|
||||
<Box sx={chipSx('#ef4444')}>{row.kms || 0} km</Box>
|
||||
<Box sx={chipSx('#10b981')}>{row.cumulativekms || 0} km</Box>
|
||||
<Box sx={chipSx('#ef4444')}>{Number(row.kms || 0).toFixed(2)} km</Box>
|
||||
<Box sx={chipSx('#10b981')}>{Number(row.cumulativekms || 0).toFixed(2)} km</Box>
|
||||
</Stack>
|
||||
</MobileField>
|
||||
<MobileField label="Amount · chg / amt">
|
||||
@@ -1833,7 +1908,7 @@ const Deliveries = () => {
|
||||
<TableCell align="left">
|
||||
<Tooltip title="Location Name-Suburb" placement="top">
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
{`${row.locationname}-(${row.locationsuburb})`}
|
||||
{row.locationsuburb ? `${row.locationname}-(${row.locationsuburb})` : row.locationname}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1844,12 +1919,18 @@ const Deliveries = () => {
|
||||
{row.orderid}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
{/* express-console-api.md: Doormile timestamps are IST
|
||||
wall-clock with no timezone marker — .utc() was
|
||||
reinterpreting that naive string as local time and
|
||||
shifting it back 5:30, showing the wrong time (and
|
||||
sometimes the wrong day). Parse bare, matching the
|
||||
already-correct convention in getRowBatchId above. */}
|
||||
<Tooltip title="Ordered date" placement="top">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(row.orderdate).utc().format('DD/MM/YYYY')}
|
||||
{dayjs(row.orderdate).format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{dayjs(row.orderdate).utc().format('hh:mm A')}
|
||||
{dayjs(row.orderdate).format('hh:mm A')}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
@@ -1862,44 +1943,62 @@ const Deliveries = () => {
|
||||
</Tooltip>
|
||||
<Tooltip title="Delivery date" placement="top">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(row.deliverydate).utc().format('DD/MM/YYYY')}
|
||||
{dayjs(row.deliverydate).format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{dayjs(row.deliverydate).utc().format('hh:mm A')}
|
||||
{dayjs(row.deliverydate).format('hh:mm A')}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
{/* pickup */}
|
||||
<TableCell align="left">
|
||||
<Stack>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}>
|
||||
{/* pickup — capped to the same maxWidth as drop below, with CSS
|
||||
ellipsis truncation, so one long address can't stretch this
|
||||
column wider than its neighbour. row.pickuplocation is just
|
||||
the full raw address (see fetchDeliveries in api.js), not a
|
||||
pre-shortened name, so the old `.slice(0, 14)` fallback never
|
||||
actually ran — it was always overridden by the untruncated
|
||||
value on its left. Full text still available via the tooltip. */}
|
||||
<TableCell align="left" sx={{ maxWidth: 190 }}>
|
||||
<Stack sx={{ minWidth: 0 }}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}
|
||||
>
|
||||
{row.pickupcustomer}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
{row.pickupcontactno}
|
||||
</Typography>
|
||||
<Tooltip title={row.Pickupaddress} sx={{ whiteSpace: 'nowrap' }}>
|
||||
<Typography variant="caption" sx={{ color: DT.textMuted }}>
|
||||
{row.pickuplocation || (row.Pickupaddress ? row.Pickupaddress.slice(0, 14) + '…' : '—')}
|
||||
<Tooltip title={row.Pickupaddress || ''}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ color: DT.textMuted, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', display: 'block' }}
|
||||
>
|
||||
{row.pickuplocation || row.Pickupaddress || '—'}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
{/* drop */}
|
||||
<TableCell align="left">
|
||||
<Stack>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}>
|
||||
{/* drop — same maxWidth/truncation treatment as pickup above. */}
|
||||
<TableCell align="left" sx={{ maxWidth: 190 }}>
|
||||
<Stack sx={{ minWidth: 0 }}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}
|
||||
>
|
||||
{row.deliverycustomer}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
{row.deliverycontactno}
|
||||
</Typography>
|
||||
<Tooltip title={row.deliveryaddress}>
|
||||
<Typography variant="caption" sx={{ color: DT.textMuted, whiteSpace: 'nowrap' }}>
|
||||
{row.deliverylocation || (row.deliveryaddress ? row.deliveryaddress.slice(0, 14) + '…' : '—')}
|
||||
<Tooltip title={row.deliveryaddress || ''}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ color: DT.textMuted, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', display: 'block' }}
|
||||
>
|
||||
{row.deliverylocation || row.deliveryaddress || '—'}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
@@ -1973,7 +2072,7 @@ const Deliveries = () => {
|
||||
minWidth: 75
|
||||
}}
|
||||
>
|
||||
{row.kms || 0} km
|
||||
{Number(row.kms || 0).toFixed(2)} km
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Tooltip title="Actual KMS" placement="top">
|
||||
@@ -1994,7 +2093,7 @@ const Deliveries = () => {
|
||||
minWidth: 75
|
||||
}}
|
||||
>
|
||||
{row.cumulativekms || 0} km
|
||||
{Number(row.cumulativekms || 0).toFixed(2)} km
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
@@ -2371,27 +2470,29 @@ const Deliveries = () => {
|
||||
Change Rider
|
||||
</MenuItem>
|
||||
)}
|
||||
{(roleid == 1 || roleid == 2) && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setKms(selectedRow.kms);
|
||||
setCumulativeKms(selectedRow.cumulativekms);
|
||||
setDeliverylat(selectedRow.droplat);
|
||||
setDeliverylong(selectedRow.droplon);
|
||||
setNotes(selectedRow.notes);
|
||||
setDeliveryamount(selectedRow.deliveryamt ?? selectedRow.deliveryamount);
|
||||
setUpdateStatus(selectedRow.orderstatus || 'delivered');
|
||||
setCurrentorder(selectedRow);
|
||||
setDialogopen(true);
|
||||
handleMenuClose();
|
||||
}}
|
||||
>
|
||||
<AccentAvatar color="#10b981" size={22}>
|
||||
<MdCheckCircle size={13} />
|
||||
</AccentAvatar>
|
||||
Update Status
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* Was gated to roleid 1/2 (staff-only), matching the reference app's
|
||||
identical gate — opened to every login per explicit decision, so
|
||||
tenant operators can reach the same Update Status fields staff
|
||||
already could. */}
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setKms(selectedRow.kms);
|
||||
setCumulativeKms(selectedRow.cumulativekms);
|
||||
setDeliverylat(selectedRow.droplat);
|
||||
setDeliverylong(selectedRow.droplon);
|
||||
setNotes(selectedRow.notes);
|
||||
setDeliveryamount(selectedRow.deliveryamt ?? selectedRow.deliveryamount);
|
||||
setUpdateStatus(selectedRow.orderstatus || 'delivered');
|
||||
setCurrentorder(selectedRow);
|
||||
setDialogopen(true);
|
||||
handleMenuClose();
|
||||
}}
|
||||
>
|
||||
<AccentAvatar color="#10b981" size={22}>
|
||||
<MdCheckCircle size={13} />
|
||||
</AccentAvatar>
|
||||
Update Status
|
||||
</MenuItem>
|
||||
{selectedRow?.orderstatus !== 'cancelled' && selectedRow?.orderstatus !== 'delivered' && (
|
||||
<MenuItem
|
||||
sx={{ color: '#ef4444 !important' }}
|
||||
@@ -2539,7 +2640,10 @@ const Deliveries = () => {
|
||||
fullWidth
|
||||
options={ridersList}
|
||||
PaperComponent={SoftPaper}
|
||||
getOptionLabel={(option) => `${option.displayname || option.authname || ''} (${option.contactno || ''})`}
|
||||
getOptionLabel={(option) => {
|
||||
const name = option?.displayname || option?.authname || '';
|
||||
return option?.contactno ? `${name} (${option.contactno})` : name;
|
||||
}}
|
||||
onChange={(e, value) => {
|
||||
setSelectedRider(value);
|
||||
logger.debug('Rider selected in dropdown:', value ? `${value.firstname} ${value.lastname}` : 'None');
|
||||
@@ -2922,6 +3026,7 @@ const Deliveries = () => {
|
||||
} else {
|
||||
updateDeliveryMutation.mutate({
|
||||
deliveryid: currentorder.deliveryid,
|
||||
consignmentid: currentorder.consignmentid,
|
||||
orderheaderid: currentorder.orderheaderid,
|
||||
orderstatus: updateStatus,
|
||||
deliverytime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
|
||||
Reference in New Issue
Block a user