updates on the api and dispatch page and preview page

This commit is contained in:
2026-05-29 15:08:01 +05:30
parent 0269c1b26d
commit ba88501bc4
4 changed files with 1802 additions and 1736 deletions

View File

@@ -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
}
]
};

View File

@@ -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 ||============================== //

File diff suppressed because it is too large Load Diff

View File

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