// Alerts queue view — analyst's daily working surface. const { useState, useMemo, useEffect, useRef } = React; function formatAmount(n, cur) { const sym = { USD: "$", EUR: "€", GBP: "£", MXN: "MX$" }[cur] || ""; return sym + n.toLocaleString("en-US"); } function riskColor(score) { if (score >= 70) return "var(--risk-high)"; if (score >= 45) return "var(--risk-med)"; return "var(--risk-low)"; } function riskLabel(score) { if (score >= 70) return "high"; if (score >= 45) return "med"; return "low"; } function RiskBar({ score }) { const c = riskColor(score); return ( {score} ); } function SeverityDot({ s }) { const cls = s === "high" ? "dot-high" : s === "med" ? "dot-med" : "dot-low"; return ; } function TypeIcon({ subject }) { return ( {subject === "business" ? : } ); } function FlagPin({ cc }) { // Country code chip return ( {cc} ); } function Tab({ active, label, count, onClick }) { return ( ); } // Dropdown-backed filter chip. `options` is [{ value, label }]; a null-valued // option represents "Any" / clear. `value` is the currently applied option value. function FilterMenu({ label, value, options, formatValue, onSelect }) { const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { if (!open) return; const onDocClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener("mousedown", onDocClick); return () => document.removeEventListener("mousedown", onDocClick); }, [open]); const set = value != null; return (
{open && (
{options.map(opt => ( ))}
)}
); } const OPENED_WINDOWS = { "24h": 1, "7d": 7, "30d": 30 }; const AMOUNT_PRESETS = [ { value: 10000, label: "≥ $10k" }, { value: 50000, label: "≥ $50k" }, { value: 100000, label: "≥ $100k" }, { value: 250000, label: "≥ $250k" }, ]; const RISK_PRESETS = [ { value: 40, label: "≥ 40" }, { value: 55, label: "≥ 55" }, { value: 70, label: "≥ 70" }, ]; function AlertsView({ onOpenCase, rowStyle = "hairline", data, selectedAlertIds, onSelectionChange }) { const alerts = data?.alerts || window.ALERTS || []; const selected = selectedAlertIds || new Set(); const setSelected = onSelectionChange || (() => {}); const [tab, setTab] = useState("all"); const [sortBy, setSortBy] = useState("risk"); const [page, setPage] = useState(1); const [filters, setFilters] = useState({ tenant: null, riskMin: null, opened: null, channel: null, geography: null, amountMin: null, }); const pageSize = 50; const setTabAndReset = (t) => { setPage(1); setTab(t); }; const setSortAndReset = (s) => { setPage(1); setSortBy(s); }; const setFilter = (key, value) => { setPage(1); setFilters(f => ({ ...f, [key]: value })); }; const counts = useMemo(() => ({ all: alerts.length, critical: alerts.filter(a => a.severity === "critical").length, high: alerts.filter(a => a.severity === "high").length, medium: alerts.filter(a => a.severity === "med" || a.severity === "medium").length, unassigned: alerts.filter(a => a.assignee === "Unassigned").length, }), [alerts]); const tenantOptions = useMemo(() => (window.TENANTS || []).map(t => ({ value: t.id, label: t.short })), []); const channelOptions = useMemo(() => ( [...new Set(alerts.map(a => a.channel).filter(Boolean))].sort().map(v => ({ value: v, label: v })) ), [alerts]); const geographyOptions = useMemo(() => { const set = new Set(); alerts.forEach(a => { if (a.subjectCountry && a.subjectCountry !== "—") set.add(a.subjectCountry); if (a.cpCountry && a.cpCountry !== "—") set.add(a.cpCountry); }); return [...set].sort().map(v => ({ value: v, label: v })); }, [alerts]); const openedOptions = [ { value: "24h", label: "Last 24h" }, { value: "7d", label: "Last 7d" }, { value: "30d", label: "Last 30d" }, ]; const filtered = useMemo(() => { let r = alerts; if (tab === "critical") r = r.filter(a => a.severity === "critical"); else if (tab === "high") r = r.filter(a => a.severity === "high"); else if (tab === "medium") r = r.filter(a => a.severity === "med" || a.severity === "medium"); else if (tab === "unassigned") r = r.filter(a => a.assignee === "Unassigned"); if (filters.tenant) r = r.filter(a => tenantForCustomer(a.subject).id === filters.tenant); if (filters.riskMin != null) r = r.filter(a => a.risk >= filters.riskMin); if (filters.opened) { const cutoff = Date.now() - OPENED_WINDOWS[filters.opened] * 24 * 60 * 60 * 1000; r = r.filter(a => new Date(a.created_at || a.opened).getTime() >= cutoff); } if (filters.channel) r = r.filter(a => a.channel === filters.channel); if (filters.geography) r = r.filter(a => a.subjectCountry === filters.geography || a.cpCountry === filters.geography); if (filters.amountMin != null) r = r.filter(a => a.amount >= filters.amountMin); r = [...r].sort((a, b) => sortBy === "risk" ? b.risk - a.risk : new Date(b.created_at || b.opened) - new Date(a.created_at || a.opened)); return r; }, [tab, sortBy, alerts, filters]); const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); const safePage = Math.min(page, totalPages); const paged = filtered.slice((safePage - 1) * pageSize, safePage * pageSize); const toggle = (id) => { const n = new Set(selected); n.has(id) ? n.delete(id) : n.add(id); setSelected(n); }; const toggleAll = () => { setSelected(selected.size === paged.length ? new Set() : new Set(paged.map(a => a.id))); }; return ( <>
setTabAndReset("all")} /> setTabAndReset("critical")} /> setTabAndReset("high")} /> setTabAndReset("medium")} /> setTabAndReset("unassigned")} />
tenantOptions.find(o => o.value === v)?.label} onSelect={v => setFilter("tenant", v)} /> RISK_PRESETS.find(o => o.value === v)?.label ?? `≥ ${v}`} onSelect={v => setFilter("riskMin", v)} /> openedOptions.find(o => o.value === v)?.label.toLowerCase()} onSelect={v => setFilter("opened", v)} /> setFilter("channel", v)} /> setFilter("geography", v)} /> AMOUNT_PRESETS.find(o => o.value === v)?.label ?? `≥ $${v}`} onSelect={v => setFilter("amountMin", v)} />
{selected.size > 0 && (
{selected.size} selected · Create one case from the {selected.size} selected alert{selected.size === 1 ? "" : "s"}
)}
{paged.map(a => ( onOpenCase?.(a)}> ))}
0 && selected.size === paged.length} onChange={toggleAll} style={{ accentColor: "var(--fg)" }} /> Alert Customer Tenant Risk Rule Subject → Counterparty Amount Channel Assignee SLA
e.stopPropagation()}> toggle(a.id)} style={{ accentColor: "var(--fg)" }} /> {a.external_id}
{a.case}
{a.customerId || "—"} {a.rule}
{a.subject} {a.subjectCountry} {a.counterparty} {a.cpCountry !== "—" && a.cpCountry}
{formatAmount(a.amount, a.currency)} {a.currency} {a.txnCount > 1 && (
{a.txnCount} txns
)}
{a.channel} {a.assignee === "Unassigned" ? ( ) : a.assignee === "Auto-rules" ? ( Auto ) : ( {a.assignee.split(" ").map(x => x[0]).join("").slice(0,2)} {a.assignee.split(" ")[0]} )} {a.slaWarn && } {a.sla}
Showing {paged.length} of {filtered.length} alerts · Last updated 14s ago Live Page {safePage} / {totalPages}
); } Object.assign(window, { AlertsView, formatAmount, riskColor, riskLabel, RiskBar });