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:
2026-03-24 22:03:48 +05:30
parent ae94a390df
commit 488f524f84
21 changed files with 462 additions and 107 deletions

View File

@@ -0,0 +1,71 @@
import { useState, useEffect, useCallback } from 'react';
export type MaintAction = {
endpoint: string;
label: string;
description: string;
};
const MAINT_ACTIONS: Record<string, MaintAction> = {
forceReady: {
endpoint: 'force-ready',
label: 'Force Ready',
description: 'Logout and re-login the agent to force Ready state on Ozonetel.',
},
unlockAgent: {
endpoint: 'unlock-agent',
label: 'Unlock Agent',
description: 'Release the Redis session lock so the agent can log in again.',
},
backfill: {
endpoint: 'backfill-missed-calls',
label: 'Backfill Missed Calls',
description: 'Match existing missed calls with lead records by phone number.',
},
fixTimestamps: {
endpoint: 'fix-timestamps',
label: 'Fix Timestamps',
description: 'Correct call timestamps that were stored with IST double-offset.',
},
};
export const useMaintShortcuts = () => {
const [activeAction, setActiveAction] = useState<MaintAction | null>(null);
const [isOpen, setIsOpen] = useState(false);
const openAction = useCallback((action: MaintAction) => {
setActiveAction(action);
setIsOpen(true);
}, []);
const close = useCallback(() => {
setIsOpen(false);
setActiveAction(null);
}, []);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.ctrlKey && e.shiftKey && e.key === 'R') {
e.preventDefault();
openAction(MAINT_ACTIONS.forceReady);
}
if (e.ctrlKey && e.shiftKey && e.key === 'U') {
e.preventDefault();
openAction(MAINT_ACTIONS.unlockAgent);
}
if (e.ctrlKey && e.shiftKey && e.key === 'B') {
e.preventDefault();
openAction(MAINT_ACTIONS.backfill);
}
if (e.ctrlKey && e.shiftKey && e.key === 'T') {
e.preventDefault();
openAction(MAINT_ACTIONS.fixTimestamps);
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [openAction]);
return { isOpen, activeAction, close };
};