import { useEffect, useMemo, useRef, useState } from 'react'; import type { FC } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPhoneArrowDown, faPhoneArrowUp, faPhoneXmark, faPlay, faPause, faMagnifyingGlass, } from '@fortawesome/pro-duotone-svg-icons'; const SearchLg: FC<{ className?: string }> = ({ className }) => ; import { Table } from '@/components/application/table/table'; import { Badge } from '@/components/base/badges/badges'; import { Button } from '@/components/base/buttons/button'; import { Input } from '@/components/base/input/input'; import { Select } from '@/components/base/select/select'; import { PageHeader } from '@/components/layout/page-header'; import { PhoneActionCell } from '@/components/call-desk/phone-action-cell'; import { formatShortDate, formatPhone } from '@/lib/format'; // cx removed — no longer used after SLA column removal import { useData } from '@/providers/data-provider'; import { useAuth } from '@/providers/auth-provider'; import { PaginationCardDefault } from '@/components/application/pagination/pagination'; import type { Call, CallDirection, CallDisposition } from '@/types/entities'; type FilterKey = 'all' | 'inbound' | 'outbound' | 'missed'; const allFilterItems = [ { id: 'all' as const, label: 'All Calls' }, { id: 'inbound' as const, label: 'Inbound' }, { id: 'outbound' as const, label: 'Outbound' }, { id: 'missed' as const, label: 'Missed' }, ]; const agentFilterItems = [ { id: 'all' as const, label: 'All Calls' }, { id: 'inbound' as const, label: 'Inbound' }, { id: 'outbound' as const, label: 'Outbound' }, ]; const dispositionConfig: Record = { APPOINTMENT_BOOKED: { label: 'Appt Booked', color: 'success' }, APPOINTMENT_RESCHEDULED: { label: 'Appt Rescheduled', color: 'warning' }, APPOINTMENT_CANCELLED: { label: 'Appt Cancelled', color: 'error' }, FOLLOW_UP_SCHEDULED: { label: 'Follow-up', color: 'brand' }, INFO_PROVIDED: { label: 'Info Provided', color: 'blue-light' }, NO_ANSWER: { label: 'No Answer', color: 'warning' }, WRONG_NUMBER: { label: 'Wrong Number', color: 'gray' }, NOT_INTERESTED: { label: 'Not Interested', color: 'error' }, CALLBACK_REQUESTED: { label: 'Callback', color: 'brand' }, CALL_DROPPED: { label: 'Call Dropped', color: 'gray' }, }; const formatDuration = (seconds: number | null): string => { if (seconds === null || seconds === 0) return '\u2014'; 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 DirectionIcon: FC<{ direction: CallDirection | null; status: Call['callStatus'] }> = ({ direction, status }) => { if (status === 'MISSED') { return ; } if (direction === 'OUTBOUND') { return ; } return ; }; const RecordingPlayer: FC<{ url: string }> = ({ url }) => { const audioRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const togglePlay = () => { const audio = audioRef.current; if (!audio) return; if (isPlaying) { audio.pause(); setIsPlaying(false); } else { audio.play().then(() => setIsPlaying(true)).catch(() => {}); } }; const handleEnded = () => setIsPlaying(false); return ( <> } onClick={togglePlay} aria-label={isPlaying ? 'Pause recording' : 'Play recording'} /> > ); }; const PAGE_SIZE = 20; export const CallHistoryPage = () => { const { calls, leads } = useData(); const { user, isAdmin } = useAuth(); const [search, setSearch] = useState(''); const [filter, setFilter] = useState('all'); const [page, setPage] = useState(1); const leadNameMap = useMemo(() => { const map = new Map(); for (const lead of leads) { if (lead.id && lead.contactName) { const name = `${lead.contactName.firstName ?? ''} ${lead.contactName.lastName ?? ''}`.trim(); if (name) map.set(lead.id, name); } } return map; }, [leads]); // Agent sees only their own calls; supervisor sees all const agentConfig = localStorage.getItem('helix_agent_config'); const myAgentId = agentConfig ? (() => { try { return JSON.parse(agentConfig).ozonetelAgentId; } catch { return null; } })() : null; const filteredCalls = useMemo(() => { let result = [...calls].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; }); // CC agent: filter to own calls only. // Match on the authoritative agent relation (set by CDR enrichment) // or the raw agentName for unenriched rows. Chain names like // "RamaiahAdmin -> GlobalHealthX" are split — last segment is // the final handler. Missed calls have no handler and are excluded // from the agent's personal history (they belong on the Missed // Calls queue). if (!isAdmin && myAgentId) { const myId = myAgentId.toLowerCase(); result = result.filter((c) => { // Missed calls have no handler — exclude from agent history if (c.callStatus === 'MISSED') return false; // Authoritative: agent relation from CDR enrichment if (c.agent?.ozonetelAgentId?.toLowerCase() === myId) return true; // Fallback: parse chain in agentName, match last segment if (c.agentName) { const segments = c.agentName.split('->').map(s => s.trim().toLowerCase()); const finalHandler = segments[segments.length - 1]; if (finalHandler === myId) return true; } return false; }); } if (filter === 'inbound') result = result.filter((c) => c.callDirection === 'INBOUND' && c.callStatus !== 'MISSED'); else if (filter === 'outbound') result = result.filter((c) => c.callDirection === 'OUTBOUND'); else if (filter === 'missed') result = result.filter((c) => c.callStatus === 'MISSED'); if (search.trim()) { const q = search.toLowerCase(); result = result.filter((c) => { const name = c.leadName ?? leadNameMap.get(c.leadId ?? '') ?? ''; const phone = c.callerNumber?.[0]?.number ?? ''; const agent = c.agentName ?? ''; return name.toLowerCase().includes(q) || phone.includes(q) || agent.toLowerCase().includes(q); }); } return result; }, [calls, filter, search, leadNameMap, isAdmin, myAgentId, user.id]); const completedCount = filteredCalls.filter((c) => c.callStatus === 'COMPLETED').length; const missedCount = filteredCalls.filter((c) => c.callStatus === 'MISSED').length; const totalPages = Math.max(1, Math.ceil(filteredCalls.length / PAGE_SIZE)); const pagedCalls = filteredCalls.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); useEffect(() => { setPage(1); }, [filter, search]); // eslint-disable-line react-hooks/exhaustive-deps return ( setFilter(key as FilterKey)} items={isAdmin ? allFilterItems : agentFilterItems} aria-label="Filter calls" > {(item) => ( {item.label} )} setSearch(value)} aria-label="Search calls" /> > } /> {filteredCalls.length === 0 ? ( No calls found {search ? 'Try a different search term' : 'No call history available yet.'} ) : ( {/* Agent columns — only visible for supervisor */} {isAdmin && } {isAdmin && } {(call) => { const phoneRaw = call.callerNumber?.[0]?.number ?? ''; const patientName = call.leadName ?? leadNameMap.get(call.leadId ?? '') ?? (phoneRaw ? formatPhone({ number: phoneRaw, callingCode: '+91' }) : 'Unknown'); const dispositionCfg = call.disposition !== null ? dispositionConfig[call.disposition] : null; return ( {patientName} {phoneRaw ? ( ) : ( {'\u2014'} )} {formatDuration(call.durationSeconds)} {dispositionCfg ? ( {dispositionCfg.label} ) : ( {'\u2014'} )} {isAdmin && ( {call.agent?.name ?? call.agentName ?? '\u2014'} )} {isAdmin && ( {call.recordingUrl ? ( ) : ( {'\u2014'} )} )} {call.startedAt ? formatShortDate(call.startedAt) : '\u2014'} ); }} )} {totalPages > 1 && ( )} ); };
{search ? 'Try a different search term' : 'No call history available yet.'}