refine queue metrics and devices ui

This commit is contained in:
Esdras Renan 2025-11-04 19:53:54 -03:00
parent 1e45324460
commit c2acd65764
11 changed files with 181 additions and 116 deletions

View file

@ -1,6 +1,7 @@
"use client"
import { useEffect, useState } from "react"
import { useEffect, useMemo, useState } from "react"
import Link from "next/link"
import { useQuery } from "convex/react"
import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel"
@ -8,14 +9,7 @@ import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { useAuth } from "@/lib/auth-client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
import { Badge } from "@/components/ui/badge"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
ChartConfig,
ChartContainer,
@ -25,6 +19,12 @@ import {
ChartTooltipContent,
} from "@/components/ui/chart"
import { Area, AreaChart, CartesianGrid, XAxis, Bar, BarChart, Pie, PieChart } from "recharts"
import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox"
import { TicketPriorityPill } from "@/components/tickets/priority-pill"
import { TicketStatusBadge } from "@/components/tickets/status-badge"
import { formatDistanceToNow } from "date-fns"
import { ptBR } from "date-fns/locale"
import type { TicketPriority, TicketStatus } from "@/lib/schemas/ticket"
type CompanyRecord = { id: Id<"companies">; name: string }
@ -67,11 +67,20 @@ export function CompanyReport() {
isStaff && convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip"
) as CompanyRecord[] | undefined
const companyOptions = useMemo<SearchableComboboxOption[]>(
() =>
(companies ?? []).map((company) => ({
value: company.id as string,
label: company.name,
})),
[companies]
)
useEffect(() => {
if (!selectedCompany && companies && companies.length > 0) {
setSelectedCompany(companies[0].id)
if (!selectedCompany && companyOptions.length > 0) {
setSelectedCompany(companyOptions[0]?.value ?? "")
}
}, [companies, selectedCompany])
}, [companyOptions, selectedCompany])
const report = useQuery(
api.reports.companyOverview,
@ -142,18 +151,19 @@ export function CompanyReport() {
<p className="text-sm text-neutral-500">Acompanhe tickets, inventário e colaboradores de um cliente específico.</p>
</div>
<div className="flex flex-wrap items-center gap-3">
<Select value={selectedCompany} onValueChange={setSelectedCompany} disabled={!companies?.length}>
<SelectTrigger className="w-[220px] rounded-xl border-slate-200">
<SelectValue placeholder="Selecione a empresa" />
</SelectTrigger>
<SelectContent className="rounded-xl">
{(companies ?? []).map((company) => (
<SelectItem key={company.id} value={company.id as string}>
{company.name}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="w-[260px]">
<SearchableCombobox
value={selectedCompany || null}
onValueChange={(value) => {
if (value) setSelectedCompany(value)
}}
options={companyOptions}
placeholder="Selecionar empresa"
searchPlaceholder="Buscar empresa..."
emptyText="Nenhuma empresa encontrada."
disabled={!companyOptions.length}
/>
</div>
<Select value={timeRange} onValueChange={(value) => setTimeRange(value as typeof timeRange)}>
<SelectTrigger className="w-[160px] rounded-xl border-slate-200">
<SelectValue placeholder="Período" />
@ -306,10 +316,8 @@ export function CompanyReport() {
<Card className="border-slate-200">
<CardHeader>
<CardTitle className="text-lg font-semibold text-neutral-900">Tickets recentes (máximo 6)</CardTitle>
<CardDescription className="text-neutral-600">
Chamados em aberto para a empresa filtrada.
</CardDescription>
<CardTitle className="text-lg font-semibold text-neutral-900">Tickets recentes</CardTitle>
<CardDescription className="text-neutral-600">Chamados em aberto para a empresa selecionada.</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{openTickets.length === 0 ? (
@ -317,11 +325,12 @@ export function CompanyReport() {
Nenhum chamado aberto no período selecionado.
</p>
) : (
<ul className="space-y-3">
<div className="space-y-3">
{openTickets.map((ticket) => (
<li
<Link
key={ticket.id}
className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-200 bg-white px-3 py-2 shadow-sm"
href={`/tickets/${ticket.id}`}
className="group flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm shadow-sm transition hover:-translate-y-0.5 hover:border-slate-300 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-300 focus-visible:ring-offset-2"
>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-neutral-900">
@ -329,25 +338,25 @@ export function CompanyReport() {
</p>
<p className="text-xs text-neutral-500">
Atualizado{" "}
{new Date(ticket.updatedAt).toLocaleString("pt-BR", {
day: "2-digit",
month: "short",
hour: "2-digit",
minute: "2-digit",
{formatDistanceToNow(new Date(ticket.updatedAt), {
addSuffix: true,
locale: ptBR,
})}
</p>
</div>
<div className="flex items-center gap-2">
<Badge variant="outline" className="border-slate-200 text-[11px] uppercase text-neutral-600">
{ticket.priority}
</Badge>
<Badge className="bg-indigo-600 text-[11px] uppercase tracking-wide text-white">
{STATUS_LABELS[ticket.status] ?? ticket.status}
</Badge>
<TicketPriorityPill
priority={ticket.priority as TicketPriority}
className="h-8 rounded-full px-3 text-xs"
/>
<TicketStatusBadge
status={ticket.status as TicketStatus}
className="h-8 px-3 text-xs font-semibold"
/>
</div>
</li>
</Link>
))}
</ul>
</div>
)}
</CardContent>
</Card>