import { useEffect, useState } from 'react'; import ReactECharts from 'echarts-for-react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPhoneVolume, faPhoneArrowDown, faCalendarCheck, faClock, faPercent, faRightToBracket, } from '@fortawesome/pro-duotone-svg-icons'; import { getLocalTimeZone, parseDate, today as todayDate } from '@internationalized/date'; import type { DateValue } from 'react-aria-components'; import { DatePicker } from '@/components/application/date-picker/date-picker'; import { TopBar } from '@/components/layout/top-bar'; import { apiClient } from '@/lib/api-client'; import { cx } from '@/utils/cx'; type PerformanceData = { date: string; calls: { total: number; inbound: number; outbound: number; answered: number; missed: number }; avgTalkTimeSec: number; avgHandlingTime: string; conversionRate: number; appointmentsBooked: number; timeUtilization: { totalLoginDuration: string; totalBusyTime: string; totalIdleTime: string; totalPauseTime: string; totalWrapupTime: string; totalDialTime: string; } | null; dispositions: Record; }; const parseTime = (timeStr: string): number => { const parts = timeStr.split(':').map(Number); if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]; return 0; }; const formatDuration = (seconds: number): string => { if (seconds < 60) return `${seconds}s`; const mins = Math.floor(seconds / 60); const secs = seconds % 60; if (mins < 60) return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`; const hrs = Math.floor(mins / 60); return `${hrs}h ${mins % 60}m`; }; const BRAND = { blue600: 'rgb(32, 96, 160)', blue500: 'rgb(56, 120, 180)', blue400: 'rgb(96, 150, 200)', blue300: 'rgb(138, 180, 220)', success: 'rgb(23, 178, 106)', warning: 'rgb(247, 144, 9)', error: 'rgb(240, 68, 56)', gray400: 'rgb(164, 167, 174)', purple: 'rgb(158, 119, 237)', }; type KpiCardProps = { icon: any; iconColor: string; label: string; value: string | number; subtitle?: string; }; const KpiCard = ({ icon, iconColor, label, value, subtitle }: KpiCardProps) => (
{label}

{value}

{subtitle &&

{subtitle}

}
); export const MyPerformancePage = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [selectedDate, setSelectedDate] = useState(() => new Date().toISOString().split('T')[0]); useEffect(() => { setLoading(true); const agentCfg = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}'); const agentId = agentCfg.ozonetelAgentId ?? ''; apiClient.get(`/api/ozonetel/performance?date=${selectedDate}&agentId=${agentId}`, { silent: true }) .then(setData) .catch(() => setData(null)) .finally(() => setLoading(false)); }, [selectedDate]); const now = todayDate(getLocalTimeZone()); const dateValue = selectedDate ? parseDate(selectedDate) : now; const handleDateChange = (value: DateValue | null) => { if (value) { setSelectedDate(value.toString()); } }; return (
{/* Date selector */}
{loading ? (

Loading performance data...

) : !data ? (

No performance data available for this date.

) : ( <> {/* KPI Cards */}
{/* Charts row */}
{/* Call breakdown bar chart */}

Call Breakdown

{/* Disposition donut */}

Disposition Breakdown

{Object.keys(data.dispositions).length === 0 ? (

No dispositions recorded

) : ( ({ name, value, itemStyle: { color: [BRAND.blue600, BRAND.success, BRAND.warning, BRAND.purple, BRAND.gray400, BRAND.error][i % 6], }, })), }], }} style={{ height: 240 }} /> )}
{/* Time utilization */} {data.timeUtilization && (

Time Utilization

)} )}
); }; const TimeBar = ({ utilization }: { utilization: NonNullable }) => { const busy = parseTime(utilization.totalBusyTime); const idle = parseTime(utilization.totalIdleTime); const pause = parseTime(utilization.totalPauseTime); const wrapup = parseTime(utilization.totalWrapupTime); const dial = parseTime(utilization.totalDialTime); const total = busy + idle + pause + wrapup + dial; if (total === 0) { return

No time data available

; } const segments = [ { label: 'Busy', value: busy, color: BRAND.blue600 }, { label: 'Dialing', value: dial, color: BRAND.blue400 }, { label: 'Idle', value: idle, color: BRAND.gray400 }, { label: 'Wrap-up', value: wrapup, color: BRAND.warning }, { label: 'Pause', value: pause, color: BRAND.error }, ].filter(s => s.value > 0); return (
{/* Stacked bar */}
{segments.map(s => (
))}
{/* Legend */}
{segments.map(s => (
{s.label} {formatDuration(s.value)} ({Math.round((s.value / total) * 100)}%)
))}
); };