From 73cf283f068f1df4eaeb0e109d17b465a2320014 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:11 +0200 Subject: [PATCH] Add admin page with 4 tabs for managing queues, lifecycles, scrips, and custom fields --- web/src/app/admin/page.tsx | 745 +++++++++++++++++++++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 web/src/app/admin/page.tsx diff --git a/web/src/app/admin/page.tsx b/web/src/app/admin/page.tsx new file mode 100644 index 0000000..7cf1687 --- /dev/null +++ b/web/src/app/admin/page.tsx @@ -0,0 +1,745 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { Plus, RefreshCw } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +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 { + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@/components/ui/tabs"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; +import { + getQueues, + createQueue, + getLifecycles, + createLifecycle, + getScrips, + createScrip, + getCustomFields, + createCustomField, +} from "@/lib/api"; +import type { Queue, Lifecycle, Scrip, CustomField } from "@/lib/types"; + +const LIFECYCLE_PLACEHOLDER = `{ + "statuses": { + "initial": ["new"], + "active": ["open", "in_progress"], + "inactive": ["resolved", "closed"] + }, + "transitions": { + "new": ["open"], + "open": ["in_progress", "resolved"], + "in_progress": ["resolved"], + "*": ["closed"] + } +}`; + +export default function AdminPage() { + return ( +
+

Admin

+ + + Queues + Lifecycles + Scrips + Custom Fields + + + + + + + + + + + + + + +
+ ); +} + +function QueuesTab() { + const [queues, setQueues] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [saving, setSaving] = useState(false); + const [saveError, setSaveError] = useState(null); + + const fetchQueues = useCallback(async () => { + setLoading(true); + setError(null); + const { data, error } = await getQueues(); + if (error) setError(error); + else setQueues(data ?? []); + setLoading(false); + }, []); + + useEffect(() => { + fetchQueues(); + }, [fetchQueues]); + + const handleCreate = async () => { + if (!name.trim()) return; + setSaving(true); + setSaveError(null); + const { error } = await createQueue({ name: name.trim(), description: description.trim() || undefined }); + setSaving(false); + if (error) { + setSaveError(error); + } else { + setDialogOpen(false); + setName(""); + setDescription(""); + fetchQueues(); + } + }; + + return ( +
+
+

Queues

+ +
+ + {error &&
{error}
} + {loading ? ( +
Loading...
+ ) : ( + + + + Name + Description + + + + {queues.length === 0 ? ( + + + No queues yet. + + + ) : ( + queues.map((q) => ( + + {q.name} + {q.description ?? "-"} + + )) + )} + +
+ )} + + + + + Add Queue + Create a new ticket queue. + +
+
+ + setName(e.target.value)} + /> +
+
+ +