This commit is contained in:
2026-07-01 13:07:58 +05:30
parent 5f7df5b9ab
commit 55af1caad0
2 changed files with 32 additions and 16 deletions

View File

@@ -15,6 +15,7 @@ import { OpenToast } from 'components/nearle_components/OpenToast';
const ActiveSection = ({ const ActiveSection = ({
visibleRiders, visibleRiders,
riders, riders,
resetKey,
focusedStop, focusedStop,
handleRiderFocus, handleRiderFocus,
setFocusedStop, setFocusedStop,
@@ -35,6 +36,14 @@ const ActiveSection = ({
// Toast notifications: detect status transitions across renders // Toast notifications: detect status transitions across renders
const prevStatusMapRef = useRef(null); const prevStatusMapRef = useRef(null);
// Tracks which batch/date/location the baseline above belongs to. When it
// changes (e.g. operator switches Morning Batch → Afternoon Batch) the
// whole order set is replaced wholesale — that is a view switch, not a
// stream of real status transitions, so we must reseed the baseline
// silently instead of diffing against the old batch's orders (which would
// otherwise treat every order in the new batch as "brand-new and already
// active" and re-fire the delivering/reached toasts for all of them).
const prevResetKeyRef = useRef(resetKey);
useEffect(() => { useEffect(() => {
const currentMap = {}; const currentMap = {};
@@ -48,8 +57,9 @@ const ActiveSection = ({
}); });
}); });
const viewSwitched = prevResetKeyRef.current !== resetKey;
const prev = prevStatusMapRef.current; const prev = prevStatusMapRef.current;
if (prev !== null) { if (prev !== null && !viewSwitched) {
Object.entries(currentMap).forEach(([oid, cur]) => { Object.entries(currentMap).forEach(([oid, cur]) => {
const old = prev[oid]; const old = prev[oid];
if (!old) { if (!old) {
@@ -71,7 +81,8 @@ const ActiveSection = ({
} }
prevStatusMapRef.current = currentMap; prevStatusMapRef.current = currentMap;
}, [visibleRiders]); prevResetKeyRef.current = resetKey;
}, [visibleRiders, resetKey]);
if (isLoading) { if (isLoading) {
return ( return (

View File

@@ -1606,9 +1606,10 @@ const Dispatch = ({
: riders, : riders,
[isAllActiveView, riders, activeOrderRiderIdSet] [isAllActiveView, riders, activeOrderRiderIdSet]
); );
// Active section sidebar riders — built from the FULL day's unfiltered rows // Active section sidebar riders — built from the currently selected batch's
// (liveRows, all batches, no GPS-status gate). Uses a sticky cache so cards // rows (filteredLiveRows), so switching to e.g. "Afternoon Batch" doesn't
// only disappear when the API returns an explicit terminal status (delivered / // keep showing morning-batch orders. Uses a sticky cache so cards only
// disappear when the API returns an explicit terminal status (delivered /
// cancelled / skipped). Temporary row omissions during the 15-second poll // cancelled / skipped). Temporary row omissions during the 15-second poll
// cycle (single-page refetch, brief API inconsistency) no longer cause cards // cycle (single-page refetch, brief API inconsistency) no longer cause cards
// to flash away and reappear. // to flash away and reappear.
@@ -1620,7 +1621,7 @@ const Dispatch = ({
const TERMINAL = new Set(['delivered', 'cancelled', 'skipped', 'complete', 'completed']); const TERMINAL = new Set(['delivered', 'cancelled', 'skipped', 'complete', 'completed']);
liveRows.forEach((o) => { filteredLiveRows.forEach((o) => {
const id = String(o.orderid); const id = String(o.orderid);
const s = String(o.orderstatus || '').toLowerCase(); const s = String(o.orderstatus || '').toLowerCase();
if (s === 'active') { if (s === 'active') {
@@ -1653,7 +1654,7 @@ const Dispatch = ({
} }
}); });
return Object.values(byRider); return Object.values(byRider);
}, [isAllActiveView, liveRows, riders]); }, [isAllActiveView, filteredLiveRows, riders]);
// Live GPS coordinates of exactly the riders this view shows (active + has an // Live GPS coordinates of exactly the riders this view shows (active + has an
// order), fed to MapController's auto-fit so the map frames what's rendered. // order), fed to MapController's auto-fit so the map frames what's rendered.
const allViewLivePoints = useMemo( const allViewLivePoints = useMemo(
@@ -1666,11 +1667,13 @@ const Dispatch = ({
[isAllActiveView, liveRiderLocations, activeOrderRiderIdSet] [isAllActiveView, liveRiderLocations, activeOrderRiderIdSet]
); );
// Clear the sticky Active-view cache when the operator switches date or hub // Clear the sticky Active-view cache when the operator switches date, hub, or
// otherwise cards from a previously viewed day/location could linger. // batch — otherwise cards from a previously viewed day/location/batch (e.g.
// Morning Batch orders still marked "active") could linger after switching
// to Afternoon Batch.
useEffect(() => { useEffect(() => {
stickyActiveRef.current = {}; stickyActiveRef.current = {};
}, [selectedDate, selectedAppLocationId]); }, [selectedDate, selectedAppLocationId, selectedBatch, selectedTimeField]);
// Per-rider canvas renderer for the actual (right) map in Compare mode. // Per-rider canvas renderer for the actual (right) map in Compare mode.
// Single setter used by every interactive site in the UI. In uncontrolled mode it // Single setter used by every interactive site in the UI. In uncontrolled mode it
@@ -1752,14 +1755,15 @@ const Dispatch = ({
} }
// "All Active Routes": the header must reflect exactly what the list/map // "All Active Routes": the header must reflect exactly what the list/map
// shows — the in-progress orders and the riders working them — NOT the whole // shows — the in-progress orders and the riders working them — NOT the whole
// day's totals. We count only active deliveries (and `visibleRiders`, which // day's totals. Derived from `activeViewRiders` (the same sticky-cached data
// is already gated to active-order riders) so the tiles can't disagree with // the sidebar list below renders) instead of the GPS-status-gated
// the list/map below. // allViewOrders/visibleRiders, which could disagree with the list and read
// 0 even while riders/orders are visible below.
if (isAllActiveView) { if (isAllActiveView) {
const activeOrders = allViewOrders.filter((o) => String(o?.orderstatus || '').toLowerCase() === 'active'); const activeOrders = activeViewRiders.flatMap((r) => r.orders);
return { return {
orders: activeOrders.length, orders: activeOrders.length,
riders: visibleRiders.length, riders: activeViewRiders.length,
km: activeOrders.reduce((s, o) => s + parseFloat(o.actualkms || o.kms || 0), 0), km: activeOrders.reduce((s, o) => s + parseFloat(o.actualkms || o.kms || 0), 0),
profit: activeOrders.reduce((s, o) => s + parseFloat(o.profit || 0), 0), profit: activeOrders.reduce((s, o) => s + parseFloat(o.profit || 0), 0),
label: 'Active Fleet' label: 'Active Fleet'
@@ -1772,7 +1776,7 @@ const Dispatch = ({
profit: stats.totalProfit, profit: stats.totalProfit,
label: 'Total Fleet' label: 'Total Fleet'
}; };
}, [focusedRider, focusedKitchen, isAllActiveView, allViewOrders, visibleRiders, stats]); }, [focusedRider, focusedKitchen, isAllActiveView, activeViewRiders, stats]);
// Count of in-progress deliveries shown in the Active view list. Derives from // Count of in-progress deliveries shown in the Active view list. Derives from
// the sticky cache (via activeViewRiders) so header and card list always agree. // the sticky cache (via activeViewRiders) so header and card list always agree.
@@ -4605,6 +4609,7 @@ const Dispatch = ({
<ActiveSection <ActiveSection
visibleRiders={activeViewRiders} visibleRiders={activeViewRiders}
riders={riders} riders={riders}
resetKey={`${selectedDate}|${selectedAppLocationId}|${selectedBatch}`}
focusedStop={focusedStop} focusedStop={focusedStop}
handleRiderFocus={handleRiderFocus} handleRiderFocus={handleRiderFocus}
setFocusedStop={setFocusedStop} setFocusedStop={setFocusedStop}