Files
helix-engage/src/hooks/use-agent-state.ts
saridsa2 42e23a52ec feat: call-desk refresh — disposition modal, active-call UI, worklist + perf updates
- Call-desk: active-call-card supervisor presence badges, incoming-call-card polish, transfer-dialog, call-log
- Disposition modal: auto-lock based on actions taken, not-interested split
- Forms: appointment-form + enquiry-form improvements (placeholder handling, phone format)
- Worklist-panel: pagination awareness, filter chips
- Pages: all-leads/patients/patient-360/missed-calls/team-performance/call-history/appointments polish
- SIP: sip-client reconnect, sip-provider + sip-manager state, agent-status-toggle spinner
- Hooks: use-agent-state supervisor SSE events, use-worklist, use-performance-alerts
- Types: entities.ts extended

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 06:49:36 +05:30

95 lines
3.6 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { notify } from '@/lib/toast';
export type OzonetelState = 'ready' | 'break' | 'training' | 'calling' | 'in-call' | 'acw' | 'offline';
export type SupervisorPresence = 'none' | 'whisper' | 'barge';
const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:4100';
export const useAgentState = (agentId: string | null): { state: OzonetelState; supervisorPresence: SupervisorPresence } => {
const [state, setState] = useState<OzonetelState>('offline');
const [supervisorPresence, setSupervisorPresence] = useState<SupervisorPresence>('none');
const prevStateRef = useRef<OzonetelState>('offline');
const esRef = useRef<EventSource | null>(null);
useEffect(() => {
if (!agentId) {
setState('offline');
return;
}
// Fetch current state on connect
fetch(`${API_URL}/api/supervisor/agent-state?agentId=${agentId}`)
.then(res => res.json())
.then(data => {
if (data.state) {
console.log(`[SSE] Initial state for ${agentId}: ${data.state}`);
prevStateRef.current = data.state;
setState(data.state);
}
})
.catch(() => {});
// Open SSE stream
const url = `${API_URL}/api/supervisor/agent-state/stream?agentId=${agentId}`;
console.log(`[SSE] Connecting: ${url}`);
const es = new EventSource(url);
esRef.current = es;
es.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log(`[SSE] State update: ${agentId}${data.state}`);
// Force-logout: only triggered by explicit admin action, not normal Ozonetel logout
if (data.state === 'force-logout') {
console.log('[SSE] Force-logout received — clearing session');
notify.info('Session Ended', 'Your session was ended by an administrator.');
es.close();
localStorage.removeItem('helix_access_token');
localStorage.removeItem('helix_refresh_token');
localStorage.removeItem('helix_agent_config');
localStorage.removeItem('helix_user');
import('@/state/sip-manager').then(({ disconnectSip }) => disconnectSip(false, 'agent-state-offline')).catch(() => {});
setTimeout(() => { window.location.href = '/login'; }, 1500);
return;
}
// Supervisor presence events — don't replace agent state
if (data.state === 'supervisor-whisper') {
setSupervisorPresence('whisper');
return;
}
if (data.state === 'supervisor-barge') {
setSupervisorPresence('barge');
return;
}
if (data.state === 'supervisor-left') {
setSupervisorPresence('none');
return;
}
prevStateRef.current = data.state;
setState(data.state);
} catch {
console.warn('[SSE] Failed to parse event:', event.data);
}
};
es.onerror = () => {
console.warn('[SSE] Connection error — will auto-reconnect');
};
return () => {
console.log('[SSE] Closing connection');
es.close();
esRef.current = null;
};
}, [agentId]);
return { state, supervisorPresence };
};