diff --git a/public/admin/assets/js/intel-signals.js b/public/admin/assets/js/intel-signals.js index 393b5ca..ffc7a08 100644 --- a/public/admin/assets/js/intel-signals.js +++ b/public/admin/assets/js/intel-signals.js @@ -2,22 +2,15 @@ // cards are glance-only; clicking opens a modal with the full breakdown. // depends on: app.js, intel-shared.js +// full dataset kept in memory so filters/sort never re-fetch +let allSignals = []; + // local lookup of signal rows keyed by company_id — so the modal can // render without re-fetching. references are still lazy-loaded per open. const signalsById = new Map(); -async function loadSignals() { - let data; - try { - data = await api("/admin/api/intelligence/signals"); - } catch (_) { - data = []; - } - - signalsById.clear(); - (data || []).forEach(s => signalsById.set(s.company_id, s)); - +function renderSignals(data) { const grid = document.getElementById("intel-signals-grid"); const empty = document.getElementById("intel-signals-empty"); @@ -61,6 +54,55 @@ async function loadSignals() { } +function applyFilters() { + const search = (document.getElementById("sf-search").value || "").trim().toLowerCase(); + const signal = document.getElementById("sf-signal").value; + const confidence = document.getElementById("sf-confidence").value; + const risk = document.getElementById("sf-risk").value; + const sort = document.getElementById("sf-sort").value; + + let results = allSignals.filter(s => { + if (search && !s.company_name.toLowerCase().includes(search) && !s.ticker.toLowerCase().includes(search)) return false; + if (signal && s.signal !== signal) return false; + if (confidence && (s.confidence || "").toLowerCase() !== confidence) return false; + if (risk && (s.risk_level || "").toLowerCase() !== risk) return false; + return true; + }); + + results.sort((a, b) => { + if (sort === "event_desc") { + const da = a.latest_event_date || ""; + const db = b.latest_event_date || ""; + return da < db ? 1 : da > db ? -1 : 0; + } + if (sort === "company_asc") return a.company_name.localeCompare(b.company_name); + if (sort === "company_desc") return b.company_name.localeCompare(a.company_name); + // generated_desc (default) + const ga = a.generated_at || ""; + const gb = b.generated_at || ""; + return ga < gb ? 1 : ga > gb ? -1 : 0; + }); + + renderSignals(results); +} + + +async function loadSignals() { + let data; + try { + data = await api("/admin/api/intelligence/signals"); + } catch (_) { + data = []; + } + + allSignals = data || []; + signalsById.clear(); + allSignals.forEach(s => signalsById.set(s.company_id, s)); + + applyFilters(); +} + + let currentSignal = null; function openSignalModal(companyId) { @@ -187,16 +229,9 @@ async function regenerateSignal() { await api(`/admin/api/intelligence/signals/${companyId}`, { method: "DELETE" }); toast("Signal cleared — worker will regenerate on next cycle"); signalsById.delete(companyId); - - const card = document.querySelector(`.signal-card[onclick*="(${companyId})"]`); - if (card) card.remove(); - - const grid = document.getElementById("intel-signals-grid"); - if (!grid.children.length) { - document.getElementById("intel-signals-empty").style.display = ""; - } - + allSignals = allSignals.filter(s => s.company_id !== companyId); closeSignalModal(); + applyFilters(); } catch (_) { toast("Failed to clear signal", true); } @@ -207,6 +242,12 @@ document.addEventListener("DOMContentLoaded", () => { document.getElementById("signalCloseBtn").onclick = closeSignalModal; document.getElementById("signalRegenBtn").onclick = regenerateSignal; + document.getElementById("sf-search").addEventListener("input", applyFilters); + document.getElementById("sf-signal").addEventListener("change", applyFilters); + document.getElementById("sf-confidence").addEventListener("change", applyFilters); + document.getElementById("sf-risk").addEventListener("change", applyFilters); + document.getElementById("sf-sort").addEventListener("change", applyFilters); + // fire both in parallel — signals api returns [] when intelligence // db is unavailable, so no need to gate on the stats call Promise.all([loadIntelStatsRow(), loadSignals()]); diff --git a/public/admin/pages/intelligence/signals.html b/public/admin/pages/intelligence/signals.html index 25bff82..a75c046 100644 --- a/public/admin/pages/intelligence/signals.html +++ b/public/admin/pages/intelligence/signals.html @@ -36,9 +36,45 @@ intelligence.sqlite not found — is the intelligence worker running? +