diff --git a/db/migrations/20260601000020_normalize_assigned_city.sql b/db/migrations/20260601000020_normalize_assigned_city.sql new file mode 100644 index 0000000..4b4be9d --- /dev/null +++ b/db/migrations/20260601000020_normalize_assigned_city.sql @@ -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. diff --git a/web/fleet-core.js b/web/fleet-core.js index 658a23e..c5ad26b 100644 --- a/web/fleet-core.js +++ b/web/fleet-core.js @@ -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': [ diff --git a/web/index-live.html b/web/index-live.html index 9c57421..a8409b7 100644 --- a/web/index-live.html +++ b/web/index-live.html @@ -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 @@
Fleet now
-
-
SLOs
-
-
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);