/** * TicketDetailPage.tsx * * Página de detalhe de um chamado — visão do Agente. * Exibe informações completas, histórico de comentários e ações. */ import { useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api, type TicketStatus } from '@/lib/api'; import { useAuth } from '@/context/AuthContext'; import { useToast } from '@/context/ToastContext'; import { StatusBadge } from '@/components/tickets/StatusBadge'; import { PriorityBadge } from '@/components/tickets/PriorityBadge'; import styles from './TicketDetailPage.module.css'; // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDateTime(iso: string) { return new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short', }).format(new Date(iso)); } const NEXT_STATUSES: Record = { open: ['in_progress', 'closed'], in_progress: ['waiting', 'resolved', 'closed'], waiting: ['in_progress', 'resolved', 'closed'], resolved: ['closed', 'open'], closed: [], }; const STATUS_LABELS: Record = { open: 'Aberto', in_progress: 'Em Andamento', waiting: 'Aguardando', resolved: 'Resolvido', closed: 'Fechado', }; // ─── Componente ─────────────────────────────────────────────────────────────── export default function TicketDetailPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { user } = useAuth(); const { addToast } = useToast(); const ticketId = Number(id); // ── Queries ──────────────────────────────────────────────────────────────── const ticketQuery = useQuery({ queryKey: ['tickets', ticketId], queryFn: () => api.tickets.get(ticketId), enabled: Boolean(ticketId), }); const commentsQuery = useQuery({ queryKey: ['tickets', ticketId, 'comments'], queryFn: () => api.comments.list(ticketId), enabled: Boolean(ticketId), }); const settingsQuery = useQuery({ queryKey: ['settings'], queryFn: () => api.settings.get(), }); // ── State do formulário de comentário ───────────────────────────────────── const [commentText, setCommentText] = useState(''); const [isInternal, setIsInternal] = useState(false); // ── Mutations ────────────────────────────────────────────────────────────── const addCommentMutation = useMutation({ mutationFn: () => api.comments.add(ticketId, { content: commentText.trim(), isInternal, authorId: user?.id ?? 0, }), onSuccess: () => { setCommentText(''); setIsInternal(false); void queryClient.invalidateQueries({ queryKey: ['tickets', ticketId, 'comments'] }); }, }); const updateStatusMutation = useMutation({ mutationFn: (status: TicketStatus) => api.tickets.updateStatus(ticketId, status), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ['tickets', ticketId] }); void queryClient.invalidateQueries({ queryKey: ['tickets'] }); addToast('Status atualizado', 'success'); }, }); const assignTicketMutation = useMutation({ mutationFn: () => api.tickets.assign(ticketId, user!.id), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ['tickets', ticketId] }); void queryClient.invalidateQueries({ queryKey: ['tickets'] }); addToast('Você assumiu o chamado', 'success'); }, }); const deleteTicketMutation = useMutation({ mutationFn: () => api.tickets.delete(ticketId), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ['tickets'] }); addToast('Chamado excluído com sucesso.', 'success'); navigate('/dashboard'); }, onError: (err: any) => { addToast(`Erro ao excluir chamado: ${err.message}`, 'error'); } }); // ── Loading / Error states ───────────────────────────────────────────────── if (ticketQuery.isLoading) { return (

Carregando chamado...

); } if (ticketQuery.isError || !ticketQuery.data) { return (

Chamado não encontrado

); } const ticket = ticketQuery.data; const comments = commentsQuery.data ?? []; const nextStatuses = NEXT_STATUSES[ticket.status]; return (
{/* ── Header do ticket ─────────────────────────────────────────────── */}
#{ticket.id}

{ticket.title}

Aberto em {formatDateTime(ticket.createdAt)}
{/* ── Layout de 2 colunas ──────────────────────────────────────────── */}
{/* ── Coluna principal ─────────────────────────────────────────── */}
{/* Descrição */}

Descrição

{ticket.description}

{/* Timeline de comentários */}

Histórico ({comments.length})

{commentsQuery.isLoading && (
)} {comments.length === 0 && !commentsQuery.isLoading && (

Nenhuma interação ainda.

)}
{comments.map((comment) => (
{comment.isInternal ? '🔒' : '💬'}
{comment.authorName ?? `Usuário #${comment.authorId}`} {comment.isInternal && ( Nota Interna )}

{comment.content}

))}
{/* Formulário de resposta */} {ticket.status !== 'closed' && ticket.status !== 'resolved' && !(user?.role === 'requester' && settingsQuery.data && !settingsQuery.data.allowRequesterChat) && (

Adicionar Resposta

<>