refine queue metrics and devices ui
This commit is contained in:
parent
1e45324460
commit
c2acd65764
11 changed files with 181 additions and 116 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue