@@ -2652,7 +2666,13 @@ function renderUnitEventTable(events, total, container, bucket, assignmentsTotal
? '
FT '
: '';
+ const checked = _ueSelectedEventIds.has(ev.id) ? 'checked' : '';
return `
+
+
+
${ts}
${tran}
${vert}
@@ -2668,6 +2688,11 @@ function renderUnitEventTable(events, total, container, bucket, assignmentsTotal
`;
+ _ueRefreshBulkButton();
+}
+
+// ===== Bulk false-trigger flagging =====
+// Selection is keyed by event ID and persists across table re-renders, so
+// users can paginate / re-sort without losing their selection.
+const _ueSelectedEventIds = new Set();
+
+function _ueRefreshBulkButton() {
+ const n = _ueSelectedEventIds.size;
+ const lbl = document.getElementById('ue-bulk-selected');
+ const flag = document.getElementById('ue-bulk-flag-ft');
+ const clr = document.getElementById('ue-bulk-clear-ft');
+ if (lbl) lbl.textContent = `${n} selected`;
+ if (flag) flag.disabled = (n === 0);
+ if (clr) clr.disabled = (n === 0);
+}
+
+function onUnitEventRowCheck(input) {
+ const id = input.getAttribute('data-event-id');
+ if (input.checked) {
+ _ueSelectedEventIds.add(id);
+ } else {
+ _ueSelectedEventIds.delete(id);
+ // If we just unchecked a row, the master "all" checkbox shouldn't stay checked.
+ const master = document.getElementById('ue-check-all');
+ if (master) master.checked = false;
+ }
+ _ueRefreshBulkButton();
+}
+
+function toggleAllUnitEventRows(checked) {
+ document.querySelectorAll('.ue-row-check').forEach(cb => {
+ const id = cb.getAttribute('data-event-id');
+ cb.checked = checked;
+ if (checked) _ueSelectedEventIds.add(id);
+ else _ueSelectedEventIds.delete(id);
+ });
+ _ueRefreshBulkButton();
+}
+
+async function flagSelectedUnitEvents(value) {
+ // value = true → flag as false trigger
+ // value = false → clear false-trigger flag
+ if (_ueSelectedEventIds.size === 0) return;
+ const ids = Array.from(_ueSelectedEventIds);
+ const verb = value ? 'flag as false trigger' : 'clear false-trigger flag on';
+ if (!confirm(`${verb} ${ids.length} event${ids.length === 1 ? '' : 's'}?`)) {
+ return;
+ }
+
+ // SFM exposes single-row PATCH only. Fan out concurrently with a
+ // modest cap so we don't open hundreds of sockets at once.
+ const concurrency = 8;
+ let ok = 0, failed = 0;
+ let cursor = 0;
+ async function worker() {
+ while (cursor < ids.length) {
+ const i = cursor++;
+ const id = ids[i];
+ try {
+ const resp = await fetch(
+ `/api/sfm/db/events/${encodeURIComponent(id)}/false_trigger?value=${value ? 'true' : 'false'}`,
+ { method: 'PATCH' }
+ );
+ if (resp.ok) ok++;
+ else failed++;
+ } catch (_) {
+ failed++;
+ }
+ }
+ }
+ await Promise.all(Array.from({ length: concurrency }, worker));
+
+ if (failed) {
+ alert(`${ok} updated, ${failed} failed. Refreshing table.`);
+ }
+ _ueSelectedEventIds.clear();
+ loadUnitEvents();
}
// ===== Pair Device Modal Functions =====