[permissions][FE] followup design fixes 4 (#12737)

## Context
- Whole row is now clickable
- Fix padding on role tables
- Fix tab being persistant between roles
- Change various texts/descriptions
- Add un/check all on settings permissions
- Fix flash between role detail and roles
- Add "Granted for X object(s)"
- Swap permissions and assignment tabs position
- add tooltip for object level permission actions
- Add the inherited info on object-level permissions
This commit is contained in:
Weiko
2025-06-20 13:53:19 +02:00
committed by GitHub
parent 57abc246ef
commit 7687f4f285
22 changed files with 385 additions and 138 deletions

View File

@ -19,7 +19,7 @@ export const SettingsRolesContainer = () => {
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
if (settingsRolesIsLoading) {
if (settingsRolesIsLoading && !settingsAllRoles) {
return null;
}

View File

@ -60,9 +60,13 @@ export const SettingsRoleDefaultRole = ({
const options = roles.map((role) => ({
label: role.label,
value: role.id,
Icon: getIcon(role.icon),
Icon: getIcon(role.icon) ?? IconUserPin,
}));
if (options.length === 0) {
return null;
}
return (
<Section>
<H2Title

View File

@ -49,7 +49,7 @@ export const SettingsRolesList = () => {
<Section>
<H2Title
title={t`All roles`}
description={t`Assign roles to specify each member's access permissions`}
description={t`Assign roles to manage each members access and permissions`}
/>
<Table>
<SettingsRolesTableHeader />

View File

@ -113,7 +113,7 @@ export const SettingsRolePermissionsObjectLevelObjectPicker = ({
[objectMetadataItems, searchFilter, excludedObjectMetadataIds],
);
const basicObjects = filteredObjectMetadataItems.filter(
const standardObjects = filteredObjectMetadataItems.filter(
(item) => !item.isCustom,
);
const customObjects = filteredObjectMetadataItems.filter(
@ -135,11 +135,14 @@ export const SettingsRolePermissionsObjectLevelObjectPicker = ({
</StyledSearchContainer>
</Section>
{basicObjects.length > 0 && (
{standardObjects.length > 0 && (
<Section>
<H2Title title={t`Basics`} description={t`All the basic objects`} />
<H2Title
title={t`Standard`}
description={t`All the standard objects`}
/>
<StyledContainer>
{basicObjects.map((objectMetadataItem) => {
{standardObjects.map((objectMetadataItem) => {
const Icon = getIcon(objectMetadataItem.icon);
return (
<StyledCardContainer
@ -151,7 +154,7 @@ export const SettingsRolePermissionsObjectLevelObjectPicker = ({
<SettingsCard
Icon={
<Icon
size={theme.icon.size.xl}
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
/>
}
@ -181,7 +184,7 @@ export const SettingsRolePermissionsObjectLevelObjectPicker = ({
key={objectMetadataItem.id}
Icon={
<Icon
size={theme.icon.size.xl}
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
/>
}

View File

@ -1,60 +1,83 @@
import { objectPermissionKeyToHumanReadable } from '@/settings/roles/role-permissions/object-level-permissions/utils/objectPermissionKeyToHumanReadableText';
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { AppTooltip, TooltipDelay } from 'twenty-ui/display';
import { ObjectPermission } from '~/generated/graphql';
const StyledSettingsRolePermissionsObjectLevelOverrideCell = styled.div`
const StyledContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
type SettingsRolePermissionsObjectLevelOverrideCellProps = {
objectPermission: ObjectPermission;
objectPermissions: ObjectPermission;
objectPermissionKey: SettingsRoleObjectPermissionKey;
roleId: string;
objectLabel: string;
};
export const SettingsRolePermissionsObjectLevelOverrideCell = ({
objectPermission,
objectPermissions,
objectPermissionKey,
roleId,
objectLabel,
}: SettingsRolePermissionsObjectLevelOverrideCellProps) => {
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId),
);
const roleLabel = settingsDraftRole.label;
const permissionMappings =
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
const isOverridden = (permission: SettingsRoleObjectPermissionKey) => {
const rolePermission = permissionMappings[permission];
const permissionValue = objectPermissions[objectPermissionKey];
const isOverridden = (
objectPermissionKey: SettingsRoleObjectPermissionKey,
) => {
const rolePermission = permissionMappings[objectPermissionKey];
return (
isDefined(objectPermission[permission]) &&
!!settingsDraftRole[rolePermission] !== !!objectPermission[permission]
isDefined(permissionValue) &&
!!settingsDraftRole[rolePermission] !== !!permissionValue
);
};
if (!isOverridden(objectPermissionKey)) {
return null;
}
const humanReadableAction =
objectPermissionKeyToHumanReadable(objectPermissionKey);
const containerId = `object-level-permission-override-${roleId}-${objectPermissionKey}`;
return (
<StyledSettingsRolePermissionsObjectLevelOverrideCell>
{(
Object.keys(permissionMappings) as SettingsRoleObjectPermissionKey[]
).map((permission) => {
const permissionValue = objectPermission[permission];
if (!isOverridden(permission)) {
return null;
<>
<StyledContainer id={containerId}>
<PermissionIcon
permission={objectPermissionKey}
state={permissionValue === false ? 'revoked' : 'granted'}
/>
</StyledContainer>
<AppTooltip
anchorSelect={`#${containerId}`}
content={
permissionValue === false
? t`${roleLabel} can't ${humanReadableAction} ${objectLabel} records`
: t`${roleLabel} can ${humanReadableAction} ${objectLabel} records`
}
return (
<PermissionIcon
key={permission}
permission={permission}
state={permissionValue === false ? 'revoked' : 'granted'}
/>
);
})}
</StyledSettingsRolePermissionsObjectLevelOverrideCell>
delay={TooltipDelay.shortDelay}
noArrow
place="bottom"
positionStrategy="fixed"
/>
</>
);
};

View File

@ -0,0 +1,43 @@
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import styled from '@emotion/styled';
import { ObjectPermission } from '~/generated/graphql';
import { SettingsRolePermissionsObjectLevelOverrideCell } from './SettingsRolePermissionsObjectLevelOverrideCell';
const StyledSettingsRolePermissionsObjectLevelOverrideCell = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
type SettingsRolePermissionsObjectLevelOverrideCellContainerProps = {
objectPermissions: ObjectPermission;
roleId: string;
objectLabel: string;
};
export const SettingsRolePermissionsObjectLevelOverrideCellContainer = ({
objectPermissions,
roleId,
objectLabel,
}: SettingsRolePermissionsObjectLevelOverrideCellContainerProps) => {
const permissionMappings =
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
return (
<StyledSettingsRolePermissionsObjectLevelOverrideCell>
{(
Object.keys(permissionMappings) as SettingsRoleObjectPermissionKey[]
).map((permissionKey) => {
return (
<SettingsRolePermissionsObjectLevelOverrideCell
key={permissionKey}
objectPermissions={objectPermissions}
objectPermissionKey={permissionKey}
roleId={roleId}
objectLabel={objectLabel}
/>
);
})}
</StyledSettingsRolePermissionsObjectLevelOverrideCell>
);
};

View File

@ -84,8 +84,8 @@ export const SettingsRolePermissionsObjectLevelSection = ({
return (
<Section>
<H2Title
title={t`Object-Level Permissions`}
description={t`Ability to interact with specific objects`}
title={t`Object-Level`}
description={t`Actions users can perform on specific objects`}
/>
<Table>
<SettingsRolePermissionsObjectLevelTableHeader />
@ -103,7 +103,7 @@ export const SettingsRolePermissionsObjectLevelSection = ({
/>
))
) : (
<StyledNoOverride>{t`No overrides found`}</StyledNoOverride>
<StyledNoOverride>{t`No permissions found`}</StyledNoOverride>
)}
</StyledTableRows>
</Table>

View File

@ -1,5 +1,5 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SettingsRolePermissionsObjectLevelOverrideCell } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCell';
import { SettingsRolePermissionsObjectLevelOverrideCellContainer } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCellContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
@ -44,6 +44,8 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
const Icon = getIcon(objectMetadataItem.icon);
const objectLabel = objectMetadataItem.labelPlural;
return (
<TableRow
to={getSettingsPath(SettingsPath.RoleObjectLevel, {
@ -60,14 +62,15 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
stroke={theme.icon.stroke.sm}
/>
)}
<StyledNameLabel title={objectMetadataItem.labelPlural}>
<OverflowingTextWithTooltip text={objectMetadataItem.labelPlural} />
<StyledNameLabel title={objectLabel}>
<OverflowingTextWithTooltip text={objectLabel} />
</StyledNameLabel>
</StyledNameTableCell>
<TableCell>
<SettingsRolePermissionsObjectLevelOverrideCell
objectPermission={objectPermission}
<SettingsRolePermissionsObjectLevelOverrideCellContainer
objectPermissions={objectPermission}
roleId={roleId}
objectLabel={objectLabel}
/>
</TableCell>
<TableCell align={'right'}>

View File

@ -120,8 +120,8 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
return (
<Section>
<H2Title
title={t`Object-Level Permissions`}
description={t`Ability to interact with this specific object`}
title={t`Object-Level`}
description={t`Actions users can perform on this object`}
/>
<StyledTable>
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader />

View File

@ -1,4 +1,5 @@
import { OverridableCheckbox } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/OverridableCheckbox';
import { objectPermissionKeyToHumanReadable } from '@/settings/roles/role-permissions/object-level-permissions/utils/objectPermissionKeyToHumanReadableText';
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
@ -13,9 +14,10 @@ import { isDefined } from 'twenty-shared/utils';
import { ObjectPermission } from '~/generated-metadata/graphql';
import type { Role } from '~/generated/graphql';
const StyledTableRow = styled(TableRow)`
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
align-items: center;
display: flex;
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
`;
const StyledPermissionCell = styled(TableCell)`
@ -93,6 +95,15 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
settingsDraftRoleGlobalPermissionValue === true &&
isChecked === false;
const isGranted =
isDefined(settingsDraftRoleObjectPermissionValue) &&
settingsDraftRoleGlobalPermissionValue === false &&
isChecked === true;
const isGrantedAndInherited =
settingsDraftRoleObjectPermissionValue !== false &&
settingsDraftRoleGlobalPermissionValue === true;
let checkboxType: OverridableCheckboxType;
if (
@ -118,8 +129,12 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
}
};
const humanReadableAction = objectPermissionKeyToHumanReadable(
permission.key as SettingsRoleObjectPermissionKey,
);
return (
<StyledTableRow>
<StyledTableRow onClick={handleCheckboxChange} isDisabled={!isEditable}>
<StyledPermissionCell>
<StyledPermissionContent>
<PermissionIcon
@ -134,10 +149,20 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
{' · '}
{t`Revoked for this object`}
</>
) : isGranted ? (
<>
{' · '}
{t`Granted for this object`}
</>
) : isGrantedAndInherited ? (
<>
{' · '}
{t`This role can ${humanReadableAction} all records`}
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell>
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
<OverridableCheckbox
onChange={handleCheckboxChange}
disabled={!isEditable}

View File

@ -0,0 +1,15 @@
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { t } from '@lingui/core/macro';
export const objectPermissionKeyToHumanReadable = (
objectPermissionKey: SettingsRoleObjectPermissionKey,
) => {
const permissionAction: Record<SettingsRoleObjectPermissionKey, string> = {
canReadObjectRecords: t`see`,
canUpdateObjectRecords: t`update`,
canSoftDeleteObjectRecords: t`delete`,
canDestroyObjectRecords: t`destroy`,
};
return permissionAction[objectPermissionKey];
};

View File

@ -36,6 +36,12 @@ export const SettingsRolePermissionsObjectsSection = ({
{
key: 'canReadObjectRecords',
label: t`See Records on All Objects`,
grantedBy:
objectPermissions?.filter(
(permission) =>
permission.canReadObjectRecords === true &&
settingsDraftRole.canReadAllObjectRecords === false,
)?.length ?? 0,
revokedBy:
objectPermissions?.filter(
(permission) =>
@ -60,6 +66,12 @@ export const SettingsRolePermissionsObjectsSection = ({
{
key: 'canUpdateObjectRecords',
label: t`Edit Records on All Objects`,
grantedBy:
objectPermissions?.filter(
(permission) =>
permission.canUpdateObjectRecords === true &&
settingsDraftRole.canUpdateAllObjectRecords === false,
)?.length ?? 0,
revokedBy:
objectPermissions?.filter(
(permission) =>
@ -82,6 +94,12 @@ export const SettingsRolePermissionsObjectsSection = ({
{
key: 'canSoftDeleteObjectRecords',
label: t`Delete Records on All Objects`,
grantedBy:
objectPermissions?.filter(
(permission) =>
permission.canSoftDeleteObjectRecords === true &&
settingsDraftRole.canSoftDeleteAllObjectRecords === false,
)?.length ?? 0,
revokedBy:
objectPermissions?.filter(
(permission) =>
@ -104,6 +122,12 @@ export const SettingsRolePermissionsObjectsSection = ({
{
key: 'canDestroyObjectRecords',
label: t`Destroy Records on All Objects`,
grantedBy:
objectPermissions?.filter(
(permission) =>
permission.canDestroyObjectRecords === true &&
settingsDraftRole.canDestroyAllObjectRecords === false,
)?.length ?? 0,
revokedBy:
objectPermissions?.filter(
(permission) =>
@ -128,8 +152,8 @@ export const SettingsRolePermissionsObjectsSection = ({
return (
<Section>
<H2Title
title={t`Objects`}
description={t`Actions you can perform on all objects`}
title={t`All objects`}
description={t`Actions users can perform on all objects`}
/>
<StyledTable>
<SettingsRolePermissionsObjectsTableHeader

View File

@ -15,7 +15,7 @@ const StyledActionsHeader = styled(TableHeader)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
type SettingsRolePermissionsObjectsTableHeaderProps = {

View File

@ -36,12 +36,13 @@ const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledTableRow = styled(TableRow)`
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
align-items: center;
display: flex;
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
`;
type SettingsRolePermissionsObjectsTableRowProps = {
@ -54,13 +55,21 @@ export const SettingsRolePermissionsObjectsTableRow = ({
isEditable,
}: SettingsRolePermissionsObjectsTableRowProps) => {
const revokedBy = permission.revokedBy;
const grantedBy = permission.grantedBy;
const isRevoked =
revokedBy !== undefined && revokedBy !== null && revokedBy > 0;
const label = permission.label;
const pluralizedObject = pluralize('object', revokedBy);
const pluralizedRevokedObject = pluralize('object', revokedBy);
const pluralizedGrantedObject = pluralize('object', grantedBy);
const isDisabled = !isEditable;
const handleRowClick = () => {
if (isDisabled) return;
permission.setValue(!permission.value);
};
return (
<StyledTableRow>
<StyledTableRow onClick={handleRowClick} isDisabled={isDisabled}>
<StyledPermissionCell>
<StyledPermissionContent>
<PermissionIcon
@ -70,19 +79,24 @@ export const SettingsRolePermissionsObjectsTableRow = ({
<StyledPermissionLabel>{label}</StyledPermissionLabel>
</StyledPermissionContent>
<StyledOverrideInfo>
{isRevoked ? (
{isRevoked && revokedBy > 0 ? (
<>
{' · '}
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
{t`Revoked for ${revokedBy} ${pluralizedRevokedObject}`}
</>
) : grantedBy && grantedBy > 0 ? (
<>
{' · '}
{t`Granted for ${grantedBy} ${pluralizedGrantedObject}`}
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell>
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={permission.value ?? false}
onChange={() => permission.setValue(!permission.value)}
disabled={!isEditable}
disabled={isDisabled}
accent={isRevoked ? CheckboxAccent.Orange : CheckboxAccent.Blue}
/>
</StyledCheckboxCell>

View File

@ -4,6 +4,7 @@ export type SettingsRolePermissionsObjectPermission = {
label: string | ReactNode;
value?: boolean;
setValue: (value: boolean) => void;
grantedBy?: number;
revokedBy?: number;
};

View File

@ -130,7 +130,11 @@ export const SettingsRolePermissionsSettingsSection = ({
containAnimation={false}
>
<StyledTable>
<SettingsRolePermissionsSettingsTableHeader />
<SettingsRolePermissionsSettingsTableHeader
roleId={roleId}
settingsPermissionsConfig={settingsPermissionsConfig}
isEditable={isEditable}
/>
<StyledTableRows>
{settingsPermissionsConfig.map((permission) => (
<SettingsRolePermissionsSettingsTableRow

View File

@ -1,11 +1,77 @@
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { Checkbox } from 'twenty-ui/input';
export const SettingsRolePermissionsSettingsTableHeader = () => (
<TableRow gridAutoColumns="3fr 4fr 24px">
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Description`}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
);
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useRecoilState } from 'recoil';
import { v4 } from 'uuid';
type SettingsRolePermissionsSettingsTableHeaderProps = {
roleId: string;
settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[];
isEditable: boolean;
};
const StyledActionsHeader = styled(TableHeader)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(1)};
`;
export const SettingsRolePermissionsSettingsTableHeader = ({
roleId,
settingsPermissionsConfig,
isEditable,
}: SettingsRolePermissionsSettingsTableHeaderProps) => {
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
settingsDraftRoleFamilyState(roleId),
);
const allSettingsPermissionsEnabled = settingsPermissionsConfig.every(
(permission) =>
settingsDraftRole.settingPermissions?.some(
(settingPermission) => settingPermission.setting === permission.key,
),
);
const someSettingsPermissionsEnabled = settingsPermissionsConfig.some(
(permission) =>
settingsDraftRole.settingPermissions?.some(
(settingPermission) => settingPermission.setting === permission.key,
),
);
return (
<TableRow gridAutoColumns="3fr 4fr 24px">
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Description`}</TableHeader>
<StyledActionsHeader aria-label={t`Actions`}>
<Checkbox
checked={allSettingsPermissionsEnabled}
indeterminate={
someSettingsPermissionsEnabled && !allSettingsPermissionsEnabled
}
disabled={!isEditable}
aria-label={t`Toggle all settings permissions`}
onChange={() => {
const newValue = !allSettingsPermissionsEnabled;
setSettingsDraftRole({
...settingsDraftRole,
settingPermissions: newValue
? settingsPermissionsConfig.map((permission) => ({
id: v4(),
setting: permission.key,
roleId,
}))
: [],
});
}}
/>
</StyledActionsHeader>
</TableRow>
);
};

View File

@ -10,6 +10,10 @@ import { Checkbox } from 'twenty-ui/input';
import { v4 } from 'uuid';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
`;
const StyledName = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
@ -29,7 +33,7 @@ const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledIconContainer = styled.div`
@ -63,6 +67,10 @@ export const SettingsRolePermissionsSettingsTableRow = ({
(settingPermission) => settingPermission.setting === permission.key,
) ?? false;
const isChecked = isSettingPermissionEnabled || canUpdateAllSettings;
const isDisabled =
!isEditable || canUpdateAllSettings || !isPermissionsV2Enabled;
const handleChange = (value: boolean) => {
const currentPermissions = settingsDraftRole.settingPermissions ?? [];
@ -88,8 +96,18 @@ export const SettingsRolePermissionsSettingsTableRow = ({
}
};
const handleRowClick = () => {
if (isDisabled) return;
handleChange(!isChecked);
};
return (
<TableRow key={permission.key} gridAutoColumns="3fr 4fr 24px">
<StyledTableRow
key={permission.key}
gridAutoColumns="3fr 4fr 24px"
onClick={handleRowClick}
isDisabled={isDisabled}
>
<StyledPermissionCell>
<StyledIconContainer>
<permission.Icon
@ -103,15 +121,13 @@ export const SettingsRolePermissionsSettingsTableRow = ({
<StyledPermissionCell>
<StyledDescription>{permission.description}</StyledDescription>
</StyledPermissionCell>
<StyledCheckboxCell>
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={isSettingPermissionEnabled || canUpdateAllSettings}
disabled={
!isEditable || canUpdateAllSettings || !isPermissionsV2Enabled
}
checked={isChecked}
disabled={isDisabled}
onChange={(event) => handleChange(event.target.checked)}
/>
</StyledCheckboxCell>
</TableRow>
</StyledTableRow>
);
};

View File

@ -57,7 +57,7 @@ const ROLE_BASIC_KEYS: Array<keyof Role> = [
export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId,
);
const isPermissionsV2Enabled = useIsFeatureEnabled(
@ -94,16 +94,16 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
const isRoleEditable = isPermissionsV2Enabled && settingsDraftRole.isEditable;
const tabs = [
{
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT,
title: t`Assignment`,
Icon: IconUserPlus,
},
{
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS,
title: t`Permissions`,
Icon: IconLockOpen,
},
{
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT,
title: t`Assignment`,
Icon: IconUserPlus,
},
{
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS,
title: t`Settings`,
@ -135,7 +135,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
if (isCreateMode) {
const roleId = v4();
createRole({
const { data } = await createRole({
variables: {
createRoleInput: {
id: roleId,
@ -152,60 +152,63 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
settingsDraftRole.canDestroyAllObjectRecords,
},
},
onCompleted: async (data) => {
if (isDefined(dirtyFields.workspaceMembers)) {
await addWorkspaceMembersToRole({
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
});
if (!data) {
return;
}
if (isDefined(dirtyFields.workspaceMembers)) {
await addWorkspaceMembersToRole({
roleId: data.createOneRole.id,
workspaceMemberIds: settingsDraftRole.workspaceMembers.map(
(member) => member.id,
),
});
}
if (isDefined(dirtyFields.settingPermissions)) {
await upsertSettingPermissions({
variables: {
upsertSettingPermissionsInput: {
roleId: data.createOneRole.id,
workspaceMemberIds: settingsDraftRole.workspaceMembers.map(
(member) => member.id,
),
});
}
settingPermissionKeys:
settingsDraftRole.settingPermissions?.map(
(settingPermission) => settingPermission.setting,
) ?? [],
},
},
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
});
}
if (isDefined(dirtyFields.settingPermissions)) {
await upsertSettingPermissions({
variables: {
upsertSettingPermissionsInput: {
roleId: data.createOneRole.id,
settingPermissionKeys:
settingsDraftRole.settingPermissions?.map(
(settingPermission) => settingPermission.setting,
) ?? [],
},
},
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
});
}
if (isDefined(dirtyFields.objectPermissions)) {
await upsertObjectPermissions({
variables: {
upsertObjectPermissionsInput: {
roleId: data.createOneRole.id,
objectPermissions:
settingsDraftRole.objectPermissions?.map(
(objectPermission) => ({
objectMetadataId: objectPermission.objectMetadataId,
canReadObjectRecords: objectPermission.canReadObjectRecords,
canUpdateObjectRecords:
objectPermission.canUpdateObjectRecords,
canSoftDeleteObjectRecords:
objectPermission.canSoftDeleteObjectRecords,
canDestroyObjectRecords:
objectPermission.canDestroyObjectRecords,
}),
) ?? [],
},
},
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
});
}
if (isDefined(dirtyFields.objectPermissions)) {
await upsertObjectPermissions({
variables: {
upsertObjectPermissionsInput: {
roleId: data.createOneRole.id,
objectPermissions:
settingsDraftRole.objectPermissions?.map(
(objectPermission) => ({
objectMetadataId: objectPermission.objectMetadataId,
canReadObjectRecords:
objectPermission.canReadObjectRecords,
canUpdateObjectRecords:
objectPermission.canUpdateObjectRecords,
canSoftDeleteObjectRecords:
objectPermission.canSoftDeleteObjectRecords,
canDestroyObjectRecords:
objectPermission.canDestroyObjectRecords,
}),
) ?? [],
},
},
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
});
}
navigateSettings(SettingsPath.RoleDetail, {
roleId: data.createOneRole.id,
});
},
navigateSettings(SettingsPath.RoleDetail, {
roleId: data.createOneRole.id,
});
} else {
if (isDefined(dirtyFields.settingPermissions)) {
@ -244,6 +247,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
},
},
},
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
});
}
@ -302,7 +306,9 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
<TabList
tabs={tabs}
className="tab-list"
componentInstanceId={SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID}
componentInstanceId={
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId
}
/>
{activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT && (
<SettingsRoleAssignment roleId={roleId} isCreateMode={isCreateMode} />

View File

@ -18,7 +18,7 @@ export const SettingsRoleCreateEffect = ({
);
const setActiveTabId = useSetRecoilComponentStateV2(
activeTabIdComponentState,
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId,
);
const [isInitialized, setIsInitialized] = useState(false);

View File

@ -22,7 +22,7 @@ export const SettingsRoleEditEffect = ({
const role = useRecoilValue(settingsPersistedRoleFamilyState(roleId));
const setActiveTabId = useSetRecoilComponentStateV2(
activeTabIdComponentState,
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId,
);
const updateDraftRoleIfNeeded = useRecoilCallback(
@ -45,7 +45,7 @@ export const SettingsRoleEditEffect = ({
return;
}
setActiveTabId(SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT);
setActiveTabId(SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS);
updateDraftRoleIfNeeded(role);
setIsInitialized(true);
}, [isInitialized, role, setActiveTabId, updateDraftRoleIfNeeded]);

View File

@ -1,8 +1,8 @@
export const SETTINGS_ROLE_DETAIL_TABS = {
COMPONENT_INSTANCE_ID: 'settings-role-detail-tabs',
TABS_IDS: {
ASSIGNMENT: 'assignment',
PERMISSIONS: 'permissions',
ASSIGNMENT: 'assignment',
SETTINGS: 'settings',
},
} as const;