import { useMemo } from 'react'; import { Link } from 'react-router'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUserHeadset } from '@fortawesome/pro-duotone-svg-icons'; import { Avatar } from '@/components/base/avatar/avatar'; import { Badge } from '@/components/base/badges/badges'; import { Table, TableCard } from '@/components/application/table/table'; import { getInitials } from '@/lib/format'; import type { Call } from '@/types/entities'; const formatDuration = (seconds: number): string => { if (seconds < 60) return `${seconds}s`; const mins = Math.floor(seconds / 60); const secs = seconds % 60; return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`; }; const formatPercent = (value: number): string => { if (isNaN(value) || !isFinite(value)) return '0%'; return `${Math.round(value)}%`; }; interface AgentTableProps { calls: Call[]; } export const AgentTable = ({ calls }: AgentTableProps) => { const agents = useMemo(() => { // Bucket by authoritative agent.id when present (from CDR enrichment); // fall back to raw agentName for legacy rows that haven't been // enriched yet. Skips rows with no agent info at all. const agentMap = new Map(); for (const call of calls) { let key: string; let displayName: string; if (call.agent?.id) { key = call.agent.id; displayName = call.agent.name ?? call.agent.ozonetelAgentId ?? 'Unknown'; } else if (call.agentName) { key = `legacy:${call.agentName}`; displayName = call.agentName; } else { continue; } if (!agentMap.has(key)) agentMap.set(key, { displayName, calls: [] }); agentMap.get(key)!.calls.push(call); } return Array.from(agentMap.entries()).map(([key, { displayName, calls: agentCalls }]) => { const inbound = agentCalls.filter((c) => c.callDirection === 'INBOUND').length; const outbound = agentCalls.filter((c) => c.callDirection === 'OUTBOUND').length; const missed = agentCalls.filter((c) => c.callStatus === 'MISSED').length; const total = agentCalls.length; const completedCalls = agentCalls.filter((c) => (c.durationSeconds ?? 0) > 0); const totalDuration = completedCalls.reduce((sum, c) => sum + (c.durationSeconds ?? 0), 0); const avgHandle = completedCalls.length > 0 ? Math.round(totalDuration / completedCalls.length) : 0; const booked = agentCalls.filter((c) => c.disposition === 'APPOINTMENT_BOOKED').length; const conversion = total > 0 ? (booked / total) * 100 : 0; const nameParts = displayName.split(' '); return { id: key, name: displayName, initials: getInitials(nameParts[0] ?? '', nameParts[1] ?? ''), inbound, outbound, missed, total, avgHandle, conversion, }; }).sort((a, b) => b.total - a.total); }, [calls]); if (agents.length === 0) { return (

No agent data available

); } return ( {(agent) => (
{agent.name}
{agent.inbound} {agent.outbound} {agent.missed > 0 ? {agent.missed} : 0} {formatDuration(agent.avgHandle)} = 30 ? 'success' : agent.conversion >= 15 ? 'warning' : 'gray'}> {formatPercent(agent.conversion)}
)}
); };