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:
2026-03-23 11:52:33 +05:30
parent 88fc743928
commit 727a0728ee
7 changed files with 312 additions and 100 deletions

View File

@@ -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">

View File

@@ -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}>