/* global window, React */ // ============================================================ // Route Map — simplified US outline with pickup/swap/delivery // pins, route line, and metadata panel. Used in: // - Routing feasibility tab (single trip line) // - Edit drawer · Orders & drops tab (preview move impact) // ============================================================ // Highly simplified US continental outline as an SVG path (Lng/Lat-like). // Coordinates are baked in a -130..-65 lng × 24..50 lat box. const US_OUTLINE_PATH = "M -124.7 48.4 L -124.4 42.0 L -124.2 40.4 L -124.0 38.0 L -122.5 37.5 L -120.6 34.5 L -117.3 32.5 L -114.7 32.7 L -111.0 31.3 L -108.2 31.8 L -106.5 31.8 L -103.3 28.9 L -99.0 26.2 L -97.4 25.9 L -97.1 27.5 L -94.0 29.7 L -89.2 30.0 L -88.0 30.4 L -85.0 29.7 L -83.5 28.0 L -81.1 25.2 L -80.1 25.8 L -80.5 27.9 L -81.5 30.7 L -81.3 31.9 L -80.8 32.7 L -78.9 33.7 L -77.0 35.0 L -75.5 35.5 L -75.5 37.5 L -76.0 38.5 L -76.5 39.0 L -75.0 39.5 L -74.0 40.5 L -74.0 41.5 L -71.0 41.6 L -71.0 42.0 L -70.6 41.7 L -70.0 41.9 L -70.6 42.6 L -70.7 43.6 L -69.0 44.2 L -67.0 44.5 L -67.0 45.2 L -68.0 47.0 L -69.0 47.5 L -71.0 45.0 L -75.0 45.0 L -76.0 44.0 L -78.0 43.6 L -79.5 43.3 L -83.0 41.7 L -83.5 41.7 L -83.0 42.3 L -82.5 43.0 L -82.4 44.0 L -82.1 45.0 L -83.5 45.8 L -83.6 46.0 L -84.4 45.9 L -84.7 46.0 L -85.0 46.0 L -86.8 41.7 L -87.5 41.7 L -88.0 47.2 L -90.2 46.7 L -90.4 47.7 L -94.7 49.4 L -95.2 49.0 L -96.0 49.0 L -123.3 49.0 L -124.7 48.4 Z"; function lngLatToSvg(lng, lat, w, h) { // bounding box: lng -125 to -66 (59 deg), lat 24 to 50 (26 deg) const padX = 30, padY = 18; const x = padX + ((lng + 125) / 59) * (w - padX * 2); // svg y grows down; lat 50 → top const y = padY + ((50 - lat) / 26) * (h - padY * 2); return { x, y }; } function RouteMap({ trip, width = 720, height = 360, showLegend = true, extraTrips = null, highlightDropId = null }) { const W = width, H = height; const from = (window.CITY_COORDS || {})[trip.route.from]; const to = (window.CITY_COORDS || {})[trip.route.to]; const wpKey = `${trip.route.from}|${trip.route.to}`; const waypoints = (window.ROUTE_WAYPOINTS || {})[wpKey] || []; if (!from || !to) { return
No route coordinates for {trip.route.from} → {trip.route.to}
; } const stops = [ { ...from, role: 'pickup', label: from.label, sub: 'Pickup · ' + (trip.routing?.nodes?.[0]?.time || '') }, ...waypoints.map(w => ({ ...w, role: 'swap', sub: '' })), { ...to, role: 'delivery', label: to.label, sub: 'Delivery · ' + (trip.routing?.nodes?.[2]?.time || '') }, ]; // Build the route polyline points const points = stops.map(s => lngLatToSvg(s.lng, s.lat, W, H)); const polyline = points.map(p => `${p.x},${p.y}`).join(' '); // Major cities for context const contextCities = [ { lng: -87.65, lat: 41.85, n: 'Chicago' }, { lng: -96.80, lat: 32.78, n: 'Dallas' }, { lng: -80.19, lat: 25.76, n: 'Miami' }, { lng: -118.24, lat: 34.05, n: 'LA' }, { lng: -122.33, lat: 47.61, n: 'Seattle' }, { lng: -74.00, lat: 40.71, n: 'NYC' }, { lng: -84.39, lat: 33.75, n: 'Atlanta' }, { lng: -104.99, lat: 39.74, n: 'Denver' }, ].filter(c => !stops.some(s => Math.abs(s.lng - c.lng) < 0.5 && Math.abs(s.lat - c.lat) < 0.5)); // Build US outline points const outlinePoints = US_OUTLINE_PATH.split(/\s+/).filter(t => /^-?\d/.test(t)).reduce((acc, tok, i, arr) => { if (i % 2 === 0) acc.push({ lng: parseFloat(tok), lat: parseFloat(arr[i + 1]) }); return acc; }, []); const outlineSvgPath = outlinePoints.map((p, i) => { const { x, y } = lngLatToSvg(p.lng, p.lat, W, H); return (i === 0 ? 'M' : 'L') + ' ' + x.toFixed(1) + ' ' + y.toFixed(1); }).join(' ') + ' Z'; // Render extra trips (e.g. consolidation / move-drops preview) const renderExtraTrip = (et, idx) => { const ef = (window.CITY_COORDS || {})[et.route.from]; const et2 = (window.CITY_COORDS || {})[et.route.to]; if (!ef || !et2) return null; const ek = `${et.route.from}|${et.route.to}`; const ewp = (window.ROUTE_WAYPOINTS || {})[ek] || []; const epts = [ef, ...ewp, et2].map(s => lngLatToSvg(s.lng, s.lat, W, H)); const epoly = epts.map(p => `${p.x},${p.y}`).join(' '); return ( {epts.map((p, i) => ( ))} ); }; return (
{/* Background grid (latitude lines) */} {[30, 35, 40, 45].map(lat => { const { y } = lngLatToSvg(-95, lat, W, H); return ; })} {[-120, -110, -100, -90, -80, -70].map(lng => { const { x } = lngLatToSvg(lng, 35, W, H); return ; })} {/* US outline */} {/* Context cities */} {contextCities.map((c, i) => { const p = lngLatToSvg(c.lng, c.lat, W, H); return ( {c.n} ); })} {/* Route polyline — primary */} {/* Extra trips (for move-drops preview etc.) */} {extraTrips && extraTrips.map(renderExtraTrip)} {/* Stops */} {stops.map((s, i) => { const p = points[i]; const isFirst = i === 0; const isLast = i === stops.length - 1; const color = isFirst ? '#2F7A4A' : isLast ? '#C13333' : '#E8893C'; const ring = isFirst ? '#1F5A35' : isLast ? '#6B1A1A' : '#A8631F'; const r = isFirst || isLast ? 8 : 5.5; const labelDx = isLast ? -8 : 12; const anchor = isLast ? 'end' : 'start'; return ( {(isFirst || isLast) && {isFirst ? 'P' : 'D'}} {s.label} {s.sub && ( {s.sub} )} ); })} {/* Trip metadata pinned bottom-left */} {trip.id} · {trip.mode || 'FTL'} {Math.round(haversine(from, to))} mi · {trip.routing?.hos?.drive || '—'} {stops.length - 2} swap{stops.length - 2 === 1 ? '' : 's'} · {trip.fleetLabel} {showLegend && (
Pickup Driver swap / fuel Delivery {extraTrips && Adjacent / preview}
)}
); } // great-circle distance, miles function haversine(a, b) { if (!a || !b) return 0; const toRad = (d) => d * Math.PI / 180; const R = 3959; // miles const dLat = toRad(b.lat - a.lat); const dLng = toRad(b.lng - a.lng); const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a.lat)) * Math.cos(toRad(b.lat)) * Math.sin(dLng / 2) ** 2; return 2 * R * Math.asin(Math.sqrt(h)); } Object.assign(window, { RouteMap });