updates on the dispatch page active section regarding the map sectoin
This commit is contained in:
@@ -1266,7 +1266,7 @@ const Dispatch = ({
|
|||||||
// show ONLY currently-active riders — their cards, routes, drop markers and
|
// show ONLY currently-active riders — their cards, routes, drop markers and
|
||||||
// live bike markers — and hide everyone who is offline/idle for the slot.
|
// live bike markers — and hide everyone who is offline/idle for the slot.
|
||||||
const activeRiderIdSet = useMemo(
|
const activeRiderIdSet = useMemo(
|
||||||
() => new Set(liveRiderLocations.filter((r) => r.status === 'active').map((r) => String(r.id))),
|
() => new Set(liveRiderLocations.filter((r) => r.status === 'active' || r.status === 'pending').map((r) => String(r.id))),
|
||||||
[liveRiderLocations]
|
[liveRiderLocations]
|
||||||
);
|
);
|
||||||
// Default to the slot containing the current wall-clock time. Use a
|
// Default to the slot containing the current wall-clock time. Use a
|
||||||
@@ -1319,6 +1319,12 @@ const Dispatch = ({
|
|||||||
if (liveHasNextPage && !liveIsFetchingNextPage) liveFetchNextPage();
|
if (liveHasNextPage && !liveIsFetchingNextPage) liveFetchNextPage();
|
||||||
}, [shouldFetchLive, liveHasNextPage, liveIsFetchingNextPage, liveFetchNextPage]);
|
}, [shouldFetchLive, liveHasNextPage, liveIsFetchingNextPage, liveFetchNextPage]);
|
||||||
|
|
||||||
|
// 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(() => {
|
const liveRows = useMemo(() => {
|
||||||
// Flatten infinite-query pages, then dedupe by orderid. The deliveries API
|
// Flatten infinite-query pages, then dedupe by orderid. The deliveries API
|
||||||
// can return the same orderid more than once (e.g. when page bookkeeping
|
// can return the same orderid more than once (e.g. when page bookkeeping
|
||||||
@@ -1586,7 +1592,7 @@ const Dispatch = ({
|
|||||||
() =>
|
() =>
|
||||||
new Set(
|
new Set(
|
||||||
(isAllActiveView ? allViewOrders : [])
|
(isAllActiveView ? allViewOrders : [])
|
||||||
.filter((o) => String(o?.orderstatus || '').toLowerCase() === 'active')
|
.filter(isActiveDelivery)
|
||||||
.map((o) => String(o.rider_id || o.userid || ''))
|
.map((o) => String(o.rider_id || o.userid || ''))
|
||||||
),
|
),
|
||||||
[isAllActiveView, allViewOrders]
|
[isAllActiveView, allViewOrders]
|
||||||
@@ -1600,18 +1606,72 @@ const Dispatch = ({
|
|||||||
: riders,
|
: riders,
|
||||||
[isAllActiveView, riders, activeOrderRiderIdSet]
|
[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]) {
|
||||||
|
byRider[key] = {
|
||||||
|
id: key,
|
||||||
|
riderName: riderMeta[key]?.riderName || o.rider_name || o.ridername || o.username || `Rider ${key}`,
|
||||||
|
orders: [],
|
||||||
|
color: riderMeta[key]?.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
|
// 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(
|
||||||
() =>
|
() =>
|
||||||
isAllActiveView
|
isAllActiveView
|
||||||
? liveRiderLocations
|
? liveRiderLocations
|
||||||
.filter((r) => r.status === 'active' && activeOrderRiderIdSet.has(String(r.id)))
|
.filter((r) => (r.status === 'active' || r.status === 'pending') && activeOrderRiderIdSet.has(String(r.id)))
|
||||||
.map((r) => [r.lat, r.lon])
|
.map((r) => [r.lat, r.lon])
|
||||||
: [],
|
: [],
|
||||||
[isAllActiveView, liveRiderLocations, activeOrderRiderIdSet]
|
[isAllActiveView, liveRiderLocations, activeOrderRiderIdSet]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Clear the sticky Active-view cache when the operator switches date or hub —
|
||||||
|
// otherwise cards from a previously viewed day/location could linger.
|
||||||
|
useEffect(() => {
|
||||||
|
stickyActiveRef.current = {};
|
||||||
|
}, [selectedDate, selectedAppLocationId]);
|
||||||
|
|
||||||
// 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
|
||||||
// updates local state; in controlled mode it only notifies the parent.
|
// updates local state; in controlled mode it only notifies the parent.
|
||||||
@@ -1714,13 +1774,11 @@ const Dispatch = ({
|
|||||||
};
|
};
|
||||||
}, [focusedRider, focusedKitchen, isAllActiveView, allViewOrders, visibleRiders, stats]);
|
}, [focusedRider, focusedKitchen, isAllActiveView, allViewOrders, visibleRiders, stats]);
|
||||||
|
|
||||||
// Count of in-progress deliveries shown in the Active view list. Drives the
|
// Count of in-progress deliveries shown in the Active view list. Derives from
|
||||||
// sidebar header visibility — when the active fleet has nothing in progress,
|
// the sticky cache (via activeViewRiders) so header and card list always agree.
|
||||||
// the header (RIDER DISPATCH title + Active Fleet badge + order/rider tiles)
|
|
||||||
// is hidden so the "No active deliveries" empty state stands on its own.
|
|
||||||
const activeDeliveryCount = useMemo(
|
const activeDeliveryCount = useMemo(
|
||||||
() => (isAllActiveView ? allViewOrders.filter((o) => String(o?.orderstatus || '').toLowerCase() === 'active').length : 0),
|
() => (isAllActiveView ? activeViewRiders.reduce((sum, r) => sum + r.orders.length, 0) : 0),
|
||||||
[isAllActiveView, allViewOrders]
|
[isAllActiveView, activeViewRiders]
|
||||||
);
|
);
|
||||||
|
|
||||||
// List of deliveryids we want GPS logs for. Drives two pipelines:
|
// List of deliveryids we want GPS logs for. Drives two pipelines:
|
||||||
@@ -2719,36 +2777,12 @@ const Dispatch = ({
|
|||||||
// duplicate, slightly-offset pins that clutter the view.
|
// duplicate, slightly-offset pins that clutter the view.
|
||||||
if (compareOpen && focusedRider && compareViewMode === 'actual') return null;
|
if (compareOpen && focusedRider && compareViewMode === 'actual') return null;
|
||||||
|
|
||||||
// In "All Active Routes" view the base set is restricted to active riders'
|
let ordersToRender = allOrders;
|
||||||
// orders (allViewOrders); a focus selection still overrides as usual.
|
|
||||||
let ordersToRender = allViewOrders;
|
|
||||||
if (focusedZone) ordersToRender = focusedZone.orders;
|
if (focusedZone) ordersToRender = focusedZone.orders;
|
||||||
if (focusedKitchen) ordersToRender = focusedKitchen.orders;
|
if (focusedKitchen) ordersToRender = focusedKitchen.orders;
|
||||||
if (focusedRider) ordersToRender = focusedRider.orders;
|
if (focusedRider) ordersToRender = focusedRider.orders;
|
||||||
ordersToRender = ordersToRender.filter(hasValidDrop);
|
ordersToRender = ordersToRender.filter(hasValidDrop);
|
||||||
|
|
||||||
// Active view drop pins: show ONE flag per active rider — the drop of the
|
|
||||||
// leg they're currently heading to (getActiveOrder) — so the flag marks
|
|
||||||
// "where the rider is going" and sits exactly at the end of that rider's
|
|
||||||
// route line. No other drops are flagged in this view.
|
|
||||||
// • Focused rider → just that rider's in-progress drop.
|
|
||||||
// • Overview (no focus) → each visible active rider's in-progress drop.
|
|
||||||
if (isAllActiveView) {
|
|
||||||
if (focusedRider) {
|
|
||||||
const active = getActiveOrder(focusedRider.orders);
|
|
||||||
const id = active ? String(active.orderid) : null;
|
|
||||||
ordersToRender = id ? ordersToRender.filter((o) => String(o.orderid) === id) : [];
|
|
||||||
} else {
|
|
||||||
const destOrderIds = new Set(
|
|
||||||
visibleRiders
|
|
||||||
.map((r) => getActiveOrder(r.orders))
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((o) => String(o.orderid))
|
|
||||||
);
|
|
||||||
ordersToRender = ordersToRender.filter((o) => destOrderIds.has(String(o.orderid)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-build the deliveryid → sequenceStep lookup once per render so each
|
// Pre-build the deliveryid → sequenceStep lookup once per render so each
|
||||||
// marker can resolve its step palette color without an O(N) scan.
|
// marker can resolve its step palette color without an O(N) scan.
|
||||||
const compareDeliveryToStep =
|
const compareDeliveryToStep =
|
||||||
@@ -4568,7 +4602,7 @@ const Dispatch = ({
|
|||||||
))
|
))
|
||||||
) : isAllActiveView ? (
|
) : isAllActiveView ? (
|
||||||
<ActiveSection
|
<ActiveSection
|
||||||
visibleRiders={visibleRiders}
|
visibleRiders={activeViewRiders}
|
||||||
riders={riders}
|
riders={riders}
|
||||||
focusedStop={focusedStop}
|
focusedStop={focusedStop}
|
||||||
handleRiderFocus={handleRiderFocus}
|
handleRiderFocus={handleRiderFocus}
|
||||||
@@ -4576,7 +4610,7 @@ const Dispatch = ({
|
|||||||
calculateEstMeters={calculateEstMeters}
|
calculateEstMeters={calculateEstMeters}
|
||||||
getRiderColor={getRiderColor}
|
getRiderColor={getRiderColor}
|
||||||
formatMeters={formatMeters}
|
formatMeters={formatMeters}
|
||||||
isLoading={shouldFetchLive && liveIsFetching && visibleRiders.length === 0}
|
isLoading={shouldFetchLive && liveIsFetching && activeViewRiders.length === 0}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
visibleRiders.map(renderRiderCard)
|
visibleRiders.map(renderRiderCard)
|
||||||
@@ -4651,12 +4685,12 @@ const Dispatch = ({
|
|||||||
{liveRiderLocations
|
{liveRiderLocations
|
||||||
.filter((r) =>
|
.filter((r) =>
|
||||||
isAllActiveView
|
isAllActiveView
|
||||||
? (r.status === 'active' && activeOrderRiderIdSet.has(String(r.id)))
|
? ((r.status === 'active' || r.status === 'pending') && activeOrderRiderIdSet.has(String(r.id)))
|
||||||
: riders.some((rd) => String(rd.id) === String(r.id))
|
: riders.some((rd) => String(rd.id) === String(r.id))
|
||||||
)
|
)
|
||||||
.filter((r) => !focusedRider || String(focusedRider.id) === String(r.id))
|
.filter((r) => !focusedRider || String(focusedRider.id) === String(r.id))
|
||||||
.map((r) => {
|
.map((r) => {
|
||||||
const isActive = r.status === 'active';
|
const isActive = r.status === 'active' || r.status === 'pending';
|
||||||
const pinColor = isActive ? '#16a34a' : '#dc2626';
|
const pinColor = isActive ? '#16a34a' : '#dc2626';
|
||||||
// Look up the rider's in-progress order so the popup can show
|
// Look up the rider's in-progress order so the popup can show
|
||||||
// where they're heading next (drop customer/area + originating
|
// where they're heading next (drop customer/area + originating
|
||||||
|
|||||||
Reference in New Issue
Block a user