object level override form (#11672)
This commit is contained in:
@ -2,10 +2,12 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||
import { settingsRoleIdsState } from '@/settings/roles/states/settingsRoleIdsState';
|
||||
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Role, useGetRolesQuery } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const SettingsRolesQueryEffect = () => {
|
||||
const { data, loading } = useGetRolesQuery({
|
||||
@ -17,11 +19,18 @@ export const SettingsRolesQueryEffect = () => {
|
||||
);
|
||||
|
||||
const populateRoles = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({ set, snapshot }) =>
|
||||
(roles: Role[]) => {
|
||||
const roleIds = roles.map((role) => role.id);
|
||||
set(settingsRoleIdsState, roleIds);
|
||||
roles.forEach((role) => {
|
||||
const persistedRole = getSnapshotValue(
|
||||
snapshot,
|
||||
settingsPersistedRoleFamilyState(role.id),
|
||||
);
|
||||
if (isDeeplyEqual(role, persistedRole)) {
|
||||
return;
|
||||
}
|
||||
set(settingsDraftRoleFamilyState(role.id), role);
|
||||
set(settingsPersistedRoleFamilyState(role.id), role);
|
||||
});
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPSERT_OBJECT_PERMISSIONS = gql`
|
||||
${OBJECT_PERMISSION_FRAGMENT}
|
||||
mutation UpsertObjectPermissions(
|
||||
$upsertObjectPermissionsInput: UpsertObjectPermissionsInput!
|
||||
) {
|
||||
upsertObjectPermissions(
|
||||
upsertObjectPermissionsInput: $upsertObjectPermissionsInput
|
||||
) {
|
||||
...ObjectPermissionFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -14,11 +14,13 @@ const StyledRolePermissionsContainer = styled.div`
|
||||
type SettingsRolePermissionsProps = {
|
||||
roleId: string;
|
||||
isEditable: boolean;
|
||||
isCreateMode: boolean;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissions = ({
|
||||
roleId,
|
||||
isEditable,
|
||||
isCreateMode,
|
||||
}: SettingsRolePermissionsProps) => {
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
@ -30,7 +32,7 @@ export const SettingsRolePermissions = ({
|
||||
roleId={roleId}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
{isPermissionsV2Enabled && (
|
||||
{isPermissionsV2Enabled && !isCreateMode && (
|
||||
<SettingsRolePermissionsObjectLevelSection
|
||||
roleId={roleId}
|
||||
isEditable={isEditable}
|
||||
|
||||
@ -3,10 +3,10 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
import { PENDING_ROLE_ID } from '~/pages/settings/roles/SettingsRoleCreate';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
|
||||
const SettingsRolePermissionsWrapper = (
|
||||
args: React.ComponentProps<typeof SettingsRolePermissions>,
|
||||
@ -25,6 +25,7 @@ const SettingsRolePermissionsWrapper = (
|
||||
<SettingsRolePermissions
|
||||
roleId={args.roleId}
|
||||
isEditable={args.isEditable}
|
||||
isCreateMode={args.isCreateMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -42,6 +43,7 @@ export const Default: Story = {
|
||||
args: {
|
||||
roleId: '1',
|
||||
isEditable: true,
|
||||
isCreateMode: false,
|
||||
},
|
||||
};
|
||||
|
||||
@ -49,11 +51,14 @@ export const ReadOnly: Story = {
|
||||
args: {
|
||||
roleId: '1',
|
||||
isEditable: false,
|
||||
isCreateMode: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const PendingRole: Story = {
|
||||
args: {
|
||||
roleId: PENDING_ROLE_ID,
|
||||
isEditable: true,
|
||||
isCreateMode: true,
|
||||
},
|
||||
};
|
||||
|
||||
@ -19,7 +19,8 @@ export const SettingsRolePermissionsObjectLevelObjectPickerDropdownContent = ({
|
||||
}: SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps) => {
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
|
||||
const { objectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
const { alphaSortedActiveObjectMetadataItems: objectMetadataItems } =
|
||||
useFilteredObjectMetadataItems();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
|
||||
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
@ -46,12 +47,8 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
|
||||
settingsDraftRoleFamilyState(objectPermission.roleId),
|
||||
);
|
||||
|
||||
const permissionMappings = {
|
||||
canReadObjectRecords: 'canReadAllObjectRecords',
|
||||
canUpdateObjectRecords: 'canUpdateAllObjectRecords',
|
||||
canSoftDeleteObjectRecords: 'canSoftDeleteAllObjectRecords',
|
||||
canDestroyObjectRecords: 'canDestroyAllObjectRecords',
|
||||
} as const;
|
||||
const permissionMappings =
|
||||
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
||||
|
||||
type ObjectPermissionKey = keyof typeof permissionMappings;
|
||||
|
||||
|
||||
@ -1,24 +1,30 @@
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsRolePermissionsObjectLevelObjectPickerDropdownContent } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent';
|
||||
import { SettingsRolePermissionsObjectLevelTableHeader } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableHeader';
|
||||
import { SettingsRolePermissionsObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { H2Title, IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { v4 } from 'uuid';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
// const StyledCreateObjectOverrideSection = styled(Section)`
|
||||
// border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
// display: flex;
|
||||
// justify-content: flex-end;
|
||||
// padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
// padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
// `;
|
||||
const StyledCreateObjectOverrideSection = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledTableRows = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
@ -36,11 +42,14 @@ const StyledNoOverride = styled(TableCell)`
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
roleId,
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsObjectLevelSectionProps) => {
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
const objectMetadataItems = useObjectMetadataItems();
|
||||
|
||||
const objectMetadataMap = objectMetadataItems.objectMetadataItems.reduce(
|
||||
@ -51,29 +60,52 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
{} as Record<string, ObjectMetadataItem>,
|
||||
);
|
||||
|
||||
const objectPermissions = settingsDraftRole.objectPermissions;
|
||||
const filteredObjectPermissions = settingsDraftRole.objectPermissions?.filter(
|
||||
(objectPermission) =>
|
||||
(isDefined(objectPermission.canReadObjectRecords) &&
|
||||
objectPermission.canReadObjectRecords !==
|
||||
settingsDraftRole.canReadAllObjectRecords) ||
|
||||
(isDefined(objectPermission.canUpdateObjectRecords) &&
|
||||
objectPermission.canUpdateObjectRecords !==
|
||||
settingsDraftRole.canUpdateAllObjectRecords) ||
|
||||
(isDefined(objectPermission.canSoftDeleteObjectRecords) &&
|
||||
objectPermission.canSoftDeleteObjectRecords !==
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords) ||
|
||||
(isDefined(objectPermission.canDestroyObjectRecords) &&
|
||||
objectPermission.canDestroyObjectRecords !==
|
||||
settingsDraftRole.canDestroyAllObjectRecords),
|
||||
);
|
||||
|
||||
// const handleSelectObjectMetadata = (objectMetadataId: string) => {
|
||||
// setSettingsDraftRole((draftRole) => ({
|
||||
// ...draftRole,
|
||||
// objectPermissions: [
|
||||
// ...(draftRole.objectPermissions ?? []),
|
||||
// { objectMetadataId, roleId, id: v4() },
|
||||
// ],
|
||||
// }));
|
||||
// };
|
||||
const handleSelectObjectMetadata = (objectMetadataId: string) => {
|
||||
setSettingsDraftRole((draftRole) => ({
|
||||
...draftRole,
|
||||
objectPermissions: [
|
||||
...(draftRole.objectPermissions ?? []).filter(
|
||||
(permission) =>
|
||||
permission.objectMetadataId !== objectMetadataId ||
|
||||
permission.roleId !== roleId,
|
||||
),
|
||||
{ objectMetadataId, roleId, id: v4() },
|
||||
],
|
||||
}));
|
||||
navigate(SettingsPath.RoleObjectLevel, {
|
||||
roleId,
|
||||
objectMetadataId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Object-Level Permissions`}
|
||||
description={t`Set additional object-level permissions`}
|
||||
description={t`Ability to interact with specific objects`}
|
||||
/>
|
||||
<Table>
|
||||
<SettingsRolePermissionsObjectLevelTableHeader />
|
||||
<StyledTableRows>
|
||||
{isDefined(objectPermissions) && objectPermissions?.length > 0 ? (
|
||||
objectPermissions?.map((objectPermission) => (
|
||||
{isDefined(filteredObjectPermissions) &&
|
||||
filteredObjectPermissions?.length > 0 ? (
|
||||
filteredObjectPermissions?.map((objectPermission) => (
|
||||
<SettingsRolePermissionsObjectLevelTableRow
|
||||
key={objectPermission.id}
|
||||
objectPermission={objectPermission}
|
||||
@ -87,14 +119,14 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
)}
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
{/* <StyledCreateObjectOverrideSection>
|
||||
<StyledCreateObjectOverrideSection>
|
||||
<Dropdown
|
||||
dropdownId="role-object-select"
|
||||
dropdownHotkeyScope={{ scope: 'roleObject' }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add Object`}
|
||||
title={t`Add rule`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
disabled={!isEditable}
|
||||
@ -102,12 +134,16 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
}
|
||||
dropdownComponents={
|
||||
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
|
||||
excludedObjectMetadataIds={[]}
|
||||
excludedObjectMetadataIds={
|
||||
filteredObjectPermissions?.map(
|
||||
(objectPermission) => objectPermission.objectMetadataId,
|
||||
) ?? []
|
||||
}
|
||||
onSelect={handleSelectObjectMetadata}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledCreateObjectOverrideSection> */}
|
||||
</StyledCreateObjectOverrideSection>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,9 +3,9 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelTableHeader = () => (
|
||||
<TableRow>
|
||||
<TableRow gridAutoColumns="180px 1fr 1fr">
|
||||
<TableHeader>{t`Object`}</TableHeader>
|
||||
<TableHeader>{t`Permission overrides`}</TableHeader>
|
||||
<TableHeader>{t`Permissions`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@ -5,7 +5,11 @@ import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconChevronRight, useIcons } from 'twenty-ui/display';
|
||||
import {
|
||||
IconChevronRight,
|
||||
OverflowingTextWithTooltip,
|
||||
useIcons,
|
||||
} from 'twenty-ui/display';
|
||||
import { ObjectPermission } from '~/generated/graphql';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
@ -44,6 +48,7 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||
roleId: objectPermission.roleId,
|
||||
objectMetadataId: objectPermission.objectMetadataId,
|
||||
})}
|
||||
gridAutoColumns="180px 1fr 1fr"
|
||||
>
|
||||
<StyledNameTableCell>
|
||||
{!!Icon && (
|
||||
@ -54,7 +59,7 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||
/>
|
||||
)}
|
||||
<StyledNameLabel title={objectMetadataItem.labelPlural}>
|
||||
{objectMetadataItem.labelPlural}
|
||||
<OverflowingTextWithTooltip text={objectMetadataItem.labelPlural} />
|
||||
</StyledNameLabel>
|
||||
</StyledNameTableCell>
|
||||
<TableCell>
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconReload, IconX } from 'twenty-ui/display';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
|
||||
export type OverridableCheckboxType = 'default' | 'override' | 'no_cta';
|
||||
|
||||
const StyledOverridableCheckboxContainer = styled.div`
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
justify-content: flex-start;
|
||||
width: 48px;
|
||||
`;
|
||||
|
||||
const StyledOverridableCheckboxContainerItem = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
`;
|
||||
|
||||
const StyledIconWrapper = styled.div<{
|
||||
isDisabled?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
cursor: ${({ isDisabled }) => (isDisabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
opacity: ${({ isDisabled }) => (isDisabled ? 0.5 : 1)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export type OverridableCheckboxProps = {
|
||||
type?: OverridableCheckboxType;
|
||||
onChange: () => void;
|
||||
checked: boolean;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export const OverridableCheckbox = ({
|
||||
type = 'default',
|
||||
onChange,
|
||||
checked,
|
||||
disabled,
|
||||
}: OverridableCheckboxProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledOverridableCheckboxContainer>
|
||||
{type === 'default' && (
|
||||
<>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<Checkbox checked={true} disabled={true} />
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<StyledIconWrapper
|
||||
onClick={disabled ? undefined : onChange}
|
||||
isDisabled={disabled}
|
||||
>
|
||||
<IconX
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.secondary}
|
||||
/>
|
||||
</StyledIconWrapper>
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
</>
|
||||
)}
|
||||
{type === 'override' && (
|
||||
<>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<Checkbox checked={false} disabled={true} />
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<StyledIconWrapper
|
||||
onClick={disabled ? undefined : onChange}
|
||||
isDisabled={disabled}
|
||||
>
|
||||
<IconReload
|
||||
size={theme.icon.size.md}
|
||||
color={theme.adaptiveColors.orange4}
|
||||
/>
|
||||
</StyledIconWrapper>
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
</>
|
||||
)}
|
||||
{type === 'no_cta' && (
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<Checkbox checked={checked} disabled={disabled} onChange={onChange} />
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
)}
|
||||
</StyledOverridableCheckboxContainer>
|
||||
);
|
||||
};
|
||||
@ -1,13 +1,14 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader';
|
||||
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader';
|
||||
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow';
|
||||
import { SettingsRolePermissionsObjectLevelPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTable = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
@ -27,7 +28,7 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
|
||||
roleId,
|
||||
objectMetadataItem,
|
||||
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelProps) => {
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
@ -42,40 +43,56 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
|
||||
|
||||
const objectLabel = objectMetadataItem.labelPlural;
|
||||
|
||||
const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [
|
||||
{
|
||||
key: 'canReadObjectRecords',
|
||||
label: t`See Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canReadObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
const updateObjectPermission = (
|
||||
permissionKey: keyof ObjectPermission,
|
||||
value: boolean | null,
|
||||
) => {
|
||||
setSettingsDraftRole((currentRole) => {
|
||||
const updatedPermissions = currentRole.objectPermissions?.map((perm) => {
|
||||
if (perm.objectMetadataId === objectMetadataItem.id) {
|
||||
return { ...perm, [permissionKey]: value };
|
||||
}
|
||||
return perm;
|
||||
});
|
||||
return { ...currentRole, objectPermissions: updatedPermissions };
|
||||
});
|
||||
};
|
||||
|
||||
const objectPermissionsConfig: SettingsRolePermissionsObjectLevelPermission[] =
|
||||
[
|
||||
{
|
||||
key: 'canReadObjectRecords',
|
||||
label: t`See Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canReadObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canReadObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'canUpdateObjectRecords',
|
||||
label: t`Edit Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canUpdateObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
{
|
||||
key: 'canUpdateObjectRecords',
|
||||
label: t`Edit Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canUpdateObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canUpdateObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'canSoftDeleteObjectRecords',
|
||||
label: t`Delete Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canSoftDeleteObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
{
|
||||
key: 'canSoftDeleteObjectRecords',
|
||||
label: t`Delete Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canSoftDeleteObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canSoftDeleteObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'canDestroyObjectRecords',
|
||||
label: t`Destroy Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canDestroyObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
{
|
||||
key: 'canDestroyObjectRecords',
|
||||
label: t`Destroy Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canDestroyObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canDestroyObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
return (
|
||||
<Section>
|
||||
@ -84,13 +101,17 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
|
||||
description={t`Ability to interact with this specific object`}
|
||||
/>
|
||||
<StyledTable>
|
||||
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader />
|
||||
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader />
|
||||
<StyledTableRows>
|
||||
{objectPermissionsConfig.map((permission) => (
|
||||
<SettingsRolePermissionsObjectsTableRow
|
||||
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow
|
||||
key={permission.key}
|
||||
permission={permission}
|
||||
isEditable={settingsDraftRole.isEditable}
|
||||
settingsDraftRoleObjectPermissions={
|
||||
settingsDraftRoleObjectPermissions
|
||||
}
|
||||
roleId={roleId}
|
||||
/>
|
||||
))}
|
||||
</StyledTableRows>
|
||||
|
||||
@ -2,9 +2,9 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader =
|
||||
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader =
|
||||
() => (
|
||||
<TableRow gridAutoColumns="1fr 24px">
|
||||
<TableRow gridAutoColumns="1fr 48px">
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader aria-label={t`Actions`}></TableHeader>
|
||||
</TableRow>
|
||||
@ -0,0 +1,141 @@
|
||||
import { OverridableCheckbox } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/OverridableCheckbox';
|
||||
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
|
||||
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
|
||||
import { SettingsRolePermissionsObjectLevelPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
import type { Role } from '~/generated/graphql';
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledOverrideInfo = styled.span`
|
||||
background: ${({ theme }) => theme.adaptiveColors.orange1};
|
||||
border-radius: ${({ theme }) => theme.spacing(1)};
|
||||
color: ${({ theme }) => theme.color.orange};
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
|
||||
|
||||
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
|
||||
permission: SettingsRolePermissionsObjectLevelPermission;
|
||||
isEditable: boolean;
|
||||
settingsDraftRoleObjectPermissions: ObjectPermission;
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
|
||||
({
|
||||
permission,
|
||||
isEditable,
|
||||
settingsDraftRoleObjectPermissions,
|
||||
roleId,
|
||||
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const label = permission.label;
|
||||
|
||||
const { Icon } =
|
||||
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
|
||||
|
||||
const permissionMappings =
|
||||
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
||||
|
||||
const settingsDraftRoleObjectPermissionValue =
|
||||
settingsDraftRoleObjectPermissions[
|
||||
permission.key as keyof ObjectPermission
|
||||
];
|
||||
|
||||
const rolePermission =
|
||||
permissionMappings[permission.key as keyof typeof permissionMappings];
|
||||
|
||||
const settingsDraftRoleGlobalPermissionValue =
|
||||
settingsDraftRole[rolePermission as keyof Role];
|
||||
|
||||
const isChecked = !!settingsDraftRoleObjectPermissionValue;
|
||||
|
||||
const isRevoked =
|
||||
isDefined(settingsDraftRoleObjectPermissionValue) &&
|
||||
settingsDraftRoleGlobalPermissionValue === true &&
|
||||
isChecked === false;
|
||||
|
||||
let checkboxType: OverridableCheckboxType;
|
||||
|
||||
if (
|
||||
settingsDraftRoleGlobalPermissionValue === true &&
|
||||
settingsDraftRoleObjectPermissionValue === false
|
||||
) {
|
||||
checkboxType = 'override';
|
||||
} else if (settingsDraftRoleGlobalPermissionValue === false) {
|
||||
checkboxType = 'no_cta';
|
||||
} else {
|
||||
checkboxType = 'default';
|
||||
}
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
if (!isEditable) return;
|
||||
|
||||
if (checkboxType === 'default') {
|
||||
permission.setValue(false);
|
||||
} else if (checkboxType === 'override') {
|
||||
permission.setValue(null);
|
||||
} else if (checkboxType === 'no_cta') {
|
||||
permission.setValue(!isChecked);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTableRow>
|
||||
<StyledPermissionCell>
|
||||
<Icon size={theme.icon.size.sm} />
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
{isRevoked ? (
|
||||
<StyledOverrideInfo>
|
||||
{t`Revoked for this object`}
|
||||
</StyledOverrideInfo>
|
||||
) : null}
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell>
|
||||
<OverridableCheckbox
|
||||
onChange={handleCheckboxChange}
|
||||
disabled={!isEditable}
|
||||
type={checkboxType}
|
||||
checked={isChecked}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
@ -5,7 +5,6 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
|
||||
@ -37,12 +36,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canReadObjectRecords',
|
||||
label: t`See Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canReadObjectRecords) &&
|
||||
permission.canReadObjectRecords !==
|
||||
settingsDraftRole.canReadAllObjectRecords,
|
||||
permission.canReadObjectRecords === false &&
|
||||
settingsDraftRole.canReadAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canReadAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -55,12 +53,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canUpdateObjectRecords',
|
||||
label: t`Edit Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canUpdateObjectRecords) &&
|
||||
permission.canUpdateObjectRecords !==
|
||||
settingsDraftRole.canUpdateAllObjectRecords,
|
||||
permission.canUpdateObjectRecords === false &&
|
||||
settingsDraftRole.canUpdateAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canUpdateAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -73,12 +70,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canSoftDeleteObjectRecords',
|
||||
label: t`Delete Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canSoftDeleteObjectRecords) &&
|
||||
permission.canSoftDeleteObjectRecords !==
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||
permission.canSoftDeleteObjectRecords === false &&
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -91,12 +87,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canDestroyObjectRecords',
|
||||
label: t`Destroy Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canDestroyObjectRecords) &&
|
||||
permission.canDestroyObjectRecords !==
|
||||
settingsDraftRole.canDestroyAllObjectRecords,
|
||||
permission.canDestroyObjectRecords === false &&
|
||||
settingsDraftRole.canDestroyAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canDestroyAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -112,7 +107,7 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Objects`}
|
||||
description={t`Ability to interact with each object`}
|
||||
description={t`Actions you can perform on all objects`}
|
||||
/>
|
||||
<StyledTable>
|
||||
<SettingsRolePermissionsObjectsTableHeader
|
||||
|
||||
@ -50,10 +50,10 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isOverriddenBy = permission.overriddenBy;
|
||||
const isOverridden = isOverriddenBy && isOverriddenBy > 0;
|
||||
const revokedBy = permission.revokedBy;
|
||||
const isRevoked = revokedBy && revokedBy > 0;
|
||||
const label = permission.label;
|
||||
const pluralizedObject = pluralize('object', isOverriddenBy);
|
||||
const pluralizedObject = pluralize('object', revokedBy);
|
||||
|
||||
const { Icon } = SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
|
||||
|
||||
@ -62,9 +62,9 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
<StyledPermissionCell>
|
||||
<Icon size={theme.icon.size.sm} />
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
{isOverridden ? (
|
||||
{isRevoked ? (
|
||||
<StyledOverrideInfo>
|
||||
{t`Overridden on ${isOverriddenBy} ${pluralizedObject}`}
|
||||
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
|
||||
</StyledOverrideInfo>
|
||||
) : null}
|
||||
</StyledPermissionCell>
|
||||
@ -73,7 +73,7 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
checked={permission.value ?? false}
|
||||
onChange={() => permission.setValue(!permission.value)}
|
||||
disabled={!isEditable}
|
||||
accent={isOverridden ? CheckboxAccent.Orange : CheckboxAccent.Blue}
|
||||
accent={isRevoked ? CheckboxAccent.Orange : CheckboxAccent.Blue}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export const SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING =
|
||||
{
|
||||
canReadObjectRecords: 'canReadAllObjectRecords',
|
||||
canUpdateObjectRecords: 'canUpdateAllObjectRecords',
|
||||
canSoftDeleteObjectRecords: 'canSoftDeleteAllObjectRecords',
|
||||
canDestroyObjectRecords: 'canDestroyAllObjectRecords',
|
||||
} as const;
|
||||
@ -2,7 +2,14 @@ import { ReactNode } from 'react';
|
||||
export type SettingsRolePermissionsObjectPermission = {
|
||||
key: string;
|
||||
label: string | ReactNode;
|
||||
value?: boolean | null;
|
||||
value?: boolean;
|
||||
setValue: (value: boolean) => void;
|
||||
overriddenBy?: number;
|
||||
revokedBy?: number;
|
||||
};
|
||||
|
||||
export type SettingsRolePermissionsObjectLevelPermission = {
|
||||
key: string;
|
||||
label: string | ReactNode;
|
||||
value?: boolean | null;
|
||||
setValue: (value: boolean | null) => void;
|
||||
};
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
Role,
|
||||
useCreateOneRoleMutation,
|
||||
useUpdateOneRoleMutation,
|
||||
useUpsertObjectPermissionsMutation,
|
||||
useUpsertSettingPermissionsMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
@ -67,6 +68,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
const [createRole] = useCreateOneRoleMutation();
|
||||
const [updateRole] = useUpdateOneRoleMutation();
|
||||
const [upsertSettingPermissions] = useUpsertSettingPermissionsMutation();
|
||||
const [upsertObjectPermissions] = useUpsertObjectPermissionsMutation();
|
||||
|
||||
const { addWorkspaceMembersToRole } = useUpdateWorkspaceMemberRole(roleId);
|
||||
|
||||
@ -142,25 +144,54 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
},
|
||||
},
|
||||
onCompleted: async (data) => {
|
||||
await addWorkspaceMembersToRole({
|
||||
roleId: data.createOneRole.id,
|
||||
workspaceMemberIds: settingsDraftRole.workspaceMembers.map(
|
||||
(member) => member.id,
|
||||
),
|
||||
});
|
||||
if (isDefined(dirtyFields.workspaceMembers)) {
|
||||
await addWorkspaceMembersToRole({
|
||||
roleId: data.createOneRole.id,
|
||||
workspaceMemberIds: settingsDraftRole.workspaceMembers.map(
|
||||
(member) => member.id,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
await upsertSettingPermissions({
|
||||
variables: {
|
||||
upsertSettingPermissionsInput: {
|
||||
roleId: data.createOneRole.id,
|
||||
settingPermissionKeys:
|
||||
settingsDraftRole.settingPermissions?.map(
|
||||
(settingPermission) => settingPermission.setting,
|
||||
) ?? [],
|
||||
if (isDefined(dirtyFields.settingPermissions)) {
|
||||
await upsertSettingPermissions({
|
||||
variables: {
|
||||
upsertSettingPermissionsInput: {
|
||||
roleId: data.createOneRole.id,
|
||||
settingPermissionKeys:
|
||||
settingsDraftRole.settingPermissions?.map(
|
||||
(settingPermission) => settingPermission.setting,
|
||||
) ?? [],
|
||||
},
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
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,
|
||||
@ -206,6 +237,30 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(dirtyFields.objectPermissions)) {
|
||||
await upsertObjectPermissions({
|
||||
variables: {
|
||||
upsertObjectPermissionsInput: {
|
||||
roleId: roleId,
|
||||
objectPermissions:
|
||||
settingsDraftRole.objectPermissions?.map(
|
||||
(objectPermission) => ({
|
||||
objectMetadataId: objectPermission.objectMetadataId,
|
||||
canReadObjectRecords: objectPermission.canReadObjectRecords,
|
||||
canUpdateObjectRecords:
|
||||
objectPermission.canUpdateObjectRecords,
|
||||
canSoftDeleteObjectRecords:
|
||||
objectPermission.canSoftDeleteObjectRecords,
|
||||
canDestroyObjectRecords:
|
||||
objectPermission.canDestroyObjectRecords,
|
||||
}),
|
||||
) ?? [],
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -251,6 +306,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
<SettingsRolePermissions
|
||||
roleId={roleId}
|
||||
isEditable={isRoleEditable}
|
||||
isCreateMode={isCreateMode}
|
||||
/>
|
||||
)}
|
||||
{activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS && (
|
||||
|
||||
@ -2,10 +2,13 @@ import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/Setti
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Role } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
type SettingsRoleEditEffectProps = {
|
||||
roleId: string;
|
||||
@ -17,24 +20,35 @@ export const SettingsRoleEditEffect = ({
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const role = useRecoilValue(settingsPersistedRoleFamilyState(roleId));
|
||||
const setDraftRole = useSetRecoilState(settingsDraftRoleFamilyState(roleId));
|
||||
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const updateDraftRoleIfNeeded = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(newRole: Role) => {
|
||||
const currentPersistedRole = getSnapshotValue(
|
||||
snapshot,
|
||||
settingsPersistedRoleFamilyState(newRole.id),
|
||||
);
|
||||
|
||||
if (!isDeeplyEqual(newRole, currentPersistedRole)) {
|
||||
set(settingsDraftRoleFamilyState(newRole.id), newRole);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
if (isInitialized || !isDefined(role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTabId(SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT);
|
||||
|
||||
if (isDefined(role)) {
|
||||
setDraftRole(role);
|
||||
setIsInitialized(true);
|
||||
}
|
||||
}, [isInitialized, role, setActiveTabId, setDraftRole]);
|
||||
updateDraftRoleIfNeeded(role);
|
||||
setIsInitialized(true);
|
||||
}, [isInitialized, role, setActiveTabId, updateDraftRoleIfNeeded]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user