sistema-de-chamados/src/components/tickets/play-next-ticket-card.tsx
2025-11-14 19:41:47 -03:00

172 lines
7.7 KiB
TypeScript

"use client"
import Link from "next/link"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { IconArrowRight, IconPlayerPlayFilled } from "@tabler/icons-react"
import { useMutation, useQuery } from "convex/react"
import { api } from "@/convex/_generated/api"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { useAuth } from "@/lib/auth-client"
import type { Ticket, TicketPlayContext, TicketQueueSummary } from "@/lib/schemas/ticket"
import type { Id } from "@/convex/_generated/dataModel"
import { mapTicketFromServer } from "@/lib/mappers/ticket"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Separator } from "@/components/ui/separator"
import { TicketPriorityPill } from "@/components/tickets/priority-pill"
import { TicketStatusBadge } from "@/components/tickets/status-badge"
import { Spinner } from "@/components/ui/spinner"
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"
interface PlayNextTicketCardProps {
context?: TicketPlayContext
}
const queueBadgeClass = "inline-flex items-center rounded-full border border-slate-200 bg-white px-2.5 py-1 text-xs font-semibold text-neutral-700"
const startButtonClass = "inline-flex items-center gap-2 rounded-lg border border-black bg-[#00e8ff] px-3 py-2 text-sm font-semibold text-black transition hover:bg-[#00d6eb]"
const secondaryButtonClass = "inline-flex items-center gap-2 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm font-semibold text-neutral-700 hover:bg-slate-100"
export function PlayNextTicketCard({ context }: PlayNextTicketCardProps) {
const router = useRouter()
const { convexUserId, isStaff } = useAuth()
const queuesEnabled = Boolean(isStaff && convexUserId)
const queueSummaryResult = useQuery(
api.queues.summary,
queuesEnabled ? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> } : "skip"
)
const queueSummary: TicketQueueSummary[] = Array.isArray(queueSummaryResult) ? queueSummaryResult : []
const playNext = useMutation(api.tickets.playNext)
const [selectedQueueId, setSelectedQueueId] = useState<string | undefined>(undefined)
const nextTicketFromServer = useQuery(
api.tickets.list,
convexUserId
? {
tenantId: DEFAULT_TENANT_ID,
viewerId: convexUserId as Id<"users">,
status: undefined,
priority: undefined,
channel: undefined,
queueId: (selectedQueueId as Id<"queues">) || undefined,
limit: 1,
}
: "skip"
)?.[0]
const nextTicketUi: Ticket | null = nextTicketFromServer ? mapTicketFromServer(nextTicketFromServer as unknown) : null
const sanitizedNextTicket = nextTicketUi
? ({ ...nextTicketUi, lastTimelineEntry: nextTicketUi.lastTimelineEntry ?? undefined } as Ticket)
: null
const cardContext: TicketPlayContext | null =
context ??
(sanitizedNextTicket
? {
queue: {
id: "default",
name: "Geral",
pending: queueSummary.reduce((acc, item) => acc + item.pending, 0),
inProgress: queueSummary.reduce((acc, item) => acc + item.inProgress, 0),
paused: queueSummary.reduce((acc, item) => acc + item.paused, 0),
breached: queueSummary.reduce((acc, item) => acc + item.breached, 0),
},
nextTicket: sanitizedNextTicket,
}
: null)
if (!cardContext || !cardContext.nextTicket) {
return (
<Card className="rounded-2xl border border-slate-200 bg-white shadow-sm">
<CardHeader>
<CardTitle className="text-lg font-semibold text-neutral-900">Fila sem tickets pendentes</CardTitle>
</CardHeader>
<CardContent className="text-sm text-neutral-600">
Nenhum ticket disponível no momento. Excelente trabalho!
</CardContent>
</Card>
)
}
const ticket = cardContext.nextTicket
return (
<Card className="rounded-2xl border border-slate-200 bg-white shadow-sm">
<CardHeader className="flex flex-row items-center justify-between gap-2">
<CardTitle className="text-lg font-semibold text-neutral-900">
Próximo ticket #{ticket.reference}
</CardTitle>
<TicketPriorityPill priority={ticket.priority} />
</CardHeader>
<CardContent className="flex flex-col gap-4 text-sm text-neutral-700">
<div className="flex items-center justify-end gap-2">
<span className="text-xs uppercase tracking-wide text-neutral-500">Fila</span>
<Select value={selectedQueueId ?? "ALL"} onValueChange={(value) => setSelectedQueueId(value === "ALL" ? undefined : value)}>
<SelectTrigger className="h-8 w-[180px] rounded-lg border border-slate-300 bg-white px-3 text-left text-sm font-medium text-neutral-800 shadow-sm focus:ring-0 data-[state=open]:border-[#00d6eb]">
<SelectValue placeholder="Todas" />
</SelectTrigger>
<SelectContent className="rounded-lg border border-slate-200 bg-white text-neutral-800 shadow-sm">
<SelectItem value="ALL">Todas</SelectItem>
{queueSummary.map((queue) => (
<SelectItem key={queue.id} value={queue.id}>
{queue.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<h2 className="text-xl font-semibold text-neutral-900">{ticket.subject}</h2>
</div>
<div className="flex flex-wrap items-center gap-2 text-xs text-neutral-600">
<Badge className={queueBadgeClass}>{ticket.queue ?? "Sem fila"}</Badge>
<TicketStatusBadge status={ticket.status} />
<span className="font-medium text-neutral-900">Solicitante: {ticket.requester.name}</span>
</div>
<Separator className="bg-slate-200" />
<div className="flex flex-col gap-3 text-sm text-neutral-700">
<div className="flex items-center justify-between">
<span>Pendentes na fila</span>
<span className="font-semibold text-neutral-900">{cardContext.queue.pending}</span>
</div>
<div className="flex items-center justify-between">
<span>Em andamento</span>
<span className="font-semibold text-neutral-900">{cardContext.queue.inProgress}</span>
</div>
<div className="flex items-center justify-between">
<span>Pausados</span>
<span className="font-semibold text-neutral-900">{cardContext.queue.paused}</span>
</div>
<div className="flex items-center justify-between">
<span>Fora do SLA</span>
<span className="font-semibold text-red-600">{cardContext.queue.breached}</span>
</div>
</div>
<Button
className={startButtonClass}
onClick={async () => {
if (!convexUserId) return
const chosen = await playNext({ tenantId: DEFAULT_TENANT_ID, queueId: (selectedQueueId as Id<"queues">) || undefined, agentId: convexUserId as Id<"users"> })
if (chosen?.id) router.push(`/tickets/${chosen.id}`)
}}
>
{convexUserId ? (
<>
<IconPlayerPlayFilled className="size-4 text-black" /> Iniciar atendimento
</>
) : (
<>
<Spinner className="me-2" /> Carregando...
</>
)}
</Button>
<Button variant="ghost" asChild className={secondaryButtonClass}>
<Link href="/tickets">
Ver lista completa
<IconArrowRight className="size-4" />
</Link>
</Button>
</CardContent>
</Card>
)
}