mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: worklist sorting, contextual disposition, context panel redesign, notifications
- Worklist default sort descending (newest first), sortable column headers (PRIORITY, PATIENT, SLA) via React Aria - Contextual disposition: auto-selects based on in-call actions (appointment → APPOINTMENT_BOOKED, enquiry → INFO_PROVIDED, transfer → FOLLOW_UP_SCHEDULED) - Context panel redesign: collapsible AI Insight, Upcoming (appointments + follow-ups + linked patient), Recent (calls + activities) sections; auto-collapse on AI chat start - Appointments added to DataProvider with APPOINTMENTS_QUERY, Appointment type, transform - Notification bell for admin/supervisor: performance alerts (idle time, NPS, conversion thresholds) with toast on load + bell dropdown with dismiss; demo alerts as fallback - Slideout z-index fix: added z-50 to slideout ModalOverlay matching modal component Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { faPhoneArrowDown, faPhoneArrowUp, faMagnifyingGlass } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import type { SortDescriptor } from 'react-aria-components';
|
||||
import { faIcon } from '@/lib/icon-wrapper';
|
||||
import { Table } from '@/components/application/table/table';
|
||||
|
||||
@@ -60,6 +61,7 @@ interface WorklistPanelProps {
|
||||
loading: boolean;
|
||||
onSelectLead: (lead: WorklistLead) => void;
|
||||
selectedLeadId: string | null;
|
||||
onDialMissedCall?: (missedCallId: string) => void;
|
||||
}
|
||||
|
||||
type TabKey = 'all' | 'missed' | 'leads' | 'follow-ups';
|
||||
@@ -82,6 +84,7 @@ type WorklistRow = {
|
||||
contactAttempts: number;
|
||||
source: string | null;
|
||||
lastDisposition: string | null;
|
||||
missedCallId: string | null;
|
||||
};
|
||||
|
||||
const priorityConfig: Record<string, { color: 'error' | 'warning' | 'brand' | 'gray'; label: string; sort: number }> = {
|
||||
@@ -164,6 +167,7 @@ const buildRows = (missedCalls: MissedCall[], followUps: WorklistFollowUp[], lea
|
||||
contactAttempts: 0,
|
||||
source: call.callsourcenumber ?? null,
|
||||
lastDisposition: call.disposition ?? null,
|
||||
missedCallId: call.id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,6 +194,7 @@ const buildRows = (missedCalls: MissedCall[], followUps: WorklistFollowUp[], lea
|
||||
contactAttempts: 0,
|
||||
source: null,
|
||||
lastDisposition: null,
|
||||
missedCallId: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -216,6 +221,7 @@ const buildRows = (missedCalls: MissedCall[], followUps: WorklistFollowUp[], lea
|
||||
contactAttempts: lead.contactAttempts ?? 0,
|
||||
source: lead.leadSource ?? lead.utmCampaign ?? null,
|
||||
lastDisposition: null,
|
||||
missedCallId: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -226,16 +232,17 @@ const buildRows = (missedCalls: MissedCall[], followUps: WorklistFollowUp[], lea
|
||||
const pa = priorityConfig[a.priority]?.sort ?? 2;
|
||||
const pb = priorityConfig[b.priority]?.sort ?? 2;
|
||||
if (pa !== pb) return pa - pb;
|
||||
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||||
});
|
||||
|
||||
return actionableRows;
|
||||
};
|
||||
|
||||
export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelectLead, selectedLeadId }: WorklistPanelProps) => {
|
||||
export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelectLead, selectedLeadId, onDialMissedCall }: WorklistPanelProps) => {
|
||||
const [tab, setTab] = useState<TabKey>('all');
|
||||
const [search, setSearch] = useState('');
|
||||
const [missedSubTab, setMissedSubTab] = useState<MissedSubTab>('pending');
|
||||
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({ column: 'sla', direction: 'descending' });
|
||||
|
||||
const missedByStatus = useMemo(() => ({
|
||||
pending: missedCalls.filter(c => c.callbackstatus === 'PENDING_CALLBACK' || !c.callbackstatus),
|
||||
@@ -268,8 +275,30 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
);
|
||||
}
|
||||
|
||||
if (sortDescriptor.column) {
|
||||
const dir = sortDescriptor.direction === 'ascending' ? 1 : -1;
|
||||
rows = [...rows].sort((a, b) => {
|
||||
switch (sortDescriptor.column) {
|
||||
case 'priority': {
|
||||
const pa = priorityConfig[a.priority]?.sort ?? 2;
|
||||
const pb = priorityConfig[b.priority]?.sort ?? 2;
|
||||
return (pa - pb) * dir;
|
||||
}
|
||||
case 'name':
|
||||
return a.name.localeCompare(b.name) * dir;
|
||||
case 'sla': {
|
||||
const ta = new Date(a.lastContactedAt ?? a.createdAt).getTime();
|
||||
const tb = new Date(b.lastContactedAt ?? b.createdAt).getTime();
|
||||
return (ta - tb) * dir;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}, [allRows, tab, search]);
|
||||
}, [allRows, tab, search, sortDescriptor, missedSubTabRows]);
|
||||
|
||||
const missedCount = allRows.filter((r) => r.type === 'missed').length;
|
||||
const leadCount = allRows.filter((r) => r.type === 'lead').length;
|
||||
@@ -373,13 +402,13 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-1 flex-col min-h-0 overflow-hidden px-2 pt-3">
|
||||
<Table size="sm">
|
||||
<Table size="sm" sortDescriptor={sortDescriptor} onSortChange={setSortDescriptor}>
|
||||
<Table.Header>
|
||||
<Table.Head label="PRIORITY" className="w-20" isRowHeader />
|
||||
<Table.Head label="PATIENT" />
|
||||
<Table.Head id="priority" label="PRIORITY" className="w-20" isRowHeader allowsSorting />
|
||||
<Table.Head id="name" label="PATIENT" allowsSorting />
|
||||
<Table.Head label="PHONE" />
|
||||
<Table.Head label={tab === 'missed' ? 'BRANCH' : 'SOURCE'} className="w-28" />
|
||||
<Table.Head label="SLA" className="w-24" />
|
||||
<Table.Head id="sla" label="SLA" className="w-24" allowsSorting />
|
||||
</Table.Header>
|
||||
<Table.Body items={pagedRows}>
|
||||
{(row) => {
|
||||
@@ -432,6 +461,7 @@ export const WorklistPanel = ({ missedCalls, followUps, leads, loading, onSelect
|
||||
phoneNumber={row.phoneRaw}
|
||||
displayNumber={row.phone}
|
||||
leadId={row.leadId ?? undefined}
|
||||
onDial={row.missedCallId ? () => onDialMissedCall?.(row.missedCallId!) : undefined}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-xs text-quaternary italic">No phone</span>
|
||||
|
||||
Reference in New Issue
Block a user