import { useState, useMemo } from 'react' import { Avatar } from '../../../design-system/primitives/Avatar/Avatar' import { Badge } from '../../../design-system/primitives/Badge/Badge' import { Button } from '../../../design-system/primitives/Button/Button' import { Input } from '../../../design-system/primitives/Input/Input' import { MonoText } from '../../../design-system/primitives/MonoText/MonoText' import { SectionHeader } from '../../../design-system/primitives/SectionHeader/SectionHeader' import { Tag } from '../../../design-system/primitives/Tag/Tag' import { ConfirmDialog } from '../../../design-system/composites/ConfirmDialog/ConfirmDialog' import { SplitPane } from '../../../design-system/composites/SplitPane/SplitPane' import { EntityList } from '../../../design-system/composites/EntityList/EntityList' import { useToast } from '../../../design-system/composites/Toast/Toast' import { MOCK_ROLES, MOCK_GROUPS, MOCK_USERS, getEffectiveRoles, type MockRole } from './rbacMocks' import styles from './UserManagement.module.css' export function RolesTab() { const { toast } = useToast() const [roles, setRoles] = useState(MOCK_ROLES) const [search, setSearch] = useState('') const [selectedId, setSelectedId] = useState(null) const [creating, setCreating] = useState(false) const [deleteTarget, setDeleteTarget] = useState(null) const [newName, setNewName] = useState('') const [newDesc, setNewDesc] = useState('') const filtered = useMemo(() => { if (!search) return roles const q = search.toLowerCase() return roles.filter((r) => r.name.toLowerCase().includes(q) || r.description.toLowerCase().includes(q) ) }, [roles, search]) const selected = roles.find((r) => r.id === selectedId) ?? null function handleCreate() { if (!newName.trim()) return const newRole: MockRole = { id: `role-${Date.now()}`, name: newName.trim().toUpperCase(), description: newDesc.trim(), scope: 'custom', system: false, } setRoles((prev) => [...prev, newRole]) setCreating(false) setNewName(''); setNewDesc('') setSelectedId(newRole.id) toast({ title: 'Role created', description: newRole.name, variant: 'success' }) } function handleDelete() { if (!deleteTarget) return setRoles((prev) => prev.filter((r) => r.id !== deleteTarget.id)) if (selectedId === deleteTarget.id) setSelectedId(null) setDeleteTarget(null) toast({ title: 'Role deleted', description: deleteTarget.name, variant: 'warning' }) } const duplicateRoleName = newName.trim() !== '' && roles.some((r) => r.name === newName.trim().toUpperCase()) // Role assignments const assignedGroups = selected ? MOCK_GROUPS.filter((g) => g.directRoles.includes(selected.name)) : [] const directUsers = selected ? MOCK_USERS.filter((u) => u.directRoles.includes(selected.name)) : [] const effectivePrincipals = selected ? MOCK_USERS.filter((u) => getEffectiveRoles(u).some((r) => r.role === selected.name)) : [] function getAssignmentCount(role: MockRole): number { const groups = MOCK_GROUPS.filter((g) => g.directRoles.includes(role.name)).length const users = MOCK_USERS.filter((u) => u.directRoles.includes(role.name)).length return groups + users } return ( <> {creating && (
setNewName(e.target.value)} /> {duplicateRoleName && Role name already exists} setNewDesc(e.target.value)} />
)} ( <>
{role.name} {role.system && }
{role.description} ยท {getAssignmentCount(role)} assignments
{MOCK_GROUPS.filter((g) => g.directRoles.includes(role.name)) .map((g) => )} {MOCK_USERS.filter((u) => u.directRoles.includes(role.name)) .map((u) => )}
)} getItemId={(role) => role.id} selectedId={selectedId ?? undefined} onSelect={setSelectedId} searchPlaceholder="Search roles..." onSearch={setSearch} addLabel="+ Add role" onAdd={() => setCreating(true)} emptyMessage="No roles match your search" /> } detail={selected ? ( <>
{selected.name}
{selected.description && (
{selected.description}
)}
{!selected.system && ( )}
ID {selected.id} Scope {selected.scope} {selected.system && ( <> Type System role (read-only) )}
Assigned to groups
{assignedGroups.map((g) => )} {assignedGroups.length === 0 && (none)}
Assigned to users (direct)
{directUsers.map((u) => )} {directUsers.length === 0 && (none)}
Effective principals
{effectivePrincipals.map((u) => { const isDirect = u.directRoles.includes(selected.name) return ( ) })} {effectivePrincipals.length === 0 && (none)}
{effectivePrincipals.some((u) => !u.directRoles.includes(selected.name)) && ( Dashed entries inherit this role through group membership )} ) : null} emptyMessage="Select a role to view details" /> setDeleteTarget(null)} onConfirm={handleDelete} message={`Delete role "${deleteTarget?.name}"? This cannot be undone.`} confirmText={deleteTarget?.name ?? ''} /> ) }