94 lines
4.1 KiB
TypeScript
94 lines
4.1 KiB
TypeScript
import { format } from "date-fns"
|
|
import type { ComponentType } from "react"
|
|
import { ptBR } from "date-fns/locale"
|
|
import {
|
|
IconClockHour4,
|
|
IconNote,
|
|
IconSquareCheck,
|
|
IconUserCircle,
|
|
} from "@tabler/icons-react"
|
|
|
|
import type { TicketWithDetails } from "@/lib/schemas/ticket"
|
|
import { cn } from "@/lib/utils"
|
|
import { Card, CardContent } from "@/components/ui/card"
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
import { Separator } from "@/components/ui/separator"
|
|
|
|
const timelineIcons: Record<string, ComponentType<{ className?: string }>> = {
|
|
CREATED: IconUserCircle,
|
|
STATUS_CHANGED: IconSquareCheck,
|
|
ASSIGNEE_CHANGED: IconUserCircle,
|
|
COMMENT_ADDED: IconNote,
|
|
}
|
|
|
|
const timelineLabels: Record<string, string> = {
|
|
CREATED: "Criado",
|
|
STATUS_CHANGED: "Status alterado",
|
|
ASSIGNEE_CHANGED: "Responsável alterado",
|
|
COMMENT_ADDED: "Comentário adicionado",
|
|
QUEUE_CHANGED: "Fila alterada",
|
|
}
|
|
|
|
interface TicketTimelineProps {
|
|
ticket: TicketWithDetails
|
|
}
|
|
|
|
export function TicketTimeline({ ticket }: TicketTimelineProps) {
|
|
return (
|
|
<Card className="rounded-xl border bg-card shadow-sm">
|
|
<CardContent className="space-y-6 px-4 pb-6">
|
|
{ticket.timeline.map((entry, index) => {
|
|
const Icon = timelineIcons[entry.type] ?? IconClockHour4
|
|
const isLast = index === ticket.timeline.length - 1
|
|
return (
|
|
<div key={entry.id} className="relative pl-10">
|
|
{!isLast && (
|
|
<span className="absolute left-[17px] top-6 h-full w-px bg-border" aria-hidden />
|
|
)}
|
|
<span className="absolute left-0 top-0 flex size-8 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
|
<Icon className="size-4" />
|
|
</span>
|
|
<div className="flex flex-col gap-2">
|
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-1">
|
|
<span className="text-sm font-medium text-foreground">
|
|
{timelineLabels[entry.type] ?? entry.type}
|
|
</span>
|
|
{entry.payload?.actorName ? (
|
|
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
<Avatar className="size-5">
|
|
<AvatarImage src={entry.payload.actorAvatar} alt={entry.payload.actorName} />
|
|
<AvatarFallback>
|
|
{entry.payload.actorName.split(' ').slice(0,2).map((p:string)=>p[0]).join('').toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
por {entry.payload.actorName}
|
|
</span>
|
|
) : null}
|
|
<span className="text-xs text-muted-foreground">
|
|
{format(entry.createdAt, "dd MMM yyyy HH:mm", { locale: ptBR })}
|
|
</span>
|
|
</div>
|
|
{(() => {
|
|
const p: any = entry.payload || {}
|
|
let message: string | null = null
|
|
if (entry.type === "STATUS_CHANGED" && (p.toLabel || p.to)) message = `Status alterado para ${p.toLabel || p.to}`
|
|
if (entry.type === "ASSIGNEE_CHANGED" && (p.assigneeName || p.assigneeId)) message = `Responsável alterado${p.assigneeName ? ` para ${p.assigneeName}` : ""}`
|
|
if (entry.type === "QUEUE_CHANGED" && (p.queueName || p.queueId)) message = `Fila alterada${p.queueName ? ` para ${p.queueName}` : ""}`
|
|
if (entry.type === "CREATED" && (p.requesterName)) message = `Criado por ${p.requesterName}`
|
|
if (entry.type === "COMMENT_ADDED" && (p.authorName || p.authorId)) message = `Comentário adicionado${p.authorName ? ` por ${p.authorName}` : ""}`
|
|
if (!message) return null
|
|
return (
|
|
<div className="rounded-lg border border-dashed bg-card px-3 py-2 text-sm text-muted-foreground">
|
|
{message}
|
|
</div>
|
|
)
|
|
})()}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|