From ab5af479c74809000c2ae6c9d15d604a1e35e454 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Tue, 1 Sep 2026 17:48:52 -0400 Subject: [PATCH] feat(helpdesk): add ticket edit and vote controls Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../frontend/helpdesk/EditTicketDialog.tsx | 189 ++++++++++++++++++ .../frontend/helpdesk/TicketVoteButton.tsx | 67 +++++++ 2 files changed, 256 insertions(+) create mode 100644 src/components/frontend/helpdesk/EditTicketDialog.tsx create mode 100644 src/components/frontend/helpdesk/TicketVoteButton.tsx diff --git a/src/components/frontend/helpdesk/EditTicketDialog.tsx b/src/components/frontend/helpdesk/EditTicketDialog.tsx new file mode 100644 index 0000000..9accfa2 --- /dev/null +++ b/src/components/frontend/helpdesk/EditTicketDialog.tsx @@ -0,0 +1,189 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { PencilIcon } from "lucide-react"; +import { toast } from "sonner"; +import type { Ticket } from "@/payload-types"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { editTicket } from "@/app/(frontend)/helpdesk/actions"; +import { + TICKET_CATEGORIES, + TICKET_PRIORITIES, + type TicketCategory, + type TicketPriority, +} from "@/lib/tickets/ticketMeta"; + +interface EditTicketDialogProps { + readonly ticket: Ticket; + readonly description: string; +} + +function isTicketCategory(value: string): value is TicketCategory { + return TICKET_CATEGORIES.some((option) => option.value === value); +} + +function isTicketPriority(value: string): value is TicketPriority { + return TICKET_PRIORITIES.some((option) => option.value === value); +} + +export function EditTicketDialog({ ticket, description }: EditTicketDialogProps) { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [title, setTitle] = useState(ticket.title); + const [details, setDetails] = useState(description); + const [category, setCategory] = useState(ticket.category); + const [priority, setPriority] = useState(ticket.priority); + const [pending, setPending] = useState(false); + const [error, setError] = useState(null); + + async function handleSubmit() { + setPending(true); + setError(null); + const result = await editTicket({ + ticketId: ticket.id, + title, + description: details, + category, + priority, + }); + setPending(false); + + if (!result.success) { + setError(result.error ?? "Could not edit the ticket."); + return; + } + + setOpen(false); + toast.success("Ticket updated"); + router.refresh(); + } + + return ( + + + + + + + Edit ticket + + Update the request details. The previous and new values are retained in the audit trail. + + +
{ + event.preventDefault(); + void handleSubmit(); + }} + > +
+ + setTitle(event.target.value)} + /> +
+
+
+ + +
+
+ + +
+
+
+ +