diff --git a/web/fleet-core.js b/web/fleet-core.js index 3dce904..7fbbdf9 100644 --- a/web/fleet-core.js +++ b/web/fleet-core.js @@ -623,6 +623,26 @@ const SELECTION_PALETTE = [ '#84cc16', // lime ]; +// 12-colour palette for per-trip colouring inside a single vehicle's day. +// Cycles past 12 (rare). +const TRIP_PALETTE = [ + '#ef4444', // red + '#f97316', // orange + '#f59e0b', // amber + '#84cc16', // lime + '#10b981', // emerald + '#06b6d4', // cyan + '#3b82f6', // blue + '#8b5cf6', // violet + '#ec4899', // pink + '#facc15', // yellow + '#14b8a6', // teal + '#a855f7', // purple +]; +function _tripColor(tripId) { + return TRIP_PALETTE[((tripId - 1) % TRIP_PALETTE.length + TRIP_PALETTE.length) % TRIP_PALETTE.length]; +} + let _tripAnimRAF = null; // vehicleId → { plate, driver, color, payload | null } const _selection = new Map(); @@ -723,20 +743,27 @@ async function _fetchAndDraw(map, vid) { ); } catch (err) { entry.payload = { error: err.message || String(err), trips: [] }; - return; } - // Draw this vehicle's all-day routes as a static overlay. - _drawVehicleDayPaths(map, vid, entry.payload, entry.color); + // Drawing decision is mode-dependent and happens in _renderDock, + // so just stash the payload here. } -function _drawVehicleDayPaths(map, vid, payload, color) { +function _drawVehicleDayPaths(map, vid, payload, fixedColor) { + // If `fixedColor` is set (multi-mode), every trip uses that colour and the + // vehicle reads as one route. If null (single-mode), each trip is coloured + // independently from TRIP_PALETTE so the day's segments are visually + // distinguishable. _clearVehicleLayers(map, vid); const trips = (payload.trips || []).filter(t => t.path && t.path.coordinates); if (trips.length === 0) return; const features = trips.map(t => ({ type: 'Feature', geometry: t.path, - properties: { trip_id: t.trip_id, vehicle_id: vid }, + properties: { + trip_id: t.trip_id, + vehicle_id: vid, + color: fixedColor || _tripColor(t.trip_id), + }, })); const srcId = `vroute-${vid}`; const layerId = `vroute-line-${vid}`; @@ -750,7 +777,7 @@ function _drawVehicleDayPaths(map, vid, payload, color) { source: srcId, layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { - 'line-color': color, + 'line-color': ['get', 'color'], 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1.5, 14, 3, 17, 5], 'line-opacity': 0.85, }, @@ -773,12 +800,23 @@ function _renderDock(map, els) { return; } + // Redraw all polylines from scratch so we transition cleanly between + // single-mode (per-trip colours) and multi-mode (per-vehicle colour). + for (const [vid] of _selection) _clearVehicleLayers(map, vid); if (_selection.size === 1) { + const [[vid, entry]] = _selection; + if (entry.payload && !entry.payload.error) { + _drawVehicleDayPaths(map, vid, entry.payload, null); // per-trip palette + } _renderSingle(map, els); } else { + for (const [vid, entry] of _selection) { + if (entry.payload && !entry.payload.error) { + _drawVehicleDayPaths(map, vid, entry.payload, entry.color); + } + } _renderMulti(map, els); } - // Fit map to union of all selected vehicles' route bounds _fitSelectionBounds(map); } @@ -808,8 +846,10 @@ function _renderSingle(map, els) { els.list.innerHTML = '
No trips on this day.
'; return; } - els.list.innerHTML = trips.map(trip => ` -
+ els.list.innerHTML = trips.map(trip => { + const color = _tripColor(trip.trip_id); + return ` +
Trip ${trip.trip_id} ${_fmtNum(trip.distance_km, 1)} km @@ -823,7 +863,8 @@ function _renderSingle(map, els) { ${_reasonLabel(trip.end_reason)}
- `).join(''); + `; + }).join(''); for (const card of els.list.querySelectorAll('.trip-card')) { card.addEventListener('click', () => { els.list.querySelectorAll('.trip-card.selected') diff --git a/web/index-live.html b/web/index-live.html index c790495..8c58448 100644 --- a/web/index-live.html +++ b/web/index-live.html @@ -165,7 +165,9 @@ cursor: pointer; border-left: 3px solid transparent; } .trip-card:hover { background: #111a2c; } - .trip-card.selected { border-left-color: var(--accent); } + /* Don't change border-left on selection — that's the per-trip colour swatch. + Use an outline instead so the trip colour stays readable. */ + .trip-card.selected { background: #111a2c; outline: 2px solid var(--accent); outline-offset: 1px; } .trip-card-top { display: flex; justify-content: space-between; align-items: center; font-size: 12.5px; font-weight: 600; margin-bottom: 4px;