From bae653b7a93c5acb5c2755809e53543cf2873fa4 Mon Sep 17 00:00:00 2001 From: Oleg Date: Sat, 8 Aug 2026 12:27:54 +0000 Subject: [PATCH] Split agent workload chart on /stats into one chart per status Previously showed a single combined bar chart across all ticket statuses; now renders a separate sorted bar chart per status (new/open/pending/closed), each scaled to its own max. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012o9j9RezxbZVKQMrB7oRLY --- src/app/(admin)/stats/page.tsx | 49 +++++++++++++++++++++------------- src/lib/tickets/service.ts | 40 +++++++++++++++++---------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/app/(admin)/stats/page.tsx b/src/app/(admin)/stats/page.tsx index 82e3b38..815e00e 100644 --- a/src/app/(admin)/stats/page.tsx +++ b/src/app/(admin)/stats/page.tsx @@ -28,7 +28,9 @@ export default async function StatsPage() { const stats = await getStatsOverview(); const maxChannelCount = Math.max(...Object.values(stats.channelCounts), 1); - const maxAgentWorkload = Math.max(...stats.agentWorkload.map((a) => a.ticketCount), 1); + const statusesWithWorkload = (Object.keys(STATUS_META) as (keyof typeof STATUS_META)[]).filter( + (status) => stats.agentWorkloadByStatus[status].length > 0, + ); return (
@@ -88,24 +90,35 @@ export default async function StatsPage() {
-
-

Заявки по агентам (все статусы)

- {stats.agentWorkload.length === 0 ? ( + {statusesWithWorkload.length === 0 ? ( +
+

Заявки по агентам

Пока нет данных.

- ) : ( -
- {stats.agentWorkload.map((agent) => ( - - ))} -
- )} -
+
+ ) : ( + statusesWithWorkload.map((status) => { + const workload = stats.agentWorkloadByStatus[status]; + const max = Math.max(...workload.map((a) => a.count), 1); + return ( +
+

+ Заявки по агентам — {STATUS_META[status].label} +

+
+ {workload.map((agent) => ( + + ))} +
+
+ ); + }) + )} ); diff --git a/src/lib/tickets/service.ts b/src/lib/tickets/service.ts index 19a0054..3c302e3 100644 --- a/src/lib/tickets/service.ts +++ b/src/lib/tickets/service.ts @@ -705,7 +705,7 @@ export interface StatsOverview { statusCounts: Record; channelCounts: Record; ticketsPerDay: { label: string; count: number }[]; - agentWorkload: { name: string; ticketCount: number }[]; + agentWorkloadByStatus: Record; } /** @@ -771,20 +771,32 @@ export async function getStatsOverview(): Promise { : null; const agents = await listAgents(); - const ticketCountByAgent = new Map(); - let unassignedCount = 0; - for (const t of allTickets) { - if (t.assigneeId) { - ticketCountByAgent.set(t.assigneeId, (ticketCountByAgent.get(t.assigneeId) ?? 0) + 1); - } else { - unassignedCount += 1; + + /** Per-agent ticket count for one status, sorted busiest-first. */ + function workloadForStatus(status: TicketStatus): { name: string; count: number }[] { + const countByAgent = new Map(); + let unassignedCount = 0; + for (const t of allTickets) { + if (t.status !== status) continue; + if (t.assigneeId) { + countByAgent.set(t.assigneeId, (countByAgent.get(t.assigneeId) ?? 0) + 1); + } else { + unassignedCount += 1; + } } + const workload = agents.map((a) => ({ name: a.name, count: countByAgent.get(a.id) ?? 0 })); + if (unassignedCount > 0) { + workload.push({ name: "Не назначено", count: unassignedCount }); + } + return workload.sort((a, b) => b.count - a.count); } - const agentWorkload = agents.map((a) => ({ name: a.name, ticketCount: ticketCountByAgent.get(a.id) ?? 0 })); - if (unassignedCount > 0) { - agentWorkload.push({ name: "Не назначено", ticketCount: unassignedCount }); - } - agentWorkload.sort((a, b) => b.ticketCount - a.ticketCount); + + const agentWorkloadByStatus: Record = { + new: workloadForStatus("new"), + open: workloadForStatus("open"), + pending: workloadForStatus("pending"), + closed: workloadForStatus("closed"), + }; return { totalTickets: allTickets.length, @@ -794,6 +806,6 @@ export async function getStatsOverview(): Promise { statusCounts, channelCounts, ticketsPerDay, - agentWorkload, + agentWorkloadByStatus, }; }