mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
- Missed call queue with FIFO auto-assignment, dedup, SLA tracking - Status sub-tabs (Pending/Attempted/Completed/Invalid) in worklist - missedCallId passed through disposition flow for callback tracking - Login page redesigned: centered white card on blue background - Disposition button changed to content-width - NavAccountCard popover close fix on menu item click Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
102 lines
5.1 KiB
TypeScript
102 lines
5.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
|
|
import { useAuth } from '@/providers/auth-provider';
|
|
import { useData } from '@/providers/data-provider';
|
|
import { useWorklist } from '@/hooks/use-worklist';
|
|
import { useSip } from '@/providers/sip-provider';
|
|
import { WorklistPanel } from '@/components/call-desk/worklist-panel';
|
|
import type { WorklistLead } from '@/components/call-desk/worklist-panel';
|
|
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 { AgentStatusToggle } from '@/components/call-desk/agent-status-toggle';
|
|
import { cx } from '@/utils/cx';
|
|
|
|
export const CallDeskPage = () => {
|
|
const { user } = useAuth();
|
|
const { leadActivities } = useData();
|
|
const { connectionStatus, isRegistered, callState, callerNumber, callUcid } = useSip();
|
|
const { missedCalls, followUps, marketingLeads, totalPending, loading } = useWorklist();
|
|
const [selectedLead, setSelectedLead] = useState<WorklistLead | null>(null);
|
|
const [contextOpen, setContextOpen] = useState(true);
|
|
const [activeMissedCallId, setActiveMissedCallId] = useState<string | null>(null);
|
|
|
|
const isInCall = 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;
|
|
|
|
const activeLead = isInCall ? (callerLead ?? selectedLead) : selectedLead;
|
|
const activeLeadFull = activeLead as any;
|
|
|
|
return (
|
|
<div className="flex flex-1 flex-col overflow-hidden">
|
|
{/* Compact header: title + name on left, status + toggle on right */}
|
|
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-3">
|
|
<div className="flex items-center gap-3">
|
|
<h1 className="text-lg font-bold text-primary">Call Desk</h1>
|
|
<span className="text-sm text-tertiary">{user.name}</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<AgentStatusToggle isRegistered={isRegistered} connectionStatus={connectionStatus} />
|
|
{totalPending > 0 && (
|
|
<Badge size="sm" color="brand" type="pill-color">{totalPending} pending</Badge>
|
|
)}
|
|
<button
|
|
onClick={() => setContextOpen(!contextOpen)}
|
|
className="flex size-8 items-center justify-center rounded-lg text-fg-quaternary hover:text-fg-secondary hover:bg-primary_hover transition duration-100 ease-linear"
|
|
title={contextOpen ? 'Hide AI panel' : 'Show AI panel'}
|
|
>
|
|
<FontAwesomeIcon icon={contextOpen ? faSidebarFlip : faSidebar} className="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main content */}
|
|
<div className="flex flex-1 overflow-hidden">
|
|
{/* Main panel */}
|
|
<div className="flex flex-1 flex-col overflow-y-auto">
|
|
{/* Active call */}
|
|
{isInCall && (
|
|
<div className="p-5">
|
|
<ActiveCallCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} missedCallId={activeMissedCallId} onCallComplete={() => setActiveMissedCallId(null)} />
|
|
</div>
|
|
)}
|
|
|
|
{/* Worklist — no wrapper, tabs + table fill the space */}
|
|
{!isInCall && (
|
|
<WorklistPanel
|
|
missedCalls={missedCalls}
|
|
followUps={followUps}
|
|
leads={marketingLeads}
|
|
loading={loading}
|
|
onSelectLead={(lead) => setSelectedLead(lead)}
|
|
selectedLeadId={selectedLead?.id ?? null}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Context panel — collapsible with smooth transition */}
|
|
<div className={cx(
|
|
"shrink-0 border-l border-secondary bg-primary flex flex-col overflow-hidden transition-all duration-200 ease-linear",
|
|
contextOpen ? "w-[400px]" : "w-0 border-l-0",
|
|
)}>
|
|
{contextOpen && (
|
|
<ContextPanel
|
|
selectedLead={activeLeadFull}
|
|
activities={leadActivities}
|
|
callerPhone={callerNumber ?? undefined}
|
|
isInCall={isInCall}
|
|
callUcid={callUcid}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|