import { format } from "date-fns" import type { ComponentType, ReactNode } from "react" import { ptBR } from "date-fns/locale" import { IconCalendar, IconClockHour4, IconFolders, IconNote, IconPaperclip, IconSquareCheck, IconStar, IconUserCircle, } from "@tabler/icons-react" import type { TicketWithDetails } from "@/lib/schemas/ticket" import { Card, CardContent } from "@/components/ui/card" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Separator } from "@/components/ui/separator" import { TICKET_TIMELINE_LABELS } from "@/lib/ticket-timeline-labels" const timelineIcons: Record> = { CREATED: IconUserCircle, STATUS_CHANGED: IconSquareCheck, ASSIGNEE_CHANGED: IconUserCircle, COMMENT_ADDED: IconNote, COMMENT_EDITED: IconNote, WORK_STARTED: IconClockHour4, WORK_PAUSED: IconClockHour4, SUBJECT_CHANGED: IconNote, SUMMARY_CHANGED: IconNote, QUEUE_CHANGED: IconSquareCheck, PRIORITY_CHANGED: IconSquareCheck, ATTACHMENT_REMOVED: IconPaperclip, CATEGORY_CHANGED: IconFolders, MANAGER_NOTIFIED: IconUserCircle, VISIT_SCHEDULED: IconCalendar, CSAT_RECEIVED: IconStar, CSAT_RATED: IconStar, } const timelineLabels: Record = TICKET_TIMELINE_LABELS interface TicketTimelineProps { ticket: TicketWithDetails } export function TicketTimeline({ ticket }: TicketTimelineProps) { const formatDuration = (durationMs: number) => { if (!durationMs || durationMs <= 0) return "0s" const totalSeconds = Math.floor(durationMs / 1000) const hours = Math.floor(totalSeconds / 3600) const minutes = Math.floor((totalSeconds % 3600) / 60) const seconds = totalSeconds % 60 if (hours > 0) { return `${hours}h ${minutes.toString().padStart(2, "0")}m` } if (minutes > 0) { return `${minutes}m ${seconds.toString().padStart(2, "0")}s` } return `${seconds}s` } return ( {ticket.timeline.map((entry, index) => { const Icon = timelineIcons[entry.type] ?? IconClockHour4 const isLast = index === ticket.timeline.length - 1 return (
{!isLast && ( )}
{timelineLabels[entry.type] ?? entry.type} {entry.payload?.actorName ? ( {String(entry.payload?.actorName ?? "").split(" ").slice(0, 2).map((part: string) => part[0]).join("").toUpperCase()} por {String(entry.payload?.actorName ?? "")} ) : null} {format(entry.createdAt, "dd MMM yyyy HH:mm", { locale: ptBR })}
{(() => { const payload = (entry.payload || {}) as { toLabel?: string to?: string assigneeName?: string assigneeId?: string queueName?: string queueId?: string requesterName?: string authorName?: string authorId?: string actorName?: string actorId?: string from?: string attachmentName?: string sessionDurationMs?: number categoryName?: string subcategoryName?: string pauseReason?: string pauseReasonLabel?: string pauseNote?: string managerName?: string managerUserName?: string manager?: string managerId?: string managerUserId?: string scheduledFor?: number | string scheduledAt?: number | string score?: number rating?: number maxScore?: number max?: number } let message: ReactNode = null if (entry.type === "STATUS_CHANGED" && (payload.toLabel || payload.to)) { message = "Status alterado para " + (payload.toLabel || payload.to) } if (entry.type === "ASSIGNEE_CHANGED" && (payload.assigneeName || payload.assigneeId)) { message = "Responsável alterado" + (payload.assigneeName ? " para " + payload.assigneeName : "") } if (entry.type === "QUEUE_CHANGED" && (payload.queueName || payload.queueId)) { message = "Fila alterada" + (payload.queueName ? " para " + payload.queueName : "") } if (entry.type === "PRIORITY_CHANGED" && (payload.toLabel || payload.to)) { message = "Prioridade alterada para " + (payload.toLabel || payload.to) } if (entry.type === "CREATED" && payload.requesterName) { message = "Criado por " + payload.requesterName } if (entry.type === "COMMENT_ADDED" && (payload.authorName || payload.authorId)) { message = "Comentário adicionado" + (payload.authorName ? " por " + payload.authorName : "") } if (entry.type === "COMMENT_EDITED" && (payload.actorName || payload.actorId || payload.authorName)) { const name = payload.actorName ?? payload.authorName message = "Comentário editado" + (name ? " por " + name : "") } if (entry.type === "SUBJECT_CHANGED" && (payload.to || payload.toLabel)) { message = "Assunto alterado" + (payload.to ? " para \"" + payload.to + "\"" : "") } if (entry.type === "SUMMARY_CHANGED") { message = "Resumo atualizado" } if (entry.type === "ATTACHMENT_REMOVED" && payload.attachmentName) { message = `Anexo removido: ${payload.attachmentName}` } if (entry.type === "WORK_PAUSED") { const parts: string[] = [] if (payload.pauseReasonLabel || payload.pauseReason) { parts.push(`Motivo: ${payload.pauseReasonLabel ?? payload.pauseReason}`) } if (typeof payload.sessionDurationMs === "number") { parts.push(`Tempo registrado: ${formatDuration(payload.sessionDurationMs)}`) } message = (
{parts.length > 0 ? parts.join(" • ") : "Atendimento pausado"} {payload.pauseNote ? ( Observação: {payload.pauseNote} ) : null}
) } if (entry.type === "CATEGORY_CHANGED") { if (payload.categoryName || payload.subcategoryName) { message = `Categoria alterada para ${payload.categoryName ?? ""}${ payload.subcategoryName ? ` • ${payload.subcategoryName}` : "" }` } else { message = "Categoria removida" } } if (entry.type === "MANAGER_NOTIFIED") { const manager = payload.managerName ?? payload.managerUserName ?? payload.manager ?? payload.managerId ?? payload.managerUserId message = manager ? `Gestor notificado: ${manager}` : "Gestor notificado" } if (entry.type === "VISIT_SCHEDULED") { const scheduledRaw = payload.scheduledFor ?? payload.scheduledAt let formatted: string | null = null if (typeof scheduledRaw === "number" || typeof scheduledRaw === "string") { const date = new Date(scheduledRaw) if (!Number.isNaN(date.getTime())) { formatted = format(date, "dd MMM yyyy HH:mm", { locale: ptBR }) } } message = formatted ? `Visita agendada para ${formatted}` : "Visita agendada" } if (entry.type === "CSAT_RECEIVED") { message = "CSAT recebido" } if (entry.type === "CSAT_RATED") { const score = typeof payload.score === "number" ? payload.score : payload.rating const maxScore = typeof payload.maxScore === "number" ? payload.maxScore : typeof payload.max === "number" ? payload.max : undefined message = typeof score === "number" ? `CSAT avaliado: ${score}${typeof maxScore === "number" ? `/${maxScore}` : ""}` : "CSAT avaliado" } if (!message) return null return (
{message}
) })()}
) })}
) }