refactor admin data queries; consolidate multiple count queries and optimize article fetching

This commit is contained in:
ImBenji
2026-04-24 01:20:08 +01:00
parent a48b1eef67
commit 653a58b3d8
2 changed files with 98 additions and 21 deletions
+61 -20
View File
@@ -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()]);