[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:
@ -19,7 +19,7 @@ export const SettingsRolesContainer = () => {
|
||||
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
||||
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
|
||||
|
||||
if (settingsRolesIsLoading) {
|
||||
if (settingsRolesIsLoading && !settingsAllRoles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 member’s access and permissions`}
|
||||
/>
|
||||
<Table>
|
||||
<SettingsRolesTableHeader />
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
|
||||
@ -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'}>
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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];
|
||||
};
|
||||
@ -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
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -4,6 +4,7 @@ export type SettingsRolePermissionsObjectPermission = {
|
||||
label: string | ReactNode;
|
||||
value?: boolean;
|
||||
setValue: (value: boolean) => void;
|
||||
grantedBy?: number;
|
||||
revokedBy?: number;
|
||||
};
|
||||
|
||||
|
||||
@ -130,7 +130,11 @@ export const SettingsRolePermissionsSettingsSection = ({
|
||||
containAnimation={false}
|
||||
>
|
||||
<StyledTable>
|
||||
<SettingsRolePermissionsSettingsTableHeader />
|
||||
<SettingsRolePermissionsSettingsTableHeader
|
||||
roleId={roleId}
|
||||
settingsPermissionsConfig={settingsPermissionsConfig}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
<StyledTableRows>
|
||||
{settingsPermissionsConfig.map((permission) => (
|
||||
<SettingsRolePermissionsSettingsTableRow
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user