feat(ui): checklist com rolagem e progresso
This commit is contained in:
parent
7c82ef18b3
commit
e0fcd5bf7c
1 changed files with 176 additions and 141 deletions
|
|
@ -15,6 +15,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Progress } from "@/components/ui/progress"
|
import { Progress } from "@/components/ui/progress"
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
|
|
||||||
type ChecklistTemplateRow = {
|
type ChecklistTemplateRow = {
|
||||||
|
|
@ -52,7 +53,8 @@ export function TicketChecklistCard({
|
||||||
const requiredPending = useMemo(() => countRequiredPending(checklist), [checklist])
|
const requiredPending = useMemo(() => countRequiredPending(checklist), [checklist])
|
||||||
const allDone = useMemo(() => countAllDone(checklist), [checklist])
|
const allDone = useMemo(() => countAllDone(checklist), [checklist])
|
||||||
|
|
||||||
const progress = requiredTotal > 0 ? Math.round((requiredDone / requiredTotal) * 100) : 100
|
const totalItems = checklist.length
|
||||||
|
const progress = totalItems > 0 ? Math.round((allDone / totalItems) * 100) : 0
|
||||||
|
|
||||||
const addChecklistItem = useMutation(api.tickets.addChecklistItem)
|
const addChecklistItem = useMutation(api.tickets.addChecklistItem)
|
||||||
const updateChecklistItemText = useMutation(api.tickets.updateChecklistItemText)
|
const updateChecklistItemText = useMutation(api.tickets.updateChecklistItemText)
|
||||||
|
|
@ -92,6 +94,7 @@ export function TicketChecklistCard({
|
||||||
const [selectedTemplateId, setSelectedTemplateId] = useState<string>("")
|
const [selectedTemplateId, setSelectedTemplateId] = useState<string>("")
|
||||||
const [applyingTemplate, setApplyingTemplate] = useState(false)
|
const [applyingTemplate, setApplyingTemplate] = useState(false)
|
||||||
const [completingAll, setCompletingAll] = useState(false)
|
const [completingAll, setCompletingAll] = useState(false)
|
||||||
|
const [onlyPending, setOnlyPending] = useState(false)
|
||||||
|
|
||||||
const handleAdd = async () => {
|
const handleAdd = async () => {
|
||||||
if (!actorId || !canEdit || isResolved) return
|
if (!actorId || !canEdit || isResolved) return
|
||||||
|
|
@ -178,6 +181,10 @@ export function TicketChecklistCard({
|
||||||
}
|
}
|
||||||
|
|
||||||
const canComplete = checklist.length > 0 && requiredPending > 0 && canEdit && !isResolved
|
const canComplete = checklist.length > 0 && requiredPending > 0 && canEdit && !isResolved
|
||||||
|
const visibleChecklist = useMemo(() => {
|
||||||
|
return onlyPending ? checklist.filter((item) => !item.done) : checklist
|
||||||
|
}, [checklist, onlyPending])
|
||||||
|
const isLargeChecklist = totalItems > 12
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="rounded-2xl border border-slate-200 bg-white shadow-sm">
|
<Card className="rounded-2xl border border-slate-200 bg-white shadow-sm">
|
||||||
|
|
@ -188,14 +195,31 @@ export function TicketChecklistCard({
|
||||||
Checklist
|
Checklist
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-sm text-neutral-600">
|
<CardDescription className="text-sm text-neutral-600">
|
||||||
{requiredTotal > 0
|
{totalItems > 0 ? (
|
||||||
? `${requiredDone}/${requiredTotal} itens obrigatórios concluídos`
|
<>
|
||||||
: checklist.length > 0
|
{allDone}/{totalItems} itens concluídos
|
||||||
? `${allDone}/${checklist.length} itens concluídos`
|
{requiredTotal > 0 ? ` • ${requiredDone}/${requiredTotal} obrigatórios` : ""}
|
||||||
: "Nenhum item cadastrado."}
|
</>
|
||||||
|
) : (
|
||||||
|
"Nenhum item cadastrado."
|
||||||
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{allDone > 0 && !isResolved ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingId(null)
|
||||||
|
setEditingText("")
|
||||||
|
setOnlyPending((prev) => !prev)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{onlyPending ? "Ver todos" : "Somente pendentes"}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
{canEdit && !isResolved ? (
|
{canEdit && !isResolved ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -238,15 +262,24 @@ export function TicketChecklistCard({
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<Progress value={progress} className="h-2" indicatorClassName="bg-emerald-500" />
|
<div className="flex items-center gap-3">
|
||||||
|
<Progress value={progress} className="h-2 flex-1" indicatorClassName="bg-neutral-900" />
|
||||||
|
<span className="text-xs font-semibold tabular-nums text-neutral-800">{progress}%</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{checklist.length === 0 ? (
|
{checklist.length === 0 ? (
|
||||||
<div className="rounded-xl border border-dashed border-slate-200 p-4 text-sm text-neutral-600">
|
<div className="rounded-xl border border-dashed border-slate-200 p-4 text-sm text-neutral-600">
|
||||||
{canEdit && !isResolved ? "Adicione itens para controlar o atendimento antes de encerrar." : "Nenhum checklist informado."}
|
{canEdit && !isResolved ? "Adicione itens para controlar o atendimento antes de encerrar." : "Nenhum checklist informado."}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<ScrollArea className={isLargeChecklist ? "max-h-[60vh] pr-1" : undefined}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{checklist.map((item) => {
|
{visibleChecklist.length === 0 ? (
|
||||||
|
<div className="rounded-xl border border-dashed border-slate-200 bg-white p-4 text-sm text-neutral-600">
|
||||||
|
Nenhum item pendente.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
visibleChecklist.map((item) => {
|
||||||
const required = item.required ?? true
|
const required = item.required ?? true
|
||||||
const canToggle = canToggleDone && !isResolved
|
const canToggle = canToggleDone && !isResolved
|
||||||
const templateLabel = item.templateId ? templateNameById.get(String(item.templateId)) ?? null : null
|
const templateLabel = item.templateId ? templateNameById.get(String(item.templateId)) ?? null : null
|
||||||
|
|
@ -388,8 +421,10 @@ export function TicketChecklistCard({
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canEdit && !isResolved ? (
|
{canEdit && !isResolved ? (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue