From 68b897c30cf92ec813d3521357d8ca29ff024e26 Mon Sep 17 00:00:00 2001
From: codex-bot
Date: Tue, 21 Oct 2025 13:35:06 -0300
Subject: [PATCH] Reports: add charts to Produtividade (areas + channels), CSAT
(bar), Backlog (pie+bar), Horas (stacked bar); deploy Convex reports agent
productivity
---
src/components/reports/backlog-report.tsx | 59 ++++++++++-------
src/components/reports/csat-report.tsx | 29 ++++-----
src/components/reports/hours-report.tsx | 30 +++++++++
src/components/reports/sla-report.tsx | 79 ++++++++++++++++++++++-
4 files changed, 157 insertions(+), 40 deletions(-)
diff --git a/src/components/reports/backlog-report.tsx b/src/components/reports/backlog-report.tsx
index daa8f19..dfb61c3 100644
--- a/src/components/reports/backlog-report.tsx
+++ b/src/components/reports/backlog-report.tsx
@@ -14,6 +14,8 @@ import { Skeleton } from "@/components/ui/skeleton"
import { Badge } from "@/components/ui/badge"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { usePersistentCompanyFilter } from "@/lib/use-company-filter"
+import { Pie, PieChart, Bar, BarChart, CartesianGrid, XAxis } from "recharts"
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
const PRIORITY_LABELS: Record = {
LOW: "Baixa",
@@ -161,18 +163,31 @@ export function BacklogReport() {
-
- {(Object.entries(data.priorityCounts) as Array<[string, number]>).map(([priority, total]) => (
-
-
- {PRIORITY_LABELS[priority] ?? priority}
-
-
- {total} ticket{total === 1 ? "" : "s"}
-
-
- ))}
-
+ {Object.keys(data.priorityCounts).length === 0 ? (
+ Sem dados para o período.
+ ) : (
+
+
+
+ } />
+ ).map(([priority, total]) => ({ name: PRIORITY_LABELS[priority] ?? priority, total, fill: `var(--chart-${{LOW:1,MEDIUM:2,HIGH:3,URGENT:4}[priority as keyof typeof PRIORITY_LABELS]||5})` }))}
+ dataKey="total"
+ nameKey="name"
+ label
+ />
+
+
+
+ {(Object.entries(data.priorityCounts) as Array<[string, number]>).map(([priority, total]) => (
+ -
+ {PRIORITY_LABELS[priority] ?? priority}
+ {total} ticket{total === 1 ? "" : "s"}
+
+ ))}
+
+
+ )}
@@ -189,18 +204,14 @@ export function BacklogReport() {
Nenhuma fila com tickets abertos no momento.
) : (
-
- {data.queueCounts.map((queue: { id: string; name: string; total: number }) => (
- -
-
- {queue.name}
-
-
- {queue.total} em aberto
-
-
- ))}
-
+
+ ({ name: q.name, total: q.total }))}>
+
+
+
+ } />
+
+
)}
diff --git a/src/components/reports/csat-report.tsx b/src/components/reports/csat-report.tsx
index b1fe88f..a4ce42b 100644
--- a/src/components/reports/csat-report.tsx
+++ b/src/components/reports/csat-report.tsx
@@ -10,6 +10,8 @@ import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle }
import { Skeleton } from "@/components/ui/skeleton"
import { Badge } from "@/components/ui/badge"
import { useState } from "react"
+import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
import { Button } from "@/components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
@@ -131,21 +133,18 @@ export function CsatReport() {
-
+ {data.totalSurveys === 0 ? (
+ Sem respostas no período.
+ ) : (
+
+ ({ score: `Nota ${d.score}`, total: d.total }))}>
+
+
+
+ } />
+
+
+ )}
diff --git a/src/components/reports/hours-report.tsx b/src/components/reports/hours-report.tsx
index 50ed6f8..9540d54 100644
--- a/src/components/reports/hours-report.tsx
+++ b/src/components/reports/hours-report.tsx
@@ -15,6 +15,8 @@ import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { usePersistentCompanyFilter } from "@/lib/use-company-filter"
import { Progress } from "@/components/ui/progress"
+import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
type HoursItem = {
companyId: string
@@ -92,6 +94,34 @@ export function HoursReport() {
return (
+
+
+ Top clientes por horas
+ Comparativo empilhado de horas internas x externas (top 10).
+
+
+ {!filteredWithComputed || filteredWithComputed.length === 0 ? (
+ Sem dados para o período.
+ ) : (
+
+ b.total - a.total)
+ .slice(0, 10)
+ .map((r) => ({ name: r.name, internas: r.internal, externas: r.external }))}
+ >
+
+
+
+
+ } />
+
+
+ )}
+
+
+
Horas
diff --git a/src/components/reports/sla-report.tsx b/src/components/reports/sla-report.tsx
index dc3e479..0bad3c9 100644
--- a/src/components/reports/sla-report.tsx
+++ b/src/components/reports/sla-report.tsx
@@ -15,7 +15,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { useState } from "react"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { usePersistentCompanyFilter } from "@/lib/use-company-filter"
-import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
+import { Area, AreaChart, Bar, BarChart, CartesianGrid, XAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
function formatMinutes(value: number | null) {
@@ -44,6 +44,20 @@ export function SlaReport() {
? ({ tenantId, viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">) })
: "skip"
) as { rangeDays: number; items: Array<{ agentId: string; name: string | null; email: string | null; open: number; resolved: number; avgFirstResponseMinutes: number | null; avgResolutionMinutes: number | null; workedHours: number }> } | undefined
+
+ const openedResolved = useQuery(
+ api.reports.openedResolvedByDay,
+ convexUserId
+ ? ({ tenantId, viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">) })
+ : "skip"
+ ) as { rangeDays: number; series: Array<{ date: string; opened: number; resolved: number }> } | undefined
+
+ const channelsSeries = useQuery(
+ api.reports.ticketsByChannel,
+ convexUserId
+ ? ({ tenantId, viewerId: convexUserId as Id<"users">, range: timeRange, companyId: companyId === "all" ? undefined : (companyId as Id<"companies">) })
+ : "skip"
+ ) as { rangeDays: number; channels: string[]; points: Array<{ date: string; values: Record }> } | undefined
const companies = useQuery(api.companies.list, convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip") as Array<{ id: Id<"companies">; name: string }> | undefined
const queueTotal = useMemo(
@@ -173,6 +187,69 @@ export function SlaReport() {
+
+
+
+
+ Abertos x Resolvidos
+ Comparativo diário no período selecionado.
+
+
+
+
+ {!openedResolved || openedResolved.series.length === 0 ? (
+ Sem dados para o período.
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ } />
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ Volume por canal
+ Distribuição diária por canal (empilhado).
+
+
+
+
+ {!channelsSeries || channelsSeries.points.length === 0 ? (
+ Sem dados para o período.
+ ) : (
+
+ ({ date: p.date, ...p.values }))}>
+
+
+ } />
+ {channelsSeries.channels.map((ch, idx) => (
+
+ ))}
+
+
+ )}
+
+
+