// Nexa — Internal compliance dashboard (rebuild)
// Single-file React app wired to https://api.usenexa.io
const { useState, useEffect, useMemo, useCallback } = React;
const API_BASE = window.location.origin;
const RESET_KEY = "3F8869A9-609D-4892-B380-D0933E0C263B";
// ── Formatters ─────────────────────────────────────────────────────────
const fmtMoney = (amt, ccy) => {
if (amt == null) return "—";
const sym = { GBP: "£", USD: "$", EUR: "€", JPY: "¥", CAD: "C$", CHF: "CHF ", CNY: "¥" }[ccy] || "";
const n = Number(amt).toLocaleString("en-GB", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
return `${sym}${n}${sym ? "" : " " + (ccy || "")}`;
};
const fmtCompact = (n) => {
if (n == null) return "—";
if (Math.abs(n) >= 1e9) return (n / 1e9).toFixed(1) + "B";
if (Math.abs(n) >= 1e6) return (n / 1e6).toFixed(1) + "M";
if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(0) + "k";
return String(n);
};
const fmtDateTime = (iso) => {
if (!iso) return "—";
const d = new Date(iso);
if (isNaN(d)) return iso;
const date = d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", timeZone: "UTC" });
const time = d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: "UTC" });
return `${date} · ${time}`;
};
const fmtDate = (iso) => {
if (!iso) return "—";
const d = new Date(iso);
if (isNaN(d)) return iso;
return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric", timeZone: "UTC" });
};
const fmtRelative = (iso) => {
if (!iso) return "—";
const d = new Date(iso);
if (isNaN(d)) return iso;
const diff = (d - Date.now()) / 1000;
const abs = Math.abs(diff);
const sign = diff < 0 ? "ago" : "from now";
if (abs < 60) return `${Math.round(abs)}s ${sign}`;
if (abs < 3600) return `${Math.round(abs / 60)}m ${sign}`;
if (abs < 86400) return `${Math.round(abs / 3600)}h ${sign}`;
return `${Math.round(abs / 86400)}d ${sign}`;
};
// ── API helpers ────────────────────────────────────────────────────────
async function apiGet(path) {
const headers = {};
if (path === "/internal/state" || path.startsWith("/internal/state?")) {
headers["X-Internal-Key"] = RESET_KEY;
}
const r = await fetch(`${API_BASE}${path}`, { headers });
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
return r.json();
}
async function apiPost(path, body) {
const headers = { "Content-Type": "application/json" };
if (path.startsWith("/internal/")) {
headers["X-Internal-Key"] = RESET_KEY;
}
const r = await fetch(`${API_BASE}${path}`, {
method: "POST",
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
const text = await r.text();
return text ? JSON.parse(text) : null;
}
async function apiReset() {
const r = await fetch(`${API_BASE}/internal/reset-data`, {
method: "POST",
headers: { "X-Internal-Key": RESET_KEY },
});
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
return r.json();
}
// ── Toast bus ──────────────────────────────────────────────────────────
let toastSetter = null;
function toast(msg, opts = {}) {
if (!toastSetter) return;
toastSetter({ msg, error: !!opts.error, key: Date.now() });
setTimeout(() => toastSetter && toastSetter(null), 2400);
}
// ── Common cells ───────────────────────────────────────────────────────
const Badge = ({ kind, children }) => (
{children}
);
const RiskBadge = ({ r }) => {
const label = { mandatory_high: "mandatory high" }[r] || r || "—";
return {label};
};
const TxStatusBadge = ({ s }) => {
const label = { pending_approval: "pending approval" }[s] || s;
return {label};
};
const RfiStatusBadge = ({ status, overdue }) => {
const label = overdue && status === "open" ? "open · overdue" : status;
return {label};
};
const StageBadge = ({ s }) => {
const label = { kyb_review: "kyb review", edd_required: "edd required", documents_pending: "docs pending", hard_blocked: "hard blocked" }[s] || s || "—";
return {label};
};
const FlagPills = ({ flags }) => {
if (!flags || flags.length === 0) return —;
return (
{flags.map((f, i) => {
const label = typeof f === "string" ? f : f.label || f.type || "flag";
const crit = (typeof f !== "string" && (f.type === "sanctions_near_miss" || f.type === "agent_auto_suspended")) ? "is-critical" : "";
return {label};
})}
);
};
const BizCell = ({ id, businesses }) => {
const b = businesses?.[id];
if (!b) return {id};
return (
{b.name}
{b.country || "—"} · {b.industry || "—"}
);
};
// ── Side nav ───────────────────────────────────────────────────────────
const NAV_ITEMS = [
{ id: "overview", label: "Overview", group: "Platform" },
{ id: "monitoring", label: "Transactions", group: "Monitoring" },
{ id: "rfis", label: "RFIs", group: "Monitoring" },
{ id: "alerts", label: "Alerts", group: "Monitoring" },
{ id: "businesses", label: "Businesses", group: "KYB" },
{ id: "onboarding", label: "Onboarding queue", group: "KYB" },
{ id: "reviews", label: "Periodic reviews", group: "KYB" },
{ id: "audit", label: "Audit log", group: "Platform" },
];
const NavIcon = ({ name }) => {
const p = { className: "tc-nav-item-icon", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" };
switch (name) {
case "overview": return ;
case "monitoring": return ;
case "rfis": return ;
case "alerts": return ;
case "businesses": return ;
case "onboarding": return ;
case "reviews": return ;
case "audit": return ;
default: return null;
}
};
function SideNav({ page, onNav, counts, you }) {
const groups = ["Platform", "Monitoring", "KYB"];
const alertish = new Set(["rfis", "alerts", "onboarding", "monitoring", "reviews"]);
return (
);
}
// ── Topbar ─────────────────────────────────────────────────────────────
const PAGE_TITLES = {
overview: { eyebrow: "Platform · live", h: "Compliance overview" },
monitoring: { eyebrow: "Monitoring · 48h window", h: "Transaction monitor" },
rfis: { eyebrow: "Monitoring", h: "Requests for information" },
alerts: { eyebrow: "Monitoring", h: "Alerts feed" },
businesses: { eyebrow: "KYB", h: "Onboarded businesses" },
onboarding: { eyebrow: "KYB", h: "Onboarding queue" },
reviews: { eyebrow: "KYB · ongoing monitoring", h: "Periodic client reviews" },
audit: { eyebrow: "Platform", h: "Audit log" },
};
function TopBar({ page, lastFetch, onRefresh }) {
const t = PAGE_TITLES[page] || { eyebrow: "", h: page };
return (
);
}
// ── Pages ──────────────────────────────────────────────────────────────
function OverviewPage({ state, onNav, onOpen }) {
const { businesses = [], biz_by_id = {}, transactions = [], rfis = [], alerts = [], applications = [] } = state;
const today = new Date().toISOString().slice(0, 10);
const activeBiz = businesses.filter(b => b.status === "active").length;
const openRfis = rfis.filter(r => r.status === "open" || r.status === "responded").length;
const overdueRfis = rfis.filter(r => r.overdue && r.status !== "resolved").length;
const pendingApprovals = transactions.filter(t => t.status === "pending_approval").length;
const flaggedToday = transactions.filter(t => (t.flags?.length > 0) && (t.ts || "").startsWith(today)).length;
// Pipeline funnel
const onboarding = applications.length ? applications : businesses.filter(b => b.status === "onboarding").map(b => ({
id: b.id, name: b.name, onboardingStage: b.onboardingStage, daysInQueue: b.daysInQueue, riskRating: b.riskRating, country: b.country, industry: b.industry, submittedDate: b.submittedDate, assignedAnalyst: b.assignedAnalyst,
}));
const stages = [
{ k: "submitted", label: "Submitted" },
{ k: "kyb_review", label: "KYB review" },
{ k: "edd_required", label: "EDD required", flag: "is-edd" },
{ k: "documents_pending", label: "Docs pending" },
{ k: "hard_blocked", label: "Hard blocked", flag: "is-blocked" },
];
const stageCount = (k) => onboarding.filter(a => a.onboardingStage === k).length;
const recentAlerts = [...alerts].sort((a, b) => (b.ts || "").localeCompare(a.ts || "")).slice(0, 6);
const recentTx = [...transactions].sort((a, b) => (b.ts || "").localeCompare(a.ts || "")).slice(0, 8);
return (
<>
Active businesses{activeBiz}{businesses.length} total
Open RFIs{openRfis}awaiting / responded
Overdue RFIs{overdueRfis}past due date
Pending approvals{pendingApprovals}awaiting decision
Flagged today{flaggedToday}transactions
Onboarding pipeline
onNav("onboarding")} style={{ cursor: "pointer" }}>view queue →
{stages.map(s => (
{s.label}
{stageCount(s.k)}
applications
))}
Recent alerts
onNav("alerts")} style={{ cursor: "pointer" }}>all alerts →
| Severity | Trigger | Business | When |
{recentAlerts.length === 0 && | no alerts |
}
{recentAlerts.map(a => (
| {a.severity} |
{a.trigger} |
|
{fmtRelative(a.ts)} |
))}
Recent transactions
onNav("monitoring")} style={{ cursor: "pointer" }}>monitor →
| Time | Counterparty | Amount | Status |
{recentTx.length === 0 && | no transactions |
}
{recentTx.map(t => (
| {fmtRelative(t.ts)} |
{t.counterparty || "—"} |
{fmtMoney(t.amount, t.ccy)} |
|
))}
>
);
}
function MonitoringPage({ state, refresh, onOpenBiz }) {
const { transactions = [], biz_by_id = {} } = state;
const [filter, setFilter] = useState("all");
const [search, setSearch] = useState("");
const [busy, setBusy] = useState(null);
const rows = useMemo(() => {
return transactions
.filter(t => filter === "all" || t.status === filter)
.filter(t => !search || (t.counterparty || "").toLowerCase().includes(search.toLowerCase()) || (t.id || "").toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => (b.ts || "").localeCompare(a.ts || ""));
}, [transactions, filter, search]);
async function doApproval(t, action) {
setBusy(t.id);
try {
// Find approval id for this tx
const list = await apiGet(`/approvals?business_id=${encodeURIComponent(t.bizId)}`);
const approval = (Array.isArray(list) ? list : list?.approvals || []).find(a => a.tx_id === t.id || a.transaction_id === t.id || a.id === t.id);
const apId = approval?.id || approval?.approval_id || t.id;
await apiPost(`/approvals/${encodeURIComponent(apId)}/${action}`);
toast(`${action === "approve" ? "Approved" : "Rejected"} ${t.id}`);
refresh();
} catch (e) {
toast(`${action} failed: ${e.message}`, { error: true });
} finally {
setBusy(null);
}
}
return (
| Time (UTC) |
Business |
Agent |
Op |
Counterparty |
Amount |
Status |
Flags |
|
{rows.length === 0 && | no transactions match filters |
}
{rows.map(t => (
| {fmtDateTime(t.ts)} {t.id} |
onOpenBiz?.(t.bizId)} style={{ cursor: "pointer" }}> |
{t.agent || "—"} |
{t.op || "—"} |
{t.counterparty || "—"} {t.reason} |
{fmtMoney(t.amount, t.ccy)} |
|
|
{t.status === "pending_approval" && (
)}
|
))}
);
}
function RfisPage({ state, onOpenRfi }) {
const { rfis = [], biz_by_id = {} } = state;
const [filter, setFilter] = useState("all");
const rows = useMemo(() => {
return rfis
.filter(r => filter === "all" || r.status === filter || (filter === "overdue" && r.overdue))
.sort((a, b) => {
// overdue first, then by createdAt desc
if (a.overdue !== b.overdue) return a.overdue ? -1 : 1;
return (b.createdAt || "").localeCompare(a.createdAt || "");
});
}, [rfis, filter]);
return (
{rows.length} of {rfis.length}
| RFI |
Business |
Status |
Urgency |
Created |
Due |
|
{rows.length === 0 && | no RFIs |
}
{rows.map(r => (
onOpenRfi(r.id)}>
| {r.id} {(r.questions || []).length} question{(r.questions || []).length === 1 ? "" : "s"} |
|
|
{r.urgency || "standard"} |
{fmtDateTime(r.createdAt)} |
{fmtDate(r.dueAt)}{r.overdue && overdue} |
→ |
))}
);
}
function AlertsPage({ state, onOpenRfi }) {
const { alerts = [], biz_by_id = {} } = state;
const [sev, setSev] = useState("all");
const rows = useMemo(() => alerts
.filter(a => sev === "all" || a.severity === sev)
.sort((a, b) => (b.ts || "").localeCompare(a.ts || "")), [alerts, sev]);
return (
{rows.length} of {alerts.length}
| Alert | Severity | Trigger | Business | RFI | When |
{rows.length === 0 && | no alerts |
}
{rows.map(a => (
| {a.id} {a.type} |
{a.severity} |
{a.trigger} |
|
{a.relatedRfi ? onOpenRfi(a.relatedRfi)}>{a.relatedRfi} : —} |
{fmtDateTime(a.ts)} |
))}
);
}
function BusinessesPage({ state, onOpenBiz }) {
const { businesses = [] } = state;
const [search, setSearch] = useState("");
const [risk, setRisk] = useState("all");
const [status, setStatus] = useState("all");
const rows = useMemo(() => businesses
.filter(b => b.status !== "onboarding")
.filter(b => risk === "all" || b.riskRating === risk)
.filter(b => status === "all" || b.status === status)
.filter(b => !search || (b.name || "").toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => (b.volume30d || 0) - (a.volume30d || 0)), [businesses, search, risk, status]);
return (
| Business | Status | Risk | Active agents | 30d volume | Open RFIs | |
{rows.length === 0 && | no businesses |
}
{rows.map(b => (
onOpenBiz(b.id)}>
{b.name}{b.id} · {b.country || "—"} · {b.industry || "—"} |
{b.status?.replace("_", " ")} |
|
{b.activeAgents ?? 0}{b.suspendedAgents ? +{b.suspendedAgents} susp : null} |
{fmtMoney(b.volume30d, b.volumeCcy)} |
{b.openRfis || 0} |
→ |
))}
);
}
function OnboardingPage({ state, onOpenBiz }) {
const { applications = [], businesses = [], analysts = [] } = state;
const apps = applications.length ? applications : businesses.filter(b => b.status === "onboarding");
const analystName = (id) => analysts.find(a => a.id === id)?.name || (id ? id : "unassigned");
const rows = [...apps].sort((a, b) => (b.daysInQueue || 0) - (a.daysInQueue || 0));
return (
Onboarding queue
{rows.length} applications
| Application | Stage | Risk | Country | Days in queue | Submitted | Analyst |
{rows.length === 0 && | no applications in queue |
}
{rows.map(a => (
onOpenBiz(a.id)}>
{a.name}{a.id} · {a.industry || "—"} |
|
|
{a.country || "—"} |
{a.daysInQueue ?? "—"} |
{fmtDate(a.submittedDate)} |
{analystName(a.assignedAnalyst)} |
))}
);
}
function AuditPage({ state }) {
const { audit = [], biz_by_id = {} } = state;
const [search, setSearch] = useState("");
const rows = useMemo(() => audit
.filter(e => !search || (e.type || "").includes(search.toLowerCase()) || (e.details || "").toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => (b.ts || "").localeCompare(a.ts || "")), [audit, search]);
return (
| Time (UTC) | Event | Principal | Business | Detail |
{rows.length === 0 && | no audit entries |
}
{rows.map(e => (
| {fmtDateTime(e.ts)} {e.id} |
{e.type} |
{e.principal || "—"} |
|
{e.details || "—"} |
))}
);
}
function ReviewsPage({ state, refresh }) {
const { reviews_due = [], reviews_pending = [], biz_by_id = {} } = state;
const [tab, setTab] = useState("pending");
const [reviewDrawer, setReviewDrawer] = useState(null);
const pendingWithClient = reviews_pending.map(r => ({
...r,
company_name: (() => {
// look up company name via client_id or business_id
const cf = Object.values(biz_by_id).find(b => b.client_id === r.client_id) || biz_by_id[r.client_id];
return cf ? cf.name : r.client_id;
})(),
}));
return (
{tab === "pending" && (
| Client | Review ID | Type | Opened | Assigned to | Progress | |
{pendingWithClient.length === 0 && | no in-progress periodic reviews |
}
{pendingWithClient.map(r => {
const total = r.checklist.length;
const done = r.checklist.filter(i => i.checked).length;
return (
setReviewDrawer(r)}>
| {r.company_name} {r.client_id} |
{r.review_id} |
{r.review_type} |
{fmtDateTime(r.opened_at)} |
{r.assigned_to || unassigned} |
|
|
);
})}
)}
{tab === "due" && (
| Client | Risk rating | Review date | Days overdue |
{reviews_due.length === 0 && | no clients currently due for review |
}
{reviews_due.map(c => {
const overdue = c.next_review_date ? Math.max(0, Math.floor((Date.now() - new Date(c.next_review_date)) / 86400000)) : null;
return (
| {c.company_name} {c.client_id} |
|
{fmtDate(c.next_review_date)} |
{overdue != null ? 30 ? "var(--crit)" : "var(--warn)" }}>{overdue}d : "—"} |
);
})}
)}
{reviewDrawer && (
{ setReviewDrawer(null); refresh(); }}
refresh={refresh}
/>
)}
);
}
function ReviewDrawer({ review, onClose, refresh }) {
const [checklist, setChecklist] = useState(() => (review.checklist || []).map(i => ({ ...i })));
const [decision, setDecision] = useState("");
const [notes, setNotes] = useState("");
const [nextDate, setNextDate] = useState("");
const [ratingAfter, setRatingAfter] = useState("");
const [busy, setBusy] = useState(false);
const total = checklist.length;
const done = checklist.filter(i => i.checked).length;
async function saveChecklist(updated) {
setChecklist(updated);
try {
await apiPost(`/internal/clients/${encodeURIComponent(review.client_id)}/reviews/${encodeURIComponent(review.review_id)}/checklist`, { items: updated });
} catch (e) {
toast(`Checklist save failed: ${e.message}`, { error: true });
}
}
function toggleItem(idx) {
const updated = checklist.map((item, i) => i === idx ? { ...item, checked: !item.checked } : item);
saveChecklist(updated);
}
async function completeReview() {
if (!decision) { toast("Select a decision", { error: true }); return; }
setBusy(true);
try {
await apiPost(`/internal/clients/${encodeURIComponent(review.client_id)}/reviews/${encodeURIComponent(review.review_id)}/complete`, {
decision,
notes,
risk_rating_after: ratingAfter || null,
next_review_date: nextDate || null,
});
toast("Review completed");
onClose();
} catch (e) {
toast(`Complete failed: ${e.message}`, { error: true });
} finally {
setBusy(false);
}
}
return (
<>
>
);
}
// ── Drawers ────────────────────────────────────────────────────────────
function Drawer({ open, onClose, eyebrow, title, meta, children, footer }) {
useEffect(() => {
if (!open) return;
const onKey = (e) => { if (e.key === "Escape") onClose(); };
document.addEventListener("keydown", onKey);
document.body.style.overflow = "hidden";
return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
}, [open, onClose]);
if (!open) return null;
return (
<>
>
);
}
const DSec = ({ title, children, meta }) => (
);
function BusinessDrawer({ id, state, onClose, refresh }) {
const b = state.biz_by_id?.[id];
const [busy, setBusy] = useState(null);
const [reviewerId, setReviewerId] = useState("");
const [approveJustification, setApproveJustification] = useState("");
const [rejectReason, setRejectReason] = useState("");
const [docs, setDocs] = useState([]);
const [docsLoading, setDocsLoading] = useState(false);
const [verifications, setVerifications] = useState([]);
useEffect(() => {
if (!b?.client_id) { setDocs([]); setVerifications([]); return; }
setDocsLoading(true);
apiGet(`/internal/clients/${encodeURIComponent(b.client_id)}/documents`)
.then(d => setDocs(d.documents || []))
.catch(() => setDocs([]))
.finally(() => setDocsLoading(false));
apiGet(`/internal/clients/${encodeURIComponent(b.client_id)}/verifications`)
.then(d => setVerifications(d.verifications || []))
.catch(() => setVerifications([]));
}, [id, b?.client_id]);
if (!b) return null;
const isOnboarding = b.status === "onboarding";
const txForBiz = (state.transactions || []).filter(t => t.bizId === id).slice(0, 5);
// Synthesise agent list from transactions if not provided directly
const agents = useMemo(() => {
const m = new Map();
(state.transactions || []).filter(t => t.bizId === id).forEach(t => {
if (!t.agent) return;
if (!m.has(t.agent)) m.set(t.agent, { id: t.agent, lastTx: t.ts, status: "active" });
else if ((t.ts || "") > m.get(t.agent).lastTx) m.get(t.agent).lastTx = t.ts;
});
return [...m.values()];
}, [state.transactions, id]);
async function toggleAgent(agentId, action) {
setBusy(agentId);
try {
await apiPost(`/agents/${encodeURIComponent(agentId)}/${action}`);
toast(`Agent ${action}d`);
refresh();
} catch (e) {
toast(`${action} failed: ${e.message}`, { error: true });
} finally {
setBusy(null);
}
}
async function manualApproveOnboarding() {
const who = reviewerId.trim();
const notes = approveJustification.trim();
if (!who || !notes) return;
setBusy("approve-onb");
try {
await apiPost(`/internal/onboarding/${encodeURIComponent(id)}/approve`, {
notes,
approved_by: who,
});
toast("Application approved · manual override logged");
await refresh();
onClose();
} catch (e) {
toast(`Approve failed: ${e.message}`, { error: true });
} finally {
setBusy(null);
}
}
async function manualRejectOnboarding() {
const who = reviewerId.trim();
const reason = rejectReason.trim();
if (!who || !reason) return;
setBusy("reject-onb");
try {
await apiPost(`/internal/onboarding/${encodeURIComponent(id)}/reject`, {
reason,
rejected_by: who,
});
toast("Application rejected · logged");
await refresh();
onClose();
} catch (e) {
toast(`Reject failed: ${e.message}`, { error: true });
} finally {
setBusy(null);
}
}
return (
{(b.status || "active").replace("_", " ")}
{b.country || "—"} · {b.industry || "—"}
>}
>
{isOnboarding && b.customer_update ? (
{b.customer_update}
) : null}
{isOnboarding && (
Use when EDD or KYB queue requires an explicit ops decision. Provide your reviewer identity and a written justification (approve) or reason (reject).
Approve · justification (required)
Reject · reason (required)
)}
Legal{b.legal || b.name}
Reg. number{b.regNumber || "—"}
Jurisdiction{b.jurisdiction || b.countryName || b.country || "—"}
Onboarded{fmtDate(b.onboardedDate)}
30d volume{fmtMoney(b.volume30d, b.volumeCcy)}
Open RFIs{b.openRfis || 0}
{b.profile?.summary && {b.profile.summary}
}
{agents.length === 0 && No agents observed in recent transactions.}
{agents.map(a => (
{a.id}
last tx · {fmtRelative(a.lastTx)}
))}
{txForBiz.length === 0 && none}
{txForBiz.map(t => (
{t.counterparty || "—"}
{fmtDateTime(t.ts)} · {t.op}
{fmtMoney(t.amount, t.ccy)}
))}
{verifications.length > 0 && (
{verifications.map(v => {
const statusColor = v.status === "completed" ? "var(--ok)" : v.status === "failed" ? "var(--crit)" : v.status === "expired" ? "var(--ink-3)" : "var(--warn)";
const statusIcon = v.status === "completed" ? "✓" : v.status === "failed" ? "✗" : v.status === "expired" ? "—" : "○";
return (
{v.selfie_url && (
)}
{v.director_name}
{statusIcon} {v.status}
{v.match_score != null && (
score {(v.match_score * 100).toFixed(0)}%
)}
{v.status === "pending" && v.verification_url && (
{v.verification_url}
)}
{v.completed_at && (
{fmtDateTime(v.completed_at)}
)}
);
})}
)}
{docsLoading && Loading…}
{!docsLoading && !b.client_id && No client file linked to this business.}
{!docsLoading && b.client_id && docs.length === 0 && No documents on file.}
{docs.map(d => (
{d.doc_type || "Document"}
{d.file_name}
{d.uploaded_by ? ` · uploaded by ${d.uploaded_by}` : ""}
{" · "}{fmtDate(d.uploaded_at)}
{d.file_size ? ` · ${d.file_size < 1048576 ? (d.file_size/1024).toFixed(1)+"KB" : (d.file_size/1048576).toFixed(1)+"MB"}` : ""}
{d.extracted_data && Object.keys(d.extracted_data).length > 0 && (
{Object.entries(d.extracted_data).slice(0, 6).map(([k, v]) => (
{k.replace(/_/g, " ")}
{" "}{String(v).slice(0, 60)}
))}
)}
{d.category}
{d.has_file && (
Download
)}
))}
);
}
function RfiDrawer({ id, state, onClose, refresh }) {
const r = (state.rfis || []).find(x => x.id === id);
const [response, setResponse] = useState("");
const [escReason, setEscReason] = useState("");
const [busy, setBusy] = useState(null);
if (!r) return null;
async function act(action, body) {
setBusy(action);
try {
await apiPost(`/rfis/${encodeURIComponent(r.id)}/${action}`, body);
toast(`RFI ${action}`);
setResponse(""); setEscReason("");
refresh();
onClose();
} catch (e) {
toast(`${action} failed: ${e.message}`, { error: true });
} finally {
setBusy(null);
}
}
return (
{r.urgency || "standard"}
due · {fmtDate(r.dueAt)}
>}
footer={
r.status !== "resolved" && (
<>
{r.status === "responded" && (
)}
>
)
}
>
Created {fmtDateTime(r.createdAt)} · {r.respondedAt ? `responded ${fmtRelative(r.respondedAt)}` : "no response yet"}
{(r.questions || []).map((q, i) => (
{String(i + 1).padStart(2, "0")}
{q}
))}
{r.response && (
{r.response.body}
)}
{r.status !== "resolved" && (
<>
>
)}
);
}
// ── Dev toolbar ────────────────────────────────────────────────────────
function DevBar({ onReset, lastFetch }) {
const [busy, setBusy] = useState(false);
async function reset() {
if (busy) return;
if (!confirm("Reset all server data to seed fixtures?")) return;
setBusy(true);
try {
await apiReset();
toast("Data reset · refreshing");
await onReset();
} catch (e) {
toast(`Reset failed: ${e.message}`, { error: true });
} finally {
setBusy(false);
}
}
return (
DEV
API · {API_BASE.replace("https://", "")}
);
}
// ── App shell ──────────────────────────────────────────────────────────
function App() {
const [page, setPage] = useState("overview");
const [state, setState] = useState({});
const [boot, setBoot] = useState({ phase: "loading", error: null });
const [lastFetch, setLastFetch] = useState(null);
const [bizDrawer, setBizDrawer] = useState(null);
const [rfiDrawer, setRfiDrawer] = useState(null);
const [toastMsg, setToastMsg] = useState(null);
toastSetter = setToastMsg;
const refresh = useCallback(async () => {
setBoot({ phase: "loading", error: null });
try {
const d = await apiGet("/internal/state");
setState(d);
setLastFetch(new Date().toISOString());
setBoot({ phase: "ready", error: null });
} catch (e) {
const msg = e.message || String(e);
setBoot({ phase: "error", error: msg });
toast(`Fetch failed: ${msg}`, { error: true });
}
}, []);
useEffect(() => { refresh(); }, [refresh]);
// Esc closes drawers
useEffect(() => {
const k = (e) => { if (e.key === "Escape") { setBizDrawer(null); setRfiDrawer(null); } };
window.addEventListener("keydown", k);
return () => window.removeEventListener("keydown", k);
}, []);
const counts = useMemo(() => {
const tx = state.transactions || [];
const r = state.rfis || [];
const a = state.alerts || [];
const apps = state.applications && state.applications.length ? state.applications : (state.businesses || []).filter(b => b.status === "onboarding");
return {
monitoring: tx.filter(t => t.status === "pending_approval").length,
rfis: r.filter(x => x.status === "open" || x.status === "responded" || x.status === "escalated").length,
alerts: a.filter(x => x.status === "new").length,
onboarding: apps.length,
reviews: (state.reviews_pending || []).length,
};
}, [state]);
const you = (state.analysts || [])[0] || { name: "Diana Castellanos", role: "Senior compliance officer", initials: "DC" };
function openBiz(id) { setRfiDrawer(null); setBizDrawer(id); }
function openRfi(id) { setBizDrawer(null); setRfiDrawer(id); }
return (
{boot.phase === "loading" && LOADING /internal/state …
}
{boot.phase === "error" && (
Cannot load dashboard
GET /internal/state failed: {boot.error}
- 404 — redeploy the API so internal_api.py includes the /internal/state route.
- 401 — set Railway TENOR_INTERNAL_KEY to match RESET_KEY in this page's script.
)}
{boot.phase === "ready" && page === "overview" && }
{boot.phase === "ready" && page === "monitoring" && }
{boot.phase === "ready" && page === "rfis" && }
{boot.phase === "ready" && page === "alerts" && }
{boot.phase === "ready" && page === "businesses" && }
{boot.phase === "ready" && page === "onboarding" && }
{boot.phase === "ready" && page === "reviews" && }
{boot.phase === "ready" && page === "audit" && }
{bizDrawer &&
setBizDrawer(null)} refresh={refresh} />}
{rfiDrawer && setRfiDrawer(null)} refresh={refresh} />}
{toastMsg && {toastMsg.msg}
}
);
}
ReactDOM.createRoot(document.getElementById("root")).render();