Trip dock: default date to vehicle's last-active day; UI review fixes
- Plain click defaults the trip date to the vehicle's most recent fix day (no more empty 'today' at night); manual date picks are respected. - Animated trip path/marker now use the trip's palette colour (matched the card). - Filter dropdown skips no-op rebuilds on the 15s refresh and won't rebuild under an open popover. - applyClientFilter: drop redundant length>0 guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d410216a4d
commit
558f095392
1 changed files with 35 additions and 5 deletions
|
|
@ -553,6 +553,7 @@ function _buildMultiSelect(root, { label, plural, showSwatch }) {
|
|||
|
||||
const listeners = [];
|
||||
let options = []; // [{value, color?}]
|
||||
let lastSig = ''; // skip no-op rebuilds on the 15s live refresh
|
||||
|
||||
const updateLabel = () => {
|
||||
const checked = [...optsRoot.querySelectorAll('input:checked')];
|
||||
|
|
@ -597,6 +598,12 @@ function _buildMultiSelect(root, { label, plural, showSwatch }) {
|
|||
|
||||
return {
|
||||
setOptions(opts) {
|
||||
// Skip the DOM rebuild when the option set is unchanged (the 15s live
|
||||
// refresh re-sends the same list) or while the popover is open — both
|
||||
// would otherwise flicker the list / disrupt a mid-selection.
|
||||
const sig = opts.map(o => `${o.value}:${o.color || ''}`).join('|');
|
||||
if (sig === lastSig || !pop.hasAttribute('hidden')) return;
|
||||
lastSig = sig;
|
||||
options = opts;
|
||||
// Preserve current selections by value when re-rendering
|
||||
const prevChecked = new Set(
|
||||
|
|
@ -635,10 +642,10 @@ function _buildMultiSelect(root, { label, plural, showSwatch }) {
|
|||
export function applyClientFilter(map, { costCentres = [], cities = [] } = {}) {
|
||||
const layers = ['vehicles-circle', 'vehicles-arrow', 'vehicles-label'];
|
||||
const conds = [];
|
||||
if (costCentres.length > 0 && costCentres.length > 1) {
|
||||
if (costCentres.length > 1) {
|
||||
conds.push(['in', ['get', 'cost_centre'], ['literal', costCentres]]);
|
||||
}
|
||||
if (cities.length > 0 && cities.length > 1) {
|
||||
if (cities.length > 1) {
|
||||
conds.push(['in', ['get', 'assigned_city'], ['literal', cities]]);
|
||||
}
|
||||
const filter = conds.length === 0 ? null
|
||||
|
|
@ -703,6 +710,9 @@ let _tripAnimRAF = null;
|
|||
// vehicleId → { plate, driver, color, payload | null }
|
||||
const _selection = new Map();
|
||||
let _currentDate = null;
|
||||
// Once the user picks a date in the dock we stop auto-jumping to a vehicle's
|
||||
// last-active day on click, so date-browsing isn't fought by every click.
|
||||
let _dateUserPicked = false;
|
||||
|
||||
function _nextColor() {
|
||||
const used = new Set([..._selection.values()].map(v => v.color));
|
||||
|
|
@ -725,6 +735,7 @@ export function initTripPanel(map, panelRoot) {
|
|||
|
||||
els.close.addEventListener('click', () => _closeTripPanel(map, panelRoot, els));
|
||||
els.date.addEventListener('change', async () => {
|
||||
_dateUserPicked = true;
|
||||
_currentDate = els.date.value;
|
||||
// Re-fetch every currently-selected vehicle for the new date.
|
||||
for (const vid of [..._selection.keys()]) {
|
||||
|
|
@ -749,7 +760,15 @@ export function initTripPanel(map, panelRoot) {
|
|||
const driver = f.properties.driver_name || '';
|
||||
const multi = e.originalEvent.metaKey || e.originalEvent.ctrlKey || e.originalEvent.shiftKey;
|
||||
|
||||
if (!els.date.value) els.date.value = _todayEat();
|
||||
// Plain click → jump to the vehicle's most recent active day (its last
|
||||
// fix), so the dock never opens on an empty "today" before the fleet has
|
||||
// moved. Once the user has chosen a date, respect it. Multi-click keeps
|
||||
// the current date so compared vehicles share one day.
|
||||
if (!multi && !_dateUserPicked) {
|
||||
els.date.value = _eatDate(f.properties.occurred_at);
|
||||
} else if (!els.date.value) {
|
||||
els.date.value = _todayEat();
|
||||
}
|
||||
_currentDate = els.date.value;
|
||||
panelRoot.classList.add('open');
|
||||
panelRoot.setAttribute('aria-hidden', 'false');
|
||||
|
|
@ -1048,6 +1067,9 @@ function _showAndAnimateTrip(map, trip) {
|
|||
_clearSingleTripLayers(map);
|
||||
if (!trip.path || !trip.path.coordinates || trip.path.coordinates.length < 2) return;
|
||||
const coords = trip.path.coordinates;
|
||||
// Match the trip card's palette colour so the animated route reads as the
|
||||
// same trip the user clicked.
|
||||
const color = _tripColor(trip.trip_id);
|
||||
|
||||
map.addSource(TRIP_PATH_SOURCE, { type: 'geojson', data: trip.path });
|
||||
map.addLayer({
|
||||
|
|
@ -1056,7 +1078,7 @@ function _showAndAnimateTrip(map, trip) {
|
|||
source: TRIP_PATH_SOURCE,
|
||||
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
||||
paint: {
|
||||
'line-color': '#10b981',
|
||||
'line-color': color,
|
||||
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 2, 14, 4, 17, 6],
|
||||
'line-opacity': 0.85,
|
||||
},
|
||||
|
|
@ -1073,7 +1095,7 @@ function _showAndAnimateTrip(map, trip) {
|
|||
paint: {
|
||||
'circle-radius': ['interpolate', ['linear'], ['zoom'], 8, 4, 14, 8, 17, 12],
|
||||
'circle-color': '#ffffff',
|
||||
'circle-stroke-color': '#10b981',
|
||||
'circle-stroke-color': color,
|
||||
'circle-stroke-width': 3,
|
||||
},
|
||||
});
|
||||
|
|
@ -1161,6 +1183,14 @@ function _todayEat() {
|
|||
return eat.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
// EAT (UTC+3) calendar date of a UTC ISO timestamp, as YYYY-MM-DD. Falls back
|
||||
// to today when the timestamp is missing/unparseable.
|
||||
function _eatDate(iso) {
|
||||
const d = iso ? new Date(iso) : new Date();
|
||||
if (Number.isNaN(d.getTime())) return _todayEat();
|
||||
return new Date(d.getTime() + 3 * 3600 * 1000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function _formatTimeOnly(iso) {
|
||||
if (!iso) return '—';
|
||||
const d = new Date(iso);
|
||||
|
|
|
|||
Loading…
Reference in a new issue