From 51a84b66c61a4aa8a03830d8293e01662e09f8cc Mon Sep 17 00:00:00 2001 From: david kiania Date: Thu, 18 Jun 2026 17:53:50 +0300 Subject: [PATCH] feat(tickets map): closures-by-engineer panel, drill-down + dispatcher popup details - New "Closures by engineer" leaderboard panel (metrics.by_owner): engineer, closed, breached, avg MTTR. Clicking an engineer toggles a drill-down that filters the closed map pins to only their closures (and ensures the closed layer is visible). - Popups now carry the details dispatchers need: open popups show location_name and the true coordinates (copyable; the fan-out keeps the real coord even when the pin is offset); closed popups show "closed by ". Backed by fn_inc_dashboard migration 12 (owner case-normalized server-side). Co-Authored-By: Claude Opus 4.8 --- src/index.html | 54 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/index.html b/src/index.html index f610721..b4b76d2 100644 --- a/src/index.html +++ b/src/index.html @@ -439,6 +439,11 @@
+
+

Closures by engineer

+
Loading…
+
+

By status

Loading…
@@ -952,6 +957,7 @@ function vehState(p) { let tkMap = null, tkPopup = null, tkLivePoll = null, tkClosureChart = null; const tkMarkers = new Map(); // imei → maplibregl.Marker const tkLayerState = { open: true, closed: true, vehicles: true }; +let tkOwnerFilter = null; // when set, the closed layer is filtered to this engineer (drill-down) let incData = null, incDropdownsInit = false, vehCount = 0; // ── INC helpers ─────────────────────────────────────────────────────────── @@ -1039,7 +1045,9 @@ function fanOutColocated(openFC, closedFC) { coords = [c[0] + (r * Math.cos(a)) / Math.max(Math.cos(latRad), 0.2), c[1] + r * Math.sin(a)]; } (it.layer === 'open' ? outOpen : outClosed).push( - { type: 'Feature', properties: it.f.properties, geometry: { type: 'Point', coordinates: coords } }); + { type: 'Feature', + properties: { ...it.f.properties, __lng: c[0], __lat: c[1] }, // keep TRUE coords for popups + geometry: { type: 'Point', coordinates: coords } }); }); } return { open: { type: 'FeatureCollection', features: outOpen }, @@ -1047,7 +1055,12 @@ function fanOutColocated(openFC, closedFC) { } function renderIncMap() { if (!tkMap || !incData) return; - const { open, closed } = fanOutColocated(incData.open, incData.closed); + let closedFC = incData.closed || EMPTY_FC; + if (tkOwnerFilter) { // drill-down: only the selected engineer's closures + closedFC = { type: 'FeatureCollection', + features: (closedFC.features || []).filter((f) => (f.properties || {}).owner === tkOwnerFilter) }; + } + const { open, closed } = fanOutColocated(incData.open, closedFC); if (tkMap.getSource('inc-open')) tkMap.getSource('inc-open').setData(open); if (tkMap.getSource('inc-closed')) tkMap.getSource('inc-closed').setData(closed); } @@ -1153,6 +1166,33 @@ function renderIncTables(m) { const cn = incTable(nbo), cc = incTable(coast); $('tk-nbo-wrap').innerHTML = cn.html; $('tk-nbo-count').textContent = cn.n ? `(${cn.n})` : ''; $('tk-coast-wrap').innerHTML = cc.html; $('tk-coast-count').textContent = cc.n ? `(${cc.n})` : ''; + renderIncOwners(m.by_owner); +} + +// Closures-by-engineer leaderboard (metrics.by_owner — owner case-normalized in SQL). +// Clicking a row toggles a drill-down that filters the closed map pins to that engineer. +function renderIncOwners(arr) { + arr = arr || []; + $('tk-owner-count').textContent = arr.length ? `(${arr.length})` : ''; + const wrap = $('tk-owner-wrap'); + if (!arr.length) { wrap.innerHTML = '
No closures in window.
'; return; } + const body = arr.map((o) => { + const hl = (o.owner === tkOwnerFilter) ? ' style="background:rgba(232,149,74,.14)"' : ''; + const mttrH = (o.avg_mttr_min != null) ? num(o.avg_mttr_min / 60, 1) : '—'; + return ` + ${escapeHtml(o.owner)}${intg(o.closed)} + ${intg(o.breached || 0)}${mttrH}`; + }).join(''); + wrap.innerHTML = `${body}
EngineerClosedBreachMTTR h
`; + wrap.querySelectorAll('tr[data-owner]').forEach((tr) => tr.addEventListener('click', () => { + const o = tr.getAttribute('data-owner'); + tkOwnerFilter = (tkOwnerFilter === o) ? null : o; // toggle off if re-clicked + if (tkOwnerFilter && tkMap && !tkLayerState.closed) { // make sure closures are visible + tkLayerState.closed = true; tkMap.setLayoutProperty('inc-closed', 'visibility', 'visible'); buildIncLayers(); + } + renderIncOwners(arr); // re-highlight selection + renderIncMap(); // re-filter the closed pins + })); } function renderClosureChart(cr) { @@ -1271,16 +1311,20 @@ function showIncPopup(f, closed) { const p = f.properties || {}; const lines = [`
${escapeHtml(p.normalized_status || '—')}
`]; if (p.cluster) lines.push(`
${escapeHtml(p.cluster)}${p.region ? ' · ' + escapeHtml(p.region) : ''}
`); - const who = p.assigned_team || p.owner; - if (who) lines.push(`
${escapeHtml(who)}
`); + if (p.location_name) lines.push(`
${escapeHtml(p.location_name)}
`); if (closed) { + if (p.owner) lines.push(`
closed by ${escapeHtml(p.owner)}
`); lines.push(`
closed ${escapeHtml(eatShort(p.closed_at))} · MTTR ${mttrFmt(p.mttr)}
`); if (p.sla_status) lines.push(`
${escapeHtml(p.sla_status)}
`); } else { + const who = p.assigned_team || p.owner; + if (who) lines.push(`
${escapeHtml(who)}
`); const st = p.sla_state || 'unknown'; lines.push(`
${SLA_LABELS[st] || st}${p.hours_open != null ? ' · ' + num(p.hours_open, 0) + 'h open' : ''}
`); } - if (p.geo_source === 'cluster') lines.push('
approx — cluster location
'); + // true location for the dispatcher (copyable; the pin itself may be fanned out) + if (p.__lat != null && p.__lng != null) + lines.push(`
${num(p.__lat, 5)}, ${num(p.__lng, 5)}${p.geo_source === 'cluster' ? ' · approx (cluster)' : ''}
`); tkPopup.setLngLat(f.geometry.coordinates).setHTML(`
${escapeHtml(p.ticket_id || '—')} ${closed ? 'CLOSED' : 'OPEN'} ${lines.join('')}
`).addTo(tkMap);