100 lines
4 KiB
TypeScript
100 lines
4 KiB
TypeScript
"use client"
|
|
|
|
import { format } from "date-fns"
|
|
import { formatDistanceToNow } from "date-fns"
|
|
import { ptBR } from "date-fns/locale"
|
|
import Link from "next/link"
|
|
import { Tag } from "lucide-react"
|
|
|
|
import type { Ticket } from "@/lib/schemas/ticket"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Card, CardContent, CardHeader } from "@/components/ui/card"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const statusLabel: Record<Ticket["status"], string> = {
|
|
PENDING: "Pendente",
|
|
AWAITING_ATTENDANCE: "Aguardando atendimento",
|
|
PAUSED: "Pausado",
|
|
RESOLVED: "Resolvido",
|
|
}
|
|
|
|
const statusTone: Record<Ticket["status"], string> = {
|
|
PENDING: "bg-slate-200 text-slate-800",
|
|
AWAITING_ATTENDANCE: "bg-sky-100 text-sky-700",
|
|
PAUSED: "bg-violet-100 text-violet-700",
|
|
RESOLVED: "bg-emerald-100 text-emerald-700",
|
|
}
|
|
|
|
const priorityLabel: Record<Ticket["priority"], string> = {
|
|
LOW: "Baixa",
|
|
MEDIUM: "Média",
|
|
HIGH: "Alta",
|
|
URGENT: "Urgente",
|
|
}
|
|
|
|
const priorityTone: Record<Ticket["priority"], string> = {
|
|
LOW: "bg-slate-100 text-slate-600",
|
|
MEDIUM: "bg-sky-100 text-sky-700",
|
|
HIGH: "bg-amber-100 text-amber-700",
|
|
URGENT: "bg-rose-100 text-rose-700",
|
|
}
|
|
|
|
interface PortalTicketCardProps {
|
|
ticket: Ticket
|
|
}
|
|
|
|
export function PortalTicketCard({ ticket }: PortalTicketCardProps) {
|
|
const updatedAgo = formatDistanceToNow(ticket.updatedAt, {
|
|
addSuffix: true,
|
|
locale: ptBR,
|
|
})
|
|
|
|
return (
|
|
<Link href={`/portal/tickets/${ticket.id}`} className="block">
|
|
<Card className="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm transition hover:-translate-y-0.5 hover:shadow-md">
|
|
<CardHeader className="flex flex-row items-start justify-between gap-3 px-5 pb-3 pt-5">
|
|
<div>
|
|
<div className="flex items-center gap-2 text-sm text-neutral-500">
|
|
<span className="font-semibold text-neutral-900">#{ticket.reference}</span>
|
|
<span>·</span>
|
|
<span>{format(ticket.createdAt, "dd/MM/yyyy")}</span>
|
|
</div>
|
|
<h3 className="mt-1 text-lg font-semibold text-neutral-900">{ticket.subject}</h3>
|
|
{ticket.summary ? (
|
|
<p className="mt-1 line-clamp-2 text-sm text-neutral-600">{ticket.summary}</p>
|
|
) : null}
|
|
</div>
|
|
<div className="flex flex-col items-end gap-2 text-right">
|
|
<Badge className={cn("rounded-full px-3 py-1 text-xs font-semibold uppercase", statusTone[ticket.status])}>
|
|
{statusLabel[ticket.status]}
|
|
</Badge>
|
|
<Badge className={cn("rounded-full px-3 py-1 text-xs font-semibold uppercase", priorityTone[ticket.priority])}>
|
|
{priorityLabel[ticket.priority]}
|
|
</Badge>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-wrap items-center justify-between gap-4 border-t border-slate-100 px-5 py-4 text-sm text-neutral-600">
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-xs uppercase tracking-wide text-neutral-500">Fila</span>
|
|
<span className="font-medium text-neutral-800">{ticket.queue ?? "Sem fila"}</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-xs uppercase tracking-wide text-neutral-500">Status</span>
|
|
<span className="font-medium text-neutral-800">{statusLabel[ticket.status]}</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-xs uppercase tracking-wide text-neutral-500">Última atualização</span>
|
|
<span className="font-medium text-neutral-800">{updatedAgo}</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-xs uppercase tracking-wide text-neutral-500">Categoria</span>
|
|
<span className="flex items-center gap-2 font-medium text-neutral-800">
|
|
<Tag className="size-4 text-neutral-500" />
|
|
{ticket.category?.name ?? "Não classificada"}
|
|
</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
)
|
|
}
|