- Pages /settings/roles (liste + filtres + create), /settings/roles/:id (matrix permissions + danger zone), /settings/users/:userId/roles (multi-select + preview union) - PermissionMatrix : groupes Mantine cards, wildcard <group>:* qui grise les individuals, admin:* qui court-circuite tout, indeterminate states, tooltips avec descriptions du catalogue - React Query hooks pour CRUD roles + assignations user-roles, notifications Mantine sur succes / erreurs avec extraction du message backend - Hook useAcadenicePermissions : best-effort lecture du claim JWT R2.1, fallback sur role natif Docmost (defense en profondeur — backend reste source de verite) - i18n complet FR + EN (~80 cles) - Vitest + Testing Library introduits dans apps/client (devDeps + config + setup) - 22 tests couvrant matrix wildcards, list filters, detail save/delete flow, multi-select assignments - Patches upstream minimaux : 3 routes ajoutees au router, 1 entree sidebar (visible si canManageRoles) - Documente comme Patch 004 dans ACADENICE_PATCHES.md
69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
import { ActionIcon, Badge, Group, Table, Text, Tooltip } from "@mantine/core";
|
|
import { IconChevronRight, IconLock } from "@tabler/icons-react";
|
|
import { Link } from "react-router-dom";
|
|
import { useTranslation } from "react-i18next";
|
|
import { IRole } from "@/features/acadenice/rbac/types/rbac.types";
|
|
|
|
export interface RoleRowProps {
|
|
role: IRole;
|
|
}
|
|
|
|
export function RoleRow({ role }: RoleRowProps) {
|
|
const { t } = useTranslation();
|
|
const target = `/settings/roles/${role.id}`;
|
|
|
|
return (
|
|
<Table.Tr data-testid={`role-row-${role.id}`}>
|
|
<Table.Td>
|
|
<Group gap="sm" wrap="nowrap">
|
|
<Text fw={500} size="sm">
|
|
{role.name}
|
|
</Text>
|
|
{role.isSystemRole ? (
|
|
<Tooltip
|
|
label={t("System role — name and existence are protected")}
|
|
withArrow
|
|
>
|
|
<Badge
|
|
size="xs"
|
|
color="gray"
|
|
variant="light"
|
|
leftSection={<IconLock size={10} />}
|
|
data-testid={`role-badge-system-${role.id}`}
|
|
>
|
|
{t("system")}
|
|
</Badge>
|
|
</Tooltip>
|
|
) : (
|
|
<Badge size="xs" color="blue" variant="light">
|
|
{t("custom")}
|
|
</Badge>
|
|
)}
|
|
</Group>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Text size="sm" c="dimmed" lineClamp={2}>
|
|
{role.description ?? "—"}
|
|
</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Text size="sm">
|
|
{typeof role.memberCount === "number" ? role.memberCount : "—"}
|
|
</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<ActionIcon
|
|
component={Link}
|
|
to={target}
|
|
variant="subtle"
|
|
aria-label={t("Open role {{name}}", { name: role.name })}
|
|
data-testid={`role-open-${role.id}`}
|
|
>
|
|
<IconChevronRight size={16} />
|
|
</ActionIcon>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
);
|
|
}
|
|
|
|
export default RoleRow;
|