updates on the api and dispatch page and preview page
This commit is contained in:
@@ -63,6 +63,13 @@ const nearle = {
|
||||
icon: icons.FileDoneOutlined,
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
id: 'dispatch',
|
||||
title: <FormattedMessage id="dispatch" />,
|
||||
type: 'item',
|
||||
url: '/nearle/dispatch',
|
||||
icon: icons.DirectionsBikeOutlinedIcon
|
||||
},
|
||||
{
|
||||
id: 'orders',
|
||||
title: <FormattedMessage id="orders" />,
|
||||
@@ -151,13 +158,6 @@ const nearle = {
|
||||
type: 'item',
|
||||
url: '/nearle/invoice',
|
||||
icon: icons.ReceiptOutlinedIcon
|
||||
},
|
||||
{
|
||||
id: 'dispatch',
|
||||
title: <FormattedMessage id="dispatch" />,
|
||||
type: 'item',
|
||||
url: '/nearle/dispatch',
|
||||
icon: icons.DirectionsBikeOutlinedIcon
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -173,8 +173,20 @@ export const fetchBatchEfficiency = async ({ batch, tenantId }) => {
|
||||
// ==============================|| finalCreatedeliveries (orders) ||============================== //
|
||||
|
||||
export const finalCreatedeliveries = async (deliveryData) => {
|
||||
console.log('deliveryData', deliveryData.deliveries);
|
||||
const response = await axios.post(`https://jupiter.nearle.app/live/api/v1/deliveries/createdeliveries`, deliveryData.deliveries);
|
||||
// Go backend types Deliveries.userid (and rider_id) as int. Coerce at the
|
||||
// boundary so any upstream string — including ones that only surface in the
|
||||
// deployed build's data flow — can't cause a 500 unmarshal error.
|
||||
const toInt = (v) => {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : v;
|
||||
};
|
||||
const deliveries = (deliveryData.deliveries || []).map((d) => ({
|
||||
...d,
|
||||
userid: toInt(d.userid),
|
||||
rider_id: toInt(d.rider_id)
|
||||
}));
|
||||
console.log('deliveryData', deliveries);
|
||||
const response = await axios.post(`https://jupiter.nearle.app/live/api/v1/deliveries/createdeliveries`, deliveries);
|
||||
return response.data;
|
||||
};
|
||||
// ==============================|| createAutomationDeliveries (orders) Auto rider Assign ||============================== //
|
||||
|
||||
@@ -2383,8 +2383,8 @@ const Dispatch = ({
|
||||
|
||||
let ordersToRender = allOrders;
|
||||
if (focusedZone) ordersToRender = focusedZone.orders;
|
||||
if (focusedRider) ordersToRender = focusedRider.orders;
|
||||
if (focusedKitchen) ordersToRender = focusedKitchen.orders;
|
||||
if (focusedRider) ordersToRender = focusedRider.orders;
|
||||
ordersToRender = ordersToRender.filter(hasValidDrop);
|
||||
|
||||
// Pre-build the deliveryid → sequenceStep lookup once per render so each
|
||||
@@ -2418,6 +2418,7 @@ const Dispatch = ({
|
||||
}
|
||||
|
||||
const isRiderFocused = !!focusedRider;
|
||||
const showNumbers = isRiderFocused || !!focusedKitchen;
|
||||
const statusStyle = getStatusStyle(o.orderstatus);
|
||||
const statusLow = String(o.orderstatus || '').toLowerCase();
|
||||
const isDelivered = statusLow === 'delivered';
|
||||
@@ -2431,7 +2432,7 @@ const Dispatch = ({
|
||||
</svg>`
|
||||
: '';
|
||||
|
||||
const icon = isRiderFocused
|
||||
const icon = showNumbers
|
||||
? (() => {
|
||||
const seq = compareSeq || o.step || (ordersToRender.indexOf(o) + 1);
|
||||
const sz = 32;
|
||||
@@ -2453,7 +2454,7 @@ const Dispatch = ({
|
||||
|
||||
return (
|
||||
<Marker
|
||||
key={o.orderid}
|
||||
key={`${o.orderid}-${showNumbers ? 'num' : 'flag'}`}
|
||||
position={[parseFloat(o.droplat || o.deliverylat), parseFloat(o.droplon || o.deliverylong)]}
|
||||
icon={icon}
|
||||
zIndexOffset={rid ? 100 : 0}
|
||||
|
||||
@@ -255,6 +255,23 @@ const Preview = () => {
|
||||
// derives its rider list from it, and Assign Orders sends a flattened copy
|
||||
// of it to the API.
|
||||
const [dispatchPreviewData, setDispatchPreviewData] = useState(stateData.dispatchPreviewData || null);
|
||||
|
||||
// The AI response arrives via location.state, which the browser stores in
|
||||
// history.state and persists across reloads. That means a reload of
|
||||
// /dispatch/preview would re-hydrate the stale snapshot — including any
|
||||
// pending edits the user thought they discarded. Bounce to /orders when
|
||||
// there's no fresh response, and wipe the history snapshot once consumed
|
||||
// so a later reload / back-forward also bounces instead of re-using it.
|
||||
useEffect(() => {
|
||||
if (!stateData.dispatchPreviewData) {
|
||||
navigate('/nearle/orders', { replace: true });
|
||||
return;
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.history?.state) {
|
||||
window.history.replaceState({ ...window.history.state, usr: null }, '');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const [csvExportData, setCsvExportData] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
@@ -262,6 +279,11 @@ const Preview = () => {
|
||||
const [reconcileLoading, setReconcileLoading] = useState(false);
|
||||
const [hasReconciled, setHasReconciled] = useState(false);
|
||||
|
||||
// Tracks riders whose orders have been edited since the last AI response
|
||||
// or successful reconcile. Only these are sent to the reconcile API — the
|
||||
// server-side step re-ordering only needs to see what actually changed.
|
||||
const [dirtyRiderIds, setDirtyRiderIds] = useState(() => new Set());
|
||||
|
||||
// Change-rider dialog state
|
||||
const [changeDialogOpen, setChangeDialogOpen] = useState(false);
|
||||
const [selectedOrder, setSelectedOrder] = useState(null);
|
||||
@@ -343,6 +365,7 @@ const Preview = () => {
|
||||
// Brand new response = brand new source of truth.
|
||||
setDispatchPreviewData(data);
|
||||
setHasReconciled(false);
|
||||
setDirtyRiderIds(new Set());
|
||||
setIsLoading(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -372,8 +395,17 @@ const Preview = () => {
|
||||
onMutate: () => setReconcileLoading(true),
|
||||
onSuccess: (data) => {
|
||||
if (Array.isArray(data?.riders)) {
|
||||
// Merge: applyReconcileResponse replaces orders for riders present
|
||||
// in the response and leaves the rest of the cache untouched.
|
||||
setDispatchPreviewData((prev) => applyReconcileResponse(prev, data));
|
||||
setHasReconciled(true);
|
||||
// Clear only the riders we just reconciled from the dirty set, so
|
||||
// any unrelated edits made meanwhile are preserved.
|
||||
setDirtyRiderIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
data.riders.forEach((r) => next.delete(String(r.rider_id)));
|
||||
return next;
|
||||
});
|
||||
OpenToast('Steps reconciled — preview updated', 'success', 2000);
|
||||
} else {
|
||||
OpenToast('Reconcile returned no rider data', 'warning', 3000);
|
||||
@@ -423,10 +455,18 @@ const Preview = () => {
|
||||
OpenToast('No riders to reconcile', 'warning', 3000);
|
||||
return;
|
||||
}
|
||||
// Payload built straight from the cache — whatever Change Rider edited
|
||||
// is what gets sent.
|
||||
// Only send riders that were edited since the last AI response / reconcile.
|
||||
// Their step ordering is the only thing that can be stale — untouched
|
||||
// riders are skipped to keep the payload small.
|
||||
const dirty = reconcileRiders.filter((r) =>
|
||||
dirtyRiderIds.has(String(r.rider_id))
|
||||
);
|
||||
if (!dirty.length) {
|
||||
OpenToast('No edits to reconcile', 'info', 2500);
|
||||
return;
|
||||
}
|
||||
reconcileMutation.mutate({
|
||||
riders: reconcileRiders.map((r) => ({
|
||||
riders: dirty.map((r) => ({
|
||||
rider_id: r.rider_id,
|
||||
orders: r.orders
|
||||
}))
|
||||
@@ -460,6 +500,15 @@ const Preview = () => {
|
||||
newRiderName
|
||||
})
|
||||
);
|
||||
// Both riders' step sequences are now potentially stale: the old rider
|
||||
// lost a stop, the new rider gained one. Mark both as dirty so the next
|
||||
// Reconcile sends exactly these two.
|
||||
setDirtyRiderIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (selectedOldRiderId != null) next.add(String(selectedOldRiderId));
|
||||
if (newRiderId != null && Number.isFinite(newRiderId)) next.add(String(newRiderId));
|
||||
return next;
|
||||
});
|
||||
setHasReconciled(false);
|
||||
setChangeDialogOpen(false);
|
||||
OpenToast('Rider changed — click Reconcile to verify steps', 'info', 2500);
|
||||
@@ -653,10 +702,14 @@ const Preview = () => {
|
||||
size="large"
|
||||
startIcon={<MdSwapHoriz />}
|
||||
onClick={handleReconcile}
|
||||
disabled={reconcileLoading}
|
||||
disabled={reconcileLoading || dirtyRiderIds.size === 0}
|
||||
sx={{ minWidth: 220, borderRadius: '10px', textTransform: 'none', fontWeight: 700 }}
|
||||
>
|
||||
{reconcileLoading ? 'Reconciling...' : 'Reconcile'}
|
||||
{reconcileLoading
|
||||
? 'Reconciling...'
|
||||
: dirtyRiderIds.size === 0
|
||||
? 'Reconcile'
|
||||
: `Reconcile (${dirtyRiderIds.size})`}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user