mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
fix: call desk layout — collapsible context panel, worklist/calls tabs, phone numbers
- Context panel now collapsible via toggle button (sidebar icon in status bar) - Fixed width 400px when open, full-width worklist when closed - Worklist and Today's Calls as separate tabs instead of stacked - Fix missed calls callerNumber transform (PHONES composite → array) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,9 @@ export const useWorklist = (): UseWorklistResult => {
|
|||||||
...call,
|
...call,
|
||||||
callDirection: call.direction ?? call.callDirection,
|
callDirection: call.direction ?? call.callDirection,
|
||||||
durationSeconds: call.durationSec ?? call.durationSeconds ?? 0,
|
durationSeconds: call.durationSec ?? call.durationSeconds ?? 0,
|
||||||
|
callerNumber: call.callerNumber?.primaryPhoneNumber
|
||||||
|
? [{ number: call.callerNumber.primaryPhoneNumber, callingCode: '+91' }]
|
||||||
|
: call.callerNumber,
|
||||||
})),
|
})),
|
||||||
followUps: (json.followUps ?? []).map((fu: any) => ({
|
followUps: (json.followUps ?? []).map((fu: any) => ({
|
||||||
...fu,
|
...fu,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useState } from 'react';
|
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 { useAuth } from '@/providers/auth-provider';
|
||||||
import { useData } from '@/providers/data-provider';
|
import { useData } from '@/providers/data-provider';
|
||||||
import { useWorklist } from '@/hooks/use-worklist';
|
import { useWorklist } from '@/hooks/use-worklist';
|
||||||
@@ -11,6 +13,9 @@ import { ActiveCallCard } from '@/components/call-desk/active-call-card';
|
|||||||
import { CallPrepCard } from '@/components/call-desk/call-prep-card';
|
import { CallPrepCard } from '@/components/call-desk/call-prep-card';
|
||||||
import { CallLog } from '@/components/call-desk/call-log';
|
import { CallLog } from '@/components/call-desk/call-log';
|
||||||
import { BadgeWithDot, Badge } from '@/components/base/badges/badges';
|
import { BadgeWithDot, Badge } from '@/components/base/badges/badges';
|
||||||
|
import { cx } from '@/utils/cx';
|
||||||
|
|
||||||
|
type MainTab = 'worklist' | 'calls';
|
||||||
|
|
||||||
const isToday = (dateStr: string): boolean => {
|
const isToday = (dateStr: string): boolean => {
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
@@ -24,6 +29,8 @@ export const CallDeskPage = () => {
|
|||||||
const { connectionStatus, isRegistered, callState, callerNumber } = useSip();
|
const { connectionStatus, isRegistered, callState, callerNumber } = useSip();
|
||||||
const { missedCalls, followUps, marketingLeads, totalPending, loading } = useWorklist();
|
const { missedCalls, followUps, marketingLeads, totalPending, loading } = useWorklist();
|
||||||
const [selectedLead, setSelectedLead] = useState<WorklistLead | null>(null);
|
const [selectedLead, setSelectedLead] = useState<WorklistLead | null>(null);
|
||||||
|
const [contextOpen, setContextOpen] = useState(true);
|
||||||
|
const [mainTab, setMainTab] = useState<MainTab>('worklist');
|
||||||
|
|
||||||
const todaysCalls = calls.filter(
|
const todaysCalls = calls.filter(
|
||||||
(c) => c.agentName === user.name && c.startedAt !== null && isToday(c.startedAt),
|
(c) => c.agentName === user.name && c.startedAt !== null && isToday(c.startedAt),
|
||||||
@@ -31,23 +38,20 @@ export const CallDeskPage = () => {
|
|||||||
|
|
||||||
const isInCall = callState === 'ringing-in' || callState === 'ringing-out' || callState === 'active';
|
const isInCall = callState === 'ringing-in' || callState === 'ringing-out' || callState === 'active';
|
||||||
|
|
||||||
// Find lead matching caller number during active call
|
|
||||||
const callerLead = callerNumber
|
const callerLead = callerNumber
|
||||||
? marketingLeads.find((l) => l.contactPhone?.[0]?.number?.endsWith(callerNumber) || callerNumber.endsWith(l.contactPhone?.[0]?.number ?? '---'))
|
? marketingLeads.find((l) => l.contactPhone?.[0]?.number?.endsWith(callerNumber) || callerNumber.endsWith(l.contactPhone?.[0]?.number ?? '---'))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const activeLead = isInCall ? (callerLead ?? selectedLead) : selectedLead;
|
const activeLead = isInCall ? (callerLead ?? selectedLead) : selectedLead;
|
||||||
|
|
||||||
// Convert worklist lead to full Lead type for components that need it
|
|
||||||
const activeLeadFull = activeLead as any;
|
const activeLeadFull = activeLead as any;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col overflow-hidden">
|
<div className="flex flex-1 flex-col overflow-hidden">
|
||||||
{/* Sticky header */}
|
|
||||||
<TopBar title="Call Desk" subtitle={`${user.name} \u00B7 Global Hospital`} />
|
<TopBar title="Call Desk" subtitle={`${user.name} \u00B7 Global Hospital`} />
|
||||||
|
|
||||||
{/* Status bar — sticky below header */}
|
{/* Status bar */}
|
||||||
<div className="flex shrink-0 items-center gap-2 border-b border-secondary px-6 py-2">
|
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<BadgeWithDot
|
<BadgeWithDot
|
||||||
color={isRegistered ? 'success' : connectionStatus === 'connecting' ? 'warning' : 'gray'}
|
color={isRegistered ? 'success' : connectionStatus === 'connecting' ? 'warning' : 'gray'}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -60,21 +64,57 @@ export const CallDeskPage = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 2-panel layout — only this area scrolls */}
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex flex-1 overflow-hidden">
|
{/* Main tab toggle */}
|
||||||
{/* Main panel (60%) */}
|
{!isInCall && (
|
||||||
<div className="flex flex-[3] flex-col overflow-y-auto">
|
<div className="flex rounded-lg border border-secondary">
|
||||||
<div className="flex-1 space-y-4 p-5">
|
<button
|
||||||
{/* Active call card (replaces worklist when in call) */}
|
onClick={() => setMainTab('worklist')}
|
||||||
{isInCall && (
|
className={cx(
|
||||||
<>
|
"px-3 py-1 text-xs font-semibold transition duration-100 ease-linear rounded-l-lg",
|
||||||
<ActiveCallCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} />
|
mainTab === 'worklist' ? "bg-active text-brand-secondary" : "text-tertiary hover:text-secondary",
|
||||||
<CallPrepCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} activities={leadActivities} />
|
)}
|
||||||
</>
|
>
|
||||||
|
Worklist
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setMainTab('calls')}
|
||||||
|
className={cx(
|
||||||
|
"px-3 py-1 text-xs font-semibold transition duration-100 ease-linear rounded-r-lg",
|
||||||
|
mainTab === 'calls' ? "bg-active text-brand-secondary" : "text-tertiary hover:text-secondary",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Today's Calls ({todaysCalls.length})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Worklist (visible when idle) */}
|
{/* Context panel toggle */}
|
||||||
{!isInCall && (
|
<button
|
||||||
|
onClick={() => setContextOpen(!contextOpen)}
|
||||||
|
className="flex size-7 items-center justify-center rounded-md text-fg-quaternary hover:text-fg-secondary hover:bg-secondary 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 — expands when context is closed */}
|
||||||
|
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||||
|
<div className="flex-1 p-5">
|
||||||
|
{/* Active call */}
|
||||||
|
{isInCall && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<ActiveCallCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} />
|
||||||
|
<CallPrepCard lead={activeLeadFull} callerPhone={callerNumber ?? ''} activities={leadActivities} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Worklist tab */}
|
||||||
|
{!isInCall && mainTab === 'worklist' && (
|
||||||
<WorklistPanel
|
<WorklistPanel
|
||||||
missedCalls={missedCalls}
|
missedCalls={missedCalls}
|
||||||
followUps={followUps}
|
followUps={followUps}
|
||||||
@@ -85,19 +125,23 @@ export const CallDeskPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Today's calls — always visible */}
|
{/* Today's Calls tab */}
|
||||||
|
{!isInCall && mainTab === 'calls' && (
|
||||||
<CallLog calls={todaysCalls} />
|
<CallLog calls={todaysCalls} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Context panel (40%) — border-left, fixed height */}
|
{/* Context panel — collapsible */}
|
||||||
<div className="hidden flex-[2] border-l border-secondary bg-primary xl:flex xl:flex-col">
|
{contextOpen && (
|
||||||
|
<div className="w-[400px] shrink-0 border-l border-secondary bg-primary flex flex-col">
|
||||||
<ContextPanel
|
<ContextPanel
|
||||||
selectedLead={activeLeadFull}
|
selectedLead={activeLeadFull}
|
||||||
activities={leadActivities}
|
activities={leadActivities}
|
||||||
callerPhone={callerNumber ?? undefined}
|
callerPhone={callerNumber ?? undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user