// Transactions explorer — real transaction stream from the API. const { useState: useTxnState, useMemo: useTxnMemo, useEffect: useTxnEffect } = React; function formatAmount(n, cur) { const sym = { USD: "$", EUR: "€", GBP: "£", MXN: "MX$" }[cur] || ""; return sym + Number(n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function formatDate(iso) { if (!iso) return "—"; const d = new Date(iso); return d.toLocaleString(undefined, { month: "short", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit" }); } function TypePill({ type }) { const color = { payment: "info", transfer: "ok", cash_out: "warn", cash_in: "low", debit: "med", wire: "high", ach: "info", card: "med", atm: "warn", crypto: "low", cash: "low", }[type?.toLowerCase()] || "outline"; return {type || "—"}; } function TransactionsView({ data, initialQuery }) { const transactions = data?.transactions || window.TRANSACTIONS_API || []; const [page, setPage] = useTxnState(1); const [pageSize, setPageSize] = useTxnState(50); const [sortBy, setSortBy] = useTxnState("timestamp"); const [filterType, setFilterType] = useTxnState("all"); const [search, setSearch] = useTxnState(""); const [selected, setSelected] = useTxnState(new Set()); useTxnEffect(() => { if (initialQuery) { setSearch(initialQuery); setPage(1); } }, [initialQuery]); const [creating, setCreating] = useTxnState(false); const [submitting, setSubmitting] = useTxnState(false); const [manualSeverity, setManualSeverity] = useTxnState("high"); const [manualTitle, setManualTitle] = useTxnState(""); const [manualNote, setManualNote] = useTxnState(""); const [manualStatus, setManualStatus] = useTxnState(null); const types = useTxnMemo(() => { const set = new Set(transactions.map(t => t.transaction_type).filter(Boolean)); return Array.from(set).sort(); }, [transactions]); const filtered = useTxnMemo(() => { let r = transactions; if (filterType !== "all") r = r.filter(t => t.transaction_type === filterType); if (search.trim()) { const q = search.trim().toLowerCase(); r = r.filter(t => (t.external_id || "").toLowerCase().includes(q) || (t.originator_id || "").toLowerCase().includes(q) || (t.beneficiary_id || "").toLowerCase().includes(q) || (t.originator_account || "").toLowerCase().includes(q) || (t.beneficiary_account || "").toLowerCase().includes(q) ); } r = [...r].sort((a, b) => { if (sortBy === "timestamp") return new Date(b.timestamp) - new Date(a.timestamp); if (sortBy === "amount") return Number(b.amount || 0) - Number(a.amount || 0); if (sortBy === "risk") return Number(b.risk_score || 0) - Number(a.risk_score || 0); return 0; }); return r; }, [transactions, filterType, search, sortBy]); 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 allPagedSelected = paged.length > 0 && paged.every(t => selected.has(t.id)); const toggleSelect = (id) => { const next = new Set(selected); if (next.has(id)) next.delete(id); else next.add(id); setSelected(next); }; const toggleAllPaged = () => { const next = new Set(selected); if (allPagedSelected) { paged.forEach(t => next.delete(t.id)); } else { paged.forEach(t => next.add(t.id)); } setSelected(next); }; const clearSelection = () => setSelected(new Set()); const createAlerts = async () => { if (selected.size === 0) return; setSubmitting(true); setManualStatus(null); try { const ids = Array.from(selected); await window.mossadApi.createManualAlerts({ transaction_ids: ids, severity: manualSeverity, title: manualTitle.trim() || undefined, description: manualNote.trim() || undefined, }); setManualStatus({ type: "ok", text: `Created 1 alert from ${ids.length} transaction${ids.length !== 1 ? "s" : ""}` }); setSelected(new Set()); if (data?.refresh) await data.refresh(); } catch (err) { setManualStatus({ type: "error", text: "Failed to create alert: " + err.message }); } finally { setSubmitting(false); } }; return (

Transactions

{transactions.length.toLocaleString()} loaded
{selected.size > 0 && ( {selected.size} selected )} {selected.size > 0 && ( )}
{ setSearch(e.target.value); setPage(1); }} placeholder="Search ID, originator, beneficiary…" style={{ minWidth: 240, flex: 1 }} />
{paged.map(t => ( ))}
Txn ID Timestamp Customer Type Originator Beneficiary Amount Status Risk Fraud
toggleSelect(t.id)} /> {t.external_id} {formatDate(t.timestamp)} {t.customer_id || "—"}
{t.originator_id}
{t.originator_country}
{t.beneficiary_id}
{t.beneficiary_country}
{formatAmount(t.amount, t.currency)} {t.status} = 70 ? "var(--risk-high)" : "var(--fg-2)" }}> {Math.round(Number(t.risk_score || 0))} {t.payload?.is_fraud ? ( Fraud ) : t.payload?.is_flagged_fraud ? ( Flagged ) : ( No )}
{paged.length === 0 && (
No transactions match your filters.
)}
Showing {paged.length} of {filtered.length} {selected.size > 0 && ( )} Page {safePage} / {totalPages}
{creating && (
setCreating(false)}>
e.stopPropagation()}>

Create manual alert

{selected.size} transaction{selected.size !== 1 ? "s" : ""} selected · combined into 1 alert

setManualTitle(e.target.value)} placeholder="e.g. Suspicious wire batch" style={{ width: "100%" }} />