fix: Team Dashboard PageHeader + Call Recordings agent name enrichment
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:
2026-04-17 06:14:45 +05:30
parent e175735d6c
commit fd7ee4fc1f
3 changed files with 48 additions and 42 deletions

View File

@@ -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;

View File

@@ -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,36 +56,36 @@ 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 key={range}
key={range} onClick={() => setDateRange(range)}
onClick={() => setDateRange(range)} className={cx(
className={cx( "px-3 py-1 text-xs font-medium transition duration-100 ease-linear",
"px-3 py-1 text-xs font-medium transition duration-100 ease-linear", dateRange === range ? 'bg-active text-brand-secondary' : 'text-tertiary hover:bg-primary_hover',
dateRange === range ? 'bg-active text-brand-secondary' : 'text-tertiary hover:bg-primary_hover', )}
)} >
> {range === 'today' ? 'Today' : range === 'week' ? 'Week' : 'Month'}
{range === 'today' ? 'Today' : range === 'week' ? 'Week' : 'Month'} </button>
</button> ))}
))} </div>
</div> <button
<button onClick={() => setAiOpen(!aiOpen)}
onClick={() => setAiOpen(!aiOpen)} className="flex size-8 items-center justify-center rounded-lg text-fg-quaternary hover:text-fg-secondary hover:bg-primary_hover transition duration-100 ease-linear"
className="flex size-8 items-center justify-center rounded-lg text-fg-quaternary hover:text-fg-secondary hover:bg-primary_hover transition duration-100 ease-linear" title={aiOpen ? 'Hide AI panel' : 'Show AI panel'}
title={aiOpen ? 'Hide AI panel' : 'Show AI panel'} >
> <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

View File

@@ -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>
); );
}; };