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 @@
By status
@@ -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 = `
| Engineer | Closed | Breach | MTTR h |
${body}
`;
+ 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);