mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-12 02:38:15 +00:00
feat: QA fixes — Patient 360 rewrite, token refresh, call flow, UI polish
- Patient 360 page queries Patient entity with appointments, calls, leads - Patients added to CC agent sidebar navigation - Auto token refresh on 401 (deduplicated concurrent refreshes) - Call desk: callDismissed flag prevents SIP race on worklist return - Missed calls skip disposition when never answered - Callbacks tab renamed to Leads tab - Branch column header on missed calls tab - F0rty2.ai link on login footer Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,8 @@ export const ActiveCallCard = ({ lead, callerPhone, missedCallId, onCallComplete
|
||||
const [enquiryOpen, setEnquiryOpen] = useState(false);
|
||||
// Capture direction at mount — survives through disposition stage
|
||||
const callDirectionRef = useRef(callState === 'ringing-out' ? 'OUTBOUND' : 'INBOUND');
|
||||
// Track if the call was ever answered (reached 'active' state)
|
||||
const wasAnsweredRef = useRef(callState === 'active');
|
||||
|
||||
const firstName = lead?.contactName?.firstName ?? '';
|
||||
const lastName = lead?.contactName?.lastName ?? '';
|
||||
@@ -174,6 +176,20 @@ export const ActiveCallCard = ({ lead, callerPhone, missedCallId, onCallComplete
|
||||
);
|
||||
}
|
||||
|
||||
// Skip disposition for unanswered calls (ringing-in → ended without ever reaching active)
|
||||
if (!wasAnsweredRef.current && postCallStage === null && (callState === 'ended' || callState === 'failed')) {
|
||||
return (
|
||||
<div className="rounded-xl border border-secondary bg-primary p-4 text-center">
|
||||
<FontAwesomeIcon icon={faPhoneHangup} className="size-6 text-fg-quaternary mb-2" />
|
||||
<p className="text-sm font-semibold text-primary">Missed Call</p>
|
||||
<p className="text-xs text-tertiary mt-1">{phoneDisplay} — not answered</p>
|
||||
<Button size="sm" color="secondary" className="mt-3" onClick={handleReset}>
|
||||
Back to Worklist
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Post-call flow takes priority over active state (handles race between hangup + SIP ended event)
|
||||
if (postCallStage !== null || callState === 'ended' || callState === 'failed') {
|
||||
// Done state
|
||||
@@ -235,6 +251,7 @@ export const ActiveCallCard = ({ lead, callerPhone, missedCallId, onCallComplete
|
||||
|
||||
// Active call
|
||||
if (callState === 'active') {
|
||||
wasAnsweredRef.current = true;
|
||||
return (
|
||||
<div className="rounded-xl border border-brand bg-primary p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -62,7 +62,7 @@ interface WorklistPanelProps {
|
||||
selectedLeadId: string | null;
|
||||
}
|
||||
|
||||
type TabKey = 'all' | 'missed' | 'callbacks' | 'follow-ups';
|
||||
type TabKey = 'all' | 'missed' | 'leads' | 'follow-ups';
|
||||
|
||||
type WorklistRow = {
|
||||
id: string;
|
||||
@@ -258,7 +258,7 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
const filteredRows = useMemo(() => {
|
||||
let rows = allRows;
|
||||
if (tab === 'missed') rows = missedSubTabRows;
|
||||
else if (tab === 'callbacks') rows = rows.filter((r) => r.type === 'callback');
|
||||
else if (tab === 'leads') rows = rows.filter((r) => r.type === 'lead');
|
||||
else if (tab === 'follow-ups') rows = rows.filter((r) => r.type === 'follow-up');
|
||||
|
||||
if (search.trim()) {
|
||||
@@ -272,7 +272,7 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
}, [allRows, tab, search]);
|
||||
|
||||
const missedCount = allRows.filter((r) => r.type === 'missed').length;
|
||||
const callbackCount = allRows.filter((r) => r.type === 'callback').length;
|
||||
const leadCount = allRows.filter((r) => r.type === 'lead').length;
|
||||
const followUpCount = allRows.filter((r) => r.type === 'follow-up').length;
|
||||
|
||||
// Notification for new missed calls
|
||||
@@ -296,7 +296,7 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
const tabItems = [
|
||||
{ id: 'all' as const, label: 'All Tasks', badge: allRows.length > 0 ? String(allRows.length) : undefined },
|
||||
{ id: 'missed' as const, label: 'Missed Calls', badge: missedCount > 0 ? String(missedCount) : undefined },
|
||||
{ id: 'callbacks' as const, label: 'Callbacks', badge: callbackCount > 0 ? String(callbackCount) : undefined },
|
||||
{ id: 'leads' as const, label: 'Leads', badge: leadCount > 0 ? String(leadCount) : undefined },
|
||||
{ id: 'follow-ups' as const, label: 'Follow-ups', badge: followUpCount > 0 ? String(followUpCount) : undefined },
|
||||
];
|
||||
|
||||
@@ -378,7 +378,7 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
<Table.Head label="PRIORITY" className="w-20" isRowHeader />
|
||||
<Table.Head label="PATIENT" />
|
||||
<Table.Head label="PHONE" />
|
||||
<Table.Head label="SOURCE" className="w-28" />
|
||||
<Table.Head label={tab === 'missed' ? 'BRANCH' : 'SOURCE'} className="w-28" />
|
||||
<Table.Head label="SLA" className="w-24" />
|
||||
</Table.Header>
|
||||
<Table.Body items={pagedRows}>
|
||||
|
||||
@@ -70,6 +70,7 @@ const getNavSections = (role: string): NavSection[] => {
|
||||
{ label: 'Call Center', items: [
|
||||
{ label: 'Call Desk', href: '/', icon: IconPhone },
|
||||
{ label: 'Call History', href: '/call-history', icon: IconClockRewind },
|
||||
{ label: 'Patients', href: '/patients', icon: IconHospitalUser },
|
||||
{ label: 'My Performance', href: '/my-performance', icon: IconChartMixed },
|
||||
]},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user