feat(ui,tickets): aplicar visual Rever (badges revertidas), header com play/pause, edição inline com cancelar, empty states e toasts centralizados; novas mutations Convex (updateSubject/updateSummary/toggleWork)

This commit is contained in:
esdrasrenan 2025-10-04 17:13:13 -03:00
parent 881bb7bfdd
commit 6c57c691f3
14 changed files with 512 additions and 307 deletions

View file

@ -5,19 +5,21 @@ import { useMutation } from "convex/react"
// @ts-ignore
import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel"
import type { TicketPriority } from "@/lib/schemas/ticket"
import { useAuth } from "@/lib/auth-client"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Badge } from "@/components/ui/badge"
import { toast } from "sonner"
import { ArrowDown, ArrowRight, ArrowUp, ChevronsUp } from "lucide-react"
const labels: Record<string, string> = {
const labels: Record<TicketPriority, string> = {
LOW: "Baixa",
MEDIUM: "Média",
HIGH: "Alta",
URGENT: "Urgente",
}
function badgeClass(p: string) {
function badgeClass(p: TicketPriority) {
switch (p) {
case "URGENT":
return "bg-red-100 text-red-700"
@ -30,20 +32,29 @@ function badgeClass(p: string) {
}
}
export function PrioritySelect({ ticketId, value }: { ticketId: Id<"tickets">; value: "LOW" | "MEDIUM" | "HIGH" | "URGENT" }) {
function PriorityIcon({ p }: { p: TicketPriority }) {
const cls = "size-3.5 text-cyan-600"
if (p === "LOW") return <ArrowDown className={cls} />
if (p === "MEDIUM") return <ArrowRight className={cls} />
if (p === "HIGH") return <ArrowUp className={cls} />
return <ChevronsUp className={cls} />
}
export function PrioritySelect({ ticketId, value }: { ticketId: string; value: TicketPriority }) {
const updatePriority = useMutation(api.tickets.updatePriority)
const [priority, setPriority] = useState(value)
const [priority, setPriority] = useState<TicketPriority>(value)
const { userId } = useAuth()
return (
<Select
value={priority}
onValueChange={async (val) => {
const prev = priority
setPriority(val as typeof priority)
const next = val as TicketPriority
setPriority(next)
toast.loading("Atualizando prioridade...", { id: "prio" })
try {
if (!userId) throw new Error("No user")
await updatePriority({ ticketId, priority: val as any, actorId: userId as Id<"users"> })
await updatePriority({ ticketId: ticketId as unknown as Id<"tickets">, priority: next, actorId: userId as Id<"users"> })
toast.success("Prioridade atualizada!", { id: "prio" })
} catch {
setPriority(prev)
@ -53,16 +64,19 @@ export function PrioritySelect({ ticketId, value }: { ticketId: Id<"tickets">; v
>
<SelectTrigger className="h-7 w-[140px] border-transparent bg-muted/50 px-2">
<SelectValue>
<Badge className={`rounded-full px-2 py-0.5 ${badgeClass(priority)}`}>{labels[priority]}</Badge>
<Badge className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 ${badgeClass(priority)}`}>
<PriorityIcon p={priority} /> {labels[priority]}
</Badge>
</SelectValue>
</SelectTrigger>
<SelectContent>
{(["LOW","MEDIUM","HIGH","URGENT"] as const).map((p) => (
<SelectItem key={p} value={p}>
<span className="inline-flex items-center gap-2"><span className={`h-2 w-2 rounded-full ${p==="URGENT"?"bg-red-500":p==="HIGH"?"bg-amber-500":p==="MEDIUM"?"bg-blue-500":"bg-slate-400"}`}></span>{labels[p]}</span>
<span className="inline-flex items-center gap-2"><PriorityIcon p={p} />{labels[p]}</span>
</SelectItem>
))}
</SelectContent>
</Select>
)
}