mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
feat(dashboard): merge Team Performance surfaces into single scrollable view
QA flagged Team Dashboard vs Team Performance as repetitive. Retire Team Performance from the sidebar; move its unique surfaces (rich agent table, time breakdown, NPS/Conversion, Performance Alerts) into Team Dashboard below the existing KPI row. - supervisor-rollup: new shared module — useSupervisorRollup hook + RichAgentTable / TimeBreakdown / NpsConversion / PerformanceAlerts - Time Breakdown rendered as a table (Agent / Active / Wrap / Idle / Break / Total + Team-average header row) — QA flagged the old stacked-bar tiles as misleading because per-agent totals varied wildly and width comparison was meaningless - team-dashboard: tabs replaced with stacked sections; everything scroll-visible so supervisors don't hunt across surfaces - sidebar: remove 'Team Performance' entry (route kept for backup) and drop the now-unused IconChartLine wiring
This commit is contained in:
@@ -3,13 +3,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSidebarFlip, faSidebar } from '@fortawesome/pro-duotone-svg-icons';
|
||||
import { AiChatPanel } from '@/components/call-desk/ai-chat-panel';
|
||||
import { DashboardKpi } from '@/components/dashboard/kpi-cards';
|
||||
import { AgentTable } from '@/components/dashboard/agent-table';
|
||||
import { MissedQueue } from '@/components/dashboard/missed-queue';
|
||||
import {
|
||||
RichAgentTable,
|
||||
TimeBreakdown,
|
||||
NpsConversion,
|
||||
PerformanceAlerts,
|
||||
useSupervisorRollup,
|
||||
} from '@/components/dashboard/supervisor-rollup';
|
||||
import { useData } from '@/providers/data-provider';
|
||||
import { cx } from '@/utils/cx';
|
||||
|
||||
type DateRange = 'today' | 'week' | 'month';
|
||||
type DashboardTab = 'agents' | 'missed' | 'campaigns';
|
||||
|
||||
const getDateRangeStart = (range: DateRange): Date => {
|
||||
const now = new Date();
|
||||
@@ -23,9 +28,13 @@ const getDateRangeStart = (range: DateRange): Date => {
|
||||
export const TeamDashboardPage = () => {
|
||||
const { calls, leads, campaigns, loading } = useData();
|
||||
const [dateRange, setDateRange] = useState<DateRange>('week');
|
||||
const [tab, setTab] = useState<DashboardTab>('agents');
|
||||
const [aiOpen, setAiOpen] = useState(true);
|
||||
|
||||
// Pull the richer supervisor rollup (NPS, idle, time breakdown, alerts)
|
||||
// from the sidecar. Only `today`/`week`/`month` overlap with the rollup's
|
||||
// date-range semantics — map them through directly.
|
||||
const { agents: rollupAgents } = useSupervisorRollup(dateRange);
|
||||
|
||||
const filteredCalls = useMemo(() => {
|
||||
const rangeStart = getDateRangeStart(dateRange);
|
||||
return calls.filter((call) => {
|
||||
@@ -36,11 +45,13 @@ export const TeamDashboardPage = () => {
|
||||
|
||||
const dateRangeLabel = dateRange === 'today' ? 'Today' : dateRange === 'week' ? 'This Week' : 'This Month';
|
||||
|
||||
const tabs = [
|
||||
{ id: 'agents' as const, label: 'Agent Performance' },
|
||||
{ id: 'missed' as const, label: `Missed Queue (${filteredCalls.filter(c => c.callStatus === 'MISSED').length})` },
|
||||
{ id: 'campaigns' as const, label: `Campaigns (${campaigns.length})` },
|
||||
];
|
||||
const convRate = useMemo(() => {
|
||||
if (filteredCalls.length === 0) return 0;
|
||||
const completed = filteredCalls.filter((c) => c.callStatus === 'COMPLETED').length;
|
||||
return Math.round((completed / filteredCalls.length) * 100);
|
||||
}, [filteredCalls]);
|
||||
|
||||
const missedQueueCount = filteredCalls.filter((c) => c.callStatus === 'MISSED').length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
@@ -76,72 +87,68 @@ export const TeamDashboardPage = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Main content */}
|
||||
{/* Main content — scrollable column with KPIs pinned at the
|
||||
top, then stacked supervisor sections (Agent table, Time
|
||||
breakdown, NPS/Conv, Alerts, Missed Queue, Campaigns).
|
||||
No tabs: everything is scroll-visible so a supervisor
|
||||
doesn't have to hunt across surfaces for their metrics. */}
|
||||
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||
{/* KPI cards — always visible */}
|
||||
<div className="px-6 pt-5 pb-3">
|
||||
<DashboardKpi calls={filteredCalls} leads={leads} />
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex items-center gap-1 border-b border-secondary px-6">
|
||||
{tabs.map((t) => (
|
||||
<button
|
||||
key={t.id}
|
||||
onClick={() => setTab(t.id)}
|
||||
className={cx(
|
||||
"px-3 py-2.5 text-xs font-semibold transition duration-100 ease-linear border-b-2",
|
||||
tab === t.id
|
||||
? "border-brand text-brand-secondary"
|
||||
: "border-transparent text-tertiary hover:text-secondary",
|
||||
)}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab content */}
|
||||
<div className="flex-1 p-6">
|
||||
{loading && (
|
||||
<div className="flex-1 space-y-5 px-6 pb-8">
|
||||
{loading && rollupAgents.length === 0 ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<p className="text-sm text-tertiary">Loading...</p>
|
||||
</div>
|
||||
)}
|
||||
) : (
|
||||
<>
|
||||
<RichAgentTable agents={rollupAgents} />
|
||||
|
||||
{!loading && tab === 'agents' && (
|
||||
<AgentTable calls={filteredCalls} />
|
||||
)}
|
||||
<TimeBreakdown agents={rollupAgents} />
|
||||
|
||||
{!loading && tab === 'missed' && (
|
||||
<MissedQueue calls={filteredCalls} />
|
||||
)}
|
||||
<NpsConversion agents={rollupAgents} convRate={convRate} />
|
||||
|
||||
{!loading && tab === 'campaigns' && (
|
||||
<div className="space-y-3">
|
||||
{campaigns.length === 0 ? (
|
||||
<p className="text-sm text-tertiary py-12 text-center">No campaigns</p>
|
||||
) : (
|
||||
campaigns.map((c) => (
|
||||
<div key={c.id} className="flex items-center justify-between rounded-xl border border-secondary bg-primary p-4 shadow-xs">
|
||||
<div>
|
||||
<span className="text-sm font-semibold text-primary">{c.campaignName}</span>
|
||||
<div className="flex items-center gap-3 mt-1 text-xs text-tertiary">
|
||||
<span>{c.campaignStatus}</span>
|
||||
<span>{c.platform}</span>
|
||||
<span>{c.leadCount} leads</span>
|
||||
<span>{c.convertedCount} converted</span>
|
||||
<PerformanceAlerts agents={rollupAgents} />
|
||||
|
||||
<div className="rounded-xl border border-secondary bg-primary p-4">
|
||||
<h3 className="text-sm font-semibold text-secondary mb-3">
|
||||
Missed Queue ({missedQueueCount})
|
||||
</h3>
|
||||
<MissedQueue calls={filteredCalls} />
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-secondary bg-primary p-4">
|
||||
<h3 className="text-sm font-semibold text-secondary mb-3">
|
||||
Campaigns ({campaigns.length})
|
||||
</h3>
|
||||
{campaigns.length === 0 ? (
|
||||
<p className="text-sm text-tertiary py-4 text-center">No campaigns</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{campaigns.map((c) => (
|
||||
<div key={c.id} className="flex items-center justify-between rounded-lg border border-secondary bg-primary p-4">
|
||||
<div>
|
||||
<span className="text-sm font-semibold text-primary">{c.campaignName}</span>
|
||||
<div className="flex items-center gap-3 mt-1 text-xs text-tertiary">
|
||||
<span>{c.campaignStatus}</span>
|
||||
<span>{c.platform}</span>
|
||||
<span>{c.leadCount} leads</span>
|
||||
<span>{c.convertedCount} converted</span>
|
||||
</div>
|
||||
</div>
|
||||
{c.budget && (
|
||||
<span className="text-sm font-medium text-secondary">
|
||||
₹{Math.round(c.budget.amountMicros / 1_000_000).toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{c.budget && (
|
||||
<span className="text-sm font-medium text-secondary">
|
||||
₹{Math.round(c.budget.amountMicros / 1_000_000).toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user