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:
2026-03-30 14:45:52 +05:30
parent 0477064b3e
commit c3c3f4b3d7
18 changed files with 882 additions and 389 deletions

View File

@@ -73,20 +73,44 @@ export const EnquiryForm = ({ isOpen, onOpenChange, callerPhone, onSaved }: Enqu
setError(null);
try {
// Create a lead with source PHONE_INQUIRY
await apiClient.graphql(
`mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`,
{
data: {
name: `Enquiry — ${patientName}`,
contactName: { firstName: patientName.split(' ')[0], lastName: patientName.split(' ').slice(1).join(' ') || '' },
contactPhone: registeredPhone ? { primaryPhoneNumber: registeredPhone } : undefined,
source: 'PHONE',
status: disposition === 'CONVERTED' ? 'CONVERTED' : 'NEW',
interestedService: queryAsked.substring(0, 100),
// Resolve caller — ensures lead+patient pair exists, returns IDs
let leadId: string | null = null;
if (registeredPhone) {
const resolved = await apiClient.post<{ leadId: string; patientId: string }>('/api/caller/resolve', { phone: registeredPhone }, { silent: true });
leadId = resolved.leadId;
}
if (leadId) {
// Update existing lead with enquiry details
await apiClient.graphql(
`mutation($id: UUID!, $data: LeadUpdateInput!) { updateLead(id: $id, data: $data) { id } }`,
{
id: leadId,
data: {
name: `Enquiry — ${patientName}`,
contactName: { firstName: patientName.split(' ')[0], lastName: patientName.split(' ').slice(1).join(' ') || '' },
source: 'PHONE',
status: disposition === 'CONVERTED' ? 'CONVERTED' : 'NEW',
interestedService: queryAsked.substring(0, 100),
},
},
},
);
);
} else {
// No phone provided — create a new lead (rare edge case)
await apiClient.graphql(
`mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`,
{
data: {
name: `Enquiry — ${patientName}`,
contactName: { firstName: patientName.split(' ')[0], lastName: patientName.split(' ').slice(1).join(' ') || '' },
contactPhone: registeredPhone ? { primaryPhoneNumber: registeredPhone } : undefined,
source: 'PHONE',
status: disposition === 'CONVERTED' ? 'CONVERTED' : 'NEW',
interestedService: queryAsked.substring(0, 100),
},
},
);
}
// Create follow-up if needed
if (followUpNeeded && followUpDate) {