91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import { redirect } from "next/navigation";
|
|
|
|
import { getSession } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { ShellPage } from "@/components/page-templates";
|
|
import { TablePlaceholder } from "@/components/placeholders";
|
|
|
|
const AVERAGE_RESPONSE_DIVISOR = 1;
|
|
|
|
function formatDuration(ms: number | null) {
|
|
if (!ms || ms < 0) {
|
|
return "-";
|
|
}
|
|
|
|
const totalMinutes = Math.floor(ms / 60000);
|
|
const minutes = totalMinutes % 60;
|
|
const seconds = Math.floor((ms % 60000) / 1000);
|
|
|
|
if (totalMinutes < 60) {
|
|
return `${totalMinutes}m ${seconds}s`;
|
|
}
|
|
|
|
const hours = Math.floor(totalMinutes / 60);
|
|
return `${hours}h ${minutes}m`;
|
|
}
|
|
|
|
export default async function TeamPerformancePage() {
|
|
const session = await getSession();
|
|
if (!session) {
|
|
redirect("/login");
|
|
}
|
|
|
|
if (session.role !== "admin_client") {
|
|
redirect("/unauthorized");
|
|
}
|
|
|
|
const agents = await prisma.user.findMany({
|
|
where: {
|
|
tenantId: session.tenantId,
|
|
role: { code: "AGENT" }
|
|
},
|
|
include: {
|
|
assignedConversations: {
|
|
select: {
|
|
status: true,
|
|
firstMessageAt: true,
|
|
lastOutboundAt: true
|
|
}
|
|
}
|
|
},
|
|
orderBy: { fullName: "asc" }
|
|
});
|
|
|
|
const rows = agents.map((agent) => {
|
|
const handled = agent.assignedConversations.length;
|
|
const resolved = agent.assignedConversations.filter((item) => item.status === "RESOLVED").length;
|
|
const workload = agent.assignedConversations.filter((item) => item.status === "OPEN" || item.status === "PENDING").length;
|
|
|
|
const responseSamples = agent.assignedConversations
|
|
.filter((item) => item.firstMessageAt && item.lastOutboundAt)
|
|
.map((item) => {
|
|
if (!item.firstMessageAt || !item.lastOutboundAt) {
|
|
return null;
|
|
}
|
|
|
|
return item.lastOutboundAt.getTime() - item.firstMessageAt.getTime();
|
|
})
|
|
.filter((item): item is number => item !== null && item >= 0);
|
|
|
|
const avgResponse =
|
|
responseSamples.length > 0
|
|
? formatDuration(
|
|
responseSamples.reduce((sum, value) => sum + value, 0) /
|
|
Math.max(AVERAGE_RESPONSE_DIVISOR, responseSamples.length)
|
|
)
|
|
: "-";
|
|
|
|
return [agent.fullName, String(handled), avgResponse, String(resolved), `${workload} open`];
|
|
});
|
|
|
|
return (
|
|
<ShellPage shell="admin" title="Team Performance" description="Leaderboard performa agent dan workload snapshot.">
|
|
<TablePlaceholder
|
|
title="Performance table"
|
|
columns={["Agent", "Handled", "Avg response", "Resolved", "Workload"]}
|
|
rows={rows}
|
|
/>
|
|
</ShellPage>
|
|
);
|
|
}
|