mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
fix: pinned header/chat input, numpad dialler, caller matching, appointment FK
- AppShell: h-screen + overflow-hidden for pinned header - AI chat: input pinned to bottom, messages scroll independently - Dialler: numpad grid (1-9,*,0,#) replaces text input - Inbound calls: don't fall back to previously selected lead - Appointment: use lead.patientId instead of leadId for FK - Added .env.production for consistent builds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { faSidebarFlip, faSidebar, faPhone, faXmark, faDeleteLeft } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { useAuth } from '@/providers/auth-provider';
|
||||
import { useData } from '@/providers/data-provider';
|
||||
import { useWorklist } from '@/hooks/use-worklist';
|
||||
@@ -12,6 +12,8 @@ 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 { apiClient } from '@/lib/api-client';
|
||||
import { notify } from '@/lib/toast';
|
||||
import { cx } from '@/utils/cx';
|
||||
|
||||
export const CallDeskPage = () => {
|
||||
@@ -23,6 +25,24 @@ export const CallDeskPage = () => {
|
||||
const [contextOpen, setContextOpen] = useState(true);
|
||||
const [activeMissedCallId, setActiveMissedCallId] = useState<string | null>(null);
|
||||
const [callDismissed, setCallDismissed] = useState(false);
|
||||
const [diallerOpen, setDiallerOpen] = useState(false);
|
||||
const [dialNumber, setDialNumber] = useState('');
|
||||
const [dialling, setDialling] = useState(false);
|
||||
|
||||
const handleDial = async () => {
|
||||
const num = dialNumber.replace(/[^0-9]/g, '');
|
||||
if (num.length < 10) { notify.error('Enter a valid phone number'); return; }
|
||||
setDialling(true);
|
||||
try {
|
||||
await apiClient.post('/api/ozonetel/dial', { phoneNumber: num });
|
||||
setDiallerOpen(false);
|
||||
setDialNumber('');
|
||||
} catch {
|
||||
notify.error('Dial failed');
|
||||
} finally {
|
||||
setDialling(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Reset callDismissed when a new call starts (ringing in or out)
|
||||
if (callDismissed && (callState === 'ringing-in' || callState === 'ringing-out')) {
|
||||
@@ -35,7 +55,11 @@ export const CallDeskPage = () => {
|
||||
? marketingLeads.find((l) => l.contactPhone?.[0]?.number?.endsWith(callerNumber) || callerNumber.endsWith(l.contactPhone?.[0]?.number ?? '---'))
|
||||
: null;
|
||||
|
||||
const activeLead = isInCall ? (callerLead ?? selectedLead) : selectedLead;
|
||||
// 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
|
||||
const activeLead = isInCall
|
||||
? (callerLead ?? (callState === 'ringing-out' ? selectedLead : null))
|
||||
: selectedLead;
|
||||
const activeLeadFull = activeLead as any;
|
||||
|
||||
return (
|
||||
@@ -102,6 +126,69 @@ export const CallDeskPage = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dialler FAB */}
|
||||
{!isInCall && (
|
||||
<div className="fixed bottom-6 right-6 z-40 flex flex-col items-end gap-3">
|
||||
{diallerOpen && (
|
||||
<div className="w-72 rounded-xl bg-primary shadow-xl ring-1 ring-secondary p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-semibold text-primary">Dial</span>
|
||||
<button onClick={() => setDiallerOpen(false)} className="text-fg-quaternary hover:text-fg-secondary">
|
||||
<FontAwesomeIcon icon={faXmark} className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Number display */}
|
||||
<div className="flex items-center gap-2 mb-3 px-3 py-2.5 rounded-lg bg-secondary min-h-[40px]">
|
||||
<span className="flex-1 text-lg font-semibold text-primary tracking-wider text-center">
|
||||
{dialNumber || <span className="text-placeholder font-normal text-sm">Enter number</span>}
|
||||
</span>
|
||||
{dialNumber && (
|
||||
<button onClick={() => setDialNumber(dialNumber.slice(0, -1))} className="text-fg-quaternary hover:text-fg-secondary shrink-0">
|
||||
<FontAwesomeIcon icon={faDeleteLeft} className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Numpad */}
|
||||
<div className="grid grid-cols-3 gap-1.5 mb-3">
|
||||
{['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'].map(key => (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => setDialNumber(prev => prev + key)}
|
||||
className="flex items-center justify-center h-11 rounded-lg text-sm font-semibold text-primary bg-primary hover:bg-secondary border border-secondary transition duration-100 ease-linear active:scale-95"
|
||||
>
|
||||
{key}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Call button */}
|
||||
<button
|
||||
onClick={handleDial}
|
||||
disabled={dialling || dialNumber.replace(/[^0-9]/g, '').length < 10}
|
||||
className="w-full flex items-center justify-center gap-2 rounded-lg bg-success-solid py-2.5 text-sm font-medium text-white hover:opacity-90 disabled:bg-disabled disabled:cursor-not-allowed transition duration-100 ease-linear"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPhone} className="size-3.5" />
|
||||
{dialling ? 'Dialling...' : 'Call'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setDiallerOpen(!diallerOpen)}
|
||||
className={cx(
|
||||
'flex size-14 items-center justify-center rounded-full shadow-lg transition duration-100 ease-linear',
|
||||
diallerOpen
|
||||
? 'bg-secondary text-fg-secondary'
|
||||
: 'bg-brand-solid text-white hover:bg-brand-solid_hover',
|
||||
)}
|
||||
title="Quick dial"
|
||||
>
|
||||
<FontAwesomeIcon icon={diallerOpen ? faXmark : faPhone} className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user