From a49e8880117ac7279dfe56f5deee8b2992f2bcf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gjermund=20H=C3=B8s=C3=B8ien=20Wiggen?= Date: Sun, 7 Jun 2026 22:02:06 +0200 Subject: [PATCH] Add ticket list page with filters, status badges, create dialog --- web/src/app/page.tsx | 315 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 258 insertions(+), 57 deletions(-) diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 3f36f7c..c313967 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -1,65 +1,266 @@ -import Image from "next/image"; +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { useRouter } from "next/navigation"; +import { Plus, Filter } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { + Table, + TableHeader, + TableBody, + TableRow, + TableHead, + TableCell, +} from "@/components/ui/table"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; +import { getTickets, getQueues, createTicket } from "@/lib/api"; +import type { Ticket, Queue } from "@/lib/types"; + +const STATUS_COLORS: Record = { + new: "bg-blue-500/10 text-blue-400 border-blue-500/30", + open: "bg-sky-500/10 text-sky-400 border-sky-500/30", + in_progress: "bg-yellow-500/10 text-yellow-400 border-yellow-500/30", + resolved: "bg-green-500/10 text-green-400 border-green-500/30", + closed: "bg-neutral-500/10 text-neutral-400 border-neutral-500/30", +}; + +function formatDate(iso: string) { + return new Date(iso).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); +} + +export default function TicketsPage() { + const router = useRouter(); + const [tickets, setTickets] = useState([]); + const [queues, setQueues] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [filterQueue, setFilterQueue] = useState(""); + const [filterStatus, setFilterStatus] = useState(""); + const [dialogOpen, setDialogOpen] = useState(false); + const [newSubject, setNewSubject] = useState(""); + const [newQueueId, setNewQueueId] = useState(""); + const [creating, setCreating] = useState(false); + const [createError, setCreateError] = useState(null); + + const fetchData = useCallback(async (queueId?: string, status?: string) => { + setLoading(true); + setError(null); + const params: { queue_id?: string; status?: string } = {}; + if (queueId) params.queue_id = queueId; + if (status) params.status = status; + const [tRes, qRes] = await Promise.all([getTickets(params), getQueues()]); + if (tRes.error) setError(tRes.error); + else setTickets(tRes.data ?? []); + if (qRes.error && !error) setError(qRes.error); + else setQueues(qRes.data ?? []); + setLoading(false); + }, []); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + const handleFilter = () => { + fetchData(filterQueue || undefined, filterStatus || undefined); + }; + + const handleCreate = async () => { + if (!newSubject.trim() || !newQueueId) return; + setCreating(true); + setCreateError(null); + const { data, error } = await createTicket({ subject: newSubject.trim(), queue_id: newQueueId }); + setCreating(false); + if (error) { + setCreateError(error); + } else { + setDialogOpen(false); + setNewSubject(""); + setNewQueueId(""); + fetchData(filterQueue || undefined, filterStatus || undefined); + if (data) router.push(`/tickets/${data.id}`); + } + }; + + const queueName = (id: string) => queues.find((q) => q.id === id)?.name ?? id; -export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

+
+
+

Tickets

+ +
+ +
+ + + + + +
+ + {error && ( +
+ {error}
- + ) : tickets.length === 0 ? ( + -
+ ) : ( +
+ + + + ID + Subject + Queue + Status + Created + + + + {tickets.map((t) => ( + router.push(`/tickets/${t.id}`)} + > + + {t.id.slice(0, 8)} + + {t.subject} + + {queueName(t.queue_id)} + + + + {t.status.replace("_", " ")} + + + + {formatDate(t.created_at)} + + + ))} + +
+
+ )} + + + + + New Ticket + + Create a new ticket by providing a subject and queue. + + +
+
+ + setNewSubject(e.target.value)} + /> +
+
+ + +
+ {createError && ( +
{createError}
+ )} +
+ + + + +
+
); }