This commit is contained in:
2026-07-01 12:50:35 +05:30
parent 1d0d7c428c
commit 360b13a467
2 changed files with 91 additions and 22 deletions

View File

@@ -5669,6 +5669,47 @@
letter-spacing: 0.04em; letter-spacing: 0.04em;
} }
/* Rider/customer/delivery-id stack on the left, Step badge pinned to the
right so operators can read the sequence position without opening the
Details section. */
.dispatch-container .dispatch-popup .pu-header-main {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
}
.dispatch-container .dispatch-popup .pu-header-left {
min-width: 0;
flex: 1;
}
.dispatch-container .dispatch-popup .pu-header-step {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
padding: 4px 10px;
border-radius: 10px;
background: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.dispatch-container .dispatch-popup .pu-header-step-label {
font-size: 9px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #7b1fa2;
}
.dispatch-container .dispatch-popup .pu-header-step-value {
font-size: 15px;
font-weight: 800;
color: #7b1fa2;
white-space: nowrap;
}
/* --- Body sections --- /* --- Body sections ---
No scroll: the popup expands to fit its content. Width is the dimension No scroll: the popup expands to fit its content. Width is the dimension
we constrain (via leaflet's maxWidth prop) so the body grows downward as we constrain (via leaflet's maxWidth prop) so the body grows downward as

View File

@@ -1987,12 +1987,19 @@ 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 filteredLiveRows (the SAME
// (liveRows, all batches, no GPS-status gate). Uses a sticky cache so cards // batch-filtered rows that drive activeStats' "Orders/Riders" header tiles
// only disappear when the API returns an explicit terminal status (delivered / // and every other view), not the raw unfiltered liveRows. Building this off
// cancelled / skipped). Temporary row omissions during the 15-second poll // liveRows previously showed active orders from every batch regardless of
// cycle (single-page refetch, brief API inconsistency) no longer cause cards // the selected Morning/Afternoon/Evening pill, while the header tiles above
// to flash away and reappear. // it (sourced from allOrders → filteredLiveRows) reported the batch-filtered
// count — e.g. tiles reading "0 Orders" while afternoon-batch cards still
// rendered underneath. 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. The cache is reset on batch change (see the effect below) so
// switching slots doesn't leak a previous batch's sticky cards forward.
const activeViewRiders = useMemo(() => { const activeViewRiders = useMemo(() => {
if (!isAllActiveView) { if (!isAllActiveView) {
stickyActiveRef.current = {}; stickyActiveRef.current = {};
@@ -2001,7 +2008,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') {
@@ -2035,7 +2042,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.
@@ -2050,11 +2057,11 @@ const Dispatch = ({
); );
// Clear the sticky active-order cache whenever the operator switches to a // 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 // different date, zone, or batch slot — stale cards from the previous filter
// into the new view. // must not leak into the new view.
useEffect(() => { useEffect(() => {
stickyActiveRef.current = {}; stickyActiveRef.current = {};
}, [selectedDate, selectedAppLocationId]); }, [selectedDate, selectedAppLocationId, selectedBatch]);
// 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.
@@ -2979,6 +2986,8 @@ const Dispatch = ({
</span> </span>
)} )}
</div> </div>
<div className="pu-header-main">
<div className="pu-header-left">
<div className="pu-rider"> <div className="pu-rider">
<MdTwoWheeler /> <span>{o.rider_name || o.ridername || 'Unassigned'}</span> <MdTwoWheeler /> <span>{o.rider_name || o.ridername || 'Unassigned'}</span>
</div> </div>
@@ -2992,6 +3001,16 @@ const Dispatch = ({
<div className="pu-delivery-id">Delivery #{o.deliveryid}</div> <div className="pu-delivery-id">Delivery #{o.deliveryid}</div>
)} )}
</div> </div>
{o.step != null && (
<div className="pu-header-step">
<span className="pu-header-step-label">Step</span>
<span className="pu-header-step-value">
{o.trip_number != null && o.trip_number > 1 ? `T${o.trip_number}·` : ''}{o.step}
</span>
</div>
)}
</div>
</div>
<div className="pu-body"> <div className="pu-body">
{POPUP_TIMELINE.some((t) => o[t.key]) && ( {POPUP_TIMELINE.some((t) => o[t.key]) && (
@@ -5019,6 +5038,15 @@ const Dispatch = ({
)) ))
) : isAllActiveView ? ( ) : isAllActiveView ? (
<ActiveSection <ActiveSection
// Remount on batch/date/zone change so its internal
// prevStatusMapRef (used to detect real status
// transitions and fire the "now delivering" /
// "reached location" toasts) resets. Without this,
// switching e.g. Morning → Afternoon swaps in a new
// set of orders the ref has never seen, so every
// already-active order in the new batch looks like a
// fresh transition and fires a stale toast.
key={`${selectedDate}-${selectedAppLocationId}-${selectedBatch}`}
visibleRiders={activeViewRiders} visibleRiders={activeViewRiders}
riders={riders} riders={riders}
focusedStop={focusedStop} focusedStop={focusedStop}