mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
fix: Team Dashboard PageHeader + Call Recordings agent name enrichment
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- team-dashboard.tsx: replaced inline header with PageHeader (was the actual page being rendered, not team-performance.tsx) - call-recordings.tsx: added agent relation to GraphQL query, render uses enriched agent.name with raw agentName fallback — matches Call History page pattern. Search + sort also use enriched name. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ type RecordingRecord = {
|
|||||||
callStatus: string | null;
|
callStatus: string | null;
|
||||||
callerNumber: { primaryPhoneNumber: string } | null;
|
callerNumber: { primaryPhoneNumber: string } | null;
|
||||||
agentName: string | null;
|
agentName: string | null;
|
||||||
|
agent: { id: string; name: string; ozonetelAgentId: string } | null;
|
||||||
startedAt: string | null;
|
startedAt: string | null;
|
||||||
durationSec: number | null;
|
durationSec: number | null;
|
||||||
disposition: string | null;
|
disposition: string | null;
|
||||||
@@ -35,7 +36,8 @@ type RecordingRecord = {
|
|||||||
|
|
||||||
const QUERY = `{ calls(first: 200, orderBy: [{ startedAt: DescNullsLast }]) { edges { node {
|
const QUERY = `{ calls(first: 200, orderBy: [{ startedAt: DescNullsLast }]) { edges { node {
|
||||||
id createdAt direction callStatus callerNumber { primaryPhoneNumber }
|
id createdAt direction callStatus callerNumber { primaryPhoneNumber }
|
||||||
agentName startedAt durationSec disposition sla
|
agentName agent { id name ozonetelAgentId }
|
||||||
|
startedAt durationSec disposition sla
|
||||||
recording { primaryLinkUrl primaryLinkLabel }
|
recording { primaryLinkUrl primaryLinkLabel }
|
||||||
} } } }`;
|
} } } }`;
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ export const CallRecordingsPage = () => {
|
|||||||
const dirColor: 'blue' | 'brand' = call.direction === 'INBOUND' ? 'blue' : 'brand';
|
const dirColor: 'blue' | 'brand' = call.direction === 'INBOUND' ? 'blue' : 'brand';
|
||||||
switch (colId) {
|
switch (colId) {
|
||||||
case 'agent':
|
case 'agent':
|
||||||
return <span className="text-sm text-primary">{call.agentName || '—'}</span>;
|
return <span className="text-sm text-primary">{call.agent?.name ?? call.agentName ?? '—'}</span>;
|
||||||
case 'caller':
|
case 'caller':
|
||||||
return phone
|
return phone
|
||||||
? <PhoneActionCell phoneNumber={phone} displayNumber={formatPhone({ number: phone, callingCode: '+91' })} />
|
? <PhoneActionCell phoneNumber={phone} displayNumber={formatPhone({ number: phone, callingCode: '+91' })} />
|
||||||
@@ -207,7 +209,7 @@ export const CallRecordingsPage = () => {
|
|||||||
if (search.trim()) {
|
if (search.trim()) {
|
||||||
const q = search.toLowerCase();
|
const q = search.toLowerCase();
|
||||||
result = result.filter(c =>
|
result = result.filter(c =>
|
||||||
(c.agentName ?? '').toLowerCase().includes(q) ||
|
(c.agent?.name ?? c.agentName ?? '').toLowerCase().includes(q) ||
|
||||||
(c.callerNumber?.primaryPhoneNumber ?? '').includes(q) ||
|
(c.callerNumber?.primaryPhoneNumber ?? '').includes(q) ||
|
||||||
(c.disposition ?? '').toLowerCase().includes(q),
|
(c.disposition ?? '').toLowerCase().includes(q),
|
||||||
);
|
);
|
||||||
@@ -217,7 +219,7 @@ export const CallRecordingsPage = () => {
|
|||||||
const dir = sortDescriptor.direction === 'ascending' ? 1 : -1;
|
const dir = sortDescriptor.direction === 'ascending' ? 1 : -1;
|
||||||
result = [...result].sort((a, b) => {
|
result = [...result].sort((a, b) => {
|
||||||
switch (sortDescriptor.column) {
|
switch (sortDescriptor.column) {
|
||||||
case 'agent': return (a.agentName ?? '').localeCompare(b.agentName ?? '') * dir;
|
case 'agent': return (a.agent?.name ?? a.agentName ?? '').localeCompare(b.agent?.name ?? b.agentName ?? '') * dir;
|
||||||
case 'dateTime': {
|
case 'dateTime': {
|
||||||
const ta = a.startedAt ? new Date(a.startedAt).getTime() : 0;
|
const ta = a.startedAt ? new Date(a.startedAt).getTime() : 0;
|
||||||
const tb = b.startedAt ? new Date(b.startedAt).getTime() : 0;
|
const tb = b.startedAt ? new Date(b.startedAt).getTime() : 0;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
|
import { faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
|
||||||
|
import { PageHeader } from '@/components/layout/page-header';
|
||||||
import { AiChatPanel } from '@/components/call-desk/ai-chat-panel';
|
import { AiChatPanel } from '@/components/call-desk/ai-chat-panel';
|
||||||
import { DashboardKpi } from '@/components/dashboard/kpi-cards';
|
import { DashboardKpi } from '@/components/dashboard/kpi-cards';
|
||||||
import { MissedQueue } from '@/components/dashboard/missed-queue';
|
import { MissedQueue } from '@/components/dashboard/missed-queue';
|
||||||
@@ -55,13 +56,12 @@ export const TeamDashboardPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col overflow-hidden">
|
<div className="flex flex-1 flex-col overflow-hidden">
|
||||||
{/* Header */}
|
<PageHeader
|
||||||
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-3">
|
title="Team Dashboard"
|
||||||
<div className="flex items-center gap-3">
|
subtitle={dateRangeLabel}
|
||||||
<h1 className="text-lg font-bold text-primary">Team Dashboard</h1>
|
infoText="Aggregated call metrics, agent performance, and operational alerts."
|
||||||
<span className="text-sm text-tertiary">{dateRangeLabel}</span>
|
controls={
|
||||||
</div>
|
<>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex rounded-lg border border-secondary overflow-hidden">
|
<div className="flex rounded-lg border border-secondary overflow-hidden">
|
||||||
{(['today', 'week', 'month'] as const).map((range) => (
|
{(['today', 'week', 'month'] as const).map((range) => (
|
||||||
<button
|
<button
|
||||||
@@ -83,8 +83,9 @@ export const TeamDashboardPage = () => {
|
|||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={aiOpen ? faSidebarFlip : faSidebar} className="size-4" />
|
<FontAwesomeIcon icon={aiOpen ? faSidebarFlip : faSidebar} className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</>
|
||||||
</div>
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
{/* Main content — scrollable column with KPIs pinned at the
|
{/* Main content — scrollable column with KPIs pinned at the
|
||||||
|
|||||||
@@ -291,25 +291,28 @@ export const TeamPerformancePage = () => {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-1 flex-col overflow-hidden">
|
||||||
<PageHeader title="Team Performance" infoText="Aggregated metrics across all agents." />
|
<PageHeader title="Team Dashboard" infoText="Aggregated metrics across all agents." />
|
||||||
<div className="flex flex-1 items-center justify-center">
|
<div className="flex flex-1 items-center justify-center">
|
||||||
<p className="text-sm text-tertiary">Loading team performance...</p>
|
<p className="text-sm text-tertiary">Loading team performance...</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-1 flex-col overflow-hidden">
|
||||||
<PageHeader title="Team Performance" infoText="Aggregated metrics across all agents." />
|
<PageHeader
|
||||||
|
title="Team Dashboard"
|
||||||
|
infoText="Aggregated metrics across all agents."
|
||||||
|
controls={<DateFilter value={range} onChange={setRange} />}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col overflow-y-auto">
|
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||||
{/* Section 1: Key Metrics */}
|
{/* Section 1: Key Metrics */}
|
||||||
<div className="px-6 pt-5">
|
<div className="px-6 pt-5">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-sm font-semibold text-secondary">Key Metrics</h3>
|
<h3 className="text-sm font-semibold text-secondary">Key Metrics</h3>
|
||||||
<DateFilter value={range} onChange={setRange} />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-5 gap-3">
|
<div className="grid grid-cols-5 gap-3">
|
||||||
<KpiCard icon={faUsers} value={activeAgents} label="Active Agents" color="bg-brand-secondary" />
|
<KpiCard icon={faUsers} value={activeAgents} label="Active Agents" color="bg-brand-secondary" />
|
||||||
@@ -510,6 +513,6 @@ export const TeamPerformancePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user