import { useEffect, useMemo, useRef, useState } from "react"; import { Pencil, Plus, Search, Trash2 } from "lucide-react"; import type { ApiSettings } from "@govoplan/core-webui"; import { createSystemRole, deleteSystemRole, fetchPermissionCatalog, fetchSystemRolesDelta, 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, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui"; import { loadDeltaRows } from "./utils/deltaRows"; 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 rolesRef = useRef([]); const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks(); const [editing, setEditing] = useState(null); const [viewing, setViewing] = useState(null); const [deleting, setDeleting] = useState(null); const [draft, setDraft] = useState(emptyDraft); const [savedDraftKey, setSavedDraftKey] = useState(draftKey(emptyDraft)); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const dirty = editing !== null && draftKey(draft) !== savedDraftKey; useUnsavedDraftGuard({ dirty, onSave: save, onDiscard: closeEditor }); async function load() { setLoading(true); setError(""); try { const [nextRoles, catalogue] = await Promise.all([ loadDeltaRows(rolesRef.current, "access:system-roles", getDeltaWatermark, setDeltaWatermark, (since) => fetchSystemRolesDelta(settings, { since }), (response) => response.roles, (role) => role.id, "access_system_role", sortSystemRoles), fetchPermissionCatalog(settings)] ); rolesRef.current = nextRoles; setRoles(nextRoles); setPermissions(catalogue.filter((item) => item.level === "system")); } catch (err) { setError(adminErrorMessage(err)); } finally { setLoading(false); } } useEffect(() => { rolesRef.current = []; resetDeltaWatermark(); void load(); }, [settings.accessToken, settings.apiBaseUrl, resetDeltaWatermark]); function openCreate() { setDraft(emptyDraft); setSavedDraftKey(draftKey(emptyDraft)); setEditing("new"); } function openEdit(role: RoleSummary) { const nextDraft = { slug: role.slug, name: role.name, description: role.description || "", permissions: role.permissions, isAssignable: role.is_assignable }; setDraft(nextDraft); setSavedDraftKey(draftKey(nextDraft)); setEditing(role); } function closeEditor() { setEditing(null); setDraft(emptyDraft); setSavedDraftKey(draftKey(emptyDraft)); } async function save(): Promise { setBusy(true); setError(""); try { if (editing === "new") { await createSystemRole(settings, { slug: draft.slug, name: draft.name, description: draft.description || null, permissions: draft.permissions }); setSuccess(i18nMessage("i18n:govoplan-access.system_role_value_created.9c1a6bc8", { value0: draft.name })); } else if (editing) { await updateSystemRole(settings, editing.id, { name: draft.name, description: draft.description || null, permissions: draft.permissions, is_assignable: draft.isAssignable }); setSuccess(i18nMessage("i18n:govoplan-access.system_role_value_updated.3a3f8862", { value0: draft.name })); } setEditing(null); await load(); await onAuthRefresh(); return true; } catch (err) { setError(adminErrorMessage(err)); return false; } finally { setBusy(false); } } async function remove() { if (!deleting) return; setBusy(true); setError(""); try { await deleteSystemRole(settings, deleting.id); setSuccess(i18nMessage("i18n:govoplan-access.system_role_value_deleted.7b18a6f8", { value0: deleting.name })); setDeleting(null); await load(); await onAuthRefresh(); } catch (err) { setError(adminErrorMessage(err)); } finally { setBusy(false); } } const columns = useMemo[]>(() => [ { id: "role", header: "i18n:govoplan-access.system_role.91762640", 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: "i18n:govoplan-access.description.55f8ebc8", 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: "i18n:govoplan-access.permissions.d06d5557", 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: "i18n:govoplan-access.assignable.a88debc5", width: 120, resizable: false, sortable: true, filterable: true, value: (row) => row.is_assignable ? "yes" : "no", render: (row) => }, { id: "assignments", header: "i18n:govoplan-access.accounts.36bae316", width: 110, resizable: false, sortable: true, filterable: true, filterType: "integer", value: (row) => row.user_assignments }, { id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", 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="i18n:govoplan-access.no_system_roles_found.051cf727" />
!busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<>}>
setDraft({ ...draft, name: event.target.value })} /> setDraft({ ...draft, slug: event.target.value })} />