feat(events): event modal + sortable tables polish
Event modal (event-modal.js): - Record Type now derived from Blastware filename's last-char code (H=Histogram, W=Waveform, M=Manual, E=Event, C=Combo). Falls back to whatever SFM reported if the code isn't recognized. Client-side workaround — SFM still hardcodes "Waveform" server-side and needs a proper fix in its sidecar parser. - PSI mic tile dropped; mic section now renders 3 tiles (dB(L), ZC Frequency, Time of Peak) instead of 4. - New "View JSON" toggle exposes a prettified inline JSON viewer with a Copy-to-clipboard button alongside the existing "Download sidecar JSON" link. - "Project Info" section header renamed to "User Notes" to reflect that these are operator-typed fields, not the terra-view project assignment. Sortable tables (sfm.html + unit_detail.html): - Both Events tables now have clickable column headers with ↕/↓/↑ indicators. Default sort is Timestamp DESC. Clicking the same column toggles direction; clicking a different column switches and resets to DESC. Sort is purely client-side over the cached rowset, so no extra fetches. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2142,6 +2142,15 @@ function clearUnitEventFilters() {
|
||||
loadUnitEvents();
|
||||
}
|
||||
|
||||
// Module-level state for the unit-events table sort. Cache lets us re-sort
|
||||
// without a refetch when the user clicks a column header.
|
||||
let _ueEventsCache = [];
|
||||
let _ueEventsTotal = 0;
|
||||
let _ueEventsBucket = 'all';
|
||||
let _ueAssignmentsTotal = 0;
|
||||
let _ueSortKey = 'timestamp';
|
||||
let _ueSortDir = 'desc';
|
||||
|
||||
async function loadUnitEvents() {
|
||||
if (!currentUnit || currentUnit.device_type !== 'seismograph') return;
|
||||
const container = document.getElementById('ue-events-container');
|
||||
@@ -2166,13 +2175,62 @@ async function loadUnitEvents() {
|
||||
throw new Error(err.detail || 'HTTP ' + r.status);
|
||||
}
|
||||
const d = await r.json();
|
||||
_ueEventsCache = d.events || [];
|
||||
_ueEventsTotal = d.count || 0;
|
||||
_ueEventsBucket = bucket;
|
||||
_ueAssignmentsTotal = d.assignments_total || 0;
|
||||
renderUnitEventStats(d.stats);
|
||||
renderUnitEventTable(d.events, d.count, container, bucket, d.assignments_total);
|
||||
renderUnitEventTable(_ueEventsCache, _ueEventsTotal, container, bucket, _ueAssignmentsTotal);
|
||||
} catch (e) {
|
||||
container.innerHTML = `<div class="text-center py-12 text-red-500 text-sm">Failed to load events: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function sortUnitEvents(key) {
|
||||
if (_ueSortKey === key) {
|
||||
_ueSortDir = _ueSortDir === 'desc' ? 'asc' : 'desc';
|
||||
} else {
|
||||
_ueSortKey = key;
|
||||
_ueSortDir = 'desc';
|
||||
}
|
||||
renderUnitEventTable(_ueEventsCache, _ueEventsTotal,
|
||||
document.getElementById('ue-events-container'), _ueEventsBucket, _ueAssignmentsTotal);
|
||||
}
|
||||
|
||||
function _ueApplySort(events) {
|
||||
const key = _ueSortKey;
|
||||
const dir = _ueSortDir === 'asc' ? 1 : -1;
|
||||
return [...events].sort((a, b) => {
|
||||
let av, bv;
|
||||
if (key === 'attribution') {
|
||||
// Sort by location name so attributed rows group together.
|
||||
av = a.attribution ? (a.attribution.location_name || '') : '';
|
||||
bv = b.attribution ? (b.attribution.location_name || '') : '';
|
||||
} else {
|
||||
av = a[key]; bv = b[key];
|
||||
}
|
||||
if (av == null && bv == null) return 0;
|
||||
if (av == null) return 1;
|
||||
if (bv == null) return -1;
|
||||
if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;
|
||||
return String(av).localeCompare(String(bv)) * dir;
|
||||
});
|
||||
}
|
||||
|
||||
function _ueSortIndicator(key) {
|
||||
if (_ueSortKey !== key) return '<span class="text-gray-400 opacity-50 ml-1">↕</span>';
|
||||
return _ueSortDir === 'desc'
|
||||
? '<span class="text-seismo-orange ml-1">↓</span>'
|
||||
: '<span class="text-seismo-orange ml-1">↑</span>';
|
||||
}
|
||||
|
||||
function _ueSortableTh(label, key) {
|
||||
return `<th onclick="sortUnitEvents('${key}')"
|
||||
class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider cursor-pointer select-none hover:bg-gray-100 dark:hover:bg-slate-600 transition-colors">
|
||||
${label}${_ueSortIndicator(key)}
|
||||
</th>`;
|
||||
}
|
||||
|
||||
function renderUnitEventStats(stats) {
|
||||
const s = stats || {};
|
||||
document.getElementById('ue-stat-total').textContent = (s.event_count ?? 0).toLocaleString();
|
||||
@@ -2269,7 +2327,8 @@ function renderUnitEventTable(events, total, container, bucket, assignmentsTotal
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = events.map(ev => {
|
||||
const sorted = _ueApplySort(events);
|
||||
const rows = sorted.map(ev => {
|
||||
const ts = ev.timestamp ? ev.timestamp.replace('T', ' ').slice(0, 19) : '—';
|
||||
const tran = _ueFmtPPV(ev.tran_ppv);
|
||||
const vert = _ueFmtPPV(ev.vert_ppv);
|
||||
@@ -2295,13 +2354,13 @@ function renderUnitEventTable(events, total, container, bucket, assignmentsTotal
|
||||
<table class="w-full text-left">
|
||||
<thead class="bg-gray-50 dark:bg-slate-700 border-b border-gray-200 dark:border-gray-600">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Timestamp</th>
|
||||
<th class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Tran</th>
|
||||
<th class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Vert</th>
|
||||
<th class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Long</th>
|
||||
<th class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">PVS</th>
|
||||
<th class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Flags</th>
|
||||
<th class="px-4 py-3 text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Attribution</th>
|
||||
${_ueSortableTh('Timestamp', 'timestamp')}
|
||||
${_ueSortableTh('Tran', 'tran_ppv')}
|
||||
${_ueSortableTh('Vert', 'vert_ppv')}
|
||||
${_ueSortableTh('Long', 'long_ppv')}
|
||||
${_ueSortableTh('PVS', 'peak_vector_sum')}
|
||||
${_ueSortableTh('Flags', 'false_trigger')}
|
||||
${_ueSortableTh('Attribution', 'attribution')}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">${rows}</tbody>
|
||||
|
||||
Reference in New Issue
Block a user