From 5632f150310a3a30dcd55a42af4861b20de6801d Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Wed, 15 Apr 2026 11:38:35 +0530 Subject: [PATCH] fix: P1 call-desk defects batch - Mute persists across calls: sip-manager's "ended/failed" branch now resets the Recoil sipIsMutedAtom + sipIsOnHoldAtom (previously only the SIP track was unmuted, leaving the UI icon + toggle logic in a muted state that the next call inherited). - Telephony-unavailable dial pad: call-desk.tsx dial-pad "Call" button was missing an isRegistered check in its disabled prop, so it stayed clickable when SIP was down. Button now shows "Telephony unavailable" and is disabled. - Past dates in Follow-up: enquiry-form's follow-up date input had no min constraint. Switched to a raw with min set to today's ISO date. - Returning-patient AI summary during call: ai-chat-panel now auto-fires a "give me a quick summary of " request whenever the caller's leadId changes (new incoming call). Clears prior chat state so each caller starts fresh. - Remove Type column in Patients page (Badge import also pruned). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/call-desk/ai-chat-panel.tsx | 24 +++++++++++++++++++++- src/components/call-desk/enquiry-form.tsx | 10 ++++++++- src/pages/call-desk.tsx | 6 +++--- src/pages/patients.tsx | 13 ------------ src/providers/sip-provider.tsx | 6 +++++- src/state/sip-manager.ts | 10 ++++++++- 6 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/components/call-desk/ai-chat-panel.tsx b/src/components/call-desk/ai-chat-panel.tsx index 6d73315..2502b0a 100644 --- a/src/components/call-desk/ai-chat-panel.tsx +++ b/src/components/call-desk/ai-chat-panel.tsx @@ -27,7 +27,7 @@ export const AiChatPanel = ({ callerContext, onChatStart }: AiChatPanelProps) => const token = localStorage.getItem('helix_access_token') ?? ''; - const { messages, input, handleSubmit, handleInputChange, isLoading, append } = useChat({ + const { messages, input, handleSubmit, handleInputChange, isLoading, append, setMessages } = useChat({ api: `${API_URL}/api/ai/stream`, streamProtocol: 'text', headers: { @@ -49,6 +49,28 @@ export const AiChatPanel = ({ callerContext, onChatStart }: AiChatPanelProps) => } }, [messages, onChatStart]); + // Auto-fire a patient-summary request when a caller with a leadId appears + // on the panel. Resets whenever the caller changes (new incoming call) so + // each call starts fresh. The sidecar's AI agent inspects the leadId and + // replies with appointment/disposition/notes history when the caller is + // a returning patient, or a brief "net-new caller" ack otherwise. + const autoFiredForLeadRef = useRef(null); + useEffect(() => { + const leadId = callerContext?.leadId ?? null; + if (!leadId) return; + if (autoFiredForLeadRef.current === leadId) return; + + // New caller — clear any prior chat state and fire the summary prompt. + autoFiredForLeadRef.current = leadId; + setMessages([]); + chatStartedRef.current = false; + const name = callerContext?.leadName ?? 'this caller'; + append({ + role: 'user', + content: `Give me a quick summary of ${name} — prior appointments, last disposition, any outstanding notes. If net-new, say so.`, + }); + }, [callerContext?.leadId, callerContext?.leadName, append, setMessages]); + const handleQuickAction = (prompt: string) => { append({ role: 'user', content: prompt }); }; diff --git a/src/components/call-desk/enquiry-form.tsx b/src/components/call-desk/enquiry-form.tsx index ba984c4..b8da10f 100644 --- a/src/components/call-desk/enquiry-form.tsx +++ b/src/components/call-desk/enquiry-form.tsx @@ -287,7 +287,15 @@ export const EnquiryForm = ({ isOpen, onOpenChange, callerPhone, leadName, leadI {followUpNeeded && (
- + setFollowUpDate(e.target.value)} + required + aria-label="Follow-up Date" + className="w-full rounded-lg border border-primary bg-primary px-3 py-2 text-sm text-primary outline-none focus:border-brand-primary" + />
)} diff --git a/src/pages/call-desk.tsx b/src/pages/call-desk.tsx index 4db54f9..618b7cc 100644 --- a/src/pages/call-desk.tsx +++ b/src/pages/call-desk.tsx @@ -19,7 +19,7 @@ import { cx } from '@/utils/cx'; export const CallDeskPage = () => { const { user } = useAuth(); const { leadActivities, calls, followUps: dataFollowUps, patients, appointments } = useData(); - const { callState, callerNumber, callUcid, dialOutbound } = useSip(); + const { callState, callerNumber, callUcid, dialOutbound, isRegistered } = useSip(); const { missedCalls, followUps, marketingLeads, loading } = useWorklist(); const [selectedLead, setSelectedLead] = useState(null); const [contextOpen, setContextOpen] = useState(true); @@ -204,11 +204,11 @@ export const CallDeskPage = () => { )} diff --git a/src/pages/patients.tsx b/src/pages/patients.tsx index 25bb978..0ea1d53 100644 --- a/src/pages/patients.tsx +++ b/src/pages/patients.tsx @@ -6,7 +6,6 @@ import { faIcon } from '@/lib/icon-wrapper'; const SearchLg = faIcon(faMagnifyingGlass); import { Avatar } from '@/components/base/avatar/avatar'; -import { Badge } from '@/components/base/badges/badges'; // Button removed — actions are icon-only now import { Input } from '@/components/base/input/input'; import { Table, TableCard } from '@/components/application/table/table'; @@ -134,7 +133,6 @@ export const PatientsPage = () => { - @@ -196,17 +194,6 @@ export const PatientsPage = () => { - {/* Type */} - - {patient.patientType ? ( - - {patient.patientType} - - ) : ( - - )} - - {/* Gender */} diff --git a/src/providers/sip-provider.tsx b/src/providers/sip-provider.tsx index 55f510c..20eb5dc 100644 --- a/src/providers/sip-provider.tsx +++ b/src/providers/sip-provider.tsx @@ -43,6 +43,8 @@ export const SipProvider = ({ children }: PropsWithChildren) => { const setCallUcid = useSetAtom(sipCallUcidAtom); const setCallDuration = useSetAtom(sipCallDurationAtom); const setCallStartTime = useSetAtom(sipCallStartTimeAtom); + const setIsMutedGlobal = useSetAtom(sipIsMutedAtom); + const setIsOnHoldGlobal = useSetAtom(sipIsOnHoldAtom); // Register Jotai setters so the singleton SIP manager can update atoms useEffect(() => { @@ -51,8 +53,10 @@ export const SipProvider = ({ children }: PropsWithChildren) => { setCallState, setCallerNumber, setCallUcid, + setIsMuted: setIsMutedGlobal, + setIsOnHold: setIsOnHoldGlobal, }); - }, [setConnectionStatus, setCallState, setCallerNumber, setCallUcid]); + }, [setConnectionStatus, setCallState, setCallerNumber, setCallUcid, setIsMutedGlobal, setIsOnHoldGlobal]); // Auto-connect SIP on mount — only if Agent entity has SIP config useEffect(() => { diff --git a/src/state/sip-manager.ts b/src/state/sip-manager.ts index 5190ca7..cb6f51f 100644 --- a/src/state/sip-manager.ts +++ b/src/state/sip-manager.ts @@ -13,6 +13,8 @@ type StateUpdater = { setCallState: (state: CallState) => void; setCallerNumber: (number: string | null) => void; setCallUcid: (ucid: string | null) => void; + setIsMuted: (muted: boolean) => void; + setIsOnHold: (onHold: boolean) => void; }; let stateUpdater: StateUpdater | null = null; @@ -83,7 +85,13 @@ export function connectSip(config: SIPConfig): void { if (ucid) stateUpdater?.setCallUcid(ucid); if (state === 'ended' || state === 'failed') { - sipClient?.unmute(); // clear any mute state so it doesn't persist to next call + // Reset both the SIP track AND the Recoil state — otherwise the + // UI icon + toggle-mute branch logic stay "muted" and the next + // call opens in a confusing half-muted state. + sipClient?.unmute(); + sipClient?.unhold(); + stateUpdater?.setIsMuted(false); + stateUpdater?.setIsOnHold(false); outboundActive = false; outboundPending = false; }