import { useEffect, useMemo, useState } from "react"; import { Pencil, Plus, Search, Trash2 } from "lucide-react"; import type { ApiSettings } from "@govoplan/core-webui"; import { createSystemRole, deleteSystemRole, fetchPermissionCatalog, fetchSystemRoles, updateSystemRole, type PermissionItem, type RoleSummary } from "../../api/admin"; import { Button } from "@govoplan/core-webui"; import { ConfirmDialog } from "@govoplan/core-webui"; import { DataGrid, type DataGridColumn } from "@govoplan/core-webui"; import { Dialog } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui"; import { StatusBadge } from "@govoplan/core-webui"; import { AdminIconButton, AdminPageLayout, AdminSelectionList, adminErrorMessage, joinLabels } from "@govoplan/core-webui"; const emptyDraft = { slug: "", name: "", description: "", permissions: [] as string[], isAssignable: true }; export default function SystemRolesPanel({ settings, canWrite, onAuthRefresh }: { settings: ApiSettings; canWrite: boolean; onAuthRefresh: () => Promise; }) { const [roles, setRoles] = useState([]); const [permissions, setPermissions] = useState([]); const [editing, setEditing] = useState(null); const [viewing, setViewing] = useState(null); const [deleting, setDeleting] = useState(null); const [draft, setDraft] = useState(emptyDraft); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); async function load() { setLoading(true); setError(""); try { const [nextRoles, catalogue] = await Promise.all([ fetchSystemRoles(settings), fetchPermissionCatalog(settings) ]); setRoles(nextRoles); setPermissions(catalogue.filter((item) => item.level === "system")); } catch (err) { setError(adminErrorMessage(err)); } finally { setLoading(false); } } useEffect(() => { void load(); }, [settings.accessToken, settings.apiBaseUrl]); function openCreate() { setDraft(emptyDraft); setEditing("new"); } function openEdit(role: RoleSummary) { setDraft({ slug: role.slug, name: role.name, description: role.description || "", permissions: role.permissions, isAssignable: role.is_assignable }); setEditing(role); } async function save() { setBusy(true); setError(""); try { if (editing === "new") { await createSystemRole(settings, { slug: draft.slug, name: draft.name, description: draft.description || null, permissions: draft.permissions }); setSuccess(`System role ${draft.name} created.`); } else if (editing) { await updateSystemRole(settings, editing.id, { name: draft.name, description: draft.description || null, permissions: draft.permissions, is_assignable: draft.isAssignable }); setSuccess(`System role ${draft.name} updated.`); } setEditing(null); await load(); await onAuthRefresh(); } catch (err) { setError(adminErrorMessage(err)); } finally { setBusy(false); } } async function remove() { if (!deleting) return; setBusy(true); setError(""); try { await deleteSystemRole(settings, deleting.id); setSuccess(`System role ${deleting.name} deleted.`); setDeleting(null); await load(); await onAuthRefresh(); } catch (err) { setError(adminErrorMessage(err)); } finally { setBusy(false); } } const columns = useMemo[]>(() => [ { id: "role", header: "System role", width: 240, minWidth: 180, maxWidth: 380, resizable: true, sticky: "start", sortable: true, filterable: true, value: (row) => `${row.name} ${row.slug}`, render: (row) =>
{row.name}
{row.slug}
}, { id: "description", header: "Description", width: 360, fill: true, minWidth: 220, maxWidth: 640, resizable: true, sortable: true, filterable: true, value: (row) => row.description || "", render: (row) => row.description || "—" }, { id: "permissions", header: "Permissions", width: 120, resizable: false, sortable: true, filterable: true, filterType: "integer", value: (row) => row.effective_permission_count, render: (row) => String(row.effective_permission_count) }, { id: "assignable", header: "Assignable", width: 120, resizable: false, sortable: true, filterable: true, value: (row) => row.is_assignable ? "yes" : "no", render: (row) => }, { id: "assignments", header: "Accounts", width: 110, resizable: false, sortable: true, filterable: true, filterType: "integer", value: (row) => row.user_assignments }, { id: "actions", header: "Actions", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => { const protectedOwner = row.slug === "system_owner"; return
} onClick={() => setViewing(row)} /> } onClick={() => openEdit(row)} disabled={!canWrite || protectedOwner} /> } variant="danger" onClick={() => setDeleting(row)} disabled={!canWrite || protectedOwner || row.user_assignments > 0} />
; } } ], [canWrite]); return ( <> } variant="primary" onClick={openCreate} disabled={!canWrite} />} >
row.id} emptyText="No system roles found." />
!busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<>} >
setDraft({ ...draft, name: event.target.value })} /> setDraft({ ...draft, slug: event.target.value })} />