From eb638c20e85cd61ff89a2800a7892cc20bea3449 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Tue, 17 Mar 2026 06:12:24 +0530 Subject: [PATCH] feat: add Follow-ups and Call History pages for CC Agent Implements full Follow-ups page with overdue/upcoming/completed sections and left-border color-coded cards. Adds Call History table filtered by current agent with disposition badges and duration formatting. Co-Authored-By: Claude Sonnet 4.6 --- src/pages/call-history.tsx | 124 ++++++++++++++++++++++++++-- src/pages/follow-ups-page.tsx | 150 ++++++++++++++++++++++++++++++++-- 2 files changed, 260 insertions(+), 14 deletions(-) diff --git a/src/pages/call-history.tsx b/src/pages/call-history.tsx index 9405750..a9ae23f 100644 --- a/src/pages/call-history.tsx +++ b/src/pages/call-history.tsx @@ -1,14 +1,124 @@ +import { Badge } from '@/components/base/badges/badges'; import { TopBar } from '@/components/layout/top-bar'; +import { formatShortDate } from '@/lib/format'; +import { useData } from '@/providers/data-provider'; +import { useAuth } from '@/providers/auth-provider'; +import type { CallDisposition } from '@/types/entities'; + +const dispositionColor = (disposition: CallDisposition | null): 'success' | 'brand' | 'blue-light' | 'warning' | 'gray' | 'error' => { + switch (disposition) { + case 'APPOINTMENT_BOOKED': + return 'success'; + case 'FOLLOW_UP_SCHEDULED': + return 'brand'; + case 'INFO_PROVIDED': + return 'blue-light'; + case 'NO_ANSWER': + return 'warning'; + case 'WRONG_NUMBER': + return 'gray'; + case 'CALLBACK_REQUESTED': + return 'brand'; + default: + return 'gray'; + } +}; + +const formatDispositionLabel = (disposition: CallDisposition | null): string => { + if (!disposition) return '—'; + return disposition + .toLowerCase() + .replace(/_/g, ' ') + .replace(/\b\w/g, (c) => c.toUpperCase()); +}; + +const formatDuration = (seconds: number | null): string => { + if (seconds === null) return '—'; + const mins = Math.round(seconds / 60); + return mins === 0 ? '<1 min' : `${mins} min`; +}; + +const formatCallerNumber = (callerNumber: { number: string; callingCode: string }[] | null): string => { + if (!callerNumber || callerNumber.length === 0) return '—'; + const first = callerNumber[0]; + return `${first.callingCode} ${first.number}`; +}; export const CallHistoryPage = () => { + const { calls } = useData(); + const { user } = useAuth(); + + const agentCalls = calls + .filter((call) => call.agentName === user.name) + .sort((a, b) => { + const dateA = a.startedAt ? new Date(a.startedAt).getTime() : 0; + const dateB = b.startedAt ? new Date(b.startedAt).getTime() : 0; + return dateB - dateA; + }); + return ( -
- -
-
-

Call History

-

Coming soon — call logs, recordings, and outcome tracking.

-
+
+ +
+ {agentCalls.length === 0 ? ( +
+
+

No calls found

+

No call history available for your account yet.

+
+
+ ) : ( +
+ + + + + + + + + + + + {agentCalls.map((call) => ( + + + + + + + + ))} + +
+ Date / Time + + Caller + + Lead Name + + Duration + + Disposition +
+ {call.startedAt ? formatShortDate(call.startedAt) : '—'} + + {formatCallerNumber(call.callerNumber)} + + {call.leadName ?? '—'} + + {formatDuration(call.durationSeconds)} + + {call.disposition ? ( + + {formatDispositionLabel(call.disposition)} + + ) : ( + + )} +
+
+ )}
); diff --git a/src/pages/follow-ups-page.tsx b/src/pages/follow-ups-page.tsx index 1abb587..0304996 100644 --- a/src/pages/follow-ups-page.tsx +++ b/src/pages/follow-ups-page.tsx @@ -1,15 +1,151 @@ +import { Badge } from '@/components/base/badges/badges'; import { TopBar } from '@/components/layout/top-bar'; +import { formatShortDate } from '@/lib/format'; +import { useFollowUps } from '@/hooks/use-follow-ups'; +import { cx } from '@/utils/cx'; +import type { FollowUp, FollowUpStatus, Priority } from '@/types/entities'; -export const FollowUpsPage = () => { +const statusColor = (status: FollowUpStatus | null): 'error' | 'brand' | 'success' | 'gray' => { + switch (status) { + case 'OVERDUE': + return 'error'; + case 'PENDING': + return 'brand'; + case 'COMPLETED': + return 'success'; + case 'CANCELLED': + default: + return 'gray'; + } +}; + +const priorityColor = (priority: Priority | null): 'error' | 'warning' | 'brand' | 'gray' => { + switch (priority) { + case 'URGENT': + return 'error'; + case 'HIGH': + return 'warning'; + case 'NORMAL': + return 'brand'; + case 'LOW': + default: + return 'gray'; + } +}; + +const cardBorderClass = (status: FollowUpStatus | null, isOverdue: boolean): string => { + if (isOverdue || status === 'OVERDUE') { + return 'border-l-error-500 bg-error-primary'; + } + if (status === 'COMPLETED') { + return 'border-l-success-500'; + } + if (status === 'CANCELLED') { + return 'border-l-gray-300'; + } + return 'border-l-brand-500'; +}; + +interface FollowUpCardProps { + followUp: FollowUp; + isOverdue?: boolean; +} + +const FollowUpCard = ({ followUp, isOverdue = false }: FollowUpCardProps) => { return ( -
- -
-
-

Follow-ups

-

Coming soon — follow-up reminders, scheduling, and task management.

+
+
+
+

+ {followUp.scheduledAt ? formatShortDate(followUp.scheduledAt) : 'No date scheduled'} + {(isOverdue || followUp.followUpStatus === 'OVERDUE') && ' — Overdue'} +

+

+ {followUp.description ?? followUp.followUpType ?? 'Follow-up'} +

+ {(followUp.patientName || followUp.patientPhone) && ( +

+ {followUp.patientName} + {followUp.patientPhone && ` · ${followUp.patientPhone}`} +

+ )} +
+
+ {followUp.followUpStatus && ( + + {followUp.followUpStatus} + + )} + {followUp.priority && ( + + {followUp.priority} + + )}
); }; + +export const FollowUpsPage = () => { + const { followUps, overdue, upcoming } = useFollowUps(); + + const completed = followUps.filter((f) => f.followUpStatus === 'COMPLETED'); + + return ( +
+ +
+ {/* Overdue section */} + {overdue.length > 0 && ( +
+

Overdue ({overdue.length})

+
+ {overdue.map((followUp) => ( + + ))} +
+
+ )} + + {/* Upcoming section */} + {upcoming.length > 0 && ( +
+

Upcoming ({upcoming.length})

+
+ {upcoming.map((followUp) => ( + + ))} +
+
+ )} + + {/* Completed section */} + {completed.length > 0 && ( +
+

Completed ({completed.length})

+
+ {completed.map((followUp) => ( + + ))} +
+
+ )} + + {overdue.length === 0 && upcoming.length === 0 && completed.length === 0 && ( +
+
+

No follow-ups

+

All caught up — no scheduled callbacks or reminders.

+
+
+ )} +
+
+ ); +};