mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user