"use client" import { useMemo, useState } from "react" import { useMutation, useQuery } from "convex/react" import { CheckCheck, ListChecks, Plus, RotateCcw, Trash2 } from "lucide-react" import { toast } from "sonner" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import type { TicketChecklistItem, TicketWithDetails } from "@/lib/schemas/ticket" import { useAuth } from "@/lib/auth-client" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Checkbox } from "@/components/ui/checkbox" import { Input } from "@/components/ui/input" import { Progress } from "@/components/ui/progress" import { ScrollArea } from "@/components/ui/scroll-area" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" type ChecklistTemplateRow = { id: Id<"ticketChecklistTemplates"> name: string company: { id: Id<"companies">; name: string } | null } function countRequiredPending(items: TicketChecklistItem[]) { return items.filter((item) => (item.required ?? true) && !item.done).length } function countRequiredDone(items: TicketChecklistItem[]) { return items.filter((item) => (item.required ?? true) && item.done).length } function countAllDone(items: TicketChecklistItem[]) { return items.filter((item) => item.done).length } export function TicketChecklistCard({ ticket, }: { ticket: Pick }) { const { convexUserId, isAdmin, isStaff, role } = useAuth() const actorId = (convexUserId ?? null) as Id<"users"> | null const canEdit = Boolean(isAdmin || role === "agent") const canToggleDone = Boolean(isStaff) const isResolved = ticket.status === "RESOLVED" const checklist = useMemo(() => ticket.checklist ?? [], [ticket.checklist]) const requiredTotal = useMemo(() => checklist.filter((item) => (item.required ?? true)).length, [checklist]) const requiredDone = useMemo(() => countRequiredDone(checklist), [checklist]) const requiredPending = useMemo(() => countRequiredPending(checklist), [checklist]) const allDone = useMemo(() => countAllDone(checklist), [checklist]) const totalItems = checklist.length const progress = totalItems > 0 ? Math.round((allDone / totalItems) * 100) : 0 const addChecklistItem = useMutation(api.tickets.addChecklistItem) const updateChecklistItemText = useMutation(api.tickets.updateChecklistItemText) const setChecklistItemDone = useMutation(api.tickets.setChecklistItemDone) const setChecklistItemRequired = useMutation(api.tickets.setChecklistItemRequired) const removeChecklistItem = useMutation(api.tickets.removeChecklistItem) const completeAllChecklistItems = useMutation(api.tickets.completeAllChecklistItems) const uncompleteAllChecklistItems = useMutation(api.tickets.uncompleteAllChecklistItems) const applyChecklistTemplate = useMutation(api.tickets.applyChecklistTemplate) const templates = useQuery( api.checklistTemplates.listActive, actorId ? { tenantId: ticket.tenantId, viewerId: actorId, companyId: ticket.company?.id ? (ticket.company.id as Id<"companies">) : undefined, } : "skip" ) as ChecklistTemplateRow[] | undefined const templateNameById = useMemo(() => { const map = new Map() for (const tpl of templates ?? []) { map.set(String(tpl.id), tpl.name) } return map }, [templates]) const [newText, setNewText] = useState("") const [newRequired, setNewRequired] = useState(true) const [adding, setAdding] = useState(false) const [editingId, setEditingId] = useState(null) const [editingText, setEditingText] = useState("") const [savingText, setSavingText] = useState(false) const [selectedTemplateId, setSelectedTemplateId] = useState("") const [applyingTemplate, setApplyingTemplate] = useState(false) const [completingAll, setCompletingAll] = useState(false) const [onlyPending, setOnlyPending] = useState(false) const handleAdd = async () => { if (!actorId || !canEdit || isResolved) return const text = newText.trim() if (!text) { toast.error("Informe o texto do item do checklist.") return } setAdding(true) try { await addChecklistItem({ ticketId: ticket.id as Id<"tickets">, actorId, text, required: newRequired, }) setNewText("") setNewRequired(true) toast.success("Item adicionado ao checklist.") } catch (error) { toast.error(error instanceof Error ? error.message : "Falha ao adicionar item.") } finally { setAdding(false) } } const handleSaveText = async () => { if (!actorId || !canEdit || isResolved || !editingId) return const text = editingText.trim() if (!text) { toast.error("Informe o texto do item do checklist.") return } setSavingText(true) try { await updateChecklistItemText({ ticketId: ticket.id as Id<"tickets">, actorId, itemId: editingId, text, }) setEditingId(null) setEditingText("") toast.success("Item atualizado.") } catch (error) { toast.error(error instanceof Error ? error.message : "Falha ao atualizar item.") } finally { setSavingText(false) } } const handleApplyTemplate = async () => { if (!actorId || !canEdit || isResolved) return const templateId = selectedTemplateId.trim() if (!templateId) return setApplyingTemplate(true) try { const result = await applyChecklistTemplate({ ticketId: ticket.id as Id<"tickets">, actorId, templateId: templateId as Id<"ticketChecklistTemplates">, }) const added = typeof result?.added === "number" ? result.added : 0 toast.success(added > 0 ? `Checklist aplicado (${added} novo${added === 1 ? "" : "s"}).` : "Checklist já estava aplicado.") setSelectedTemplateId("") } catch (error) { toast.error(error instanceof Error ? error.message : "Falha ao aplicar template.") } finally { setApplyingTemplate(false) } } const allItemsDone = checklist.length > 0 && allDone === totalItems const handleToggleAll = async () => { if (!actorId || !canEdit || isResolved) return setCompletingAll(true) try { if (allItemsDone) { await uncompleteAllChecklistItems({ ticketId: ticket.id as Id<"tickets">, actorId }) toast.success("Itens desmarcados.") } else { await completeAllChecklistItems({ ticketId: ticket.id as Id<"tickets">, actorId }) toast.success("Checklist concluído.") } } catch (error) { toast.error(error instanceof Error ? error.message : "Falha ao atualizar checklist.") } finally { setCompletingAll(false) } } const canToggleAll = checklist.length > 0 && canEdit && !isResolved const visibleChecklist = useMemo(() => { return onlyPending ? checklist.filter((item) => !item.done) : checklist }, [checklist, onlyPending]) const isLargeChecklist = totalItems > 12 return (
Checklist {totalItems > 0 ? ( <> {allDone}/{totalItems} itens concluídos {requiredTotal > 0 ? ` • ${requiredDone}/${requiredTotal} obrigatórios` : ""} ) : ( "Nenhum item cadastrado." )}
{allDone > 0 && !isResolved ? ( ) : null} {canEdit && !isResolved && canToggleAll ? ( ) : null} {canEdit && !isResolved && (templates ?? []).length > 0 ? (
) : null}
{progress}%
{checklist.length === 0 ? (
{canEdit && !isResolved ? "Adicione itens para controlar o atendimento antes de encerrar." : "Nenhum checklist informado."}
) : (
{visibleChecklist.length === 0 ? (
Nenhum item pendente.
) : ( visibleChecklist.map((item) => { const required = item.required ?? true const canToggle = canToggleDone && !isResolved const templateLabel = item.templateId ? templateNameById.get(String(item.templateId)) ?? null : null return (
{canEdit && !isResolved ? (
) : null}
) }) )}
)} {canEdit && !isResolved ? (
setNewText(e.target.value)} placeholder="Adicionar item do checklist..." className="h-8 w-full bg-white sm:max-w-md" onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() handleAdd() } }} />
) : null}
) }