import {
Alert,
Box,
Button,
Chip,
IconButton,
Paper,
Stack,
TextField,
Tooltip,
Typography
} from "@exotel-npm-dev/signal-design-system";
import { useMemo, useState, type MouseEvent } from "react";
import { safeJson } from "../utils/safeJson";
export type EventEntry = {
id: string;
ts: number;
name: string;
data: unknown;
};
function formatTs(ts: number): string {
const d = new Date(ts);
const hh = String(d.getHours()).padStart(2, "0");
const mm = String(d.getMinutes()).padStart(2, "0");
const ss = String(d.getSeconds()).padStart(2, "0");
const ms = String(d.getMilliseconds()).padStart(3, "0");
return `${hh}:${mm}:${ss}.${ms}`;
}
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
return (
) => {
e.stopPropagation();
void navigator.clipboard
.writeText(text)
.then(() => {
setCopied(true);
window.setTimeout(() => setCopied(false), 1200);
})
.catch(() => undefined);
}}
aria-label="Copy"
>
{copied ? "✓" : "⧉"}
);
}
export function EventsPanel({
events,
onClear,
registered
}: {
events: EventEntry[];
onClear: () => void;
registered: string[];
}) {
const [filter, setFilter] = useState("");
const [expanded, setExpanded] = useState>({});
const visible = useMemo(() => {
const q = filter.trim().toLowerCase();
const matched = q
? events.filter((e) => {
if (e.name.toLowerCase().includes(q)) return true;
try {
return safeJson(e.data).toLowerCase().includes(q);
} catch {
return false;
}
})
: events;
// Force newest-first regardless of how the parent stored them — defensive
// against future state changes / restored sessionStorage order.
return [...matched].sort((a, b) => b.ts - a.ts);
}, [events, filter]);
return (
setFilter(e.target.value)}
sx={{ minWidth: 240, flex: 1 }}
/>
Showing {visible.length} of {events.length}
{events.length === 0 && (
No events yet. Trigger something in the agent UI (start a call, change state, accept chat).
Listeners include:{" "}
{registered.slice(0, 8).join(", ")}
{registered.length > 8 ? `, +${registered.length - 8} more` : ""}
)}
{visible.map((ev) => {
const isOpen = !!expanded[ev.id];
const payloadStr = safeJson(ev.data);
return (
setExpanded((m) => ({ ...m, [ev.id]: !isOpen }))}
sx={{
p: 1.25,
cursor: "pointer",
userSelect: "none",
borderLeft: 3,
borderLeftColor: "primary.main",
"&:hover": { bgcolor: "action.hover" }
}}
>
▶
{formatTs(ev.ts)}
{isOpen && (
) => e.stopPropagation()}
sx={{
mt: 1,
mb: 0,
p: 1,
bgcolor: "action.hover",
borderRadius: 1,
fontSize: 12,
overflow: "auto",
maxHeight: 320,
cursor: "text"
}}
>
{payloadStr}
)}
);
})}
);
}