UI tweaks + city case fix
Some checks are pending
build / lint-test (push) Waiting to run
build / build-push (push) Blocked by required conditions

- Migration 20: collapse `Nairobi`/`nairobi` in domain.vehicles → 'nairobi'
- Remove the SLO panel from the top band (filter + tile rows stay)
- Offline vehicles render as solid grey instead of dim-cost-centre tint;
  opacity now only differentiates moving (1.0) vs parked (0.75) vs
  offline (0.55) so colour carries identity + state cleanly
This commit is contained in:
kianiadee 2026-05-27 22:07:03 +03:00
parent 20958c0293
commit de34103f18
3 changed files with 28 additions and 14 deletions

View file

@ -0,0 +1,16 @@
-- migrate:up
--
-- Data hygiene: 1 vehicle had `assigned_city = 'Nairobi'` while the other
-- 54 had `'nairobi'`. Collapse to lower-case so the filter dropdown shows
-- a single canonical option. The projector doesn't set assigned_city (it
-- comes from manual edits / roster imports) so no other code change is
-- needed to prevent recurrence.
UPDATE domain.vehicles
SET assigned_city = trim(lower(assigned_city)),
updated_at = now()
WHERE assigned_city IS NOT NULL
AND assigned_city != trim(lower(assigned_city));
-- migrate:down
-- No-op: re-introducing inconsistent casing would be a regression.

View file

@ -141,16 +141,19 @@ export function initMap(elementId, opts = {}) {
15, 13,
18, 20,
],
// Always tint by cost-centre colour — operational state is shown
// via opacity (moving=1, parked=0.7, offline=0.35) so colour stays
// a stable identity cue and the filter dropdown can double as a
// colour legend.
'circle-color': ['coalesce', ['get', 'cost_centre_color'], '#94a3b8'],
// Cost-centre tint for moving + parked; offline goes solid grey
// (no cost-centre signal worth showing when we haven't heard from
// the device). Opacity differentiates moving vs parked.
'circle-color': [
'case',
['==', ['get', 'operational_state'], 'offline'], '#9ca3af',
['coalesce', ['get', 'cost_centre_color'], '#94a3b8'],
],
'circle-opacity': [
'case',
['==', ['get', 'operational_state'], 'moving'], 1.0,
['==', ['get', 'operational_state'], 'parked'], 0.7,
0.35,
['==', ['get', 'operational_state'], 'parked'], 0.75,
0.55,
],
'circle-stroke-color': '#0b1220',
'circle-stroke-width': [

View file

@ -38,7 +38,7 @@
/* ─────────── top dashboard band: tiles + slos + filters ─────────── */
.top-band {
display: grid;
grid-template-columns: minmax(260px,auto) minmax(260px,1fr) minmax(280px,auto);
grid-template-columns: minmax(260px,1fr) minmax(280px,auto);
gap: 16px;
padding: 10px 16px;
background: var(--panel);
@ -210,10 +210,6 @@
<div class="band-title">Fleet now</div>
<div class="band-row" id="summary"></div>
</div>
<div class="band-block">
<div class="band-title">SLOs</div>
<div class="band-row" id="slos" style="flex-direction:column; gap:4px;"></div>
</div>
<div class="band-block">
<div class="band-title">Filters</div>
<div class="band-row" id="filters">
@ -258,7 +254,6 @@
const map = initMap('map');
const summaryEl = document.getElementById('summary');
const slosEl = document.getElementById('slos');
let currentFilters = {};
let activeSelection = { costCentres: [], cities: [] };
@ -266,7 +261,7 @@
try {
const params = Object.keys(currentFilters).length ? { filters: currentFilters } : {};
const payload = await apiFetch('/api/views/live', { params });
renderView(map, payload, { summaryRoot: summaryEl, sloRoot: slosEl });
renderView(map, payload, { summaryRoot: summaryEl });
// Repopulate filter dropdowns from the latest features
const features = (payload.geojson && payload.geojson.features) || [];
filters.updateOptions(features);