mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: add floating call widget with SIP controls, click-to-call, and SIP provider
Introduce a shared SipProvider context so all components use the same SIP connection. Add a floating CallWidget (idle pill, ringing, active with disposition, ended states) visible for CC agents on every page. Add a ClickToCallButton for the worklist. Wire SIP status badge and worklist into the call-desk page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,13 +2,16 @@ import { useState, useCallback, useRef } from 'react';
|
||||
import { TopBar } from '@/components/layout/top-bar';
|
||||
import { CallSimulator } from '@/components/call-desk/call-simulator';
|
||||
import { IncomingCallCard } from '@/components/call-desk/incoming-call-card';
|
||||
import { ClickToCallButton } from '@/components/call-desk/click-to-call-button';
|
||||
import { CallLog } from '@/components/call-desk/call-log';
|
||||
import { DailyStats } from '@/components/call-desk/daily-stats';
|
||||
import { useAuth } from '@/providers/auth-provider';
|
||||
import { useData } from '@/providers/data-provider';
|
||||
import { useLeads } from '@/hooks/use-leads';
|
||||
import { useCallEvents } from '@/hooks/use-call-events';
|
||||
import { useSip } from '@/providers/sip-provider';
|
||||
import { BadgeWithDot } from '@/components/base/badges/badges';
|
||||
import { formatPhone } from '@/lib/format';
|
||||
import type { Call, CallDisposition, Lead, LeadStatus } from '@/types/entities';
|
||||
|
||||
type CallState = 'idle' | 'ringing' | 'active' | 'completed';
|
||||
@@ -49,6 +52,9 @@ export const CallDeskPage = () => {
|
||||
const ringingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const completedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// --- SIP phone state ---
|
||||
const { connectionStatus, isRegistered } = useSip();
|
||||
|
||||
// --- Live mode state (WebSocket) ---
|
||||
const { callState: liveCallState, activeLead: liveLead, isConnected, sendDisposition } = useCallEvents(user.name);
|
||||
|
||||
@@ -221,15 +227,25 @@ export const CallDeskPage = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{mode === 'live' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<BadgeWithDot
|
||||
color={isConnected ? 'success' : 'gray'}
|
||||
color={isRegistered ? 'success' : connectionStatus === 'connecting' ? 'warning' : 'gray'}
|
||||
size="md"
|
||||
type="pill-color"
|
||||
>
|
||||
{isConnected ? 'Connected to call center' : 'Connecting...'}
|
||||
{isRegistered ? 'Phone Ready' : `Phone: ${connectionStatus}`}
|
||||
</BadgeWithDot>
|
||||
)}
|
||||
|
||||
{mode === 'live' && (
|
||||
<BadgeWithDot
|
||||
color={isConnected ? 'success' : 'gray'}
|
||||
size="md"
|
||||
type="pill-color"
|
||||
>
|
||||
{isConnected ? 'Connected to call center' : 'Connecting...'}
|
||||
</BadgeWithDot>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Demo mode simulator button */}
|
||||
@@ -240,6 +256,48 @@ export const CallDeskPage = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Worklist: leads with click-to-call */}
|
||||
{mode === 'live' && leads.length > 0 && (
|
||||
<div className="rounded-2xl border border-secondary bg-primary">
|
||||
<div className="border-b border-secondary px-5 py-3">
|
||||
<h3 className="text-sm font-bold text-primary">Worklist</h3>
|
||||
<p className="text-xs text-tertiary">Click to start an outbound call</p>
|
||||
</div>
|
||||
<div className="divide-y divide-secondary">
|
||||
{leads.slice(0, 10).map((lead) => {
|
||||
const firstName = lead.contactName?.firstName ?? '';
|
||||
const lastName = lead.contactName?.lastName ?? '';
|
||||
const fullName = `${firstName} ${lastName}`.trim() || 'Unknown';
|
||||
const phone = lead.contactPhone?.[0];
|
||||
const phoneDisplay = phone ? formatPhone(phone) : 'No phone';
|
||||
const phoneNumber = phone?.number ?? '';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={lead.id}
|
||||
className="flex items-center justify-between gap-3 px-5 py-3"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="text-sm font-semibold text-primary">
|
||||
{fullName}
|
||||
</span>
|
||||
<span className="ml-2 text-sm text-tertiary">
|
||||
{phoneDisplay}
|
||||
</span>
|
||||
{lead.interestedService !== null && (
|
||||
<span className="ml-2 text-xs text-quaternary">
|
||||
{lead.interestedService}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ClickToCallButton phoneNumber={phoneNumber} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<IncomingCallCard
|
||||
callState={effectiveCallState}
|
||||
lead={displayLead}
|
||||
|
||||
Reference in New Issue
Block a user