mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
- 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>
95 lines
3.6 KiB
TypeScript
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 };
|
|
};
|