updates on the changes arelated toe the order creation and delivery fixes as well
This commit is contained in:
@@ -110,7 +110,15 @@ const LocationAutocomplete = forwardRef(
|
|||||||
sx={{ ...sx }}
|
sx={{ ...sx }}
|
||||||
onChange={(event, value, reason) => {
|
onChange={(event, value, reason) => {
|
||||||
if (reason === 'clear') {
|
if (reason === 'clear') {
|
||||||
setAppId?.(0);
|
// null (not 0) — 0 is a real, meaningful selection here: the
|
||||||
|
// synthetic "All" option (fetchAppLocations in api.js appends
|
||||||
|
// { locationname: 'All', applocationid: 0 }). Callers that gate
|
||||||
|
// other pickers on "has a zone been chosen yet" (e.g.
|
||||||
|
// deliveries.js's Tenant/Rider dropdowns) need to tell "nothing
|
||||||
|
// picked" apart from "deliberately picked All" — both used to
|
||||||
|
// collapse to the same falsy 0, so choosing All incorrectly
|
||||||
|
// still tripped the "select a zone first" gate.
|
||||||
|
setAppId?.(null);
|
||||||
setLocoName?.('');
|
setLocoName?.('');
|
||||||
setPage?.(0);
|
setPage?.(0);
|
||||||
} else if (value) {
|
} else if (value) {
|
||||||
|
|||||||
@@ -559,7 +559,14 @@ export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
|
|||||||
tenantadress: tenant?.primaryemail || '',
|
tenantadress: tenant?.primaryemail || '',
|
||||||
locationname: tenant?.tenantname || '',
|
locationname: tenant?.tenantname || '',
|
||||||
locationsuburb: '',
|
locationsuburb: '',
|
||||||
pickupcustomer: '',
|
// Was hardcoded '' — Dispatch.js's kitchen markers read this as the
|
||||||
|
// pickup business name (`o.pickupcustomer || o.kitchen_key || 'Unknown'`,
|
||||||
|
// Dispatch.js:1660), and no booking on this API carries a `kitchen_key`
|
||||||
|
// field at all, so every kitchen pin fell through to the literal string
|
||||||
|
// 'Unknown' — rendered as a "U" marker whose hover/popup then showed
|
||||||
|
// "Unknown". The tenant IS the kitchen for a B2B booking (same value
|
||||||
|
// already used for tenantname/locationname above), so reuse it here.
|
||||||
|
pickupcustomer: tenant?.tenantname || '',
|
||||||
pickupcontactno: '',
|
pickupcontactno: '',
|
||||||
Pickupaddress: b.pickupaddress || '',
|
Pickupaddress: b.pickupaddress || '',
|
||||||
pickupaddress: b.pickupaddress || '',
|
pickupaddress: b.pickupaddress || '',
|
||||||
|
|||||||
@@ -432,7 +432,10 @@ const Deliveries = () => {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['tenantlist', appId],
|
queryKey: ['tenantlist', appId],
|
||||||
queryFn: () => getTenants(appId), // Ensure appId is passed
|
queryFn: () => getTenants(appId), // Ensure appId is passed
|
||||||
enabled: appId !== 0 // Ensures query runs only when appId is valid
|
// appId 0 is a real, deliberate choice — the synthetic "All" zone option
|
||||||
|
// (fetchAppLocations in api.js) — not "nothing picked yet"; only
|
||||||
|
// null/undefined means that now. See LocationAutocomplete.js's onChange.
|
||||||
|
enabled: appId != null
|
||||||
});
|
});
|
||||||
// ==============================|| gettenantlocations ||============================== //
|
// ==============================|| gettenantlocations ||============================== //
|
||||||
|
|
||||||
@@ -799,7 +802,7 @@ const Deliveries = () => {
|
|||||||
// throwing after already toasting there too, which showed this same
|
// throwing after already toasting there too, which showed this same
|
||||||
// error twice) — no onError needed here anymore.
|
// error twice) — no onError needed here anymore.
|
||||||
queryFn: fetchRidersList,
|
queryFn: fetchRidersList,
|
||||||
enabled: Boolean(appId)
|
enabled: appId != null
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ============================================= || updatedelivery | ============================================= */
|
/* ============================================= || updatedelivery | ============================================= */
|
||||||
@@ -814,10 +817,16 @@ const Deliveries = () => {
|
|||||||
setDialogopen(false);
|
setDialogopen(false);
|
||||||
fetchCountRefetch();
|
fetchCountRefetch();
|
||||||
countSourceRefetch();
|
countSourceRefetch();
|
||||||
|
} else {
|
||||||
|
// A 200 with success:false (the Doormile API's validation-failure
|
||||||
|
// shape, same pattern as customers.js's saveMutation) had no else
|
||||||
|
// branch here before — the dialog just sat open with zero feedback,
|
||||||
|
// looking exactly like the button didn't work.
|
||||||
|
opentoast(res.message || 'Failed to update delivery', 'error');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
opentoast(err.message, 'success');
|
opentoast(err.response?.data?.message || err.message || 'Failed to update delivery', 'error');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1127,7 +1136,7 @@ const Deliveries = () => {
|
|||||||
PaperComponent={SoftPaper}
|
PaperComponent={SoftPaper}
|
||||||
sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }}
|
sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }}
|
||||||
onOpen={(event) => {
|
onOpen={(event) => {
|
||||||
if (!appId) {
|
if (appId == null) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
OpenToast('Please select your zone first!', 'warning', 3000);
|
OpenToast('Please select your zone first!', 'warning', 3000);
|
||||||
setTimeout(() => locationRef.current?.focus(), 0);
|
setTimeout(() => locationRef.current?.focus(), 0);
|
||||||
@@ -1174,7 +1183,7 @@ const Deliveries = () => {
|
|||||||
PaperComponent={SoftPaper}
|
PaperComponent={SoftPaper}
|
||||||
sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }}
|
sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }}
|
||||||
onOpen={(event) => {
|
onOpen={(event) => {
|
||||||
if (!appId && !tenantid) {
|
if (appId == null && !tenantid) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
OpenToast('Please select your Zone and Tenant first!', 'warning', 3000);
|
OpenToast('Please select your Zone and Tenant first!', 'warning', 3000);
|
||||||
setTimeout(() => locationRef.current?.focus(), 0);
|
setTimeout(() => locationRef.current?.focus(), 0);
|
||||||
@@ -1226,7 +1235,7 @@ const Deliveries = () => {
|
|||||||
else setRiderid(value.userid);
|
else setRiderid(value.userid);
|
||||||
}}
|
}}
|
||||||
onOpen={(event) => {
|
onOpen={(event) => {
|
||||||
if (!appId) {
|
if (appId == null) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
OpenToast('Please select your zone first!', 'warning', 3000);
|
OpenToast('Please select your zone first!', 'warning', 3000);
|
||||||
setTimeout(() => locationRef.current?.focus(), 0);
|
setTimeout(() => locationRef.current?.focus(), 0);
|
||||||
@@ -1614,7 +1623,7 @@ const Deliveries = () => {
|
|||||||
</MobileField>
|
</MobileField>
|
||||||
<MobileField
|
<MobileField
|
||||||
label="ETA"
|
label="ETA"
|
||||||
value={row.expecteddeliverytime ? dayjs(row.expecteddeliverytime).format('hh:mm A') : '—'}
|
value={row.expecteddeliverytime ? parseDoormileTimestamp(row.expecteddeliverytime).format('hh:mm A') : '—'}
|
||||||
/>
|
/>
|
||||||
<MobileField label="Transit">
|
<MobileField label="Transit">
|
||||||
<Box sx={chipSx('#06b6d4')}>{row.transitminutes || 0}m</Box>
|
<Box sx={chipSx('#06b6d4')}>{row.transitminutes || 0}m</Box>
|
||||||
@@ -1920,17 +1929,19 @@ const Deliveries = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/* express-console-api.md: Doormile timestamps are IST
|
{/* express-console-api.md: Doormile timestamps are IST
|
||||||
wall-clock with no timezone marker — .utc() was
|
wall-clock with no timezone marker — bare dayjs() was
|
||||||
reinterpreting that naive string as local time and
|
still being used here despite this comment saying
|
||||||
shifting it back 5:30, showing the wrong time (and
|
otherwise (the fix was never actually applied below,
|
||||||
sometimes the wrong day). Parse bare, matching the
|
only claimed) — a Z-suffixed response reinterprets as
|
||||||
already-correct convention in getRowBatchId above. */}
|
local time and shifts 5:30, showing the wrong time
|
||||||
|
(and sometimes the wrong day). parseDoormileTimestamp
|
||||||
|
strips that, matching getRowBatchId above. */}
|
||||||
<Tooltip title="Ordered date" placement="top">
|
<Tooltip title="Ordered date" placement="top">
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(row.orderdate).format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(row.orderdate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(row.orderdate).format('hh:mm A')}
|
{parseDoormileTimestamp(row.orderdate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -1943,11 +1954,11 @@ const Deliveries = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Delivery date" placement="top">
|
<Tooltip title="Delivery date" placement="top">
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(row.deliverydate).format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(row.deliverydate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(row.deliverydate).format('hh:mm A')}
|
{parseDoormileTimestamp(row.deliverydate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -2028,7 +2039,7 @@ const Deliveries = () => {
|
|||||||
{/* Estimated Delivery Time */}
|
{/* Estimated Delivery Time */}
|
||||||
<TableCell align="left">
|
<TableCell align="left">
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}>
|
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}>
|
||||||
{row.expecteddeliverytime ? dayjs(row.expecteddeliverytime).format('hh:mm A') : '—'}
|
{row.expecteddeliverytime ? parseDoormileTimestamp(row.expecteddeliverytime).format('hh:mm A') : '—'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{/* Transit Minutes */}
|
{/* Transit Minutes */}
|
||||||
@@ -2455,7 +2466,7 @@ const Deliveries = () => {
|
|||||||
{['pending', 'accepted', 'arrived'].includes(selectedRow?.orderstatus) && (
|
{['pending', 'accepted', 'arrived'].includes(selectedRow?.orderstatus) && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!appId) {
|
if (appId == null) {
|
||||||
opentoast('Please select a location first!', 'warning');
|
opentoast('Please select a location first!', 'warning');
|
||||||
locationRef.current?.focus();
|
locationRef.current?.focus();
|
||||||
return;
|
return;
|
||||||
@@ -2476,6 +2487,22 @@ const Deliveries = () => {
|
|||||||
already could. */}
|
already could. */}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
// PUT /admin/consignments/:id/status is consignment-scoped — it
|
||||||
|
// only exists once the rider marks pickup-complete (doormile-flow.md).
|
||||||
|
// Without a real consignmentid, updateDeliveryAPI's `?? deliveryid`
|
||||||
|
// fallback was sending the BOOKING id where a consignment id is
|
||||||
|
// required — a different id space, so the call failed against
|
||||||
|
// the wrong (or nonexistent) resource. Combined with the mutation's
|
||||||
|
// onSuccess only handling `res.success === true` with no else
|
||||||
|
// branch, that failure was completely silent — dialog just sat
|
||||||
|
// there looking like nothing happened. Block it here instead,
|
||||||
|
// with a message that explains why, rather than letting the
|
||||||
|
// submit silently no-op.
|
||||||
|
if (!selectedRow.consignmentid) {
|
||||||
|
opentoast("This order hasn't been picked up yet — status can only be updated once it becomes a consignment.", 'warning');
|
||||||
|
handleMenuClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setKms(selectedRow.kms);
|
setKms(selectedRow.kms);
|
||||||
setCumulativeKms(selectedRow.cumulativekms);
|
setCumulativeKms(selectedRow.cumulativekms);
|
||||||
setDeliverylat(selectedRow.droplat);
|
setDeliverylat(selectedRow.droplat);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
MdAccessTime,
|
MdAccessTime,
|
||||||
MdInventory2
|
MdInventory2
|
||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import dayjs from 'dayjs';
|
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||||
import { CircularProgress } from '@mui/material';
|
import { CircularProgress } from '@mui/material';
|
||||||
import { getStatusStyle, getActiveOrder } from './dispatchShared';
|
import { getStatusStyle, getActiveOrder } from './dispatchShared';
|
||||||
import { OpenToast } from 'components/third-party/OpenToast';
|
import { OpenToast } from 'components/third-party/OpenToast';
|
||||||
@@ -147,7 +147,9 @@ const ActiveSection = ({
|
|||||||
</span>
|
</span>
|
||||||
{o.deliverytime && (
|
{o.deliverytime && (
|
||||||
<span className="adcard-time" style={{ fontSize: '11px', color: '#64748b' }}>
|
<span className="adcard-time" style={{ fontSize: '11px', color: '#64748b' }}>
|
||||||
{dayjs(o.deliverytime).isValid() ? dayjs(o.deliverytime).format('HH:mm:ss') : String(o.deliverytime)}
|
{parseDoormileTimestamp(o.deliverytime).isValid()
|
||||||
|
? parseDoormileTimestamp(o.deliverytime).format('HH:mm:ss')
|
||||||
|
: String(o.deliverytime)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1459,11 +1459,16 @@ const Dispatch = ({
|
|||||||
const status = String(r.orderstatus ?? r.status ?? '').toLowerCase();
|
const status = String(r.orderstatus ?? r.status ?? '').toLowerCase();
|
||||||
if (SKIPPED_STATUSES.has(status)) return;
|
if (SKIPPED_STATUSES.has(status)) return;
|
||||||
|
|
||||||
// Filter by selectedDate to align with daily slot aggregations
|
// Filter by selectedDate to align with daily slot aggregations.
|
||||||
|
// parseDoormileTimestamp (not bare dayjs()) avoids the false-trailing-Z
|
||||||
|
// parse bug (utils/doormileTimestamp.js) — near a midnight boundary an
|
||||||
|
// unstripped Z could shift a row +5:30 into the wrong calendar day,
|
||||||
|
// silently dropping it from (or wrongly adding it to) a rider's daily
|
||||||
|
// profit total for selectedDate.
|
||||||
const dateStr = r.assigntime
|
const dateStr = r.assigntime
|
||||||
? dayjs(r.assigntime).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(r.assigntime).format('YYYY-MM-DD')
|
||||||
: r.deliverydate
|
: r.deliverydate
|
||||||
? dayjs(r.deliverydate).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(r.deliverydate).format('YYYY-MM-DD')
|
||||||
: 'unknown';
|
: 'unknown';
|
||||||
if (selectedDate && dateStr !== selectedDate) return;
|
if (selectedDate && dateStr !== selectedDate) return;
|
||||||
|
|
||||||
@@ -2071,10 +2076,15 @@ const Dispatch = ({
|
|||||||
}
|
}
|
||||||
const kmDelta = actualKm - plannedKm;
|
const kmDelta = actualKm - plannedKm;
|
||||||
const kmDeltaPct = plannedKm > 0 ? (kmDelta / plannedKm) * 100 : null;
|
const kmDeltaPct = plannedKm > 0 ? (kmDelta / plannedKm) * 100 : null;
|
||||||
|
// parseDoormileTimestamp on both sides (not bare dayjs()) — even though
|
||||||
|
// a false-Z shift would cancel out in the diff below if BOTH fields
|
||||||
|
// always had it consistently, that's not guaranteed per-field, and an
|
||||||
|
// inconsistent shift on just one side would corrupt timeDeltaMin and
|
||||||
|
// the anomaly flag it drives.
|
||||||
const expectedTs = order?.expecteddeliverytime
|
const expectedTs = order?.expecteddeliverytime
|
||||||
? dayjs(order.expecteddeliverytime)
|
? parseDoormileTimestamp(order.expecteddeliverytime)
|
||||||
: null;
|
: null;
|
||||||
const actualTs = order?.deliverytime ? dayjs(order.deliverytime) : null;
|
const actualTs = order?.deliverytime ? parseDoormileTimestamp(order.deliverytime) : null;
|
||||||
const timeDeltaMin =
|
const timeDeltaMin =
|
||||||
expectedTs?.isValid() && actualTs?.isValid()
|
expectedTs?.isValid() && actualTs?.isValid()
|
||||||
? actualTs.diff(expectedTs, 'minute')
|
? actualTs.diff(expectedTs, 'minute')
|
||||||
@@ -5063,7 +5073,9 @@ const Dispatch = ({
|
|||||||
<span className="pu-info-label">Last Seen</span>
|
<span className="pu-info-label">Last Seen</span>
|
||||||
<span className="pu-info-value pu-time-stamp">
|
<span className="pu-info-value pu-time-stamp">
|
||||||
<MdAccessTime className="inline-icon" />{' '}
|
<MdAccessTime className="inline-icon" />{' '}
|
||||||
{dayjs(r.logdate).isValid() ? dayjs(r.logdate).format('hh:mm:ss A') : r.logdate}
|
{parseDoormileTimestamp(r.logdate).isValid()
|
||||||
|
? parseDoormileTimestamp(r.logdate).format('hh:mm:ss A')
|
||||||
|
: r.logdate}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -5339,7 +5351,7 @@ const Dispatch = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="cmp-tip-sub">
|
<div className="cmp-tip-sub">
|
||||||
{t.deliverytime
|
{t.deliverytime
|
||||||
? `Delivered ${dayjs(t.deliverytime).format('hh:mm A')}`
|
? `Delivered ${parseDoormileTimestamp(t.deliverytime).format('hh:mm A')}`
|
||||||
: `${t.coords.length} GPS pings`}
|
: `${t.coords.length} GPS pings`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useMemo, useCallback } from 'react';
|
import React, { useState, useMemo, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||||
import {
|
import {
|
||||||
MdTrendingUp,
|
MdTrendingUp,
|
||||||
MdTrendingDown,
|
MdTrendingDown,
|
||||||
@@ -93,10 +94,13 @@ function calcRiderMetrics(rider, selectedDate, batches) {
|
|||||||
// context. Only the money/km math below excludes them.
|
// context. Only the money/km math below excludes them.
|
||||||
const orders = (rider.orders ?? []).filter((o) => {
|
const orders = (rider.orders ?? []).filter((o) => {
|
||||||
if (!selectedDate) return true;
|
if (!selectedDate) return true;
|
||||||
|
// parseDoormileTimestamp (not bare dayjs()) avoids the false-trailing-Z
|
||||||
|
// parse bug (utils/doormileTimestamp.js) — near a midnight boundary an
|
||||||
|
// unstripped Z could shift a row +5:30 out of selectedDate entirely.
|
||||||
const dateStr = o.assigntime
|
const dateStr = o.assigntime
|
||||||
? dayjs(o.assigntime).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(o.assigntime).format('YYYY-MM-DD')
|
||||||
: o.deliverydate
|
: o.deliverydate
|
||||||
? dayjs(o.deliverydate).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(o.deliverydate).format('YYYY-MM-DD')
|
||||||
: 'unknown';
|
: 'unknown';
|
||||||
return dateStr === selectedDate;
|
return dateStr === selectedDate;
|
||||||
});
|
});
|
||||||
@@ -116,9 +120,9 @@ function calcRiderMetrics(rider, selectedDate, batches) {
|
|||||||
if (!slot) continue;
|
if (!slot) continue;
|
||||||
|
|
||||||
const dateStr = o.assigntime
|
const dateStr = o.assigntime
|
||||||
? dayjs(o.assigntime).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(o.assigntime).format('YYYY-MM-DD')
|
||||||
: o.deliverydate
|
: o.deliverydate
|
||||||
? dayjs(o.deliverydate).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(o.deliverydate).format('YYYY-MM-DD')
|
||||||
: null;
|
: null;
|
||||||
if (!dateStr) continue;
|
if (!dateStr) continue;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
|||||||
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||||
import MainCard from 'components/MainCard';
|
import MainCard from 'components/MainCard';
|
||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
import { fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider } from '../../api/api';
|
import { fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider } from '../../api/api';
|
||||||
@@ -103,10 +104,10 @@ const MobileOrdersList = ({ list, aiMode }) => {
|
|||||||
<Tooltip title="Ordered date">
|
<Tooltip title="Ordered date">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(val.orderdate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(val.orderdate).utc().format('hh:mm A')}
|
{parseDoormileTimestamp(val.orderdate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -114,10 +115,10 @@ const MobileOrdersList = ({ list, aiMode }) => {
|
|||||||
<Tooltip title="Delivery date">
|
<Tooltip title="Delivery date">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(val.deliverydate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(val.deliverydate).utc().format('hh:mm A')}
|
{parseDoormileTimestamp(val.deliverydate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -492,20 +493,20 @@ const OrdersPreview = () => {
|
|||||||
<Stack display={'flex'} flexDirection={'row'} gap={3}>
|
<Stack display={'flex'} flexDirection={'row'} gap={3}>
|
||||||
<Tooltip title="Ordered date">
|
<Tooltip title="Ordered date">
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(val.orderdate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(val.orderdate).utc().format('hh:mm A')}
|
{parseDoormileTimestamp(val.orderdate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
-
|
-
|
||||||
<Tooltip title="Delivery date">
|
<Tooltip title="Delivery date">
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(val.deliverydate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(val.deliverydate).utc().format('hh:mm A')}
|
{parseDoormileTimestamp(val.deliverydate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -607,20 +608,20 @@ const OrdersPreview = () => {
|
|||||||
<Stack display={'flex'} flexDirection={'row'} gap={3}>
|
<Stack display={'flex'} flexDirection={'row'} gap={3}>
|
||||||
<Tooltip title="Ordered date">
|
<Tooltip title="Ordered date">
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(val.orderdate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(val.orderdate).utc().format('hh:mm A')}
|
{parseDoormileTimestamp(val.orderdate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
-
|
-
|
||||||
<Tooltip title="Delivery date">
|
<Tooltip title="Delivery date">
|
||||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||||
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
|
{parseDoormileTimestamp(val.deliverydate).format('DD/MM/YYYY')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||||
{dayjs(val.deliverydate).utc().format('hh:mm A')}
|
{parseDoormileTimestamp(val.deliverydate).format('hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { MapContainer, TileLayer, Marker, Polyline, Tooltip } from 'react-leafle
|
|||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||||
import { Chip, Stack, Typography, Box } from '@mui/material';
|
import { Chip, Stack, Typography, Box } from '@mui/material';
|
||||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||||
import CircularLoader from 'components/CircularLoader';
|
import CircularLoader from 'components/CircularLoader';
|
||||||
@@ -160,12 +161,12 @@ const MapWithRoute = ({ coordinates, additionalProps, order, setMapOpen }) => {
|
|||||||
|
|
||||||
{/* START MARKER */}
|
{/* START MARKER */}
|
||||||
<Marker position={start} icon={startIcon}>
|
<Marker position={start} icon={startIcon}>
|
||||||
<Tooltip direction="bottom">{`Pickup: ${dayjs(additionalProps.riderStart).format('DD-MM-YYYY hh:mm A')}`}</Tooltip>
|
<Tooltip direction="bottom">{`Pickup: ${parseDoormileTimestamp(additionalProps.riderStart).format('DD-MM-YYYY hh:mm A')}`}</Tooltip>
|
||||||
</Marker>
|
</Marker>
|
||||||
|
|
||||||
{/* END MARKER */}
|
{/* END MARKER */}
|
||||||
<Marker position={end} icon={endIcon}>
|
<Marker position={end} icon={endIcon}>
|
||||||
<Tooltip direction="bottom">{`Drop: ${dayjs(additionalProps.riderEnd).format('DD-MM-YYYY hh:mm A')}`}</Tooltip>
|
<Tooltip direction="bottom">{`Drop: ${parseDoormileTimestamp(additionalProps.riderEnd).format('DD-MM-YYYY hh:mm A')}`}</Tooltip>
|
||||||
</Marker>
|
</Marker>
|
||||||
|
|
||||||
{/* REAL OSRM ROUTE */}
|
{/* REAL OSRM ROUTE */}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ import dayjs from 'dayjs';
|
|||||||
import { OpenToast } from 'components/third-party/OpenToast';
|
import { OpenToast } from 'components/third-party/OpenToast';
|
||||||
import TableLoader from 'components/nearle_components/TableLoader';
|
import TableLoader from 'components/nearle_components/TableLoader';
|
||||||
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
||||||
|
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||||
var utc = require('dayjs/plugin/utc');
|
var utc = require('dayjs/plugin/utc');
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
@@ -448,7 +449,10 @@ export default function OrdersDetails() {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['tenantlist', appId],
|
queryKey: ['tenantlist', appId],
|
||||||
queryFn: () => getTenants(appId),
|
queryFn: () => getTenants(appId),
|
||||||
enabled: appId !== 0
|
// appId 0 is a real, deliberate choice — the synthetic "All" zone option
|
||||||
|
// (fetchAppLocations in api.js) — not "nothing picked yet"; only
|
||||||
|
// null/undefined means that now. See LocationAutocomplete.js's onChange.
|
||||||
|
enabled: appId != null
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -459,7 +463,7 @@ export default function OrdersDetails() {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['fetchRidersList', appId],
|
queryKey: ['fetchRidersList', appId],
|
||||||
queryFn: fetchRidersList,
|
queryFn: fetchRidersList,
|
||||||
enabled: appId !== 0
|
enabled: appId != null
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -627,7 +631,7 @@ export default function OrdersDetails() {
|
|||||||
getOptionLabel={(option) => option?.tenantname || ''}
|
getOptionLabel={(option) => option?.tenantname || ''}
|
||||||
PaperComponent={SoftPaper}
|
PaperComponent={SoftPaper}
|
||||||
onOpen={(event) => {
|
onOpen={(event) => {
|
||||||
if (!appId) {
|
if (appId == null) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
OpenToast('Please select a your app location first!', 'warning', 3000);
|
OpenToast('Please select a your app location first!', 'warning', 3000);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -677,7 +681,7 @@ export default function OrdersDetails() {
|
|||||||
value={locationValue}
|
value={locationValue}
|
||||||
PaperComponent={SoftPaper}
|
PaperComponent={SoftPaper}
|
||||||
onOpen={(event) => {
|
onOpen={(event) => {
|
||||||
if (!appId && !tenantid) {
|
if (appId == null && !tenantid) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
OpenToast('Please select a your Location and Tenant first!', 'warning', 3000);
|
OpenToast('Please select a your Location and Tenant first!', 'warning', 3000);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -731,7 +735,7 @@ export default function OrdersDetails() {
|
|||||||
}}
|
}}
|
||||||
PaperComponent={SoftPaper}
|
PaperComponent={SoftPaper}
|
||||||
onOpen={() => {
|
onOpen={() => {
|
||||||
if (!appId) {
|
if (appId == null) {
|
||||||
OpenToast('Select App Location First', 'warning', 2000);
|
OpenToast('Select App Location First', 'warning', 2000);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -1105,7 +1109,7 @@ export default function OrdersDetails() {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Typography variant="caption" sx={{ color: DT.textMuted, fontWeight: 600 }}>
|
<Typography variant="caption" sx={{ color: DT.textMuted, fontWeight: 600 }}>
|
||||||
{dayjs(row.deliverydate).utc().format('DD/MM/YYYY · hh:mm A')}
|
{parseDoormileTimestamp(row.deliverydate).format('DD/MM/YYYY · hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -1376,7 +1380,7 @@ export default function OrdersDetails() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Typography variant="caption" sx={{ color: DT.textMuted }}>
|
<Typography variant="caption" sx={{ color: DT.textMuted }}>
|
||||||
{dayjs(row.deliverydate).utc().format('DD/MM/YYYY · hh:mm A')}
|
{parseDoormileTimestamp(row.deliverydate).format('DD/MM/YYYY · hh:mm A')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Box, Paper, TextField, Typography } from '@mui/material';
|
|||||||
import { MdCalendarMonth } from 'react-icons/md';
|
import { MdCalendarMonth } from 'react-icons/md';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||||
|
|
||||||
import { fetchDeliveries } from 'pages/api/api';
|
import { fetchDeliveries } from 'pages/api/api';
|
||||||
import Loader from 'components/Loader';
|
import Loader from 'components/Loader';
|
||||||
@@ -111,10 +112,12 @@ const Profitability = () => {
|
|||||||
rider.orders.forEach((o) => {
|
rider.orders.forEach((o) => {
|
||||||
const status = String(o.orderstatus || '').toLowerCase();
|
const status = String(o.orderstatus || '').toLowerCase();
|
||||||
if (SKIPPED_STATUSES.has(status)) return;
|
if (SKIPPED_STATUSES.has(status)) return;
|
||||||
|
// parseDoormileTimestamp (not bare dayjs()) avoids the false-trailing-Z
|
||||||
|
// parse bug (utils/doormileTimestamp.js) — see ProfitabilitySection.js.
|
||||||
const dateStr = o.assigntime
|
const dateStr = o.assigntime
|
||||||
? dayjs(o.assigntime).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(o.assigntime).format('YYYY-MM-DD')
|
||||||
: o.deliverydate
|
: o.deliverydate
|
||||||
? dayjs(o.deliverydate).format('YYYY-MM-DD')
|
? parseDoormileTimestamp(o.deliverydate).format('YYYY-MM-DD')
|
||||||
: null;
|
: null;
|
||||||
if (dateStr !== selectedDate) return;
|
if (dateStr !== selectedDate) return;
|
||||||
const km = parseFloat(o.riderkms || 0);
|
const km = parseFloat(o.riderkms || 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user