mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-12 02:38:15 +00:00
Linting and Formatting
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
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';
|
||||
import { useMemo } from "react";
|
||||
import { faUserHeadset } from "@fortawesome/pro-duotone-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "react-router";
|
||||
import { Table, TableCard } from "@/components/application/table/table";
|
||||
import { Avatar } from "@/components/base/avatar/avatar";
|
||||
import { Badge } from "@/components/base/badges/badges";
|
||||
import { getInitials } from "@/lib/format";
|
||||
import type { Call } from "@/types/entities";
|
||||
|
||||
const formatDuration = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
@@ -16,7 +16,7 @@ const formatDuration = (seconds: number): string => {
|
||||
};
|
||||
|
||||
const formatPercent = (value: number): string => {
|
||||
if (isNaN(value) || !isFinite(value)) return '0%';
|
||||
if (isNaN(value) || !isFinite(value)) return "0%";
|
||||
return `${Math.round(value)}%`;
|
||||
};
|
||||
|
||||
@@ -28,37 +28,44 @@ export const AgentTable = ({ calls }: AgentTableProps) => {
|
||||
const agents = useMemo(() => {
|
||||
const agentMap = new Map<string, Call[]>();
|
||||
for (const call of calls) {
|
||||
const agent = call.agentName ?? 'Unknown';
|
||||
const agent = call.agentName ?? "Unknown";
|
||||
if (!agentMap.has(agent)) agentMap.set(agent, []);
|
||||
agentMap.get(agent)!.push(call);
|
||||
}
|
||||
|
||||
return Array.from(agentMap.entries()).map(([name, 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 = name.split(' ');
|
||||
return Array.from(agentMap.entries())
|
||||
.map(([name, 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 = name.split(" ");
|
||||
|
||||
return {
|
||||
id: name,
|
||||
name,
|
||||
initials: getInitials(nameParts[0] ?? '', nameParts[1] ?? ''),
|
||||
inbound, outbound, missed, total, avgHandle, conversion,
|
||||
};
|
||||
}).sort((a, b) => b.total - a.total);
|
||||
return {
|
||||
id: name,
|
||||
name,
|
||||
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 (
|
||||
<TableCard.Root size="sm">
|
||||
<TableCard.Header title="Agent Performance" description="Call metrics by agent" />
|
||||
<div className="flex flex-col items-center justify-center py-12 gap-2">
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-12">
|
||||
<FontAwesomeIcon icon={faUserHeadset} className="size-8 text-fg-quaternary" />
|
||||
<p className="text-sm text-tertiary">No agent data available</p>
|
||||
</div>
|
||||
@@ -85,18 +92,32 @@ export const AgentTable = ({ calls }: AgentTableProps) => {
|
||||
<Link to={`/agent/${encodeURIComponent(agent.name)}`} className="no-underline">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar size="xs" initials={agent.initials} />
|
||||
<span className="text-sm font-medium text-brand-secondary hover:text-brand-secondary_hover transition duration-100 ease-linear">{agent.name}</span>
|
||||
<span className="text-sm font-medium text-brand-secondary transition duration-100 ease-linear hover:text-brand-secondary_hover">
|
||||
{agent.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</Table.Cell>
|
||||
<Table.Cell><span className="text-sm text-success-primary">{agent.inbound}</span></Table.Cell>
|
||||
<Table.Cell><span className="text-sm text-brand-secondary">{agent.outbound}</span></Table.Cell>
|
||||
<Table.Cell>
|
||||
{agent.missed > 0 ? <Badge size="sm" color="error">{agent.missed}</Badge> : <span className="text-sm text-tertiary">0</span>}
|
||||
<span className="text-sm text-success-primary">{agent.inbound}</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell><span className="text-sm text-secondary">{formatDuration(agent.avgHandle)}</span></Table.Cell>
|
||||
<Table.Cell>
|
||||
<Badge size="sm" color={agent.conversion >= 30 ? 'success' : agent.conversion >= 15 ? 'warning' : 'gray'}>
|
||||
<span className="text-sm text-brand-secondary">{agent.outbound}</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{agent.missed > 0 ? (
|
||||
<Badge size="sm" color="error">
|
||||
{agent.missed}
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-sm text-tertiary">0</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<span className="text-sm text-secondary">{formatDuration(agent.avgHandle)}</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Badge size="sm" color={agent.conversion >= 30 ? "success" : agent.conversion >= 15 ? "warning" : "gray"}>
|
||||
{formatPercent(agent.conversion)}
|
||||
</Badge>
|
||||
</Table.Cell>
|
||||
|
||||
Reference in New Issue
Block a user