feat: add SLA category breakdown report
This commit is contained in:
parent
6ab8a6ce89
commit
a62f3d5283
8 changed files with 231 additions and 10 deletions
|
|
@ -89,7 +89,7 @@ const navigation: NavigationGroup[] = [
|
|||
requiredRole: "staff",
|
||||
items: [
|
||||
{ title: "Painéis customizados", url: "/dashboards", icon: LayoutTemplate, requiredRole: "staff" },
|
||||
{ title: "Produtividade", url: "/reports/sla", icon: TrendingUp, requiredRole: "staff" },
|
||||
{ title: "SLA & Produtividade", url: "/reports/sla", icon: TrendingUp, requiredRole: "staff" },
|
||||
{ title: "Qualidade (CSAT)", url: "/reports/csat", icon: LifeBuoy, requiredRole: "staff" },
|
||||
{ title: "Backlog", url: "/reports/backlog", icon: BarChart3, requiredRole: "staff" },
|
||||
{ title: "Empresas", url: "/reports/company", icon: Building2, requiredRole: "staff" },
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
|||
import { usePersistentCompanyFilter } from "@/lib/use-company-filter"
|
||||
import { Area, AreaChart, Bar, BarChart, CartesianGrid, XAxis } from "recharts"
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
|
||||
import { formatDateDM, formatDateDMY, formatHoursCompact } from "@/lib/utils"
|
||||
import { cn, formatDateDM, formatDateDMY, formatHoursCompact } from "@/lib/utils"
|
||||
import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox"
|
||||
|
||||
const agentProductivityChartConfig = {
|
||||
|
|
@ -25,6 +25,24 @@ const agentProductivityChartConfig = {
|
|||
},
|
||||
}
|
||||
|
||||
const priorityLabelMap: Record<string, string> = {
|
||||
LOW: "Baixa",
|
||||
MEDIUM: "Média",
|
||||
HIGH: "Alta",
|
||||
URGENT: "Urgente",
|
||||
}
|
||||
|
||||
type CategoryBreakdownEntry = {
|
||||
categoryId: string | null
|
||||
categoryName: string
|
||||
priority: string
|
||||
total: number
|
||||
responseMet: number
|
||||
solutionMet: number
|
||||
responseRate: number | null
|
||||
solutionRate: number | null
|
||||
}
|
||||
|
||||
function formatMinutes(value: number | null) {
|
||||
if (value === null) return "—"
|
||||
if (value < 60) return `${value.toFixed(0)} min`
|
||||
|
|
@ -90,6 +108,7 @@ export function SlaReport() {
|
|||
() => data?.queueBreakdown.reduce((acc: number, queue: { open: number }) => acc + queue.open, 0) ?? 0,
|
||||
[data]
|
||||
)
|
||||
const categoryBreakdown = (data?.categoryBreakdown ?? []) as CategoryBreakdownEntry[]
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
|
|
@ -209,6 +228,60 @@ export function SlaReport() {
|
|||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200">
|
||||
<CardHeader>
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-lg font-semibold text-neutral-900">SLA por categoria & prioridade</CardTitle>
|
||||
<CardDescription className="text-neutral-600">
|
||||
Taxa de cumprimento de resposta/solução considerando as regras configuradas em Categorias → SLA.
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{categoryBreakdown.length === 0 ? (
|
||||
<p className="rounded-lg border border-dashed border-slate-200 p-6 text-sm text-neutral-500">
|
||||
Ainda não há tickets categorizados ou com SLA aplicado para este período.
|
||||
</p>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-2xl border border-slate-200">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-slate-50 text-xs uppercase tracking-wide text-neutral-500">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left">Categoria</th>
|
||||
<th className="px-4 py-3 text-left">Prioridade</th>
|
||||
<th className="px-4 py-3 text-right">Tickets</th>
|
||||
<th className="px-4 py-3 text-right">SLA resposta</th>
|
||||
<th className="px-4 py-3 text-right">SLA solução</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{categoryBreakdown.slice(0, 8).map((row) => (
|
||||
<tr key={`${row.categoryId ?? "none"}-${row.priority}`} className="border-t border-slate-100">
|
||||
<td className="px-4 py-3 font-medium text-neutral-900">{row.categoryName}</td>
|
||||
<td className="px-4 py-3 text-neutral-700">{priorityLabelMap[row.priority as keyof typeof priorityLabelMap] ?? row.priority}</td>
|
||||
<td className="px-4 py-3 text-right font-semibold text-neutral-900">{row.total}</td>
|
||||
<td className="px-4 py-3">
|
||||
<RateBadge value={row.responseRate} label="Resposta" colorClass="bg-emerald-500" />
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<RateBadge value={row.solutionRate} label="Solução" colorClass="bg-sky-500" />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{categoryBreakdown.length > 8 ? (
|
||||
<div className="border-t border-slate-100 bg-slate-50 px-4 py-2 text-xs text-neutral-500">
|
||||
Mostrando 8 de {categoryBreakdown.length} combinações. Refine o período ou exporte o XLSX para visão completa.
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200">
|
||||
<CardHeader>
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
||||
|
|
@ -350,3 +423,21 @@ export function SlaReport() {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function RateBadge({ value, label, colorClass }: { value: number | null; label: string; colorClass: string }) {
|
||||
const percent = value === null ? null : Math.round(value * 100)
|
||||
return (
|
||||
<div className="flex flex-col gap-1 text-right">
|
||||
<div className="flex items-center justify-end gap-2 text-xs text-neutral-600">
|
||||
<span>{label}</span>
|
||||
<span className="font-semibold text-neutral-900">{percent === null ? "—" : `${percent}%`}</span>
|
||||
</div>
|
||||
<div className="h-1.5 w-full rounded-full bg-slate-100">
|
||||
<div
|
||||
className={cn("h-full rounded-full", colorClass)}
|
||||
style={{ width: percent === null ? "0%" : `${Math.min(100, Math.max(0, percent))}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue