updates on the active apge
This commit is contained in:
195
src/pages/nearle/dispatch/ActiveSection.js
Normal file
195
src/pages/nearle/dispatch/ActiveSection.js
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
MdTwoWheeler,
|
||||||
|
MdLocationOn,
|
||||||
|
MdRestaurant,
|
||||||
|
MdMyLocation,
|
||||||
|
MdAccessTime,
|
||||||
|
MdInventory2
|
||||||
|
} from 'react-icons/md';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { CircularProgress } from '@mui/material';
|
||||||
|
import { getStatusStyle, getActiveOrder } from './dispatchShared';
|
||||||
|
import { OpenToast } from 'components/nearle_components/OpenToast';
|
||||||
|
|
||||||
|
const ActiveSection = ({
|
||||||
|
visibleRiders,
|
||||||
|
riders,
|
||||||
|
focusedStop,
|
||||||
|
handleRiderFocus,
|
||||||
|
setFocusedStop,
|
||||||
|
calculateEstMeters,
|
||||||
|
getRiderColor,
|
||||||
|
formatMeters,
|
||||||
|
isLoading,
|
||||||
|
onMapZoom
|
||||||
|
}) => {
|
||||||
|
// Sort by live distance ascending (closest drop-off first; null/unknown last)
|
||||||
|
const activeDeliveries = visibleRiders
|
||||||
|
.map((r) => getActiveOrder(r.orders))
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter((o) => String(o.orderstatus || '').toLowerCase() === 'active')
|
||||||
|
.sort((a, b) => {
|
||||||
|
const mA = calculateEstMeters(a.rider_id, a) ?? Infinity;
|
||||||
|
const mB = calculateEstMeters(b.rider_id, b) ?? Infinity;
|
||||||
|
return mA - mB;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toast notifications: detect status transitions across renders
|
||||||
|
const prevStatusMapRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentMap = {};
|
||||||
|
visibleRiders.forEach((r) => {
|
||||||
|
(r.orders || []).forEach((o) => {
|
||||||
|
currentMap[String(o.orderid)] = {
|
||||||
|
status: String(o.orderstatus || '').toLowerCase(),
|
||||||
|
riderName: o.rider_name || o.ridername || 'Rider',
|
||||||
|
customer: o.deliverycustomer || o.customername || `Order #${o.orderid}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const prev = prevStatusMapRef.current;
|
||||||
|
if (prev !== null) {
|
||||||
|
Object.entries(currentMap).forEach(([oid, cur]) => {
|
||||||
|
const old = prev[oid];
|
||||||
|
if (!old) {
|
||||||
|
if (cur.status === 'active') {
|
||||||
|
OpenToast(`🛵 ${cur.riderName} is now delivering to ${cur.customer}`, 'success', 3000);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (old.status !== 'active' && cur.status === 'active') {
|
||||||
|
OpenToast(`🛵 ${cur.riderName} is now delivering to ${cur.customer}`, 'success', 3000);
|
||||||
|
}
|
||||||
|
if (old.status === 'active' && cur.status === 'delivered') {
|
||||||
|
OpenToast(`📍 ${cur.riderName} has reached ${cur.customer}'s location`, 'info', 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
prevStatusMapRef.current = currentMap;
|
||||||
|
}, [visibleRiders]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="empty-slot" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', minHeight: '200px' }}>
|
||||||
|
<CircularProgress size={30} style={{ color: '#7b1fa2', marginBottom: '16px' }} />
|
||||||
|
<div className="empty-slot-title">Loading active deliveries...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeDeliveries.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="empty-slot">
|
||||||
|
<div className="empty-slot-icon">
|
||||||
|
<MdInventory2 />
|
||||||
|
</div>
|
||||||
|
<div className="empty-slot-title">No active deliveries</div>
|
||||||
|
<div className="empty-slot-sub">
|
||||||
|
No deliveries are currently in progress for this slot
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderActiveDeliveryCard = (o, i) => {
|
||||||
|
const rid = o.rider_id;
|
||||||
|
const rider = riders.find((r) => String(r.id) === String(rid));
|
||||||
|
const color = getRiderColor(rid);
|
||||||
|
const statusStyle = getStatusStyle(o.orderstatus);
|
||||||
|
const lat = parseFloat(o.droplat || o.deliverylat);
|
||||||
|
const lon = parseFloat(o.droplon || o.deliverylong);
|
||||||
|
const canFocus = Number.isFinite(lat) && Number.isFinite(lon);
|
||||||
|
const estMeters = calculateEstMeters(rid, o);
|
||||||
|
const customer = o.deliverycustomer || o.customername || `Order #${o.orderid}`;
|
||||||
|
const dropArea = o.deliverysuburb || o.deliveryaddress || o.zone_name || '';
|
||||||
|
const riderName = o.rider_name || o.ridername || 'Unassigned';
|
||||||
|
const isActive = canFocus && focusedStop && String(focusedStop.orderid) === String(o.orderid);
|
||||||
|
const initials =
|
||||||
|
riderName
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 2)
|
||||||
|
.map((w) => w[0])
|
||||||
|
.join('')
|
||||||
|
.toUpperCase() || '•';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={o.orderid}
|
||||||
|
className={`adcard${isActive ? ' is-active' : ''}`}
|
||||||
|
style={{ '--ad-accent': color }}
|
||||||
|
onClick={() => {
|
||||||
|
if (canFocus && onMapZoom) onMapZoom(lat, lon);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="adcard-top">
|
||||||
|
<div className="adcard-avatar" style={{ background: color }}>{initials}</div>
|
||||||
|
<div className="adcard-titles">
|
||||||
|
<div className="adcard-rider" title={riderName}>
|
||||||
|
<MdTwoWheeler style={{ fontSize: 13, flexShrink: 0 }} />
|
||||||
|
<span className="adcard-tx">{riderName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="adcard-customer" title={customer}>{customer}</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: '4px' }}>
|
||||||
|
<span
|
||||||
|
className="adcard-status"
|
||||||
|
style={{ background: `${statusStyle.bg}1a`, color: statusStyle.bg }}
|
||||||
|
title={statusStyle.label}
|
||||||
|
>
|
||||||
|
{statusStyle.label}
|
||||||
|
</span>
|
||||||
|
{o.deliverytime && (
|
||||||
|
<span className="adcard-time" style={{ fontSize: '11px', color: '#64748b' }}>
|
||||||
|
{dayjs(o.deliverytime).isValid() ? dayjs(o.deliverytime).format('HH:mm:ss') : String(o.deliverytime)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{dropArea && (
|
||||||
|
<div className="adcard-addr">
|
||||||
|
<span className="adcard-ic"><MdLocationOn /></span>
|
||||||
|
<span className="adcard-tx adcard-addr-tx" title={dropArea}>{dropArea}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="adcard-foot">
|
||||||
|
{o.pickupcustomer ? (
|
||||||
|
<span className="adcard-pickup" title={o.pickupcustomer}>
|
||||||
|
<span className="adcard-ic"><MdRestaurant /></span>
|
||||||
|
<span className="adcard-tx">{o.pickupcustomer}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
<span className="adcard-metrics">
|
||||||
|
{estMeters !== null && (
|
||||||
|
<>
|
||||||
|
<span className="adcard-m adcard-m-eta" title="Distance to drop">
|
||||||
|
<span className="adcard-ic"><MdMyLocation /></span>
|
||||||
|
{formatMeters(estMeters)}
|
||||||
|
</span>
|
||||||
|
<span className="adcard-m adcard-m-time" title="Estimated time to drop">
|
||||||
|
<span className="adcard-ic"><MdAccessTime /></span>
|
||||||
|
{(() => {
|
||||||
|
const etaMin = estMeters / 1000 / 20 * 60;
|
||||||
|
return etaMin < 1 ? '< 1 min' : `${Math.ceil(etaMin)} min`;
|
||||||
|
})()}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className="adcard-list">{activeDeliveries.map(renderActiveDeliveryCard)}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActiveSection;
|
||||||
@@ -2193,9 +2193,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dispatch-container .adcard-customer {
|
.dispatch-container .adcard-customer {
|
||||||
font-size: 14.5px;
|
font-size: 11.5px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
color: var(--text);
|
color: var(--text-muted);
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -2206,9 +2206,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
font-size: 12px;
|
font-size: 14.5px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: var(--text-muted);
|
color: var(--text);
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import {
|
|||||||
stepColor
|
stepColor
|
||||||
} from './dispatchShared';
|
} from './dispatchShared';
|
||||||
import CompareDataPanel from './CompareDataPanel';
|
import CompareDataPanel from './CompareDataPanel';
|
||||||
|
import ActiveSection from './ActiveSection';
|
||||||
import './Dispatch.css';
|
import './Dispatch.css';
|
||||||
import logger from '../../../utils/logger';
|
import logger from '../../../utils/logger';
|
||||||
|
|
||||||
@@ -482,8 +483,13 @@ const getStableRiderColor = (id) => {
|
|||||||
// extracted CompareDataPanel component can import them without forcing
|
// extracted CompareDataPanel component can import them without forcing
|
||||||
// a circular dependency on Dispatch.js.
|
// a circular dependency on Dispatch.js.
|
||||||
|
|
||||||
const MapController = ({ focusedItem, viewMode, orders, kitchens, locationKey, extraPoints }) => {
|
const MapController = ({ focusedItem, viewMode, orders, kitchens, locationKey, extraPoints, flyTo }) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!flyTo) return;
|
||||||
|
map.setView([flyTo.lat, flyTo.lon], 16, { animate: true, duration: 0.6 });
|
||||||
|
}, [flyTo, map]);
|
||||||
// Last fit signature. We only call fitBounds when this changes — otherwise
|
// Last fit signature. We only call fitBounds when this changes — otherwise
|
||||||
// every parent render (data refetch, sidebar tick, etc.) would refit the
|
// every parent render (data refetch, sidebar tick, etc.) would refit the
|
||||||
// map and snap it back mid-drag, which felt like the map was un-draggable.
|
// map and snap it back mid-drag, which felt like the map was un-draggable.
|
||||||
@@ -831,6 +837,8 @@ const Dispatch = ({
|
|||||||
const [focusedZone, setFocusedZone] = useState(null);
|
const [focusedZone, setFocusedZone] = useState(null);
|
||||||
// Single delivery stop pinned by clicking its sidebar row — overrides the rider's full-route bounds on the map.
|
// Single delivery stop pinned by clicking its sidebar row — overrides the rider's full-route bounds on the map.
|
||||||
const [focusedStop, setFocusedStop] = useState(null);
|
const [focusedStop, setFocusedStop] = useState(null);
|
||||||
|
// Fly-to target set by ActiveSection card clicks — zooms the map without touching sidebar or popup.
|
||||||
|
const [activeFlyTo, setActiveFlyTo] = useState(null);
|
||||||
// How the stops inside each trip block are ordered in the focused-rider sidebar:
|
// How the stops inside each trip block are ordered in the focused-rider sidebar:
|
||||||
// 'planned' → the dispatched route order (by step) — the default.
|
// 'planned' → the dispatched route order (by step) — the default.
|
||||||
// 'time' → re-sorted by when each delivery was actually completed, so the
|
// 'time' → re-sorted by when each delivery was actually completed, so the
|
||||||
@@ -2541,96 +2549,6 @@ const Dispatch = ({
|
|||||||
// operators monitor the work, not the people. Clicking focuses the owning
|
// operators monitor the work, not the people. Clicking focuses the owning
|
||||||
// rider and centers the map on the drop (which, per the Active-view rules,
|
// rider and centers the map on the drop (which, per the Active-view rules,
|
||||||
// collapses to that rider's single active leg + drop pin).
|
// collapses to that rider's single active leg + drop pin).
|
||||||
const renderActiveDeliveryCard = (o, i) => {
|
|
||||||
const rid = o.rider_id;
|
|
||||||
const rider = riders.find((r) => String(r.id) === String(rid));
|
|
||||||
const color = getRiderColor(rid);
|
|
||||||
const statusStyle = getStatusStyle(o.orderstatus);
|
|
||||||
const lat = parseFloat(o.droplat || o.deliverylat);
|
|
||||||
const lon = parseFloat(o.droplon || o.deliverylong);
|
|
||||||
const canFocus = Number.isFinite(lat) && Number.isFinite(lon);
|
|
||||||
const estMeters = calculateEstMeters(rid, o);
|
|
||||||
const customer = o.deliverycustomer || o.customername || `Order #${o.orderid}`;
|
|
||||||
const dropArea = o.deliverysuburb || o.deliveryaddress || o.zone_name || '';
|
|
||||||
const riderName = o.rider_name || o.ridername || 'Unassigned';
|
|
||||||
// This card is "active" (popped on the map) when its drop is the focused stop.
|
|
||||||
const isActive = canFocus && focusedStop && String(focusedStop.orderid) === String(o.orderid);
|
|
||||||
// Up-to-two-letter rider initials for the avatar.
|
|
||||||
const initials =
|
|
||||||
riderName
|
|
||||||
.split(/\s+/)
|
|
||||||
.filter(Boolean)
|
|
||||||
.slice(0, 2)
|
|
||||||
.map((w) => w[0])
|
|
||||||
.join('')
|
|
||||||
.toUpperCase() || '•';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={o.orderid}
|
|
||||||
className={`adcard${isActive ? ' is-active' : ''}`}
|
|
||||||
style={{ '--ad-accent': color, animationDelay: `${i * 0.05}s` }}
|
|
||||||
onClick={() => {
|
|
||||||
if (rider) handleRiderFocus(rider);
|
|
||||||
if (canFocus) setFocusedStop({ orderid: o.orderid, lat, lon });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="adcard-top">
|
|
||||||
<div className="adcard-avatar" style={{ background: color }}>{initials}</div>
|
|
||||||
<div className="adcard-titles">
|
|
||||||
<div className="adcard-customer" title={customer}>{customer}</div>
|
|
||||||
<div className="adcard-rider" title={riderName}>
|
|
||||||
<MdTwoWheeler style={{ fontSize: 13, flexShrink: 0 }} />
|
|
||||||
<span className="adcard-tx">{riderName}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
className="adcard-status"
|
|
||||||
style={{ background: `${statusStyle.bg}1a`, color: statusStyle.bg }}
|
|
||||||
title={statusStyle.label}
|
|
||||||
>
|
|
||||||
{statusStyle.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{dropArea && (
|
|
||||||
<div className="adcard-addr">
|
|
||||||
<span className="adcard-ic"><MdLocationOn /></span>
|
|
||||||
<span className="adcard-tx adcard-addr-tx" title={dropArea}>{dropArea}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="adcard-foot">
|
|
||||||
{o.pickupcustomer ? (
|
|
||||||
<span className="adcard-pickup" title={o.pickupcustomer}>
|
|
||||||
<span className="adcard-ic"><MdRestaurant /></span>
|
|
||||||
<span className="adcard-tx">{o.pickupcustomer}</span>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
<span className="adcard-metrics">
|
|
||||||
{estMeters !== null && (
|
|
||||||
<>
|
|
||||||
<span className="adcard-m adcard-m-eta" title="Distance to drop">
|
|
||||||
<span className="adcard-ic"><MdMyLocation /></span>
|
|
||||||
{formatMeters(estMeters)}
|
|
||||||
</span>
|
|
||||||
<span className="adcard-m adcard-m-time" title="Estimated time to drop">
|
|
||||||
<span className="adcard-ic"><MdAccessTime /></span>
|
|
||||||
{(() => {
|
|
||||||
const etaMin = estMeters / 1000 / 20 * 60;
|
|
||||||
return etaMin < 1 ? '< 1 min' : `${Math.ceil(etaMin)} min`;
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns true when the order's centered popup should stay open even after
|
// Returns true when the order's centered popup should stay open even after
|
||||||
// the cursor leaves the marker: either explicitly pinned via click, or the
|
// the cursor leaves the marker: either explicitly pinned via click, or the
|
||||||
// matching compare-step is focused (so clicking a step in the right panel
|
// matching compare-step is focused (so clicking a step in the right panel
|
||||||
@@ -2750,6 +2668,17 @@ const Dispatch = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{o.step != null && (
|
||||||
|
<div className="pu-detail">
|
||||||
|
<div className="pu-detail-icon"><MdMyLocation /></div>
|
||||||
|
<div className="pu-detail-body">
|
||||||
|
<div className="pu-detail-label">Step</div>
|
||||||
|
<div className="pu-detail-value">
|
||||||
|
{o.trip_number != null && o.trip_number > 1 ? `Trip ${o.trip_number} · ` : ''}#{o.step}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(o.actualkms != null || (!isDelivered && o.riderkms != null) || estMeters !== null) && (
|
{(o.actualkms != null || (!isDelivered && o.riderkms != null) || estMeters !== null) && (
|
||||||
@@ -4645,43 +4574,18 @@ const Dispatch = ({
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : isAllActiveView ? (
|
) : isAllActiveView ? (
|
||||||
// Active view: list exactly ONE card per active rider — the
|
<ActiveSection
|
||||||
// single in-progress leg (getActiveOrder) the map draws a
|
visibleRiders={visibleRiders}
|
||||||
// route + destination flag for. Driving the list off the
|
riders={riders}
|
||||||
// same `visibleRiders` set the map uses keeps the sidebar
|
focusedStop={focusedStop}
|
||||||
// and the map in lock-step (same count, same deliveries).
|
handleRiderFocus={handleRiderFocus}
|
||||||
(() => {
|
setFocusedStop={setFocusedStop}
|
||||||
if (shouldFetchLive && liveIsFetching && visibleRiders.length === 0) {
|
calculateEstMeters={calculateEstMeters}
|
||||||
return (
|
getRiderColor={getRiderColor}
|
||||||
<div className="empty-slot" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', minHeight: '200px' }}>
|
formatMeters={formatMeters}
|
||||||
<CircularProgress size={30} style={{ color: '#7b1fa2', marginBottom: '16px' }} />
|
isLoading={shouldFetchLive && liveIsFetching && visibleRiders.length === 0}
|
||||||
<div className="empty-slot-title">Loading active deliveries...</div>
|
onMapZoom={(lat, lon) => setActiveFlyTo({ lat, lon, t: Date.now() })}
|
||||||
</div>
|
/>
|
||||||
);
|
|
||||||
}
|
|
||||||
const activeDeliveries = visibleRiders
|
|
||||||
.map((r) => getActiveOrder(r.orders))
|
|
||||||
.filter(Boolean)
|
|
||||||
.sort((a, b) =>
|
|
||||||
String(a.rider_name || a.ridername || '').localeCompare(
|
|
||||||
String(b.rider_name || b.ridername || '')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (activeDeliveries.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="empty-slot">
|
|
||||||
<div className="empty-slot-icon">
|
|
||||||
<MdInventory2 />
|
|
||||||
</div>
|
|
||||||
<div className="empty-slot-title">No active deliveries</div>
|
|
||||||
<div className="empty-slot-sub">
|
|
||||||
No deliveries are currently in progress for this slot
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <div className="adcard-list">{activeDeliveries.map(renderActiveDeliveryCard)}</div>;
|
|
||||||
})()
|
|
||||||
) : (
|
) : (
|
||||||
visibleRiders.map(renderRiderCard)
|
visibleRiders.map(renderRiderCard)
|
||||||
)}
|
)}
|
||||||
@@ -4708,7 +4612,7 @@ const Dispatch = ({
|
|||||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution='© OpenStreetMap contributors' />
|
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution='© OpenStreetMap contributors' />
|
||||||
<ZoomControl position="bottomright" />
|
<ZoomControl position="bottomright" />
|
||||||
{compareOpen && <CaptureMap targetRef={leftMapRef} />}
|
{compareOpen && <CaptureMap targetRef={leftMapRef} />}
|
||||||
<MapController focusedItem={compareFocusItem || ((focusedRider || focusedKitchen) && focusedStop) || focusedRider || focusedKitchen || focusedZone} viewMode={viewMode} orders={allViewOrders} kitchens={kitchens} locationKey={selectedAppLocationId} extraPoints={allViewLivePoints} />
|
<MapController focusedItem={compareFocusItem || ((focusedRider || focusedKitchen) && focusedStop) || focusedRider || focusedKitchen || focusedZone} viewMode={viewMode} orders={allViewOrders} kitchens={kitchens} locationKey={selectedAppLocationId} extraPoints={allViewLivePoints} flyTo={activeFlyTo} />
|
||||||
{kitchens
|
{kitchens
|
||||||
.filter(k => Number.isFinite(k.lat) && Number.isFinite(k.lon))
|
.filter(k => Number.isFinite(k.lat) && Number.isFinite(k.lon))
|
||||||
.filter(k => !focusedRider || k.riders.has(focusedRider.id))
|
.filter(k => !focusedRider || k.riders.has(focusedRider.id))
|
||||||
|
|||||||
Reference in New Issue
Block a user