import React, { useEffect, useMemo, useState } from 'react';
import { MapContainer, TileLayer, Polyline, Marker, Popup, useMap } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { Box, IconButton, Stack, Typography, CircularProgress } from '@mui/material';
import { MdClose, MdRoute } from 'react-icons/md';
const containerStyle = { width: '100%', height: '100%' };
// Numbered step icon — brand red to match the planned-route polyline below.
// Drawn fresh per render as a data URL so the step number can be baked into
// the SVG without juggling external marker assets.
const stepIcon = (n, isFocused) => {
const size = isFocused ? 38 : 32;
const color = isFocused ? '#910E1D' : '#C01227';
const svg = encodeURIComponent(
``
);
return new L.Icon({
iconUrl: `data:image/svg+xml;charset=UTF-8,${svg}`,
iconSize: [size, size],
iconAnchor: [size / 2, size / 2]
});
};
// Fits the map to the planned path once both the map and data are ready.
// Re-runs whenever the route changes (different rider / date).
const FitBoundsController = ({ dropPath }) => {
const map = useMap();
useEffect(() => {
if (!dropPath.length) return;
if (dropPath.length === 1) {
map.setView(dropPath[0], 14);
} else {
map.fitBounds(dropPath, { padding: [48, 48] });
}
}, [dropPath, map]);
return null;
};
// Renders a single rider's PLANNED route for the date range chosen on the
// Riders Summary page. `details` is an ordered array of waypoints (sorted by
// the planning step number) shaped as:
// { step, orderid, deliveryid, customer, address,
// dropLat, dropLng, pickLat, pickLng, expectedTime }
// `dropLat/dropLng` are required; pickup coords are optional and rendered as
// faded pre-stops if present.
export default function RidersRoutes({ details, loading, riderName, dateRange, onClose }) {
const [focusedStep, setFocusedStep] = useState(null);
const [routePath, setRoutePath] = useState([]);
const [routeLoading, setRouteLoading] = useState(false);
// Step-pin coordinates in planning order — what the polyline connects.
const dropPath = useMemo(() => (details || []).map((d) => [d.dropLat, d.dropLng]), [details]);
// Resolve the rider's planned waypoints into an actual road-following path
// via OSRM. Without this, the polyline would cut across buildings / aerial
// lines — operators have no way to read the real route.
useEffect(() => {
if (dropPath.length < 2) {
setRoutePath([]);
return;
}
let cancelled = false;
(async () => {
setRouteLoading(true);
try {
const coords = dropPath.map(([lat, lng]) => `${lng},${lat}`).join(';');
const url = `https://router.project-osrm.org/route/v1/driving/${coords}?overview=full&geometries=geojson`;
const res = await fetch(url);
const data = await res.json();
if (!cancelled && data.routes?.length) {
const points = data.routes[0].geometry.coordinates.map(([lng, lat]) => [lat, lng]);
setRoutePath(points);
} else if (!cancelled) {
setRoutePath([]);
}
} catch (e) {
console.warn('OSRM route error:', e);
if (!cancelled) setRoutePath([]);
} finally {
if (!cancelled) setRouteLoading(false);
}
})();
return () => {
cancelled = true;
};
}, [dropPath]);
const headerBar = (
Planned route{riderName ? ` — ${riderName}` : ''}
{dateRange && {dateRange}}
{details && details.length > 0 && (
{details.length} {details.length === 1 ? 'stop' : 'stops'}
{routeLoading ? ' · resolving route…' : ''}
)}
{onClose && (
)}
);
// Loading state — parent is still fetching the planned route data.
if (loading) {
return (
{headerBar}
Loading planned route…
);
}
// Empty state — fetched but rider has no deliveries with drop coords in the
// selected window.
if (!details || details.length === 0) {
return (
{headerBar}
No planned route for this rider
There are no deliveries with drop coordinates assigned to this rider for the selected date range.
);
}
return (
{headerBar}
{routePath.length > 0 ? (
<>
{/* Translucent backdrop so the route stays legible on busy tiles. */}
{/* Road-following planned route from OSRM. */}
>
) : (
// Fallback while OSRM is in flight (or if it fails) — dashed
// straight-line skeleton between drop pins in step order.
)}
{details.map((d, i) => {
const stepNum = d.step || i + 1;
const isFocused = focusedStep === d.deliveryid;
return (
setFocusedStep(isFocused ? null : d.deliveryid)
}}
zIndexOffset={isFocused ? 1000 : stepNum}
>
setFocusedStep(null)}>
Step {stepNum} · {d.customer}
{d.address && {d.address}}
{d.expectedTime && (
ETA {String(d.expectedTime).slice(11, 16) || d.expectedTime}
)}
{d.orderid && Order #{d.orderid}}
);
})}
);
}