mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: worklist sorting, contextual disposition, context panel redesign, notifications
- Worklist default sort descending (newest first), sortable column headers (PRIORITY, PATIENT, SLA) via React Aria - Contextual disposition: auto-selects based on in-call actions (appointment → APPOINTMENT_BOOKED, enquiry → INFO_PROVIDED, transfer → FOLLOW_UP_SCHEDULED) - Context panel redesign: collapsible AI Insight, Upcoming (appointments + follow-ups + linked patient), Recent (calls + activities) sections; auto-collapse on AI chat start - Appointments added to DataProvider with APPOINTMENTS_QUERY, Appointment type, transform - Notification bell for admin/supervisor: performance alerts (idle time, NPS, conversion thresholds) with toast on load + bell dropdown with dismiss; demo alerts as fallback - Slideout z-index fix: added z-50 to slideout ModalOverlay matching modal component Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSidebarFlip, faSidebar, faPhone, faXmark, faDeleteLeft } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { useAuth } from '@/providers/auth-provider';
|
||||
@@ -11,12 +11,13 @@ import { ContextPanel } from '@/components/call-desk/context-panel';
|
||||
import { ActiveCallCard } from '@/components/call-desk/active-call-card';
|
||||
|
||||
import { Badge } from '@/components/base/badges/badges';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import { notify } from '@/lib/toast';
|
||||
import { cx } from '@/utils/cx';
|
||||
|
||||
export const CallDeskPage = () => {
|
||||
const { user } = useAuth();
|
||||
const { leadActivities } = useData();
|
||||
const { leadActivities, calls, followUps: dataFollowUps, patients, appointments } = useData();
|
||||
const { callState, callerNumber, callUcid, dialOutbound } = useSip();
|
||||
const { missedCalls, followUps, marketingLeads, totalPending, loading } = useWorklist();
|
||||
const [selectedLead, setSelectedLead] = useState<WorklistLead | null>(null);
|
||||
@@ -49,12 +50,53 @@ export const CallDeskPage = () => {
|
||||
|
||||
const isInCall = !callDismissed && (callState === 'ringing-in' || callState === 'ringing-out' || callState === 'active' || callState === 'ended' || callState === 'failed');
|
||||
|
||||
const callerLead = callerNumber
|
||||
? marketingLeads.find((l) => l.contactPhone?.[0]?.number?.endsWith(callerNumber) || callerNumber.endsWith(l.contactPhone?.[0]?.number ?? '---'))
|
||||
: null;
|
||||
// Resolve caller identity via sidecar (lookup-or-create lead+patient pair)
|
||||
const [resolvedCaller, setResolvedCaller] = useState<{
|
||||
leadId: string; patientId: string; firstName: string; lastName: string; phone: string;
|
||||
} | null>(null);
|
||||
const resolveAttemptedRef = useRef<string | null>(null);
|
||||
|
||||
// For inbound calls, only use matched lead (don't fall back to previously selected worklist lead)
|
||||
// For outbound (agent initiated from worklist), selectedLead is the intended target
|
||||
useEffect(() => {
|
||||
if (!callerNumber || !isInCall) return;
|
||||
if (resolveAttemptedRef.current === callerNumber) return; // already resolving/resolved this number
|
||||
resolveAttemptedRef.current = callerNumber;
|
||||
|
||||
apiClient.post<{
|
||||
leadId: string; patientId: string; firstName: string; lastName: string; phone: string; isNew: boolean;
|
||||
}>('/api/caller/resolve', { phone: callerNumber }, { silent: true })
|
||||
.then((result) => {
|
||||
setResolvedCaller(result);
|
||||
if (result.isNew) {
|
||||
notify.info('New Caller', 'Lead and patient records created');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('[RESOLVE] Caller resolution failed:', err);
|
||||
resolveAttemptedRef.current = null; // allow retry
|
||||
});
|
||||
}, [callerNumber, isInCall]);
|
||||
|
||||
// Reset resolved caller when call ends
|
||||
useEffect(() => {
|
||||
if (!isInCall) {
|
||||
setResolvedCaller(null);
|
||||
resolveAttemptedRef.current = null;
|
||||
}
|
||||
}, [isInCall]);
|
||||
|
||||
// Build activeLead from resolved caller or fallback to client-side match
|
||||
const callerLead = resolvedCaller
|
||||
? marketingLeads.find((l) => l.id === resolvedCaller.leadId) ?? {
|
||||
id: resolvedCaller.leadId,
|
||||
contactName: { firstName: resolvedCaller.firstName, lastName: resolvedCaller.lastName },
|
||||
contactPhone: [{ number: resolvedCaller.phone, callingCode: '+91' }],
|
||||
patientId: resolvedCaller.patientId,
|
||||
}
|
||||
: callerNumber
|
||||
? marketingLeads.find((l) => l.contactPhone?.[0]?.number?.endsWith(callerNumber) || callerNumber.endsWith(l.contactPhone?.[0]?.number ?? '---'))
|
||||
: null;
|
||||
|
||||
// For inbound calls, use resolved/matched lead. For outbound, use selectedLead.
|
||||
const activeLead = isInCall
|
||||
? (callerLead ?? (callState === 'ringing-out' ? selectedLead : null))
|
||||
: selectedLead;
|
||||
@@ -147,7 +189,7 @@ export const CallDeskPage = () => {
|
||||
{/* Main content */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Main panel */}
|
||||
<div className="flex flex-1 flex-col min-h-0">
|
||||
<div className="flex flex-1 flex-col min-h-0 overflow-hidden">
|
||||
{/* Active call */}
|
||||
{isInCall && (
|
||||
<div className="p-5">
|
||||
@@ -164,6 +206,7 @@ export const CallDeskPage = () => {
|
||||
loading={loading}
|
||||
onSelectLead={(lead) => setSelectedLead(lead)}
|
||||
selectedLeadId={selectedLead?.id ?? null}
|
||||
onDialMissedCall={(id) => setActiveMissedCallId(id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -177,6 +220,10 @@ export const CallDeskPage = () => {
|
||||
<ContextPanel
|
||||
selectedLead={activeLeadFull}
|
||||
activities={leadActivities}
|
||||
calls={calls}
|
||||
followUps={dataFollowUps}
|
||||
appointments={appointments}
|
||||
patients={patients}
|
||||
callerPhone={callerNumber ?? undefined}
|
||||
isInCall={isInCall}
|
||||
callUcid={callUcid}
|
||||
|
||||
Reference in New Issue
Block a user