"use client" import { useEffect, useMemo, useState } from "react" import { useMutation } from "convex/react" import { useRouter } from "next/navigation" import { Star } from "lucide-react" import { formatDistanceToNowStrict } from "date-fns" import { ptBR } from "date-fns/locale" import { toast } from "sonner" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { useAuth } from "@/lib/auth-client" import type { TicketWithDetails } from "@/lib/schemas/ticket" import { cn } from "@/lib/utils" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" type TicketCsatCardProps = { ticket: TicketWithDetails } function formatRelative(timestamp: Date | null | undefined) { if (!timestamp) return null try { return formatDistanceToNowStrict(timestamp, { locale: ptBR, addSuffix: true }) } catch { return null } } export function TicketCsatCard({ ticket }: TicketCsatCardProps) { const router = useRouter() const { session, convexUserId, role: authRole, machineContext } = useAuth() const submitCsat = useMutation(api.tickets.submitCsat) const deriveViewerRole = () => { const authRoleNormalized = authRole?.toLowerCase()?.trim() const machinePersona = machineContext?.persona ?? session?.user.machinePersona ?? null const assignedRole = machineContext?.assignedUserRole ?? null const sessionRole = session?.user.role?.toLowerCase()?.trim() if (authRoleNormalized && authRoleNormalized !== "machine") { return authRoleNormalized.toUpperCase() } if (authRoleNormalized === "machine" && machinePersona) { return machinePersona.toUpperCase() } if (machinePersona) { return machinePersona.toUpperCase() } if (assignedRole) { return assignedRole.toUpperCase() } if (sessionRole && sessionRole !== "machine") { return sessionRole.toUpperCase() } if (sessionRole === "machine") { return "COLLABORATOR" } return "COLLABORATOR" } const viewerRole = deriveViewerRole() const viewerEmail = session?.user.email?.trim().toLowerCase() ?? "" const viewerId = convexUserId as Id<"users"> | undefined const requesterEmail = ticket.requester.email.trim().toLowerCase() const isRequesterById = viewerId ? ticket.requester.id === viewerId : false const isRequesterByEmail = viewerEmail && requesterEmail ? viewerEmail === requesterEmail : false const isRequester = isRequesterById || isRequesterByEmail const isResolved = ticket.status === "RESOLVED" const initialScore = typeof ticket.csatScore === "number" ? ticket.csatScore : 0 const initialComment = ticket.csatComment ?? "" const maxScore = typeof ticket.csatMaxScore === "number" && ticket.csatMaxScore > 0 ? ticket.csatMaxScore : 5 const [score, setScore] = useState(initialScore) const [comment, setComment] = useState(initialComment) const [hasSubmitted, setHasSubmitted] = useState(initialScore > 0) const [ratedAt, setRatedAt] = useState(ticket.csatRatedAt ?? null) const [hoverScore, setHoverScore] = useState(null) const [submitting, setSubmitting] = useState(false) useEffect(() => { setScore(initialScore) setComment(initialComment) setRatedAt(ticket.csatRatedAt ?? null) setHasSubmitted(initialScore > 0) }, [initialScore, initialComment, ticket.csatRatedAt]) const effectiveScore = hasSubmitted ? score : hoverScore ?? score const viewerIsStaff = viewerRole === "ADMIN" || viewerRole === "AGENT" || viewerRole === "MANAGER" const staffCanInspect = viewerIsStaff && ticket.status !== "PENDING" const canSubmit = Boolean(viewerId && viewerRole === "COLLABORATOR" && isRequester && isResolved && !hasSubmitted) const hasRating = hasSubmitted const showCard = staffCanInspect || isRequester || hasSubmitted const ratedAtRelative = useMemo(() => formatRelative(ratedAt), [ratedAt]) if (!showCard) { return null } const handleSubmit = async () => { if (!viewerId) { toast.error("Sessão não autenticada.") return } if (!canSubmit) { toast.error("Você não pode avaliar este chamado.") return } if (score < 1) { toast.error("Selecione uma nota de 1 a 5 estrelas.") return } if (comment.length > 2000) { toast.error("Reduza o comentário para no máximo 2000 caracteres.") return } try { setSubmitting(true) const result = await submitCsat({ ticketId: ticket.id as Id<"tickets">, actorId: viewerId, score, maxScore, comment: comment.trim() ? comment.trim() : undefined, }) if (result?.score) { setScore(result.score) } if (typeof result?.comment === "string") { setComment(result.comment) } if (result?.ratedAt) { const ratedAtDate = new Date(result.ratedAt) if (!Number.isNaN(ratedAtDate.getTime())) { setRatedAt(ratedAtDate) } } setHasSubmitted(true) toast.success("Avaliação registrada. Obrigado pelo feedback!") router.refresh() } catch (error) { console.error("Failed to submit CSAT", error) toast.error("Não foi possível registrar a avaliação. Tente novamente.") } finally { setSubmitting(false) setHoverScore(null) } } const stars = Array.from({ length: maxScore }, (_, index) => index + 1) return (
Avaliação do atendimento Conte como foi sua experiência com este chamado.
{hasRating ? (
Obrigado pelo feedback!
) : null}
{stars.map((value) => { const filled = value <= effectiveScore return ( ) })}
{hasRating ? (

Nota final:{" "} {score}/{maxScore} {ratedAtRelative ? ` • ${ratedAtRelative}` : null}

) : viewerIsStaff ? (
Nenhuma avaliação registrada ainda.
) : null} {canSubmit ? (