feat(checklist): adiciona opção de desmarcar todos os itens
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 <noreply@anthropic.com>
This commit is contained in:
parent
2c21daee79
commit
bc5ba0c73a
2 changed files with 57 additions and 12 deletions
|
|
@ -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({
|
export const applyChecklistTemplate = mutation({
|
||||||
args: {
|
args: {
|
||||||
ticketId: v.id("tickets"),
|
ticketId: v.id("tickets"),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useMemo, useState } from "react"
|
import { useMemo, useState } from "react"
|
||||||
import { useMutation, useQuery } from "convex/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 { toast } from "sonner"
|
||||||
|
|
||||||
import { api } from "@/convex/_generated/api"
|
import { api } from "@/convex/_generated/api"
|
||||||
|
|
@ -62,6 +62,7 @@ export function TicketChecklistCard({
|
||||||
const setChecklistItemRequired = useMutation(api.tickets.setChecklistItemRequired)
|
const setChecklistItemRequired = useMutation(api.tickets.setChecklistItemRequired)
|
||||||
const removeChecklistItem = useMutation(api.tickets.removeChecklistItem)
|
const removeChecklistItem = useMutation(api.tickets.removeChecklistItem)
|
||||||
const completeAllChecklistItems = useMutation(api.tickets.completeAllChecklistItems)
|
const completeAllChecklistItems = useMutation(api.tickets.completeAllChecklistItems)
|
||||||
|
const uncompleteAllChecklistItems = useMutation(api.tickets.uncompleteAllChecklistItems)
|
||||||
const applyChecklistTemplate = useMutation(api.tickets.applyChecklistTemplate)
|
const applyChecklistTemplate = useMutation(api.tickets.applyChecklistTemplate)
|
||||||
|
|
||||||
const templates = useQuery(
|
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
|
if (!actorId || !canEdit || isResolved) return
|
||||||
setCompletingAll(true)
|
setCompletingAll(true)
|
||||||
try {
|
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 })
|
await completeAllChecklistItems({ ticketId: ticket.id as Id<"tickets">, actorId })
|
||||||
toast.success("Checklist concluído.")
|
toast.success("Checklist concluído.")
|
||||||
|
}
|
||||||
} catch (error) {
|
} 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 {
|
} finally {
|
||||||
setCompletingAll(false)
|
setCompletingAll(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canComplete = checklist.length > 0 && requiredPending > 0 && canEdit && !isResolved
|
const canToggleAll = checklist.length > 0 && canEdit && !isResolved
|
||||||
const visibleChecklist = useMemo(() => {
|
const visibleChecklist = useMemo(() => {
|
||||||
return onlyPending ? checklist.filter((item) => !item.done) : checklist
|
return onlyPending ? checklist.filter((item) => !item.done) : checklist
|
||||||
}, [checklist, onlyPending])
|
}, [checklist, onlyPending])
|
||||||
|
|
@ -220,18 +228,27 @@ export function TicketChecklistCard({
|
||||||
{onlyPending ? "Ver todos" : "Somente pendentes"}
|
{onlyPending ? "Ver todos" : "Somente pendentes"}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{canEdit && !isResolved ? (
|
{canEdit && !isResolved && canToggleAll ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
onClick={handleCompleteAll}
|
onClick={handleToggleAll}
|
||||||
disabled={!canComplete || completingAll}
|
disabled={completingAll}
|
||||||
title={requiredPending > 0 ? "Marcar todos os itens como concluídos" : "Checklist já está concluído"}
|
title={allItemsDone ? "Desmarcar todos os itens" : "Marcar todos os itens como concluídos"}
|
||||||
>
|
>
|
||||||
|
{allItemsDone ? (
|
||||||
|
<>
|
||||||
|
<RotateCcw className="size-4" />
|
||||||
|
Desmarcar todos
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<CheckCheck className="size-4" />
|
<CheckCheck className="size-4" />
|
||||||
Concluir todos
|
Concluir todos
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{canEdit && !isResolved && (templates ?? []).length > 0 ? (
|
{canEdit && !isResolved && (templates ?? []).length > 0 ? (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue