fix: notifications use real data + agent-detail follows new id scheme

1. notification-bell: drop the DEMO_ALERTS fallback (Riya Mehta etc.).
   Empty state ("No active alerts") shows when the live computation
   returns nothing — which is the truthful state until thresholds are
   set on Agent records.
2. use-performance-alerts: bucket calls by c.agentId === agent.id when
   the relation is set; fall back to legacy agentName matching only for
   un-enriched rows. Fixes conversion% calc going to 0 after backfill.
3. agent-table: Link target uses agent.id (UUID or "legacy:NAME") so
   the URL is a stable identifier instead of a display string.
4. agent-detail: parse the route param into UUID vs legacy:NAME, filter
   calls by c.agentId or c.agentName accordingly, and resolve display
   name via the platform Agents list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 08:47:57 +05:30
parent 8de7d7d802
commit 91a1f33d35
4 changed files with 32 additions and 41 deletions

View File

@@ -95,7 +95,7 @@ export const AgentTable = ({ calls }: AgentTableProps) => {
{(agent) => (
<Table.Row id={agent.id}>
<Table.Cell>
<Link to={`/agent/${encodeURIComponent(agent.name)}`} className="no-underline">
<Link to={`/agent/${encodeURIComponent(agent.id)}`} 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>

View File

@@ -2,46 +2,14 @@ import { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBell, faTriangleExclamation, faXmark, faCheck } from '@fortawesome/pro-duotone-svg-icons';
import { Badge } from '@/components/base/badges/badges';
import { usePerformanceAlerts, type PerformanceAlert } from '@/hooks/use-performance-alerts';
import { usePerformanceAlerts } from '@/hooks/use-performance-alerts';
import { cx } from '@/utils/cx';
const DEMO_ALERTS: PerformanceAlert[] = [
{ id: 'demo-1', agent: 'Riya Mehta', type: 'Excessive Idle Time', value: '120m', severity: 'error', dismissed: false },
{ id: 'demo-2', agent: 'Arjun Kapoor', type: 'Excessive Idle Time', value: '180m', severity: 'error', dismissed: false },
{ id: 'demo-3', agent: 'Sneha Iyer', type: 'Excessive Idle Time', value: '250m', severity: 'error', dismissed: false },
{ id: 'demo-4', agent: 'Vikrant Desai', type: 'Excessive Idle Time', value: '300m', severity: 'error', dismissed: false },
{ id: 'demo-5', agent: 'Vikrant Desai', type: 'Low NPS', value: '35', severity: 'warning', dismissed: false },
{ id: 'demo-6', agent: 'Vikrant Desai', type: 'Low Conversion', value: '40%', severity: 'warning', dismissed: false },
{ id: 'demo-7', agent: 'Pooja Rao', type: 'Excessive Idle Time', value: '200m', severity: 'error', dismissed: false },
{ id: 'demo-8', agent: 'Mohammed Rizwan', type: 'Excessive Idle Time', value: '80m', severity: 'error', dismissed: false },
];
export const NotificationBell = () => {
const { alerts: liveAlerts, dismiss: liveDismiss, dismissAll: liveDismissAll } = usePerformanceAlerts();
const [demoAlerts, setDemoAlerts] = useState<PerformanceAlert[]>(DEMO_ALERTS);
const [open, setOpen] = useState(true);
const { alerts, dismiss, dismissAll } = usePerformanceAlerts();
const [open, setOpen] = useState(false);
const panelRef = useRef<HTMLDivElement>(null);
// Use live alerts if available, otherwise demo
const alerts = liveAlerts.length > 0 ? liveAlerts : demoAlerts.filter(a => !a.dismissed);
const isDemo = liveAlerts.length === 0;
const dismiss = (id: string) => {
if (isDemo) {
setDemoAlerts(prev => prev.map(a => a.id === id ? { ...a, dismissed: true } : a));
} else {
liveDismiss(id);
}
};
const dismissAll = () => {
if (isDemo) {
setDemoAlerts(prev => prev.map(a => ({ ...a, dismissed: true })));
} else {
liveDismissAll();
}
};
// Close on outside click
useEffect(() => {
if (!open) return;

View File

@@ -47,7 +47,11 @@ export const usePerformanceAlerts = () => {
let idx = 0;
for (const agent of teamPerf.agents) {
const agentCalls = calls.filter(c => c.agentName === agent.name || c.agentName === agent.ozonetelAgentId);
const agentCalls = calls.filter((c) => {
if (c.agentId && agent.id && c.agentId === agent.id) return true;
if (!c.agentId && (c.agentName === agent.name || c.agentName === agent.ozonetelAgentId)) return true;
return false;
});
const totalCalls = agentCalls.length;
const agentAppts = agentCalls.filter((c: any) => c.disposition === 'APPOINTMENT_BOOKED').length;
const convPercent = totalCalls > 0 ? Math.round((agentAppts / totalCalls) * 100) : 0;

View File

@@ -87,20 +87,39 @@ const DirectionIcon = ({ direction, status }: { direction: CallDirection | null;
export const AgentDetailPage = () => {
const { id } = useParams<{ id: string }>();
const { calls, leads, loading } = useData();
const { calls, leads, agents, loading } = useData();
const agentName = id ? decodeURIComponent(id) : '';
// Route param is either a platform Agent UUID (new bucketing) or
// "legacy:<rawAgentName>" for calls that haven't been enriched yet.
// Older bookmarks may still pass the raw display name — handle that too.
const rawId = id ? decodeURIComponent(id) : '';
const isLegacy = rawId.startsWith('legacy:');
const agentUuid = !isLegacy ? rawId : null;
const legacyName = isLegacy ? rawId.slice('legacy:'.length) : null;
// Resolve display name: prefer Agent entity name, else the legacy string.
const agentName = useMemo(() => {
if (agentUuid) {
const a = agents.find((x: any) => x.id === agentUuid);
return a?.name ?? rawId;
}
return legacyName ?? '';
}, [agentUuid, legacyName, agents, rawId]);
const agentCalls = useMemo(
() =>
calls
.filter((c) => c.agentName === agentName)
.filter((c) => {
if (agentUuid) return c.agentId === agentUuid;
if (legacyName) return !c.agentId && c.agentName === legacyName;
return false;
})
.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;
}),
[calls, agentName],
[calls, agentUuid, legacyName],
);
// Build lead name map for enrichment