From bc5ba0c73a1b23920a3e6dcb1b4b6f7fdc891652 Mon Sep 17 00:00:00 2001 From: rever-tecnologia Date: Mon, 15 Dec 2025 11:46:13 -0300 Subject: [PATCH] =?UTF-8?q?feat(checklist):=20adiciona=20op=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20desmarcar=20todos=20os=20itens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O botão "Concluir todos" agora alterna para "Desmarcar todos" quando todos os itens do checklist estão marcados como concluídos. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- convex/tickets.ts | 28 +++++++++++++ .../tickets/ticket-checklist-card.tsx | 41 +++++++++++++------ 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/convex/tickets.ts b/convex/tickets.ts index f80342b..1b2b91a 100644 --- a/convex/tickets.ts +++ b/convex/tickets.ts @@ -2722,6 +2722,34 @@ export const completeAllChecklistItems = mutation({ }, }); +export const uncompleteAllChecklistItems = mutation({ + args: { + ticketId: v.id("tickets"), + actorId: v.id("users"), + }, + handler: async (ctx, { ticketId, actorId }) => { + const ticket = await ctx.db.get(ticketId); + if (!ticket) { + throw new ConvexError("Ticket não encontrado"); + } + const ticketDoc = ticket as Doc<"tickets">; + const viewer = await requireTicketStaff(ctx, actorId, ticketDoc); + ensureChecklistEditor(viewer); + + const checklist = normalizeTicketChecklist(ticketDoc.checklist); + if (checklist.length === 0) return { ok: true }; + + const now = Date.now(); + const nextChecklist = checklist.map((item) => { + if (item.done === false) return item; + return { ...item, done: false, doneAt: undefined, doneBy: undefined }; + }); + + await ctx.db.patch(ticketId, { checklist: nextChecklist, updatedAt: now }); + return { ok: true }; + }, +}); + export const applyChecklistTemplate = mutation({ args: { ticketId: v.id("tickets"), diff --git a/src/components/tickets/ticket-checklist-card.tsx b/src/components/tickets/ticket-checklist-card.tsx index 469ac80..bbb8dc2 100644 --- a/src/components/tickets/ticket-checklist-card.tsx +++ b/src/components/tickets/ticket-checklist-card.tsx @@ -2,7 +2,7 @@ import { useMemo, useState } from "react" import { useMutation, useQuery } from "convex/react" -import { CheckCheck, ListChecks, Plus, Trash2 } from "lucide-react" +import { CheckCheck, ListChecks, Plus, RotateCcw, Trash2 } from "lucide-react" import { toast } from "sonner" import { api } from "@/convex/_generated/api" @@ -62,6 +62,7 @@ export function TicketChecklistCard({ 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( @@ -167,20 +168,27 @@ export function TicketChecklistCard({ } } - const handleCompleteAll = async () => { + const allItemsDone = checklist.length > 0 && allDone === totalItems + + const handleToggleAll = async () => { if (!actorId || !canEdit || isResolved) return setCompletingAll(true) try { - await completeAllChecklistItems({ ticketId: ticket.id as Id<"tickets">, actorId }) - toast.success("Checklist concluído.") + 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 concluir checklist.") + toast.error(error instanceof Error ? error.message : "Falha ao atualizar checklist.") } finally { setCompletingAll(false) } } - const canComplete = checklist.length > 0 && requiredPending > 0 && canEdit && !isResolved + const canToggleAll = checklist.length > 0 && canEdit && !isResolved const visibleChecklist = useMemo(() => { return onlyPending ? checklist.filter((item) => !item.done) : checklist }, [checklist, onlyPending]) @@ -220,18 +228,27 @@ export function TicketChecklistCard({ {onlyPending ? "Ver todos" : "Somente pendentes"} ) : null} - {canEdit && !isResolved ? ( + {canEdit && !isResolved && canToggleAll ? ( ) : null} {canEdit && !isResolved && (templates ?? []).length > 0 ? (