diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 7e7916d49..6c53b738e 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1891,6 +1891,7 @@ export type Role = { id: Scalars['String']['output']; isEditable: Scalars['Boolean']['output']; label: Scalars['String']['output']; + objectPermissions?: Maybe>; settingPermissions?: Maybe>; workspaceMembers: Array; }; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index dc4f30cbe..dee6ba07b 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1693,6 +1693,7 @@ export type Role = { id: Scalars['String']; isEditable: Scalars['Boolean']; label: Scalars['String']; + objectPermissions?: Maybe>; settingPermissions?: Maybe>; workspaceMembers: Array; }; @@ -2668,6 +2669,8 @@ export type UpdateLabPublicFeatureFlagMutationVariables = Exact<{ export type UpdateLabPublicFeatureFlagMutation = { __typename?: 'Mutation', updateLabPublicFeatureFlag: { __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean } }; +export type ObjectPermissionFragmentFragment = { __typename?: 'ObjectPermission', id: string, objectMetadataId: string, roleId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }; + export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean }; export type SettingPermissionFragmentFragment = { __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }; @@ -2704,7 +2707,7 @@ export type UpsertSettingPermissionsMutation = { __typename?: 'Mutation', upsert export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, settingPermissions?: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> | null }> }; +export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, settingPermissions?: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', id: string, objectMetadataId: string, roleId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null }> }; export type CreateApprovedAccessDomainMutationVariables = Exact<{ input: CreateApprovedAccessDomainInput; @@ -3019,6 +3022,17 @@ export const AvailableSsoIdentityProvidersFragmentFragmentDoc = gql` } } `; +export const ObjectPermissionFragmentFragmentDoc = gql` + fragment ObjectPermissionFragment on ObjectPermission { + id + objectMetadataId + roleId + canReadObjectRecords + canUpdateObjectRecords + canSoftDeleteObjectRecords + canDestroyObjectRecords +} + `; export const SettingPermissionFragmentFragmentDoc = gql` fragment SettingPermissionFragment on SettingPermission { id @@ -4984,11 +4998,15 @@ export const GetRolesDocument = gql` settingPermissions { ...SettingPermissionFragment } + objectPermissions { + ...ObjectPermissionFragment + } } } ${RoleFragmentFragmentDoc} ${WorkspaceMemberQueryFragmentFragmentDoc} -${SettingPermissionFragmentFragmentDoc}`; +${SettingPermissionFragmentFragmentDoc} +${ObjectPermissionFragmentFragmentDoc}`; /** * __useGetRolesQuery__ diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index ce2751a69..6d98a647f 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -313,6 +313,12 @@ const SettingsRoleEdit = lazy(() => })), ); +const SettingsRoleObjectLevel = lazy(() => + import('~/pages/settings/roles/SettingsRoleObjectLevel').then((module) => ({ + default: module.SettingsRoleObjectLevel, + })), +); + type SettingsRoutesProps = { isFunctionSettingsEnabled?: boolean; isAdminPageEnabled?: boolean; @@ -402,6 +408,10 @@ export const SettingsRoutes = ({ path={SettingsPath.RoleCreate} element={} /> + } + /> { +type SettingsRoleDefaultRoleProps = { + roles: Role[]; +}; + +export const SettingsRoleDefaultRole = ({ + roles, +}: SettingsRoleDefaultRoleProps) => { const [updateWorkspace] = useUpdateWorkspaceMutation(); const [currentWorkspace, setCurrentWorkspace] = useRecoilState( diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx index 296587c12..c638e9859 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx @@ -4,8 +4,6 @@ import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; -import { Role } from '~/generated-metadata/graphql'; -import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { AppTooltip, Avatar, @@ -14,6 +12,8 @@ import { TooltipDelay, useIcons, } from 'twenty-ui/display'; +import { Role } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledAssignedText = styled.div` color: ${({ theme }) => theme.font.color.secondary}; @@ -52,15 +52,13 @@ const StyledTableRow = styled(TableRow)` } `; -export const SettingsRolesTableRow = ({ role }: { role: Role }) => { +type SettingsRolesTableRowProps = { + role: Role; +}; + +export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => { const theme = useTheme(); - const navigateSettings = useNavigateSettings(); - - const handleRoleClick = (roleId: string) => { - navigateSettings(SettingsPath.RoleDetail, { roleId }); - }; - const { getIcon } = useIcons(); const Icon = getIcon(role.icon ?? 'IconUser'); @@ -68,7 +66,7 @@ export const SettingsRolesTableRow = ({ role }: { role: Role }) => { handleRoleClick(role.id)} + to={getSettingsPath(SettingsPath.RoleDetail, { roleId: role.id })} > diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/objectPermissionFragment.ts b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/objectPermissionFragment.ts new file mode 100644 index 000000000..8abbc4826 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/objectPermissionFragment.ts @@ -0,0 +1,13 @@ +import { gql } from '@apollo/client'; + +export const OBJECT_PERMISSION_FRAGMENT = gql` + fragment ObjectPermissionFragment on ObjectPermission { + id + objectMetadataId + roleId + canReadObjectRecords + canUpdateObjectRecords + canSoftDeleteObjectRecords + canDestroyObjectRecords + } +`; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts b/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts index 7223ef524..902afb8df 100644 --- a/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts +++ b/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts @@ -1,3 +1,4 @@ +import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment'; import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment'; import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment'; import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment'; @@ -7,6 +8,7 @@ export const GET_ROLES = gql` ${WORKSPACE_MEMBER_QUERY_FRAGMENT} ${ROLE_FRAGMENT} ${SETTING_PERMISSION_FRAGMENT} + ${OBJECT_PERMISSION_FRAGMENT} query GetRoles { getRoles { ...RoleFragment @@ -16,6 +18,9 @@ export const GET_ROLES = gql` settingPermissions { ...SettingPermissionFragment } + objectPermissions { + ...ObjectPermissionFragment + } } } `; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx index 3c9708612..cbe9a32ad 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx @@ -1,34 +1,9 @@ -import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; -import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableHeader'; -import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableRow'; -import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableHeader'; -import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableRow'; -import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; -import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission'; -import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission'; +import { SettingsRolePermissionsObjectLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection'; +import { SettingsRolePermissionsObjectsSection } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection'; +import { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; -import { t } from '@lingui/core/macro'; -import { useRecoilState } from 'recoil'; -import { - H2Title, - IconCode, - IconEye, - IconHierarchy, - IconKey, - IconLockOpen, - IconPencil, - IconServer, - IconSettings, - IconTrash, - IconTrashX, - IconUsers, -} from 'twenty-ui/display'; -import { Card, Section } from 'twenty-ui/layout'; -import { - FeatureFlagKey, - SettingPermissionType, -} from '~/generated-metadata/graphql'; +import { FeatureFlagKey } from '~/generated-metadata/graphql'; const StyledRolePermissionsContainer = styled.div` display: flex; @@ -36,19 +11,6 @@ const StyledRolePermissionsContainer = styled.div` gap: ${({ theme }) => theme.spacing(8)}; `; -const StyledTable = styled.div` - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; -`; - -const StyledTableRows = styled.div` - padding-bottom: ${({ theme }) => theme.spacing(2)}; - padding-top: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledCard = styled(Card)` - margin-bottom: ${({ theme }) => theme.spacing(4)}; -`; - type SettingsRolePermissionsProps = { roleId: string; isEditable: boolean; @@ -58,168 +20,26 @@ export const SettingsRolePermissions = ({ roleId, isEditable, }: SettingsRolePermissionsProps) => { - const [settingsDraftRole, setSettingsDraftRole] = useRecoilState( - settingsDraftRoleFamilyState(roleId), - ); - - const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [ - { - key: 'seeRecords', - label: t`See Records on All Objects`, - Icon: IconEye, - value: settingsDraftRole.canReadAllObjectRecords, - setValue: (value: boolean) => { - setSettingsDraftRole({ - ...settingsDraftRole, - canReadAllObjectRecords: value, - }); - }, - }, - { - key: 'editRecords', - label: t`Edit Records on All Objects`, - Icon: IconPencil, - value: settingsDraftRole.canUpdateAllObjectRecords, - setValue: (value: boolean) => { - setSettingsDraftRole({ - ...settingsDraftRole, - canUpdateAllObjectRecords: value, - }); - }, - }, - { - key: 'deleteRecords', - label: t`Delete Records on All Objects`, - Icon: IconTrash, - value: settingsDraftRole.canSoftDeleteAllObjectRecords, - setValue: (value: boolean) => { - setSettingsDraftRole({ - ...settingsDraftRole, - canSoftDeleteAllObjectRecords: value, - }); - }, - }, - { - key: 'destroyRecords', - label: t`Destroy Records on All Objects`, - Icon: IconTrashX, - value: settingsDraftRole.canDestroyAllObjectRecords, - setValue: (value: boolean) => { - setSettingsDraftRole({ - ...settingsDraftRole, - canDestroyAllObjectRecords: value, - }); - }, - }, - ]; - - const settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[] = - [ - { - key: SettingPermissionType.API_KEYS_AND_WEBHOOKS, - name: t`API Keys & Webhooks`, - description: t`Manage API keys and webhooks`, - Icon: IconCode, - }, - { - key: SettingPermissionType.WORKSPACE, - name: t`Workspace`, - description: t`Set global workspace preferences`, - Icon: IconSettings, - }, - { - key: SettingPermissionType.WORKSPACE_MEMBERS, - name: t`Users`, - description: t`Add or remove users`, - Icon: IconUsers, - }, - { - key: SettingPermissionType.ROLES, - name: t`Roles`, - description: t`Define user roles and access levels`, - Icon: IconLockOpen, - }, - { - key: SettingPermissionType.DATA_MODEL, - name: t`Data Model`, - description: t`Edit CRM data structure and fields`, - Icon: IconHierarchy, - }, - { - key: SettingPermissionType.ADMIN_PANEL, - name: t`Admin Panel`, - description: t`Admin settings and system tools`, - Icon: IconServer, - }, - { - key: SettingPermissionType.SECURITY, - name: t`Security`, - description: t`Manage security policies`, - Icon: IconKey, - }, - ]; - const isPermissionsV2Enabled = useIsFeatureEnabled( FeatureFlagKey.IsPermissionsV2Enabled, ); return ( -
- + {isPermissionsV2Enabled && ( + - - - - {objectPermissionsConfig.map((permission) => ( - - ))} - - -
-
- - {isPermissionsV2Enabled && ( - - { - setSettingsDraftRole({ - ...settingsDraftRole, - canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings, - }); - }} - /> - - )} - - - - {settingsPermissionsConfig.map((permission) => ( - - ))} - - -
+ )} +
); }; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent.tsx new file mode 100644 index 000000000..662a84133 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent.tsx @@ -0,0 +1,58 @@ +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; +import { t } from '@lingui/core/macro'; +import { ChangeEvent, useState } from 'react'; +import { useIcons } from 'twenty-ui/display'; +import { MenuItem } from 'twenty-ui/navigation'; + +type SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps = { + excludedObjectMetadataIds: string[]; + onSelect: (objectMetadataId: string) => void; +}; + +export const SettingsRolePermissionsObjectLevelObjectPickerDropdownContent = ({ + excludedObjectMetadataIds, + onSelect, +}: SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps) => { + const [searchFilter, setSearchFilter] = useState(''); + + const { objectMetadataItems } = useFilteredObjectMetadataItems(); + + const { getIcon } = useIcons(); + + const handleSearchFilterChange = (event: ChangeEvent) => { + setSearchFilter(event.target.value); + }; + + const filteredObjectMetadataItems = objectMetadataItems.filter( + (objectMetadataItem) => + objectMetadataItem.labelSingular + .toLowerCase() + .includes(searchFilter.toLowerCase()) && + !excludedObjectMetadataIds.includes(objectMetadataItem.id), + ); + + return ( + + + + + {filteredObjectMetadataItems.map((objectMetadataItem) => ( + onSelect(objectMetadataItem.id)} + /> + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCell.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCell.tsx new file mode 100644 index 000000000..02159ab44 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCell.tsx @@ -0,0 +1,96 @@ +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'; +import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { ObjectPermission } from '~/generated/graphql'; + +const StyledIconWrapper = styled.div<{ isForbidden?: boolean }>` + align-items: center; + background: ${({ theme, isForbidden }) => + isForbidden ? theme.adaptiveColors.orange1 : theme.adaptiveColors.blue1}; + border: 1px solid + ${({ theme, isForbidden }) => + isForbidden ? theme.adaptiveColors.orange3 : theme.adaptiveColors.blue3}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + display: flex; + height: ${({ theme }) => theme.spacing(4)}; + justify-content: center; + width: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledIcon = styled.div<{ isForbidden?: boolean }>` + align-items: center; + display: flex; + color: ${({ theme, isForbidden }) => + isForbidden ? theme.color.orange : theme.color.blue}; + justify-content: center; +`; + +const StyledSettingsRolePermissionsObjectLevelOverrideCell = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +type SettingsRolePermissionsObjectLevelOverrideCellProps = { + objectPermission: ObjectPermission; +}; + +export const SettingsRolePermissionsObjectLevelOverrideCell = ({ + objectPermission, +}: SettingsRolePermissionsObjectLevelOverrideCellProps) => { + const theme = useTheme(); + + const settingsDraftRole = useRecoilValue( + settingsDraftRoleFamilyState(objectPermission.roleId), + ); + + const permissionMappings = { + canReadObjectRecords: 'canReadAllObjectRecords', + canUpdateObjectRecords: 'canUpdateAllObjectRecords', + canSoftDeleteObjectRecords: 'canSoftDeleteAllObjectRecords', + canDestroyObjectRecords: 'canDestroyAllObjectRecords', + } as const; + + type ObjectPermissionKey = keyof typeof permissionMappings; + + const isOverridden = (permission: ObjectPermissionKey) => { + const rolePermission = permissionMappings[permission]; + return ( + isDefined(objectPermission[permission]) && + !!settingsDraftRole[rolePermission as keyof typeof settingsDraftRole] !== + !!objectPermission[permission] + ); + }; + + return ( + + {(Object.keys(permissionMappings) as ObjectPermissionKey[]).map( + (permission) => { + const { Icon, IconForbidden: IconOverride } = + SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission]; + const permissionValue = objectPermission[permission]; + + if (!isOverridden(permission)) { + return null; + } + + return ( + + + {permissionValue === false && ( + + )} + {permissionValue === true && } + + + ); + }, + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx new file mode 100644 index 000000000..f60d7cfc4 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx @@ -0,0 +1,113 @@ +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +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 { 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 { isDefined } from 'twenty-shared/utils'; +import { H2Title } from 'twenty-ui/display'; +import { Section } from 'twenty-ui/layout'; + +// 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)}; + padding-top: ${({ theme }) => theme.spacing(2)}; +`; + +type SettingsRolePermissionsObjectLevelSectionProps = { + roleId: string; + isEditable: boolean; +}; + +const StyledNoOverride = styled(TableCell)` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + +export const SettingsRolePermissionsObjectLevelSection = ({ + roleId, +}: SettingsRolePermissionsObjectLevelSectionProps) => { + const settingsDraftRole = useRecoilValue( + settingsDraftRoleFamilyState(roleId), + ); + + const objectMetadataItems = useObjectMetadataItems(); + + const objectMetadataMap = objectMetadataItems.objectMetadataItems.reduce( + (acc, item) => { + acc[item.id] = item; + return acc; + }, + {} as Record, + ); + + const objectPermissions = settingsDraftRole.objectPermissions; + + // const handleSelectObjectMetadata = (objectMetadataId: string) => { + // setSettingsDraftRole((draftRole) => ({ + // ...draftRole, + // objectPermissions: [ + // ...(draftRole.objectPermissions ?? []), + // { objectMetadataId, roleId, id: v4() }, + // ], + // })); + // }; + + return ( +
+ + + + + {isDefined(objectPermissions) && objectPermissions?.length > 0 ? ( + objectPermissions?.map((objectPermission) => ( + + )) + ) : ( + {t`No overrides found`} + )} + +
+ {/* + + } + dropdownComponents={ + + } + /> + */} +
+ ); +}; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableHeader.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableHeader.tsx new file mode 100644 index 000000000..09bcba362 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableHeader.tsx @@ -0,0 +1,11 @@ +import { TableHeader } from '@/ui/layout/table/components/TableHeader'; +import { TableRow } from '@/ui/layout/table/components/TableRow'; +import { t } from '@lingui/core/macro'; + +export const SettingsRolePermissionsObjectLevelTableHeader = () => ( + + {t`Object`} + {t`Permission overrides`} + + +); diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx new file mode 100644 index 000000000..5575a425a --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx @@ -0,0 +1,73 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { SettingsRolePermissionsObjectLevelOverrideCell } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCell'; +import { SettingsPath } from '@/types/SettingsPath'; +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 { ObjectPermission } from '~/generated/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; + +const StyledNameTableCell = styled(TableCell)` + color: ${({ theme }) => theme.font.color.primary}; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledNameLabel = styled.div` + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +`; + +type SettingsRolePermissionsObjectLevelTableRowProps = { + objectPermission: ObjectPermission; + objectMetadataItem: ObjectMetadataItem; +}; + +export const SettingsRolePermissionsObjectLevelTableRow = ({ + objectPermission, + objectMetadataItem, +}: SettingsRolePermissionsObjectLevelTableRowProps) => { + const { getIcon } = useIcons(); + const theme = useTheme(); + + if (!objectMetadataItem) { + throw new Error('Object metadata item not found'); + } + + const Icon = getIcon(objectMetadataItem.icon); + + return ( + + + {!!Icon && ( + + )} + + {objectMetadataItem.labelPlural} + + + + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx new file mode 100644 index 000000000..ad9b9f8f4 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx @@ -0,0 +1,94 @@ +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag'; +import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; +import { SettingsRolePermissionsObjectLevelObjectFormObjectLevel } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevel'; +import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import { SettingsPath } from '@/types/SettingsPath'; +import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import styled from '@emotion/styled'; +import { t } from '@lingui/core/macro'; +import { useRecoilValue } from 'recoil'; +import { H3Title } from 'twenty-ui/display'; +import { Button } from 'twenty-ui/input'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; + +const StyledObjectTypeTag = styled(SettingsDataModelObjectTypeTag)` + box-sizing: border-box; + height: ${({ theme }) => theme.spacing(5)}; + margin-left: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledTitleContainer = styled.div` + display: flex; +`; + +type SettingsRolePermissionsObjectLevelObjectFormProps = { + roleId: string; + objectMetadataId: string; +}; + +export const SettingsRolePermissionsObjectLevelObjectForm = ({ + roleId, + objectMetadataId, +}: SettingsRolePermissionsObjectLevelObjectFormProps) => { + const settingsDraftRole = useRecoilValue( + settingsDraftRoleFamilyState(roleId), + ); + + const objectMetadata = useObjectMetadataItemById({ + objectId: objectMetadataId, + }); + + const objectMetadataItem = objectMetadata.objectMetadataItem; + + const objectTypeLabel = getObjectTypeLabel(objectMetadataItem); + + return ( + + + + + } + links={[ + { + children: 'Workspace', + href: getSettingsPath(SettingsPath.Workspace), + }, + { + children: 'Roles', + href: getSettingsPath(SettingsPath.Roles), + }, + { + children: settingsDraftRole.label, + href: getSettingsPath(SettingsPath.RoleDetail, { + roleId, + }), + }, + { + children: `Permissions ยท ${objectMetadataItem.labelSingular}`, + }, + ]} + actionButton={ +