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 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 06:12:24 +05:30
parent 58777222ca
commit eb638c20e8
2 changed files with 260 additions and 14 deletions

View File

@@ -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 (
<div className="flex flex-1 flex-col">
<TopBar title="Call History" subtitle="Past call logs and recordings" />
<div className="flex flex-1 items-center justify-center p-7">
<div className="flex flex-col items-center gap-2 text-center">
<h2 className="text-display-xs font-bold text-primary">Call History</h2>
<p className="text-sm text-tertiary">Coming soon call logs, recordings, and outcome tracking.</p>
</div>
<div className="flex flex-1 flex-col overflow-hidden">
<TopBar title="Call History" subtitle="All inbound calls" />
<div className="flex-1 overflow-y-auto p-7">
{agentCalls.length === 0 ? (
<div className="flex flex-1 items-center justify-center py-20">
<div className="flex flex-col items-center gap-2 text-center">
<h3 className="text-sm font-semibold text-primary">No calls found</h3>
<p className="text-sm text-tertiary">No call history available for your account yet.</p>
</div>
</div>
) : (
<div className="rounded-2xl border border-secondary bg-primary overflow-hidden">
<table className="w-full">
<thead>
<tr className="bg-secondary">
<th className="text-xs uppercase tracking-wider text-quaternary font-semibold px-4 py-3 text-left">
Date / Time
</th>
<th className="text-xs uppercase tracking-wider text-quaternary font-semibold px-4 py-3 text-left">
Caller
</th>
<th className="text-xs uppercase tracking-wider text-quaternary font-semibold px-4 py-3 text-left">
Lead Name
</th>
<th className="text-xs uppercase tracking-wider text-quaternary font-semibold px-4 py-3 text-left">
Duration
</th>
<th className="text-xs uppercase tracking-wider text-quaternary font-semibold px-4 py-3 text-left">
Disposition
</th>
</tr>
</thead>
<tbody>
{agentCalls.map((call) => (
<tr key={call.id} className="border-b border-tertiary hover:bg-primary_hover transition duration-100 ease-linear">
<td className="px-4 py-3 text-sm text-secondary whitespace-nowrap">
{call.startedAt ? formatShortDate(call.startedAt) : '—'}
</td>
<td className="px-4 py-3 text-sm text-secondary whitespace-nowrap">
{formatCallerNumber(call.callerNumber)}
</td>
<td className="px-4 py-3 text-sm text-primary">
{call.leadName ?? '—'}
</td>
<td className="px-4 py-3 text-sm text-secondary whitespace-nowrap">
{formatDuration(call.durationSeconds)}
</td>
<td className="px-4 py-3">
{call.disposition ? (
<Badge size="sm" color={dispositionColor(call.disposition)}>
{formatDispositionLabel(call.disposition)}
</Badge>
) : (
<span className="text-sm text-tertiary"></span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
);