chore: initial project import
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
This commit is contained in:
90
app/team/performance/page.tsx
Normal file
90
app/team/performance/page.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user