import { Headset, Pencil, Plus } from "lucide-react"; import { useEffect, useMemo, useState, type FormEvent } from "react"; import { Button, Dialog, DocumentationHelpLink, DismissibleAlert, FieldLabel, FormLayout, LoadingIndicator, PageScrollViewport, SelectionList, SelectionListItem, SelectionListItemContent, StatePanel, StatusBadge, WorkspaceActionBar, WorkspaceFrame, type PlatformRouteContext } from "@govoplan/core-webui"; import { listServiceProfiles, saveServiceProfile, type ServiceProfile } from "../../api/helpdesk"; type ProfileValues = { key: string; label: string; description: string; queueRef: string; ticketTypes: string[]; priorities: string[]; lowMinutes: string; normalMinutes: string; highMinutes: string; urgentMinutes: string; defaultMinutes: string; active: boolean; sortOrder: string; changeReason: string; }; const TYPES = ["request", "incident", "problem", "report"]; const PRIORITIES = ["low", "normal", "high", "urgent"]; export default function HelpdeskProfilesPage({ settings, auth }: PlatformRouteContext) { const [profiles, setProfiles] = useState([]); const [selectedId, setSelectedId] = useState(""); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const [editorError, setEditorError] = useState(""); const [editorOpen, setEditorOpen] = useState(false); const [editing, setEditing] = useState(null); function reload(signal?: AbortSignal) { setLoading(true); setError(""); return listServiceProfiles(settings, signal). then((result) => { setProfiles(result.profiles); setSelectedId((current) => result.profiles.some((item) => item.profile_id === current) ? current : result.profiles[0]?.profile_id ?? ""); }). catch((reason) => { if ((reason as Error).name !== "AbortError") setError(message(reason)); }). finally(() => setLoading(false)); } useEffect(() => { const controller = new AbortController(); void reload(controller.signal); return () => controller.abort(); }, [settings]); const selected = useMemo(() => profiles.find((item) => item.profile_id === selectedId) ?? null, [profiles, selectedId]); async function save(values: ProfileValues) { setSaving(true); setEditorError(""); try { const now = new Date().toISOString(); const profile: ServiceProfile = { tenant_id: auth.active_tenant?.id ?? auth.tenant.id, profile_id: editing?.profile_id ?? crypto.randomUUID(), profile_key: values.key, revision: editing ? editing.revision + 1 : 1, label: values.label, description: values.description || null, queue_ref: values.queueRef, ticket_types: values.ticketTypes, priorities: values.priorities, target_minutes: targetMinutes(values), default_target_minutes: positiveNumber(values.defaultMinutes), active: values.active, sort_order: Number(values.sortOrder), recorded_at: now, change_reason: values.changeReason }; const saved = await saveServiceProfile(settings, profile, editing?.revision ?? null); setEditorOpen(false); setEditing(null); await reload(); setSelectedId(saved.profile_id); } catch (reason) { setEditorError(message(reason)); } finally { setSaving(false); } } return (
void reload(), loading }} contextActions={{profiles.length} profiles · ordered first match wins} helpAction={} createAction={} /> {error && setError("")}>{error}}
{loading && } {!loading && profiles.length === 0 && } {profiles.map((profile) => setSelectedId(profile.profile_id)}> } title={profile.label} description={`${profile.queue_ref} · order ${profile.sort_order} · revision ${profile.revision}`} /> )} {selected ? { setEditing(selected); setEditorError(""); setEditorOpen(true); }} /> : }
{ setEditorOpen(false); setEditorError(""); }} onSave={save} />
); } function ProfileDetail({ profile, onEdit }: { profile: ServiceProfile; onEdit: () => void }) { return
{profile.profile_key} · order {profile.sort_order}

{profile.label}

{profile.description &&

{profile.description}

}

Service targets

{PRIORITIES.map((priority) => )}

Applied consequence

The first active matching profile sets the Ticket queue and target. If no profile matches, Ticket intake succeeds and authorized staff route it manually.

; } function ProfileEditorDialog({ open, profile, saving, error, onClose, onSave }: { open: boolean; profile: ServiceProfile | null; saving: boolean; error: string; onClose: () => void; onSave: (values: ProfileValues) => Promise; }) { const [values, setValues] = useState(() => profileValues(profile)); useEffect(() => { if (open) setValues(profileValues(profile)); }, [open, profile]); function set(key: K, value: ProfileValues[K]) { setValues((current) => ({ ...current, [key]: value })); } function submit(event: FormEvent) { event.preventDefault(); void onSave(values); } return }> {error && {error}}