updates on the build regarding the queries page and the dispatch page

This commit is contained in:
2026-08-28 13:21:40 +05:30
parent a4cc06d516
commit 904cbd1085
2 changed files with 191 additions and 157 deletions

View File

@@ -212,25 +212,70 @@ const haversineKm = (lat1, lon1, lat2, lon2) => {
// page's own optional-chaining render whatever fields actually come back. // page's own optional-chaining render whatever fields actually come back.
// Falls back to the miler record itself (which may carry a last-known // Falls back to the miler record itself (which may carry a last-known
// position) if the logs call fails or returns nothing. // position) if the logs call fails or returns nothing.
// Despite the parameter name (kept for caller compatibility), this must be // Handles both milerprofileid and userid inputs by resolving via getMilers().
// a milerprofileid — /admin/milers/:id/* routes 404 on a plain userid export const getRiderPeriodicLogs = async (id) => {
// (confirmed live). if (!id) return null;
export const getRiderPeriodicLogs = async (userid) => {
if (!userid) return null; let profileId = id;
let milerObj = null;
// Resolve milerprofileid if a userid or generic id was passed
try { try {
const logs = await getMilerLogs(userid, undefined, undefined, 1); const milers = await getMilers();
const found = (milers || []).find(
(m) =>
String(m.milerprofileid) === String(id) ||
String(m.userid) === String(id) ||
String(m.milerid) === String(id) ||
String(m.id) === String(id)
);
if (found) {
profileId = found.milerprofileid || found.id || id;
milerObj = found;
}
} catch (e) {
logger.debug('getRiderPeriodicLogs: milers lookup fallback', e);
}
// 1. Fetch the latest log via /admin/milers/:profileId/logs?limit=1
try {
const logs = await getMilerLogs(profileId, undefined, undefined, 1);
const latest = Array.isArray(logs) ? logs[0] : logs; const latest = Array.isArray(logs) ? logs[0] : logs;
if (latest) return latest; if (latest && typeof latest === 'object' && Object.keys(latest).length > 0) {
} catch { return {
// Falls through to the getMiler() fallback below — this is polled every ...milerObj,
// 15s by the live map, so a toast here would spam the operator on every ...latest,
// failed tick. Only surface an error if the fallback also fails. userid: latest.userid ?? milerObj?.userid ?? id,
} username:
try { (latest.username && latest.username.trim()) ||
return (await getMiler(userid)) || null; milerObj?.displayname ||
milerObj?.name ||
(latest.userid ? `Rider #${latest.userid}` : `Rider #${id}`)
};
}
} catch (err) { } catch (err) {
return null; logger.debug(`getRiderPeriodicLogs: getMilerLogs failed for profileId ${profileId}`, err);
} }
// 2. Fallback to /admin/milers/:profileId if no logs or logs call failed
try {
const miler = milerObj || (await getMiler(profileId));
if (miler) {
return {
...miler,
userid: miler.userid ?? id,
username: miler.displayname || miler.name || miler.authname || `Rider #${miler.userid ?? id}`,
latitude: miler.currentlatitude ?? miler.latitude,
longitude: miler.currentlongitude ?? miler.longitude,
status: miler.availabilitystatus ?? miler.status ?? 'offline',
logdate: miler.lastpingat ?? miler.updatedat ?? miler.createdat
};
}
} catch (err) {
logger.debug(`getRiderPeriodicLogs: getMiler fallback failed for profileId ${profileId}`, err);
}
return null;
}; };
// ==============================|| fetchAppLocations (zone/location picker) ||============================== // // ==============================|| fetchAppLocations (zone/location picker) ||============================== //
@@ -478,10 +523,10 @@ export const resolveMilerForOrder = (order, lookup) => {
const matchedVia = lookup.byUserId.has(String(riderUserId)) const matchedVia = lookup.byUserId.has(String(riderUserId))
? 'userid' ? 'userid'
: lookup.byProfileId.has(String(riderUserId)) : lookup.byProfileId.has(String(riderUserId))
? 'milerprofileid' ? 'milerprofileid'
: lookup.byName.has(normMilerName(riderName)) : lookup.byName.has(normMilerName(riderName))
? 'name' ? 'name'
: null; : null;
const rider = const rider =
lookup.byUserId.get(String(riderUserId)) ?? lookup.byProfileId.get(String(riderUserId)) ?? lookup.byName.get(normMilerName(riderName)); lookup.byUserId.get(String(riderUserId)) ?? lookup.byProfileId.get(String(riderUserId)) ?? lookup.byName.get(normMilerName(riderName));
return rider?.milerprofileid ? { rider, matchedVia } : null; return rider?.milerprofileid ? { rider, matchedVia } : null;

View File

@@ -1113,7 +1113,9 @@ const Dispatch = ({
// changing riderInfoUserid itself, which is compared against elsewhere // changing riderInfoUserid itself, which is compared against elsewhere
// in this file for sidebar row highlighting in userid-space. // in this file for sidebar row highlighting in userid-space.
queryFn: () => { queryFn: () => {
const match = liveRiderLocations.find((r) => String(r.userid) === String(riderInfoUserid)); const match =
liveRiderLocations.find((r) => String(r.userid) === String(riderInfoUserid) || String(r.milerprofileid) === String(riderInfoUserid)) ||
(ridersList || []).find((r) => String(r.userid) === String(riderInfoUserid) || String(r.milerprofileid) === String(riderInfoUserid));
return getRiderPeriodicLogs(match?.milerprofileid ?? riderInfoUserid); return getRiderPeriodicLogs(match?.milerprofileid ?? riderInfoUserid);
}, },
enabled: viewMode === 'rider-info' && riderInfoUserid != null, enabled: viewMode === 'rider-info' && riderInfoUserid != null,
@@ -1534,8 +1536,8 @@ const Dispatch = ({
const dateStr = r.assigntime const dateStr = r.assigntime
? parseDoormileTimestamp(r.assigntime).format('YYYY-MM-DD') ? parseDoormileTimestamp(r.assigntime).format('YYYY-MM-DD')
: r.deliverydate : r.deliverydate
? parseDoormileTimestamp(r.deliverydate).format('YYYY-MM-DD') ? parseDoormileTimestamp(r.deliverydate).format('YYYY-MM-DD')
: 'unknown'; : 'unknown';
if (selectedDate && dateStr !== selectedDate) return; if (selectedDate && dateStr !== selectedDate) return;
const batch = getRowBatch(r, selectedTimeField, BATCHES); const batch = getRowBatch(r, selectedTimeField, BATCHES);
@@ -1872,8 +1874,8 @@ const Dispatch = ({
() => () =>
isAllActiveView isAllActiveView
? liveRiderLocations ? liveRiderLocations
.filter((r) => r.status !== 'offline' && r.status !== 'blocked' && activeOrderRiderIdSet.has(String(r.id))) .filter((r) => r.status !== 'offline' && r.status !== 'blocked' && activeOrderRiderIdSet.has(String(r.id)))
.map((r) => [r.lat, r.lon]) .map((r) => [r.lat, r.lon])
: [], : [],
[isAllActiveView, liveRiderLocations, activeOrderRiderIdSet] [isAllActiveView, liveRiderLocations, activeOrderRiderIdSet]
); );
@@ -2437,8 +2439,8 @@ const Dispatch = ({
lp && Number.isFinite(lp.lat) && Number.isFinite(lp.lon) lp && Number.isFinite(lp.lat) && Number.isFinite(lp.lon)
? [lp.lat, lp.lon] ? [lp.lat, lp.lon]
: hasValidPickup(activeOrder) : hasValidPickup(activeOrder)
? [toNum(pickupLat(activeOrder)), toNum(pickupLon(activeOrder))] ? [toNum(pickupLat(activeOrder)), toNum(pickupLon(activeOrder))]
: null; : null;
const dLat = toNum(activeOrder.droplat || activeOrder.deliverylat); const dLat = toNum(activeOrder.droplat || activeOrder.deliverylat);
const dLon = toNum(activeOrder.droplon || activeOrder.deliverylong); const dLon = toNum(activeOrder.droplon || activeOrder.deliverylong);
if (start && Number.isFinite(dLat) && Number.isFinite(dLon)) { if (start && Number.isFinite(dLat) && Number.isFinite(dLon)) {
@@ -2579,19 +2581,19 @@ const Dispatch = ({
const stepSegs = roadPath const stepSegs = roadPath
? splitPolylineByDrops(roadPath, dropCoords) ? splitPolylineByDrops(roadPath, dropCoords)
: (() => { : (() => {
const hasPickup = aerialPath.length > dropCoords.length; const hasPickup = aerialPath.length > dropCoords.length;
const out = []; const out = [];
for (let i = 0; i < dropCoords.length; i++) { for (let i = 0; i < dropCoords.length; i++) {
const a = hasPickup ? i : i - 1; const a = hasPickup ? i : i - 1;
const b = hasPickup ? i + 1 : i; const b = hasPickup ? i + 1 : i;
if (a < 0 || a >= aerialPath.length || b >= aerialPath.length) { if (a < 0 || a >= aerialPath.length || b >= aerialPath.length) {
out.push([]); out.push([]);
} else { } else {
out.push([aerialPath[a], aerialPath[b]]); out.push([aerialPath[a], aerialPath[b]]);
}
} }
return out; }
})(); return out;
})();
// Map each path index → step index by accumulating segment lengths // Map each path index → step index by accumulating segment lengths
// (segments share endpoints so we use length-1 per segment). // (segments share endpoints so we use length-1 per segment).
const pathIdxToStepIdx = []; const pathIdxToStepIdx = [];
@@ -3055,42 +3057,37 @@ const Dispatch = ({
const flagSvg = o.orderstatus const flagSvg = o.orderstatus
? `<svg class="cmark-flag" viewBox="0 0 18 22" xmlns="http://www.w3.org/2000/svg"> ? `<svg class="cmark-flag" viewBox="0 0 18 22" xmlns="http://www.w3.org/2000/svg">
<line x1="1.5" y1="0" x2="1.5" y2="22" stroke="#0f172a" stroke-width="1.6" stroke-linecap="round"/> <line x1="1.5" y1="0" x2="1.5" y2="22" stroke="#0f172a" stroke-width="1.6" stroke-linecap="round"/>
<polygon points="2,1 17,1 13.5,5.5 17,10 2,10" fill="${ <polygon points="2,1 17,1 13.5,5.5 17,10 2,10" fill="${statusStyle.bg
statusStyle.bg }" stroke="#0f172a" stroke-width="0.6" stroke-linejoin="round"/>
}" stroke="#0f172a" stroke-width="0.6" stroke-linejoin="round"/> ${isDelivered
${ ? '<polyline points="5,5.5 7,7.5 11,3.5" fill="none" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>'
isDelivered : ''
? '<polyline points="5,5.5 7,7.5 11,3.5" fill="none" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>' }
: ''
}
</svg>` </svg>`
: ''; : '';
const icon = showNumbers const icon = showNumbers
? (() => { ? (() => {
const seq = compareSeq || o.step || ordersToRender.indexOf(o) + 1; const seq = compareSeq || o.step || ordersToRender.indexOf(o) + 1;
const sz = 32; const sz = 32;
return L.divIcon({ return L.divIcon({
className: '',
iconSize: [sz, sz],
iconAnchor: [sz / 2, sz / 2],
popupAnchor: [0, -28],
html: `<div class="cmark is-rider-focused${
isPulsing ? ' pulse' : ''
}" style="background:${color};width:${sz}px;height:${sz}px;font-size:${seq > 9 ? 12 : 14}px;opacity:${active ? 1 : 0.75}">${
seq > 0 ? seq : ''
}${flagSvg}</div>`
});
})()
: L.divIcon({
className: '', className: '',
iconSize: [24, 30], iconSize: [sz, sz],
iconAnchor: [2, 30], iconAnchor: [sz / 2, sz / 2],
popupAnchor: [10, -25], // Lift popup above the flag, not just the larger 32px marker popupAnchor: [0, -28],
html: `<div class="cmark${isPulsing ? ' pulse' : ''}" style="width:24px;height:30px;opacity:${ html: `<div class="cmark is-rider-focused${isPulsing ? ' pulse' : ''
active ? 1 : 0.75 }" style="background:${color};width:${sz}px;height:${sz}px;font-size:${seq > 9 ? 12 : 14}px;opacity:${active ? 1 : 0.75}">${seq > 0 ? seq : ''
}">${flagSvg}</div>` }${flagSvg}</div>`
}); });
})()
: L.divIcon({
className: '',
iconSize: [24, 30],
iconAnchor: [2, 30],
popupAnchor: [10, -25], // Lift popup above the flag, not just the larger 32px marker
html: `<div class="cmark${isPulsing ? ' pulse' : ''}" style="width:24px;height:30px;opacity:${active ? 1 : 0.75
}">${flagSvg}</div>`
});
return ( return (
<Marker <Marker
@@ -3208,8 +3205,8 @@ const Dispatch = ({
lp && Number.isFinite(lp.lat) && Number.isFinite(lp.lon) lp && Number.isFinite(lp.lat) && Number.isFinite(lp.lon)
? [lp.lat, lp.lon] ? [lp.lat, lp.lon]
: hasValidPickup(activeLegOrder) : hasValidPickup(activeLegOrder)
? [toNum(pickupLat(activeLegOrder)), toNum(pickupLon(activeLegOrder))] ? [toNum(pickupLat(activeLegOrder)), toNum(pickupLon(activeLegOrder))]
: null; : null;
const dLat = toNum(activeLegOrder.droplat || activeLegOrder.deliverylat); const dLat = toNum(activeLegOrder.droplat || activeLegOrder.deliverylat);
const dLon = toNum(activeLegOrder.droplon || activeLegOrder.deliverylong); const dLon = toNum(activeLegOrder.droplon || activeLegOrder.deliverylong);
if (start && Number.isFinite(dLat) && Number.isFinite(dLon)) { if (start && Number.isFinite(dLat) && Number.isFinite(dLon)) {
@@ -3455,25 +3452,22 @@ const Dispatch = ({
setFocusedZone(null); setFocusedZone(null);
setBatchMenuOpen(false); setBatchMenuOpen(false);
}} }}
className={`w-full flex items-center justify-between gap-3 px-2.5 py-2 rounded-lg text-left transition-colors cursor-pointer ${ className={`w-full flex items-center justify-between gap-3 px-2.5 py-2 rounded-lg text-left transition-colors cursor-pointer ${isSelected
isSelected
? 'bg-slate-900 text-white font-bold' ? 'bg-slate-900 text-white font-bold'
: 'text-slate-700 hover:bg-slate-100/80 font-medium' : 'text-slate-700 hover:bg-slate-100/80 font-medium'
}`} }`}
> >
<div className="flex items-center gap-2.5 min-w-0"> <div className="flex items-center gap-2.5 min-w-0">
<span <span
className={`w-2 h-2 rounded-full shrink-0 ${ className={`w-2 h-2 rounded-full shrink-0 ${isSelected ? 'bg-emerald-400' : 'bg-slate-300'
isSelected ? 'bg-emerald-400' : 'bg-slate-300' }`}
}`}
/> />
<div className="min-w-0"> <div className="min-w-0">
<div className="text-xs truncate">{b.label}</div> <div className="text-xs truncate">{b.label}</div>
{b.range && ( {b.range && (
<div <div
className={`text-[10px] ${ className={`text-[10px] ${isSelected ? 'text-slate-300' : 'text-slate-400'
isSelected ? 'text-slate-300' : 'text-slate-400' }`}
}`}
> >
{b.range} {b.range}
</div> </div>
@@ -3481,13 +3475,12 @@ const Dispatch = ({
</div> </div>
</div> </div>
<span <span
className={`text-[10px] font-bold px-2 py-0.5 rounded-full shrink-0 ${ className={`text-[10px] font-bold px-2 py-0.5 rounded-full shrink-0 ${isSelected
isSelected
? 'bg-white/20 text-white' ? 'bg-white/20 text-white'
: count > 0 : count > 0
? 'bg-amber-100 text-amber-800' ? 'bg-amber-100 text-amber-800'
: 'bg-slate-100 text-slate-500' : 'bg-slate-100 text-slate-500'
}`} }`}
> >
{count} {count === 1 ? 'order' : 'orders'} {count} {count === 1 ? 'order' : 'orders'}
</span> </span>
@@ -4075,9 +4068,8 @@ const Dispatch = ({
) : ( ) : (
<div <div
id="body" id="body"
className={`${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${compareOpen ? 'compare-mode' : ''} ${ className={`${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${compareOpen ? 'compare-mode' : ''} ${compareOpen && compareDataCollapsed ? 'compare-data-collapsed' : ''
compareOpen && compareDataCollapsed ? 'compare-data-collapsed' : '' }`.trim()}
}`.trim()}
> >
<button <button
type="button" type="button"
@@ -4247,10 +4239,10 @@ const Dispatch = ({
// original sequence and confuse the operator. // original sequence and confuse the operator.
const displayOrders = isTimeMode const displayOrders = isTimeMode
? [...tOrders].sort((a, b) => { ? [...tOrders].sort((a, b) => {
const diff = completionTs(a) - completionTs(b); const diff = completionTs(a) - completionTs(b);
if (diff !== 0) return diff; if (diff !== 0) return diff;
return (a.step || 0) - (b.step || 0); return (a.step || 0) - (b.step || 0);
}) })
: tOrders; : tOrders;
return ( return (
<div key={tNum} className="trip-block"> <div key={tNum} className="trip-block">
@@ -4349,9 +4341,8 @@ const Dispatch = ({
</div> </div>
)} )}
<div <div
className={`zone-order-card ${canFocus ? 'clickable' : ''} ${isStopActive ? 'active' : ''} ${ className={`zone-order-card ${canFocus ? 'clickable' : ''} ${isStopActive ? 'active' : ''} ${isGoingOn ? 'going-on' : ''
isGoingOn ? 'going-on' : '' } ${isUndeliveredInTimeMode ? 'is-pending-time' : ''}`}
} ${isUndeliveredInTimeMode ? 'is-pending-time' : ''}`}
role={canFocus ? 'button' : undefined} role={canFocus ? 'button' : undefined}
tabIndex={canFocus ? 0 : undefined} tabIndex={canFocus ? 0 : undefined}
onClick={ onClick={
@@ -4564,9 +4555,8 @@ const Dispatch = ({
return ( return (
<div <div
key={o.orderid} key={o.orderid}
className={`zone-order-card ${canFocus ? 'clickable' : ''} ${isStopActive ? 'active' : ''} ${ className={`zone-order-card ${canFocus ? 'clickable' : ''} ${isStopActive ? 'active' : ''} ${isGoingOn ? 'going-on' : ''
isGoingOn ? 'going-on' : '' }`}
}`}
role={canFocus ? 'button' : undefined} role={canFocus ? 'button' : undefined}
tabIndex={canFocus ? 0 : undefined} tabIndex={canFocus ? 0 : undefined}
onClick={ onClick={
@@ -4760,9 +4750,8 @@ const Dispatch = ({
return ( return (
<div <div
key={o.orderid} key={o.orderid}
className={`zone-order-card ${canFocus ? 'clickable' : ''} ${isStopActive ? 'active' : ''} ${ className={`zone-order-card ${canFocus ? 'clickable' : ''} ${isStopActive ? 'active' : ''} ${isGoingOn ? 'going-on' : ''
isGoingOn ? 'going-on' : '' }`}
}`}
role={canFocus ? 'button' : undefined} role={canFocus ? 'button' : undefined}
tabIndex={canFocus ? 0 : undefined} tabIndex={canFocus ? 0 : undefined}
onClick={ onClick={
@@ -4902,10 +4891,10 @@ const Dispatch = ({
{viewMode === 'zones' {viewMode === 'zones'
? 'Zone dispatch' ? 'Zone dispatch'
: viewMode === 'kitchens' : viewMode === 'kitchens'
? 'Kitchen dispatch' ? 'Kitchen dispatch'
: isAllActiveView : isAllActiveView
? 'Active rider dispatch' ? 'Active rider dispatch'
: 'Rider dispatch'} : 'Rider dispatch'}
</div> </div>
)} )}
<div id="rider-cards"> <div id="rider-cards">
@@ -5232,28 +5221,28 @@ const Dispatch = ({
if (!iconEntry || iconEntry.sig !== iconSig) { if (!iconEntry || iconEntry.sig !== iconSig) {
const icon = isAllActiveView const icon = isAllActiveView
? L.divIcon({ ? L.divIcon({
className: '', className: '',
iconSize: [160, 44], iconSize: [160, 44],
iconAnchor: [22, 22], iconAnchor: [22, 22],
popupAnchor: [0, -22], popupAnchor: [0, -22],
html: `<div class="live-rider-bike ${isActive ? 'is-active' : 'is-idle'}" style="--pin-color:${pinColor}"> html: `<div class="live-rider-bike ${isActive ? 'is-active' : 'is-idle'}" style="--pin-color:${pinColor}">
<span class="live-rider-bike-pulse"></span> <span class="live-rider-bike-pulse"></span>
<span class="live-rider-bike-badge"> <span class="live-rider-bike-badge">
<svg viewBox="0 0 24 24" width="20" height="20" fill="#fff" aria-hidden="true"><path d="M19.44 9.03 15.41 5H11v2h3.59l2 2H5c-2.8 0-5 2.2-5 5s2.2 5 5 5c2.46 0 4.45-1.69 4.9-4h1.65l2.77-2.77c-.21.54-.32 1.14-.32 1.77 0 2.76 2.24 5 5 5s5-2.24 5-5c0-2.65-2.06-4.77-4.66-4.97ZM7.82 15C7.4 16.15 6.28 17 5 17c-1.63 0-3-1.37-3-3s1.37-3 3-3c1.28 0 2.4.85 2.82 2H5v2h2.82ZM19 17c-1.63 0-3-1.37-3-3s1.37-3 3-3 3 1.37 3 3-1.37 3-3 3Z"/></svg> <svg viewBox="0 0 24 24" width="20" height="20" fill="#fff" aria-hidden="true"><path d="M19.44 9.03 15.41 5H11v2h3.59l2 2H5c-2.8 0-5 2.2-5 5s2.2 5 5 5c2.46 0 4.45-1.69 4.9-4h1.65l2.77-2.77c-.21.54-.32 1.14-.32 1.77 0 2.76 2.24 5 5 5s5-2.24 5-5c0-2.65-2.06-4.77-4.66-4.97ZM7.82 15C7.4 16.15 6.28 17 5 17c-1.63 0-3-1.37-3-3s1.37-3 3-3c1.28 0 2.4.85 2.82 2H5v2h2.82ZM19 17c-1.63 0-3-1.37-3-3s1.37-3 3-3 3 1.37 3 3-1.37 3-3 3Z"/></svg>
</span> </span>
<span class="live-rider-bike-label">${safeName}${safeOrder ? ` <span>#${safeOrder}</span>` : ''}</span> <span class="live-rider-bike-label">${safeName}${safeOrder ? ` <span>#${safeOrder}</span>` : ''}</span>
</div>` </div>`
}) })
: L.divIcon({ : L.divIcon({
className: '', className: '',
iconSize: [140, 56], iconSize: [140, 56],
iconAnchor: [12, 41], iconAnchor: [12, 41],
popupAnchor: [58, -40], popupAnchor: [58, -40],
html: `<div class="live-rider-pin" style="--pin-color:${pinColor}"> html: `<div class="live-rider-pin" style="--pin-color:${pinColor}">
<div class="live-rider-pin-marker"></div> <div class="live-rider-pin-marker"></div>
<div class="live-rider-pin-label">${safeName}${safeOrder ? ` <span>#${safeOrder}</span>` : ''}</div> <div class="live-rider-pin-label">${safeName}${safeOrder ? ` <span>#${safeOrder}</span>` : ''}</div>
</div>` </div>`
}); });
iconEntry = { sig: iconSig, icon }; iconEntry = { sig: iconSig, icon };
liveIconCacheRef.current.set(r.id, iconEntry); liveIconCacheRef.current.set(r.id, iconEntry);
} }
@@ -5443,8 +5432,8 @@ const Dispatch = ({
`<span class="compare-step-pin-num">${t.sequenceStep}</span>` + `<span class="compare-step-pin-num">${t.sequenceStep}</span>` +
(isDelivered (isDelivered
? '<svg class="compare-step-pin-check" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' + ? '<svg class="compare-step-pin-check" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
'<path d="M2.5 6.5 L5 9 L9.5 3.5" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' + '<path d="M2.5 6.5 L5 9 L9.5 3.5" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' +
'</svg>' '</svg>'
: '') + : '') +
'</div>'; '</div>';
const sequenceIcon = L.divIcon({ const sequenceIcon = L.divIcon({
@@ -5462,15 +5451,15 @@ const Dispatch = ({
const showStartMarker = t.sequenceStep === 1; const showStartMarker = t.sequenceStep === 1;
const startIconEl = showStartMarker const startIconEl = showStartMarker
? L.divIcon({ ? L.divIcon({
className: '', className: '',
iconSize: [40, 40], iconSize: [40, 40],
iconAnchor: [20, 20], iconAnchor: [20, 20],
html: html:
`<div class="compare-start-pin" style="--pin-color:${color}">` + `<div class="compare-start-pin" style="--pin-color:${color}">` +
'<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' + '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
'<path d="M19 6h-2c0-2.76-2.24-5-5-5S7 3.24 7 6H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-7-3c1.66 0 3 1.34 3 3H9c0-1.66 1.34-3 3-3zm0 10c-2.76 0-5-2.24-5-5h2c0 1.66 1.34 3 3 3s3-1.34 3-3h2c0 2.76-2.24 5-5 5z" fill="currentColor"/>' + '<path d="M19 6h-2c0-2.76-2.24-5-5-5S7 3.24 7 6H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-7-3c1.66 0 3 1.34 3 3H9c0-1.66 1.34-3 3-3zm0 10c-2.76 0-5-2.24-5-5h2c0 1.66 1.34 3 3 3s3-1.34 3-3h2c0 2.76-2.24 5-5 5z" fill="currentColor"/>' +
'</svg></div>' '</svg></div>'
}) })
: null; : null;
const handleStepClick = (e) => { const handleStepClick = (e) => {
@@ -5565,33 +5554,33 @@ const Dispatch = ({
eventHandlers={ eventHandlers={
orderForTrack orderForTrack
? { ? {
// Click-only: hovering the numbered pin does // Click-only: hovering the numbered pin does
// nothing. Earlier the modal opened on mouseover, // nothing. Earlier the modal opened on mouseover,
// which made the rich order card flash open while // which made the rich order card flash open while
// panning the map and blocked the operator from // panning the map and blocked the operator from
// reading other steps in the trail. Click toggles // reading other steps in the trail. Click toggles
// the modal — same `pinnedPopupsRef` pattern the // the modal — same `pinnedPopupsRef` pattern the
// planned-route marker uses, so the modal stays // planned-route marker uses, so the modal stays
// pinned (doesn't auto-close on mouse leave) until // pinned (doesn't auto-close on mouse leave) until
// the user clicks the pin again, the × button, or // the user clicks the pin again, the × button, or
// the map background. // the map background.
click: (e) => { click: (e) => {
if (e.originalEvent) e.originalEvent.stopPropagation(); if (e.originalEvent) e.originalEvent.stopPropagation();
setFocusedCompareStep((prev) => (prev === t.sequenceStep ? null : t.sequenceStep)); setFocusedCompareStep((prev) => (prev === t.sequenceStep ? null : t.sequenceStep));
if (popupHoverTimerRef.current) { if (popupHoverTimerRef.current) {
clearTimeout(popupHoverTimerRef.current); clearTimeout(popupHoverTimerRef.current);
popupHoverTimerRef.current = null; popupHoverTimerRef.current = null;
} }
const id = String(orderForTrack.orderid); const id = String(orderForTrack.orderid);
if (pinnedPopupsRef.current.has(id)) { if (pinnedPopupsRef.current.has(id)) {
pinnedPopupsRef.current.delete(id); pinnedPopupsRef.current.delete(id);
setCenterPopupOrder(null); setCenterPopupOrder(null);
} else { } else {
pinnedPopupsRef.current.add(id); pinnedPopupsRef.current.add(id);
setCenterPopupOrder(orderForTrack); setCenterPopupOrder(orderForTrack);
}
} }
} }
}
: { click: handleEndMarkerClick } : { click: handleEndMarkerClick }
} }
> >
@@ -6478,8 +6467,8 @@ const Dispatch = ({
{severity === 'warn' {severity === 'warn'
? 'Fleet imbalance detected — no feasible substitution' ? 'Fleet imbalance detected — no feasible substitution'
: severity === 'success' : severity === 'success'
? 'Fleet ran balanced — no reassignment needed' ? 'Fleet ran balanced — no reassignment needed'
: 'No action recommended'} : 'No action recommended'}
</span> </span>
</div> </div>
{rec?.reason && <div className="da-rec-desc">{rec.reason}</div>} {rec?.reason && <div className="da-rec-desc">{rec.reason}</div>}
@@ -6633,8 +6622,8 @@ const Dispatch = ({
r.free_window_minutes >= 30 r.free_window_minutes >= 30
? { background: '#ecfdf5', color: '#065f46', borderColor: '#a7f3d0' } ? { background: '#ecfdf5', color: '#065f46', borderColor: '#a7f3d0' }
: r.free_window_minutes > 0 : r.free_window_minutes > 0
? { background: '#fefce8', color: '#854d0e', borderColor: '#fde68a' } ? { background: '#fefce8', color: '#854d0e', borderColor: '#fde68a' }
: { background: '#fef2f2', color: '#991b1b', borderColor: '#fecaca' } : { background: '#fef2f2', color: '#991b1b', borderColor: '#fecaca' }
} }
title="Free window before the next batch — capacity available for a follow-up run" title="Free window before the next batch — capacity available for a follow-up run"
> >
@@ -6718,8 +6707,8 @@ const Dispatch = ({
imp > 0 imp > 0
? { background: '#dcfce7', color: '#166534' } ? { background: '#dcfce7', color: '#166534' }
: imp < 0 : imp < 0
? { background: '#fee2e2', color: '#991b1b' } ? { background: '#fee2e2', color: '#991b1b' }
: { background: '#f1f5f9', color: '#475569' } : { background: '#f1f5f9', color: '#475569' }
} }
> >
{imp > 0 ? '+' : ''} {imp > 0 ? '+' : ''}