mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: SSE agent state, UCID fix, maint module, QA bug fixes
- Fix outbound disposition: store UCID from dial API response (root cause of silent disposition failure) - SSE agent state: real-time Ozonetel state drives status toggle (ready/break/calling/in-call/acw) - Maint module with OTP-protected endpoints (force-ready, unlock-agent, backfill, fix-timestamps) - Maint OTP modal with PinInput component, keyboard shortcuts (Ctrl+Shift+R/U/B/T) - Force-logout via SSE: admin unlock pushes force-logout to connected browsers - Silence JsSIP debug flood, add structured lifecycle logging ([SIP], [DIAL], [DISPOSE], [AGENT-STATE]) - Centralize date formatting with IST-aware formatters across 11 files - Fix call history: non-overlapping aggregates (completed/missed), correct timestamp display - Auto-dismiss CallWidget ended/failed state after 3 seconds - Remove floating "Helix Phone" idle badge from all pages - Fix dead code in agent-state endpoint (auto-assign was unreachable after return) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -104,7 +104,7 @@ export const useSip = () => {
|
||||
const [connectionStatus] = useAtom(sipConnectionStatusAtom);
|
||||
const [callState, setCallState] = useAtom(sipCallStateAtom);
|
||||
const [callerNumber, setCallerNumber] = useAtom(sipCallerNumberAtom);
|
||||
const [callUcid] = useAtom(sipCallUcidAtom);
|
||||
const [callUcid, setCallUcid] = useAtom(sipCallUcidAtom);
|
||||
const [isMuted, setIsMuted] = useAtom(sipIsMutedAtom);
|
||||
const [isOnHold, setIsOnHold] = useAtom(sipIsOnHoldAtom);
|
||||
const [callDuration] = useAtom(sipCallDurationAtom);
|
||||
@@ -116,21 +116,33 @@ export const useSip = () => {
|
||||
|
||||
// Ozonetel outbound dial — single path for all outbound calls
|
||||
const dialOutbound = useCallback(async (phoneNumber: string): Promise<void> => {
|
||||
console.log(`[DIAL] Outbound dial started: phone=${phoneNumber}`);
|
||||
setCallState('ringing-out');
|
||||
setCallerNumber(phoneNumber);
|
||||
setOutboundPending(true);
|
||||
const safetyTimeout = setTimeout(() => setOutboundPending(false), 30000);
|
||||
const safetyTimeout = setTimeout(() => {
|
||||
console.warn('[DIAL] Safety timeout fired (30s) — clearing outboundPending');
|
||||
setOutboundPending(false);
|
||||
}, 30000);
|
||||
|
||||
try {
|
||||
await apiClient.post('/api/ozonetel/dial', { phoneNumber });
|
||||
} catch {
|
||||
const result = await apiClient.post<{ status: string; ucid?: string }>('/api/ozonetel/dial', { phoneNumber });
|
||||
console.log('[DIAL] Dial API response:', result);
|
||||
clearTimeout(safetyTimeout);
|
||||
// Store UCID from dial response — SIP bridge doesn't carry X-UCID for outbound
|
||||
if (result?.ucid) {
|
||||
console.log(`[DIAL] Storing UCID from dial response: ${result.ucid}`);
|
||||
setCallUcid(result.ucid);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[DIAL] Dial API failed:', err);
|
||||
clearTimeout(safetyTimeout);
|
||||
setOutboundPending(false);
|
||||
setCallState('idle');
|
||||
setCallerNumber(null);
|
||||
throw new Error('Dial failed');
|
||||
}
|
||||
}, [setCallState, setCallerNumber]);
|
||||
}, [setCallState, setCallerNumber, setCallUcid]);
|
||||
|
||||
const answer = useCallback(() => getSipClient()?.answer(), []);
|
||||
const reject = useCallback(() => getSipClient()?.reject(), []);
|
||||
|
||||
Reference in New Issue
Block a user