// 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"}
)}
Showing {paged.length} of {filtered.length} alerts
·
Last updated 14s ago
Live
Page {safePage} / {totalPages}
>
);
}
Object.assign(window, { AlertsView, formatAmount, riskColor, riskLabel, RiskBar });