+ {/* Left pane — list */}
+
+
+ setSearch(e.target.value)}
+ />
+
+
+
+ {showCreate && (
+
+ )}
+
+ {isLoading ? (
+
+ ) : (
+
+ {filtered.map((role) => {
+ const assignmentCount =
+ (role.assignedGroups?.length ?? 0) + (role.directUsers?.length ?? 0);
+ return (
+
setSelectedId(role.id)}
+ >
+
+
+
+ {role.name}
+ {role.system && }
+
+
+ {role.description || '—'} · {assignmentCount} assignment
+ {assignmentCount !== 1 ? 's' : ''}
+
+ {((role.assignedGroups?.length ?? 0) > 0 ||
+ (role.directUsers?.length ?? 0) > 0) && (
+
+ {(role.assignedGroups ?? []).map((g) => (
+
+ ))}
+ {(role.directUsers ?? []).map((u) => (
+
+ ))}
+
+ )}
+
+
+ );
+ })}
+
+ )}
+
+
+ {/* Right pane — detail */}
+
+ {!selectedId ? (
+
Select a role to view details
+ ) : detailLoading || !detail ? (
+
+ ) : (
+
setConfirmDelete(true)}
+ />
+ )}
+
+
+ {detail && (
+
setConfirmDelete(false)}
+ onConfirm={handleDelete}
+ title="Delete role"
+ message={`Delete role "${detail.name}"? This cannot be undone.`}
+ confirmText={detail.name}
+ confirmLabel="Delete"
+ variant="danger"
+ loading={deleteRole.isPending}
+ />
+ )}
+
+ );
+}
+
+// ── Detail panel ──────────────────────────────────────────────────────────────
+
+interface RoleDetailPanelProps {
+ role: RoleDetail;
+ onDeleteRequest: () => void;
+}
+
+function RoleDetailPanel({ role, onDeleteRequest }: RoleDetailPanelProps) {
+ // Build a set of directly-assigned user IDs for distinguishing inherited principals
+ const directUserIds = new Set((role.directUsers ?? []).map((u) => u.userId));
+
+ return (
+
+ {/* Header */}
+
+
+
+
{role.name}
+ {role.description && (
+
+ {role.description}
+
+ )}
+
+
+
+
+ {/* Metadata */}
+
+ ID
+ {role.id}
+
+ Scope
+ {role.scope || '—'}
+
+ Type
+ {role.system ? 'System role (read-only)' : 'Custom role'}
+
+
+ {/* Assigned to groups */}
+
Assigned to groups
+
+ {(role.assignedGroups ?? []).length === 0 ? (
+ None
+ ) : (
+ (role.assignedGroups ?? []).map((g) => (
+
+ ))
+ )}
+
+
+ {/* Assigned to users (direct) */}
+
Assigned to users (direct)
+
+ {(role.directUsers ?? []).length === 0 ? (
+ None
+ ) : (
+ (role.directUsers ?? []).map((u) => (
+
+ ))
+ )}
+
+
+ {/* Effective principals */}
+
Effective principals
+
+ {(role.effectivePrincipals ?? []).length === 0 ? (
+ None
+ ) : (
+ (role.effectivePrincipals ?? []).map((u) => {
+ const isDirect = directUserIds.has(u.userId);
+ return isDirect ? (
+
+ ) : (
+
+ );
+ })
+ )}
+
+ {(role.effectivePrincipals ?? []).some((u) => !directUserIds.has(u.userId)) && (
+
+ Dashed entries inherit this role through group membership
+
+ )}
+
+ );
+}