diff --git a/src/pages/live-monitor.tsx b/src/pages/live-monitor.tsx
index 4938012..7de058e 100644
--- a/src/pages/live-monitor.tsx
+++ b/src/pages/live-monitor.tsx
@@ -1,12 +1,14 @@
import { useEffect, useMemo, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faHeadset, faPhoneVolume, faPause, faClock } from '@fortawesome/pro-duotone-svg-icons';
+import { faHeadset, faPhoneVolume, faPause, faClock, faSparkles, faCalendarCheck, faClockRotateLeft } from '@fortawesome/pro-duotone-svg-icons';
import { TopBar } from '@/components/layout/top-bar';
import { Badge } from '@/components/base/badges/badges';
-import { Button } from '@/components/base/buttons/button';
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;
@@ -17,6 +19,18 @@ type ActiveCall = {
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);
@@ -25,10 +39,10 @@ const formatDuration = (startTime: string): string => {
};
const KpiCard = ({ value, label, icon }: { value: string | number; label: string; icon: any }) => (
-
-
-
{value}
-
{label}
+
);
@@ -36,13 +50,23 @@ 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();
// Poll active calls every 5 seconds
useEffect(() => {
const fetchCalls = () => {
apiClient.get('/api/supervisor/active-calls', { silent: true })
- .then(setActiveCalls)
+ .then(calls => {
+ setActiveCalls(calls);
+ // If selected call ended, clear selection
+ if (selectedCall && !calls.find(c => c.ucid === selectedCall.ucid)) {
+ setSelectedCall(null);
+ setCallerContext(null);
+ }
+ })
.catch(() => {})
.finally(() => setLoading(false));
};
@@ -50,9 +74,9 @@ export const LiveMonitorPage = () => {
fetchCalls();
const interval = setInterval(fetchCalls, 5000);
return () => clearInterval(interval);
- }, []);
+ }, [selectedCall?.ucid]);
- // Tick every second to update duration counters
+ // Tick every second for duration display
useEffect(() => {
const interval = setInterval(() => setTick(t => t + 1), 1000);
return () => clearInterval(interval);
@@ -82,97 +106,256 @@ export const LiveMonitorPage = () => {
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 (
<>
-
+
-
- {/* KPI Cards */}
-
-
-
-
-
+
+ {/* Left panel — KPIs + call list */}
+
+ {/* KPI Cards */}
+
+
+ {/* Active Calls Table */}
+
+
Active Calls
+
+ {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}
+
+
+
+ );
+ }}
+
+
+ )}
- {/* Active Calls Table */}
-
-
Active Calls
-
- {loading ? (
-
-
Loading...
+ {/* Right panel — context + barge controls */}
+
+ {!selectedCall ? (
+
+
+
Select a call to monitor
+
Click on any active call to see context and connect
- ) : activeCalls.length === 0 ? (
-
-
-
No active calls
-
Active calls will appear here in real-time
+ ) : contextLoading ? (
+
+
Loading caller context...
) : (
-
-
-
-
-
-
-
-
-
-
- {(call) => {
- const callerName = resolveCallerName(call.callerNumber);
- const typeLabel = call.callType === 'InBound' ? 'In' : 'Out';
- const typeColor = call.callType === 'InBound' ? 'blue' : 'brand';
+
+ {/* Caller header */}
+
+
+
+ {(callerContext?.name ?? '?')[0].toUpperCase()}
+
+
+
{callerContext?.name}
+
{callerContext?.phone}
+
+ {callerContext?.patientType && (
+
+ {callerContext.patientType === 'RETURNING' ? 'Returning' : 'New'}
+
+ )}
+
- return (
-
-
- {call.agentId}
-
-
-
- {callerName &&
{callerName}}
-
{call.callerNumber}
+ {/* 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)}
+ )}
-
-
- {typeLabel}
-
-
- {formatDuration(call.startTime)}
-
-
-
- {call.status}
+
+ {(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
+ }}
+ />
+
+
)}
-
- {/* Monitoring hint */}
- {activeCalls.length > 0 && (
-
-
-
-
Select "Listen" on any active call to start monitoring
-
Agent will not be notified during listen mode
-
-
- )}
>
);