);
});
};
const renderRoutes = () => {
if (isAnimating) {
return animatedSegments.map((s, i) => (
));
}
const routes = [];
const zoneRiderIds = focusedZone ? new Set(focusedZone.riders.map((zr) => String(zr.rider_id))) : null;
riders.forEach(r => {
const isActive = activeRiders.has(r.id);
if (focusedRider && focusedRider.id !== r.id) return;
if (focusedKitchen && !focusedKitchen.riders.has(r.id)) return;
if (zoneRiderIds && !zoneRiderIds.has(String(r.id))) return;
const rOrders = r.orders;
const trips = {};
rOrders.forEach(o => {
const t = o.trip_number || 1;
if (!trips[t]) trips[t] = [];
trips[t].push(o);
});
Object.entries(trips).forEach(([tNum, tOrders]) => {
// Filter orders by focused kitchen if active
const filteredTOrders = focusedKitchen
? tOrders.filter(o => (o.pickupcustomer || o.kitchen_key || 'Unknown').toLowerCase().trim() === focusedKitchen.id)
: tOrders;
if (filteredTOrders.length === 0) return;
const cacheKey = `${r.id}-${tNum}`;
const roadPoints = osrmRoutes[cacheKey];
const sorted = [...filteredTOrders].sort((a, b) => (a.step || 0) - (b.step || 0));
// Cache values:
// Array → OSRM road polyline (use it)
// false → OSRM permanently failed (draw aerial fallback so user sees something)
// null → request in-flight (DON'T draw anything yet — avoids the aerial flash)
// undefined → not yet requested (same as in-flight, wait)
const hasRoad = Array.isArray(roadPoints) && roadPoints.length >= 2;
const failed = roadPoints === false;
if (!hasRoad && !failed) return; // still loading — don't show aerial flash
const finalPoints = hasRoad ? roadPoints : buildTripPoints(sorted);
if (!finalPoints || finalPoints.length < 2) return;
const isKitchenView = (viewMode === 'kitchens' || focusedKitchen);
const opacity = isActive ? 1.0 : 0.1;
const weight = isKitchenView ? 7 : 6;
// Aerial fallback (OSRM permanently failed) is rendered dashed so it visually
// reads as an estimate vs. an actual routed road polyline.
const dashArray = failed ? '8 6' : undefined;
routes.push(
);
});
});
return routes;
};
const toggleRider = (rid) => {
const newActive = new Set(activeRiders);
if (newActive.has(rid)) newActive.delete(rid);
else newActive.add(rid);
setActiveRiders(newActive);
};
return (
{!embedded && (
D
Dispatch
{locationName &&
{locationName}
}
{/* Header right-cluster: profit/loss chip, total-orders pill, date picker.
Sits to the LEFT of the running clock so the operator sees fleet
health + current wave size + selected date together in one row. */}
{/* Sidebar header — replaces the top-bar meta line. Hidden when a specific
rider is focused, since the focused-rider view already shows that rider's
stats prominently (name + Orders/Distance/Profit tiles). */}
{!focusedRider && (
RIDER DISPATCH
{activeStats.label}
{activeStats.orders}
{activeStats.orders === 1 ? 'Order' : 'Orders'}
{activeStats.riders}
{activeStats.riders === 1 ? 'Rider' : 'Riders'}
)}
{/* Stats strip hidden for now — restore by removing this comment wrapper.
{/* Show the customer's delivery address rather than the kitchen's
pickup location (locationname/locationsuburb) — the kitchen is
already implied by the surrounding trip header. */}
{(o.deliveryaddress || o.deliverysuburb) && (
Order #{o.orderid} · Rider: {o.rider_name || o.ridername}
{/* In the By-Kitchen view we show the customer's delivery address,
not the kitchen's location (locationname/locationsuburb describe
the pickup spot, which is redundant when the kitchen is already
the focused context). */}
{(o.deliveryaddress || o.deliverysuburb) && (
)}
{/* Areas covered (delivery suburbs) — clicking a chip drills down to that
suburb's orders in an inline panel below the chip row. */}
{focusedZone.suburbs.length > 0 && (
{selectedSuburb && (() => {
// Suburb strings in the source data can have trailing whitespace
// and inconsistent casing (e.g. "uppilipalayam "). The tally that
// builds chip names keeps them as-is, so normalize BOTH sides to
// a trimmed lowercase key when filtering.
const norm = (v) => String(v || '').trim().toLowerCase();
const target = norm(selectedSuburb);
const suburbOrders = focusedZone.orders.filter((o) =>
norm(o.deliverysuburb || o.locationsuburb) === target
);
return (