From d72158f3e670f222209038b4e899f02b14c7f41c Mon Sep 17 00:00:00 2001 From: dharaneesh-r Date: Fri, 26 Jun 2026 12:54:42 +0530 Subject: [PATCH] updates on the console under the dispatch page active section --- src/pages/nearle/dispatch/ActiveSection.js | 21 +++- src/pages/nearle/dispatch/Dispatch.css | 12 ++- src/pages/nearle/dispatch/Dispatch.js | 108 +++++++++++++++++++-- 3 files changed, 125 insertions(+), 16 deletions(-) diff --git a/src/pages/nearle/dispatch/ActiveSection.js b/src/pages/nearle/dispatch/ActiveSection.js index 2567df1..e2a2d92 100644 --- a/src/pages/nearle/dispatch/ActiveSection.js +++ b/src/pages/nearle/dispatch/ActiveSection.js @@ -8,6 +8,7 @@ import { MdInventory2 } from 'react-icons/md'; import dayjs from 'dayjs'; +import { CircularProgress } from '@mui/material'; import { getStatusStyle, getActiveOrder } from './dispatchShared'; import { OpenToast } from 'components/third-party/OpenToast'; @@ -19,15 +20,16 @@ const ActiveSection = ({ setFocusedStop, calculateEstMeters, getRiderColor, - formatMeters + formatMeters, + isLoading }) => { // Sort by live distance ascending (closest drop-off first; null/unknown last) const activeDeliveries = visibleRiders .map((r) => getActiveOrder(r.orders)) .filter(Boolean) .sort((a, b) => { - const mA = calculateEstMeters(a.rider_id, a) ?? Infinity; - const mB = calculateEstMeters(b.rider_id, b) ?? Infinity; + const mA = calculateEstMeters(a.rider_id || a.userid, a) ?? Infinity; + const mB = calculateEstMeters(b.rider_id || b.userid, b) ?? Infinity; return mA - mB; }); @@ -71,6 +73,15 @@ const ActiveSection = ({ prevStatusMapRef.current = currentMap; }, [visibleRiders]); + if (isLoading) { + return ( +
+ +
Loading active deliveries...
+
+ ); + } + if (activeDeliveries.length === 0) { return (
@@ -86,7 +97,7 @@ const ActiveSection = ({ } const renderActiveDeliveryCard = (o, i) => { - const rid = o.rider_id; + const rid = o.rider_id || o.userid; const rider = riders.find((r) => String(r.id) === String(rid)); const color = getRiderColor(rid); const statusStyle = getStatusStyle(o.orderstatus); @@ -111,7 +122,7 @@ const ActiveSection = ({
{ if (rider) handleRiderFocus(rider); if (canFocus) setFocusedStop({ orderid: o.orderid, lat, lon }); diff --git a/src/pages/nearle/dispatch/Dispatch.css b/src/pages/nearle/dispatch/Dispatch.css index f687f03..47bdfd8 100644 --- a/src/pages/nearle/dispatch/Dispatch.css +++ b/src/pages/nearle/dispatch/Dispatch.css @@ -10754,13 +10754,23 @@ padding: 4px 0 16px; } +@keyframes adcard-in { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + .dispatch-container .adcard { background: var(--bg-card); border: 1px solid var(--border); border-radius: 14px; padding: 16px; cursor: pointer; - transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + /* Explicit properties so the CSS animation (adcard-in) isn't interrupted by transition: all */ + transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), + border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), + background 0.2s cubic-bezier(0.4, 0, 0.2, 1); + animation: adcard-in 0.28s cubic-bezier(0.4, 0, 0.2, 1) both; box-shadow: var(--shadow); display: flex; flex-direction: column; diff --git a/src/pages/nearle/dispatch/Dispatch.js b/src/pages/nearle/dispatch/Dispatch.js index 35e9bad..43563a4 100644 --- a/src/pages/nearle/dispatch/Dispatch.js +++ b/src/pages/nearle/dispatch/Dispatch.js @@ -1613,6 +1613,18 @@ const Dispatch = ({ if (liveHasNextPage && !liveIsFetchingNextPage) liveFetchNextPage(); }, [shouldFetchLive, liveHasNextPage, liveIsFetchingNextPage, liveFetchNextPage]); + // Holds the last fully-loaded row set (all pages present). Used below to + // prevent the "orders vanish then reappear" flash during background refetches + // where TanStack Query fetches pages sequentially and livePagesData temporarily + // contains only page 1 while the rest are still loading. + const prevCompleteRowsRef = useRef([]); + + // Sticky cache for active orders in the Active Section sidebar. Keys are orderid strings. + // Orders are added/refreshed when orderstatus === 'active' and evicted only when the + // API explicitly returns a terminal status. This prevents cards from flashing away + // during the 15-second poll cycle when the API temporarily omits a row. + const stickyActiveRef = useRef({}); + const liveRows = useMemo(() => { // Flatten infinite-query pages, then dedupe by orderid. The deliveries API // can return the same orderid more than once (e.g. when page bookkeeping @@ -1627,8 +1639,26 @@ const Dispatch = ({ if (key) seen.add(key); out.push(r); } + + // Background refetch loads pages one-by-one: page 1 arrives first, then the + // auto-pager fetches page 2, 3… While pages are still loading (liveHasNextPage + // is true and a fetch is in-flight), "out" is a partial snapshot that is + // smaller than the complete dataset we had before. Returning prevCompleteRowsRef + // keeps the Active section stable — no cards disappear mid-refetch. + // On the very first load prevCompleteRowsRef is empty, so we fall through and + // show whatever partial data is already available (correct for initial render). + if (liveIsFetching && liveHasNextPage && prevCompleteRowsRef.current.length > 0) { + return prevCompleteRowsRef.current; + } + + // All pages have arrived — this is a complete snapshot. Persist it so the + // next refetch cycle can return it during the partial-pages window above. + if (!liveHasNextPage) { + prevCompleteRowsRef.current = out; + } + return out; - }, [livePagesData]); + }, [livePagesData, liveIsFetching, liveHasNextPage]); // Distinct riders across the WHOLE day's rows — NOT slot-filtered. Used by // the Rider Info view so the operator can pick any rider regardless of @@ -1957,6 +1987,56 @@ const Dispatch = ({ : riders, [isAllActiveView, riders, activeOrderRiderIdSet] ); + // Active section sidebar riders — built from the FULL day's unfiltered rows + // (liveRows, all batches, no GPS-status gate). 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 + // cycle (single-page refetch, brief API inconsistency) no longer cause cards + // to flash away and reappear. + const activeViewRiders = useMemo(() => { + if (!isAllActiveView) { + stickyActiveRef.current = {}; + return []; + } + + const TERMINAL = new Set(['delivered', 'cancelled', 'skipped', 'complete', 'completed']); + + liveRows.forEach((o) => { + const id = String(o.orderid); + const s = String(o.orderstatus || '').toLowerCase(); + if (s === 'active') { + stickyActiveRef.current[id] = o; + } else if (TERMINAL.has(s)) { + delete stickyActiveRef.current[id]; + } + // Non-terminal, non-active statuses for a known order are intentionally + // ignored — a brief status regression in the poll data must not evict a card. + }); + + const stableOrders = Object.values(stickyActiveRef.current); + if (!stableOrders.length) return []; + + const riderMeta = Object.fromEntries(riders.map((r) => [r.id, r])); + const byRider = {}; + stableOrders.forEach((o) => { + const key = String(o.rider_id || o.userid || ''); + if (!key || key === 'unassigned' || key === '0') return; + if (!byRider[key]) { + const meta = riderMeta[key]; + byRider[key] = { + id: key, + riderName: meta?.riderName || o.rider_name || o.ridername || o.username || `Rider ${key}`, + orders: [], + color: meta?.color || getStableRiderColor(key) + }; + } + if (!byRider[key].orders.some((ex) => String(ex.orderid) === String(o.orderid))) { + byRider[key].orders.push(o); + } + }); + return Object.values(byRider); + }, [isAllActiveView, liveRows, riders]); + // 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. const allViewLivePoints = useMemo( @@ -1969,13 +2049,18 @@ const Dispatch = ({ [isAllActiveView, liveRiderLocations, activeOrderRiderIdSet] ); - // Count of in-progress deliveries shown in the Active view list. Drives the - // sidebar header visibility — when the active fleet has nothing in progress, - // the header (RIDER DISPATCH title + Active Fleet badge + order/rider tiles) - // is hidden so the "No active deliveries" empty state stands on its own. + // Clear the sticky active-order cache whenever the operator switches to a + // different date or zone — stale cards from the previous filter must not leak + // into the new view. + useEffect(() => { + stickyActiveRef.current = {}; + }, [selectedDate, selectedAppLocationId]); + + // 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. const activeDeliveryCount = useMemo( - () => (isAllActiveView ? allViewOrders.filter(isActiveDelivery).length : 0), - [isAllActiveView, allViewOrders] + () => (isAllActiveView ? activeViewRiders.reduce((sum, r) => sum + r.orders.length, 0) : 0), + [isAllActiveView, activeViewRiders] ); // Per-rider canvas renderer for the actual (right) map in Compare mode. @@ -2790,7 +2875,7 @@ const Dispatch = ({ html: `
${(name || 'K').charAt(0).toUpperCase()}
` }); - const getRiderColor = (rid) => riders.find(r => r.id === rid)?.color || '#475569'; + const getRiderColor = (rid) => riders.find(r => String(r.id) === String(rid))?.color || '#475569'; const calculateEstMeters = (riderId, order) => { if (!riderId || !order || !hasValidDrop(order)) return null; @@ -3681,7 +3766,9 @@ const Dispatch = ({ onClick={() => { logger.info('View mode changed: By Zone'); setViewMode('zones'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }} > By Zone - + {!embedded && ( + + )}