From f255a4c780588b7e783e9caa3413ded88d47bbb3 Mon Sep 17 00:00:00 2001
From: codex-bot
Date: Tue, 21 Oct 2025 14:52:57 -0300
Subject: [PATCH] feat(ui): improve chart spacing and labels; format hours <1h
as minutes; unify date format to dd/MM (ticks) and dd/MM/yyyy (tooltips); fix
tooltips labels ('Total', 'Resolvidos')
---
src/components/chart-area-interactive.tsx | 28 ++++-------
.../charts/chart-opened-resolved.tsx | 9 +++-
src/components/reports/backlog-report.tsx | 18 +++++--
src/components/reports/hours-report.tsx | 46 +++++++++++-------
src/components/reports/sla-report.tsx | 48 +++++++++++++++----
src/lib/utils.ts | 34 +++++++++++--
6 files changed, 133 insertions(+), 50 deletions(-)
diff --git a/src/components/chart-area-interactive.tsx b/src/components/chart-area-interactive.tsx
index 1e9eb49..2a907a5 100644
--- a/src/components/chart-area-interactive.tsx
+++ b/src/components/chart-area-interactive.tsx
@@ -18,12 +18,13 @@ import {
CardTitle,
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
-import {
- ChartConfig,
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "@/components/ui/chart"
+import {
+ ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+} from "@/components/ui/chart"
+import { formatDateDM, formatDateDMY } from "@/lib/utils"
import { Skeleton } from "@/components/ui/skeleton"
import {
Select,
@@ -233,24 +234,13 @@ export function ChartAreaInteractive() {
axisLine={false}
tickMargin={8}
minTickGap={32}
- tickFormatter={(value) => {
- const date = new Date(value)
- return date.toLocaleDateString("pt-BR", {
- month: "short",
- day: "2-digit",
- })
- }}
+ tickFormatter={(value) => formatDateDM(new Date(value))}
/>
- new Date(value).toLocaleDateString("pt-BR", {
- day: "2-digit",
- month: "long",
- })
- }
+ labelFormatter={(value) => formatDateDMY(new Date(value as string))}
indicator="dot"
/>
}
diff --git a/src/components/charts/chart-opened-resolved.tsx b/src/components/charts/chart-opened-resolved.tsx
index 278e56a..417c761 100644
--- a/src/components/charts/chart-opened-resolved.tsx
+++ b/src/components/charts/chart-opened-resolved.tsx
@@ -13,6 +13,7 @@ import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle }
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
+import { formatDateDM, formatDateDMY } from "@/lib/utils"
import { Skeleton } from "@/components/ui/skeleton"
type SeriesPoint = { date: string; opened: number; resolved: number }
@@ -99,10 +100,16 @@ export function ChartOpenedResolved() {
axisLine={false}
tickMargin={8}
minTickGap={32}
+ tickFormatter={(v) => formatDateDM(new Date(v))}
/>
}
+ content={
+ formatDateDMY(new Date(value as string))}
+ />
+ }
/>
diff --git a/src/components/reports/backlog-report.tsx b/src/components/reports/backlog-report.tsx
index 9da9ff1..307bdbc 100644
--- a/src/components/reports/backlog-report.tsx
+++ b/src/components/reports/backlog-report.tsx
@@ -206,11 +206,23 @@ export function BacklogReport() {
) : (
- ({ name: q.name, total: q.total }))}>
+ ({ name: q.name, total: q.total }))}
+ margin={{ left: 12, right: 12, bottom: 28 }}
+ barCategoryGap={16}
+ >
-
+
- } />
+ } />
)}
diff --git a/src/components/reports/hours-report.tsx b/src/components/reports/hours-report.tsx
index 73883f9..bfb7ade 100644
--- a/src/components/reports/hours-report.tsx
+++ b/src/components/reports/hours-report.tsx
@@ -17,6 +17,7 @@ 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"
+import { formatHoursCompact } from "@/lib/utils"
type HoursItem = {
companyId: string
@@ -111,12 +112,25 @@ export function HoursReport() {
.sort((a, b) => b.total - a.total)
.slice(0, 10)
.map((r) => ({ name: r.name, internas: r.internal, externas: r.external }))}
+ margin={{ left: 12, right: 12, bottom: 28 }}
>
-
+
- } />
+ (
+ <>
+ {name}
+ {formatHoursCompact(Number(value))}
+ >
+ )}
+ />
+ }
+ />
)}
@@ -165,13 +179,13 @@ export function HoursReport() {
{[
- { key: "internal", label: "Horas internas", value: numberFormatter.format(totals.internal) },
- { key: "external", label: "Horas externas", value: numberFormatter.format(totals.external) },
- { key: "total", label: "Total acumulado", value: numberFormatter.format(totals.total) },
+ { key: "internal", label: "Horas internas", value: formatHoursCompact(totals.internal) },
+ { key: "external", label: "Horas externas", value: formatHoursCompact(totals.external) },
+ { key: "total", label: "Total acumulado", value: formatHoursCompact(totals.total) },
].map((item) => (
{item.label}
-
{item.value} h
+
{item.value}
))}
@@ -199,17 +213,17 @@ export function HoursReport() {
Horas internas
- {numberFormatter.format(row.internal)} h
-
-
- Horas externas
- {numberFormatter.format(row.external)} h
-
-
- Total
- {numberFormatter.format(row.total)} h
-
+
{formatHoursCompact(row.internal)}
+
+ Horas externas
+ {formatHoursCompact(row.external)}
+
+
+ Total
+ {formatHoursCompact(row.total)}
+
+
Contratadas/mês
diff --git a/src/components/reports/sla-report.tsx b/src/components/reports/sla-report.tsx
index f6d333c..2772202 100644
--- a/src/components/reports/sla-report.tsx
+++ b/src/components/reports/sla-report.tsx
@@ -17,6 +17,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"
function formatMinutes(value: number | null) {
if (value === null) return "—"
@@ -214,8 +215,22 @@ export function SlaReport() {
-
-
} />
+
formatDateDM(new Date(v))}
+ />
+ formatDateDMY(new Date(value as string))}
+ />
+ }
+ />
@@ -240,8 +255,22 @@ export function SlaReport() {
({ date: p.date, ...p.values }))}>
-
- } />
+ formatDateDM(new Date(v))}
+ />
+ formatDateDMY(new Date(value as string))}
+ />
+ }
+ />
{channelsSeries.channels.map((ch, idx) => (
))}
@@ -272,11 +301,14 @@ export function SlaReport() {
Resolvidos por agente
- ({ name: a.name || a.email || 'Agente', resolved: a.resolved }))} margin={{ left: 12, right: 12 }}>
+ ({ name: a.name || a.email || 'Agente', resolved: a.resolved }))}
+ margin={{ left: 12, right: 12, bottom: 28 }}
+ >
-
+
- } />
+ } />
@@ -286,7 +318,7 @@ export function SlaReport() {
{agents.items.slice(0, 10).map((a) => (
{a.name || a.email || 'Agente'}
- {a.workedHours.toFixed(1)} h
+ {formatHoursCompact(a.workedHours)}
))}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 2f00a25..25f470f 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,6 +1,34 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
-export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
-}
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
+
+// Format hours so that values under 1h are shown in minutes.
+// Examples: 0.06 -> "4 min"; 0.5 -> "30 min"; 1.25 -> "1,25 h" (pt-BR)
+export function formatHoursCompact(value: number, locale: string = "pt-BR"): string {
+ const hours = Number(value) || 0
+ if (hours < 1 && hours > 0) {
+ const mins = Math.round(hours * 60)
+ return `${mins} min`
+ }
+ const nf = new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
+ return `${nf.format(hours)} h`
+}
+
+// Format date to dd/MM or dd/MM/yyyy. Accepts Date or ISO-like string (YYYY-MM-DD).
+export function formatDateDM(value: Date | string | number): string {
+ const d = typeof value === "string" || typeof value === "number" ? new Date(value) : value
+ const dd = String(d.getDate()).padStart(2, "0")
+ const mm = String(d.getMonth() + 1).padStart(2, "0")
+ return `${dd}/${mm}`
+}
+
+export function formatDateDMY(value: Date | string | number): string {
+ const d = typeof value === "string" || typeof value === "number" ? new Date(value) : value
+ const dd = String(d.getDate()).padStart(2, "0")
+ const mm = String(d.getMonth() + 1).padStart(2, "0")
+ const yyyy = d.getFullYear()
+ return `${dd}/${mm}/${yyyy}`
+}