import { useEffect, useMemo, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faHeadset, faPhoneVolume, faPause, faClock, faSparkles, faCalendarCheck, faClockRotateLeft } from '@fortawesome/pro-duotone-svg-icons'; import { PageHeader } from '@/components/layout/page-header'; import { Badge } from '@/components/base/badges/badges'; import { Table } from '@/components/application/table/table'; import { BargeControls } from '@/components/call-desk/barge-controls'; import { apiClient } from '@/lib/api-client'; import { useData } from '@/providers/data-provider'; import { formatShortDate } from '@/lib/format'; import { cx } from '@/utils/cx'; type ActiveCall = { ucid: string; agentId: string; callerNumber: string; callType: string; startTime: string; status: 'active' | 'on-hold'; }; type CallerContext = { name: string; phone: string; source: string | null; status: string | null; interestedService: string | null; aiSummary: string | null; patientType: string | null; leadId: string | null; appointments: Array<{ id: string; scheduledAt: string; doctorName: string; department: string; status: string }>; }; const formatDuration = (startTime: string): string => { const seconds = Math.max(0, Math.floor((Date.now() - new Date(startTime).getTime()) / 1000)); const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m}:${s.toString().padStart(2, '0')}`; }; const KpiCard = ({ value, label, icon }: { value: string | number; label: string; icon: any }) => (

{value}

{label}

); export const LiveMonitorPage = () => { const [activeCalls, setActiveCalls] = useState([]); const [loading, setLoading] = useState(true); const [tick, setTick] = useState(0); const [selectedCall, setSelectedCall] = useState(null); const [callerContext, setCallerContext] = useState(null); const [contextLoading, setContextLoading] = useState(false); const { leads } = useData(); // Initial load + SSE stream for real-time active call updates useEffect(() => { // Initial snapshot apiClient.get('/api/supervisor/active-calls', { silent: true }) .then(setActiveCalls) .catch(() => {}) .finally(() => setLoading(false)); // SSE stream — receives update/remove events in real-time const apiUrl = import.meta.env.VITE_API_URL ?? ''; const es = new EventSource(`${apiUrl}/api/supervisor/active-calls/stream`); es.onmessage = (msg) => { try { const event = JSON.parse(msg.data) as { type: 'update' | 'remove'; call?: ActiveCall; ucid: string }; setActiveCalls(prev => { if (event.type === 'remove') { return prev.filter(c => c.ucid !== event.ucid); } if (event.type === 'update' && event.call) { const exists = prev.find(c => c.ucid === event.ucid); if (exists) { return prev.map(c => c.ucid === event.ucid ? event.call! : c); } return [...prev, event.call]; } return prev; }); } catch {} }; es.onerror = () => { // SSE reconnects automatically; no-op }; return () => es.close(); }, []); // Clear selection if the selected call ended useEffect(() => { if (selectedCall && !activeCalls.find(c => c.ucid === selectedCall.ucid)) { setSelectedCall(null); setCallerContext(null); } }, [activeCalls, selectedCall]); // Tick every second for duration display useEffect(() => { const interval = setInterval(() => setTick(t => t + 1), 1000); return () => clearInterval(interval); }, []); const onHold = activeCalls.filter(c => c.status === 'on-hold').length; const avgDuration = useMemo(() => { if (activeCalls.length === 0) return '0:00'; const totalSec = activeCalls.reduce((sum, c) => { return sum + Math.max(0, Math.floor((Date.now() - new Date(c.startTime).getTime()) / 1000)); }, 0); const avg = Math.floor(totalSec / activeCalls.length); return `${Math.floor(avg / 60)}:${(avg % 60).toString().padStart(2, '0')}`; }, [activeCalls, tick]); // Match caller to lead const resolveCallerName = (phone: string): string | null => { if (!phone) return null; const clean = phone.replace(/\D/g, ''); const lead = leads.find(l => { const lp = (l.contactPhone?.[0]?.number ?? '').replace(/\D/g, ''); return lp && (lp.endsWith(clean) || clean.endsWith(lp)); }); if (lead) { return `${lead.contactName?.firstName ?? ''} ${lead.contactName?.lastName ?? ''}`.trim() || null; } return null; }; // Fetch caller context when a call is selected const handleSelectCall = (call: ActiveCall) => { setSelectedCall(call); setContextLoading(true); setCallerContext(null); const phoneClean = call.callerNumber.replace(/\D/g, ''); // Search for lead by phone apiClient.graphql<{ leads: { edges: Array<{ node: any }> } }>( `{ leads(first: 5, filter: { contactPhone: { primaryPhoneNumber: { like: "%${phoneClean.slice(-10)}" } } }) { edges { node { id contactName { firstName lastName } source status interestedService aiSummary patientId } } } }`, ).then(async (data) => { const lead = data.leads.edges[0]?.node; const name = lead ? `${lead.contactName?.firstName ?? ''} ${lead.contactName?.lastName ?? ''}`.trim() : resolveCallerName(call.callerNumber) ?? 'Unknown Caller'; let appointments: CallerContext['appointments'] = []; if (lead?.patientId) { try { const apptData = await apiClient.graphql<{ appointments: { edges: Array<{ node: any }> } }>( `{ appointments(first: 5, filter: { patientId: { eq: "${lead.patientId}" } }, orderBy: [{ scheduledAt: DescNullsLast }]) { edges { node { id scheduledAt doctorName department status } } } }`, ); appointments = apptData.appointments.edges.map(e => e.node); } catch { /* best effort */ } } setCallerContext({ name, phone: call.callerNumber, source: lead?.source ?? null, status: lead?.status ?? null, interestedService: lead?.interestedService ?? null, aiSummary: lead?.aiSummary ?? null, patientType: lead?.patientId ? 'RETURNING' : 'NEW', leadId: lead?.id ?? null, appointments, }); }).catch(() => { setCallerContext({ name: resolveCallerName(call.callerNumber) ?? 'Unknown Caller', phone: call.callerNumber, source: null, status: null, interestedService: null, aiSummary: null, patientType: null, leadId: null, appointments: [], }); }).finally(() => setContextLoading(false)); }; return ( <>
{/* Left panel — KPIs + call list */}
{/* KPI Cards */}
{/* Active Calls Table */}

Active Calls

{loading ? (

Loading...

) : activeCalls.length === 0 ? (

No active calls

Active calls will appear here in real-time

) : ( {(call) => { const callerName = resolveCallerName(call.callerNumber); const typeLabel = call.callType === 'InBound' ? 'In' : 'Out'; const typeColor = call.callType === 'InBound' ? 'blue' : 'brand'; const isSelected = selectedCall?.ucid === call.ucid; return ( handleSelectCall(call)} > {call.agentId}
{callerName && {callerName}} {call.callerNumber}
{typeLabel} {formatDuration(call.startTime)} {call.status}
); }}
)}
{/* Right panel — context + barge controls */}
{!selectedCall ? (

Select a call to monitor

Click on any active call to see context and connect

) : contextLoading ? (

Loading caller context...

) : (
{/* Caller header */}
{(callerContext?.name ?? '?')[0].toUpperCase()}

{callerContext?.name}

{callerContext?.phone}

{callerContext?.patientType && ( {callerContext.patientType === 'RETURNING' ? 'Returning' : 'New'} )}
{/* Source + status */} {(callerContext?.source || callerContext?.status) && (
{callerContext.source && ( {callerContext.source.replace(/_/g, ' ')} )} {callerContext.status && ( {callerContext.status.replace(/_/g, ' ')} )}
)} {callerContext?.interestedService && (

Interested in: {callerContext.interestedService}

)}
{/* AI Summary */} {callerContext?.aiSummary && (
AI Insight

{callerContext.aiSummary}

)} {/* Appointments */} {callerContext?.appointments && callerContext.appointments.length > 0 && (
Appointments
{callerContext.appointments.map(appt => (
{appt.doctorName ?? 'Appointment'} {appt.department && {appt.department}} {appt.scheduledAt && ( — {formatShortDate(appt.scheduledAt)} )}
{(appt.status ?? 'Scheduled').replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, c => c.toUpperCase())}
))}
)} {/* Call info */}
Current Call
Agent: {selectedCall.agentId}
Type: {selectedCall.callType === 'InBound' ? 'Inbound' : 'Outbound'}
Duration: {formatDuration(selectedCall.startTime)}
Status: {selectedCall.status}
{/* Barge Controls */}
{ // Keep selection visible but controls reset to idle/ended }} />
)}
); };