From 169fc21f3600bf201f6b7133e03153c3fd2dac1c Mon Sep 17 00:00:00 2001 From: kianiadee Date: Sun, 7 Jun 2026 00:21:16 +0300 Subject: [PATCH] refactor(ui): slim content-height filter dock + searchable plate combobox Right dock was a full-height 270px panel with a tall 6-row plate multi-select and lots of dead space. Now a slim (~212px) content-height card: plate becomes a searchable combobox with removable chips (hidden + +
+
+ +
+
+ +
@@ -972,11 +1004,55 @@ function applyVehicleAutoFilter() { const metas = selected.map(p => VEHICLE_META.get(p)).filter(Boolean); setSelectValue('f-cc', collapse(metas.map(m => m.cost_centre)) ?? ''); setSelectValue('f-city', collapse(metas.map(m => m.assigned_city)) ?? ''); + renderPlateChips(); applyLiveFilters(); // reflect the plate selection on the live map immediately } function collapse(values) { if (!values.length) return null; const f = values[0]; return values.every(v => v === f) ? f : null; } function setSelectValue(id, value) { const el = document.getElementById(id); const opt = Array.from(el.options).find(o => o.value === value); el.value = opt ? value : ''; } +// ── Searchable plate combobox — drives the hidden #f-vehicle multi-select ──── +const _plateSearch = document.getElementById('plate-search'); +const _plateResults = document.getElementById('plate-results'); +const _plateChips = document.getElementById('plate-chips'); +function _plateOpts() { return Array.from(document.getElementById('f-vehicle').options); } +function _fireVehChange() { document.getElementById('f-vehicle').dispatchEvent(new Event('change')); } +function renderPlateChips() { + _plateChips.innerHTML = ''; + _plateOpts().filter(o => o.selected).forEach(o => { + const chip = document.createElement('span'); + chip.className = 'plate-chip'; + chip.innerHTML = `${escapeHtml(o.value)} `; + chip.querySelector('button').addEventListener('click', () => { o.selected = false; _fireVehChange(); }); + _plateChips.appendChild(chip); + }); +} +function renderPlateResults(q) { + q = (q || '').trim().toLowerCase(); + const matches = _plateOpts().filter(o => o.value && (!q || o.textContent.toLowerCase().includes(q))).slice(0, 60); + _plateResults.innerHTML = ''; + if (!matches.length) { _plateResults.innerHTML = '
No matching plate
'; _plateResults.classList.add('show'); return; } + matches.forEach(o => { + const div = document.createElement('div'); + div.className = 'plate-opt' + (o.selected ? ' sel' : ''); + const [plate, driver] = o.textContent.split(' — '); + div.innerHTML = driver ? `${escapeHtml(plate)} — ${escapeHtml(driver)}` : escapeHtml(plate); + div.addEventListener('mousedown', e => { // mousedown: fires before the input's blur + e.preventDefault(); + o.selected = !o.selected; + _fireVehChange(); + _plateSearch.value = ''; + renderPlateResults(''); + _plateSearch.focus(); + }); + _plateResults.appendChild(div); + }); + _plateResults.classList.add('show'); +} +_plateSearch.addEventListener('focus', () => renderPlateResults(_plateSearch.value)); +_plateSearch.addEventListener('input', () => renderPlateResults(_plateSearch.value)); +_plateSearch.addEventListener('blur', () => setTimeout(() => _plateResults.classList.remove('show'), 150)); +document.addEventListener('click', e => { if (!e.target.closest('.plate-box')) _plateResults.classList.remove('show'); }); + // Scale the live markers with the zoom level so they don't bloat at country // zoom or vanish when zoomed in. Linear from z5 → z14. function updateVehScale() {