mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: add Socket.IO client and useCallEvents hook for live CTI mode
This commit is contained in:
133
src/hooks/use-call-events.ts
Normal file
133
src/hooks/use-call-events.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { disconnectSocket, getSocket } from '@/lib/socket';
|
||||
import type { CallDisposition } from '@/types/entities';
|
||||
|
||||
type CallState = 'idle' | 'ringing' | 'active' | 'completed';
|
||||
|
||||
type EnrichedLead = {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
email?: string;
|
||||
source?: string;
|
||||
status?: string;
|
||||
campaign?: string;
|
||||
interestedService?: string;
|
||||
age: number;
|
||||
aiSummary?: string;
|
||||
aiSuggestedAction?: string;
|
||||
recentActivities: { activityType: string; summary: string; occurredAt: string; performedBy: string }[];
|
||||
};
|
||||
|
||||
type IncomingCallEvent = {
|
||||
callSid: string;
|
||||
eventType: 'ringing' | 'answered' | 'ended';
|
||||
lead: EnrichedLead | null;
|
||||
callerPhone: string;
|
||||
agentName: string;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
export const useCallEvents = (agentName: string) => {
|
||||
const [callState, setCallState] = useState<CallState>('idle');
|
||||
const [activeLead, setActiveLead] = useState<EnrichedLead | null>(null);
|
||||
const [activeCallSid, setActiveCallSid] = useState<string | null>(null);
|
||||
const [callStartTime, setCallStartTime] = useState<string | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const completedTimerRef = useRef<number | null>(null);
|
||||
|
||||
// Connect to WebSocket and register agent
|
||||
useEffect(() => {
|
||||
const socket = getSocket();
|
||||
|
||||
socket.on('connect', () => {
|
||||
setIsConnected(true);
|
||||
socket.emit('agent:register', agentName);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
setIsConnected(false);
|
||||
});
|
||||
|
||||
socket.on('agent:registered', (data: { agentName: string }) => {
|
||||
console.log(`Registered as agent: ${data.agentName}`);
|
||||
});
|
||||
|
||||
socket.on('call:incoming', (event: IncomingCallEvent) => {
|
||||
if (event.eventType === 'ringing' || event.eventType === 'answered') {
|
||||
setActiveCallSid(event.callSid);
|
||||
setActiveLead(event.lead);
|
||||
setCallStartTime(event.timestamp);
|
||||
|
||||
if (event.eventType === 'ringing') {
|
||||
setCallState('ringing');
|
||||
// Auto-transition to active after 1.5s (call is answered)
|
||||
setTimeout(() => setCallState('active'), 1500);
|
||||
} else {
|
||||
setCallState('active');
|
||||
}
|
||||
} else if (event.eventType === 'ended') {
|
||||
// Call ended from Exotel side (e.g. customer hung up)
|
||||
setCallState('completed');
|
||||
completedTimerRef.current = window.setTimeout(() => {
|
||||
setCallState('idle');
|
||||
setActiveLead(null);
|
||||
setActiveCallSid(null);
|
||||
setCallStartTime(null);
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('call:disposition:ack', () => {
|
||||
// Disposition saved on server
|
||||
});
|
||||
|
||||
socket.connect();
|
||||
|
||||
return () => {
|
||||
if (completedTimerRef.current) {
|
||||
clearTimeout(completedTimerRef.current);
|
||||
}
|
||||
disconnectSocket();
|
||||
};
|
||||
}, [agentName]);
|
||||
|
||||
// Send disposition to server
|
||||
const sendDisposition = useCallback(
|
||||
(disposition: CallDisposition, notes: string) => {
|
||||
const socket = getSocket();
|
||||
const duration = callStartTime
|
||||
? Math.floor((Date.now() - new Date(callStartTime).getTime()) / 1000)
|
||||
: 0;
|
||||
|
||||
socket.emit('call:disposition', {
|
||||
callSid: activeCallSid,
|
||||
leadId: activeLead?.id ?? null,
|
||||
disposition,
|
||||
notes,
|
||||
agentName,
|
||||
callerPhone: activeLead?.phone ?? '',
|
||||
startedAt: callStartTime,
|
||||
duration,
|
||||
});
|
||||
|
||||
setCallState('completed');
|
||||
completedTimerRef.current = window.setTimeout(() => {
|
||||
setCallState('idle');
|
||||
setActiveLead(null);
|
||||
setActiveCallSid(null);
|
||||
setCallStartTime(null);
|
||||
}, 3000);
|
||||
},
|
||||
[activeCallSid, activeLead, agentName, callStartTime],
|
||||
);
|
||||
|
||||
return {
|
||||
callState,
|
||||
activeLead,
|
||||
activeCallSid,
|
||||
isConnected,
|
||||
sendDisposition,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user