Align admin tables with ticket styling and add board view
This commit is contained in:
parent
63cf9f9d45
commit
a319aa0eff
8 changed files with 783 additions and 447 deletions
156
src/components/tickets/tickets-board.tsx
Normal file
156
src/components/tickets/tickets-board.tsx
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { formatDistanceToNowStrict } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale"
|
||||
import { LayoutGrid } from "lucide-react"
|
||||
|
||||
import type { Ticket, TicketPriority, TicketStatus } from "@/lib/schemas/ticket"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyTitle } from "@/components/ui/empty"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
type TicketsBoardProps = {
|
||||
tickets: Ticket[]
|
||||
}
|
||||
|
||||
const statusLabel: Record<TicketStatus, string> = {
|
||||
PENDING: "Pendente",
|
||||
AWAITING_ATTENDANCE: "Em andamento",
|
||||
PAUSED: "Pausado",
|
||||
RESOLVED: "Resolvido",
|
||||
}
|
||||
|
||||
const statusChipClass: Record<TicketStatus, string> = {
|
||||
PENDING: "bg-amber-100 text-amber-800 ring-1 ring-amber-200",
|
||||
AWAITING_ATTENDANCE: "bg-sky-100 text-sky-700 ring-1 ring-sky-200",
|
||||
PAUSED: "bg-violet-100 text-violet-700 ring-1 ring-violet-200",
|
||||
RESOLVED: "bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200",
|
||||
}
|
||||
|
||||
const priorityLabel: Record<TicketPriority, string> = {
|
||||
LOW: "Baixa",
|
||||
MEDIUM: "Média",
|
||||
HIGH: "Alta",
|
||||
URGENT: "Urgente",
|
||||
}
|
||||
|
||||
const priorityChipClass: Record<TicketPriority, string> = {
|
||||
LOW: "bg-slate-100 text-slate-700",
|
||||
MEDIUM: "bg-sky-100 text-sky-700",
|
||||
HIGH: "bg-amber-100 text-amber-800",
|
||||
URGENT: "bg-rose-100 text-rose-700",
|
||||
}
|
||||
|
||||
function formatUpdated(date: Date) {
|
||||
return formatDistanceToNowStrict(date, { addSuffix: true, locale: ptBR })
|
||||
}
|
||||
|
||||
export function TicketsBoard({ tickets }: TicketsBoardProps) {
|
||||
if (!tickets.length) {
|
||||
return (
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-12 text-center shadow-sm">
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<LayoutGrid className="mx-auto size-9 text-neutral-400" />
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<EmptyTitle>Nenhum ticket encontrado</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
Ajuste os filtros ou crie um novo ticket para visualizar aqui na visão em quadro.
|
||||
</EmptyDescription>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
{tickets.map((ticket) => (
|
||||
<Link
|
||||
key={ticket.id}
|
||||
href={`/tickets/${ticket.id}`}
|
||||
className="group block h-full rounded-3xl border border-slate-200 bg-white p-4 shadow-sm transition hover:-translate-y-1 hover:border-slate-300 hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-300"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-slate-200 bg-slate-100 px-2.5 py-1 text-[11px] font-semibold text-neutral-700"
|
||||
>
|
||||
#{ticket.reference}
|
||||
</Badge>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-semibold transition",
|
||||
statusChipClass[ticket.status],
|
||||
)}
|
||||
>
|
||||
{statusLabel[ticket.status]}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[11px] font-semibold uppercase tracking-wide text-neutral-400">
|
||||
{formatUpdated(ticket.updatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="mt-3 line-clamp-2 text-sm font-semibold text-neutral-900">
|
||||
{ticket.subject || "Sem assunto"}
|
||||
</h3>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2 text-xs text-neutral-600">
|
||||
<span className="font-medium text-neutral-500">Fila:</span>
|
||||
<span className="rounded-full bg-slate-100 px-2.5 py-0.5 text-[11px] font-medium text-neutral-700">
|
||||
{ticket.queue ?? "Sem fila"}
|
||||
</span>
|
||||
<span className="font-medium text-neutral-500">Prioridade:</span>
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full px-2.5 py-0.5 text-[11px] font-semibold shadow-sm",
|
||||
priorityChipClass[ticket.priority],
|
||||
)}
|
||||
>
|
||||
{priorityLabel[ticket.priority]}
|
||||
</span>
|
||||
</div>
|
||||
<dl className="mt-4 space-y-2 text-xs text-neutral-600">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<dt className="font-medium text-neutral-500">Empresa</dt>
|
||||
<dd className="truncate text-right text-neutral-700">
|
||||
{ticket.company?.name ?? "Sem empresa"}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<dt className="font-medium text-neutral-500">Responsável</dt>
|
||||
<dd className="truncate text-right text-neutral-700">
|
||||
{ticket.assignee?.name ?? "Sem responsável"}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<dt className="font-medium text-neutral-500">Solicitante</dt>
|
||||
<dd className="truncate text-right text-neutral-700">
|
||||
{ticket.requester?.name ?? ticket.requester?.email ?? "—"}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{ticket.tags.length > 0 ? (
|
||||
<div className="mt-4 flex flex-wrap items-center gap-2">
|
||||
{ticket.tags.slice(0, 3).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-neutral-600"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{ticket.tags.length > 3 ? (
|
||||
<span className="text-[11px] font-semibold text-neutral-400">
|
||||
+{ticket.tags.length - 3}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue