feat(checklist): adiciona tipo pergunta e descricao nos itens

- Adiciona campo `type` (checkbox/question) nos itens do checklist
- Adiciona campo `description` para descricao do item
- Adiciona campo `options` para opcoes de resposta em perguntas
- Adiciona campo `answer` para resposta selecionada
- Atualiza UI para mostrar descricao e opcoes de pergunta
- Cria componente radio-group para selecao de respostas
- Adiciona mutation setChecklistItemAnswer para salvar respostas

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-15 16:27:23 -03:00
parent 98b23af4b2
commit 0f3ba07a5e
10 changed files with 446 additions and 76 deletions

View file

@ -15,6 +15,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { Progress } from "@/components/ui/progress"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
@ -60,6 +61,7 @@ export function TicketChecklistCard({
const updateChecklistItemText = useMutation(api.tickets.updateChecklistItemText)
const setChecklistItemDone = useMutation(api.tickets.setChecklistItemDone)
const setChecklistItemRequired = useMutation(api.tickets.setChecklistItemRequired)
const setChecklistItemAnswer = useMutation(api.tickets.setChecklistItemAnswer)
const removeChecklistItem = useMutation(api.tickets.removeChecklistItem)
const completeAllChecklistItems = useMutation(api.tickets.completeAllChecklistItems)
const uncompleteAllChecklistItems = useMutation(api.tickets.uncompleteAllChecklistItems)
@ -300,28 +302,36 @@ export function TicketChecklistCard({
const required = item.required ?? true
const canToggle = canToggleDone && !isResolved
const templateLabel = item.templateId ? templateNameById.get(String(item.templateId)) ?? null : null
const isQuestion = item.type === "question"
const options = item.options ?? []
return (
<div key={item.id} className="flex items-start justify-between gap-3 rounded-xl border border-slate-200 bg-white px-3 py-2">
<label className="flex min-w-0 flex-1 items-start gap-3">
<Checkbox
checked={item.done}
disabled={!canToggle || !actorId}
onCheckedChange={async (checked) => {
if (!actorId || !canToggle) return
try {
await setChecklistItemDone({
ticketId: ticket.id as Id<"tickets">,
actorId,
itemId: item.id,
done: Boolean(checked),
})
} catch (error) {
toast.error(error instanceof Error ? error.message : "Falha ao atualizar checklist.")
}
}}
className="mt-1"
/>
<div className="flex min-w-0 flex-1 items-start gap-3">
{isQuestion ? (
<div className="mt-1 flex size-5 shrink-0 items-center justify-center rounded-full border border-slate-300 bg-slate-50 text-xs font-medium text-slate-600">
?
</div>
) : (
<Checkbox
checked={item.done}
disabled={!canToggle || !actorId}
onCheckedChange={async (checked) => {
if (!actorId || !canToggle) return
try {
await setChecklistItemDone({
ticketId: ticket.id as Id<"tickets">,
actorId,
itemId: item.id,
done: Boolean(checked),
})
} catch (error) {
toast.error(error instanceof Error ? error.message : "Falha ao atualizar checklist.")
}
}}
className="mt-1"
/>
)}
<div className="min-w-0 flex-1">
{editingId === item.id && canEdit && !isResolved ? (
<div className="flex items-center gap-2">
@ -357,18 +367,57 @@ export function TicketChecklistCard({
</Button>
</div>
) : (
<p
className={`truncate text-sm ${item.done ? "text-neutral-500 line-through" : "text-neutral-900"}`}
title={item.text}
onDoubleClick={() => {
if (!canEdit || isResolved) return
setEditingId(item.id)
setEditingText(item.text)
}}
>
{item.text}
</p>
<>
<p
className={`text-sm ${item.done ? "text-neutral-500 line-through" : "text-neutral-900"}`}
title={item.text}
onDoubleClick={() => {
if (!canEdit || isResolved) return
setEditingId(item.id)
setEditingText(item.text)
}}
>
{item.text}
</p>
{item.description && (
<p className="mt-0.5 text-xs text-slate-500">
{item.description}
</p>
)}
</>
)}
{isQuestion && options.length > 0 && (
<RadioGroup
value={item.answer ?? ""}
onValueChange={async (value) => {
if (!actorId || !canToggle) return
try {
await setChecklistItemAnswer({
ticketId: ticket.id as Id<"tickets">,
actorId,
itemId: item.id,
answer: value,
})
} catch (error) {
toast.error(error instanceof Error ? error.message : "Falha ao responder pergunta.")
}
}}
disabled={!canToggle || !actorId}
className="mt-2 flex flex-wrap gap-3"
>
{options.map((option) => (
<label
key={option}
className="flex cursor-pointer items-center gap-1.5 text-sm text-slate-700"
>
<RadioGroupItem value={option} />
{option}
</label>
))}
</RadioGroup>
)}
<div className="mt-1 flex flex-wrap items-center gap-2">
{required ? (
<Badge variant="secondary" className="rounded-full text-[11px]">
@ -379,6 +428,11 @@ export function TicketChecklistCard({
Opcional
</Badge>
)}
{isQuestion && (
<Badge variant="outline" className="rounded-full border-cyan-200 bg-cyan-50 text-[11px] text-cyan-700">
Pergunta
</Badge>
)}
{templateLabel ? (
<Badge variant="outline" className="rounded-full text-[11px]">
Template: {templateLabel}
@ -386,7 +440,7 @@ export function TicketChecklistCard({
) : null}
</div>
</div>
</label>
</div>
{canEdit && !isResolved ? (
<div className="flex shrink-0 items-center gap-1">