diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useComputeActionsBasedOnContextStore.tsx b/packages/twenty-front/src/modules/action-menu/hooks/useComputeActionsBasedOnContextStore.tsx index 0145042d2847..899908fb2a4c 100644 --- a/packages/twenty-front/src/modules/action-menu/hooks/useComputeActionsBasedOnContextStore.tsx +++ b/packages/twenty-front/src/modules/action-menu/hooks/useComputeActionsBasedOnContextStore.tsx @@ -3,6 +3,7 @@ import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount'; import { useDeleteTableData } from '@/object-record/record-index/options/hooks/useDeleteTableData'; import { @@ -55,12 +56,13 @@ export const useComputeActionsBasedOnContextStore = ({ filename: `${objectMetadataItem.nameSingular}.csv`, }); - const isRemoteObject = objectMetadataItem.isRemote; + const isRemote = objectMetadataItem.isRemote; const numberOfSelectedRecords = contextStoreTargetedRecordIds.length; const canDelete = - !isRemoteObject && numberOfSelectedRecords < DELETE_MAX_COUNT; + !isObjectMetadataReadOnly(objectMetadataItem) && + numberOfSelectedRecords < DELETE_MAX_COUNT; const menuActions: ActionMenuEntry[] = useMemo( () => @@ -125,7 +127,7 @@ export const useComputeActionsBasedOnContextStore = ({ return { availableActionsInContext: [ ...menuActions, - ...(!isRemoteObject && isFavorite && hasOnlyOneRecordSelected + ...(!isRemote && isFavorite && hasOnlyOneRecordSelected ? [ { label: 'Remove from favorites', @@ -134,7 +136,7 @@ export const useComputeActionsBasedOnContextStore = ({ }, ] : []), - ...(!isRemoteObject && !isFavorite && hasOnlyOneRecordSelected + ...(!isRemote && !isFavorite && hasOnlyOneRecordSelected ? [ { label: 'Add to favorites', diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts deleted file mode 100644 index 0f3295dc5c29..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const useObjectIsRemote = (objectMetadataItem: ObjectMetadataItem) => { - return objectMetadataItem.isRemote ?? false; -}; diff --git a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts index dd496e70d3d2..8ac533f76aac 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts @@ -31,4 +31,5 @@ export enum CoreObjectNameSingular { Workflow = 'workflow', MessageChannelMessageAssociation = 'messageChannelMessageAssociation', WorkflowVersion = 'workflowVersion', + WorkflowRun = 'workflowRun', } diff --git a/packages/twenty-front/src/modules/object-metadata/utils/isObjectMetadataReadOnly.ts b/packages/twenty-front/src/modules/object-metadata/utils/isObjectMetadataReadOnly.ts new file mode 100644 index 000000000000..c6455e009c9d --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/isObjectMetadataReadOnly.ts @@ -0,0 +1,8 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata'; + +export const isObjectMetadataReadOnly = ( + objectMetadataItem: Pick, +) => + objectMetadataItem.isRemote || + isWorkflowSubObjectMetadata(objectMetadataItem.nameSingular); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObjectMetadata.ts b/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObjectMetadata.ts new file mode 100644 index 000000000000..1ad6c0cbb290 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObjectMetadata.ts @@ -0,0 +1,7 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; + +export const isWorkflowSubObjectMetadata = ( + objectMetadataNameSingular?: string, +) => + objectMetadataNameSingular === CoreObjectNameSingular.WorkflowVersion || + objectMetadataNameSingular === CoreObjectNameSingular.WorkflowRun; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts index e4e4970c0b0d..48bb034244ea 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts @@ -3,14 +3,16 @@ import { useContext } from 'react'; import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor'; import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText'; import { FieldContext } from '../contexts/FieldContext'; +import { isFieldMetadataReadOnly } from '../utils/isFieldMetadataReadOnly'; export const useIsFieldReadOnly = () => { const { fieldDefinition } = useContext(FieldContext); + const { metadata } = fieldDefinition; + return ( - fieldDefinition.metadata.fieldName === 'noteTargets' || - fieldDefinition.metadata.fieldName === 'taskTargets' || isFieldActor(fieldDefinition) || - isFieldRichText(fieldDefinition) + isFieldRichText(fieldDefinition) || + isFieldMetadataReadOnly(metadata) ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldMetadataReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldMetadataReadOnly.ts new file mode 100644 index 000000000000..a24ed100352d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldMetadataReadOnly.ts @@ -0,0 +1,19 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; + +export const isFieldMetadataReadOnly = (fieldMetadata: FieldMetadata) => { + if ( + fieldMetadata.fieldName === 'noteTargets' || + fieldMetadata.fieldName === 'taskTargets' + ) { + return true; + } + + return ( + isWorkflowSubObjectMetadata(fieldMetadata.objectMetadataNameSingular) || + (fieldMetadata.objectMetadataNameSingular === + CoreObjectNameSingular.Workflow && + fieldMetadata.fieldName !== 'name') + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx index bb8c0197a940..f167ad13f19d 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx @@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil'; import { useIcons } from 'twenty-ui'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; @@ -30,8 +31,11 @@ export const RecordIndexPageHeader = () => { const recordIndexViewType = useRecoilValue(recordIndexViewTypeState); - const isTable = - recordIndexViewType === ViewType.Table && !objectMetadataItem?.isRemote; + const shouldDisplayAddButton = objectMetadataItem + ? !isObjectMetadataReadOnly(objectMetadataItem) + : false; + + const isTable = recordIndexViewType === ViewType.Table; const pageHeaderTitle = objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural); @@ -43,11 +47,12 @@ export const RecordIndexPageHeader = () => { return ( - {isTable ? ( - - ) : ( - - )} + {shouldDisplayAddButton && + (isTable ? ( + + ) : ( + + ))} ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx index 36a3d7a1a2fe..eefeb3540878 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx @@ -24,6 +24,7 @@ import { } from '@/object-record/record-field/contexts/FieldContext'; import { usePersistField } from '@/object-record/record-field/hooks/usePersistField'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; @@ -180,6 +181,8 @@ export const RecordDetailRelationRecordsListItem = ({ [isExpanded], ); + const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata); + return ( <> @@ -195,37 +198,39 @@ export const RecordDetailRelationRecordsListItem = ({ accent="tertiary" /> - - - } - dropdownComponents={ - - + - {!isAccountOwnerRelation && ( + } + dropdownComponents={ + - )} - - } - dropdownHotkeyScope={{ scope: dropdownScopeId }} - /> - + {!isAccountOwnerRelation && ( + + )} + + } + dropdownHotkeyScope={{ scope: dropdownScopeId }} + /> + + )} diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx index 4e2d194036f6..81e0c37c76e7 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx @@ -12,6 +12,7 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi import { RelationFromManyFieldInputMultiRecordsEffect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly'; import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList'; import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection'; import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader'; @@ -158,6 +159,8 @@ export const RecordDetailRelationSection = ({ recordId, }); + const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata); + if (loading) return null; return ( @@ -178,49 +181,51 @@ export const RecordDetailRelationSection = ({ hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile} areRecordsAvailable={relationRecords.length > 0} rightAdornment={ - - - } - dropdownComponents={ - - {isToOneObject ? ( - - ) : ( - <> - - - + + } + dropdownComponents={ + + {isToOneObject ? ( + - - )} - - } - dropdownHotkeyScope={{ - scope: dropdownId, - }} - /> - + ) : ( + <> + + + + + )} + + } + dropdownHotkeyScope={{ + scope: dropdownId, + }} + /> + + ) } /> {showContent()} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index ccec1297bf3b..f195b2b68649 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -70,7 +70,9 @@ export const RecordTable = ({ ) : ( - + )} diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx index c25a3cf11990..7ea1deb12616 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx @@ -1,4 +1,3 @@ -import { useObjectIsRemote } from '@/object-metadata/hooks/useObjectIsRemote'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll'; @@ -18,7 +17,7 @@ export const RecordTableEmptyState = () => { const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 }); const noRecordAtAll = totalCount === 0; - const isRemote = useObjectIsRemote(objectMetadataItem); + const isRemote = objectMetadataItem.isRemote; const isSoftDeleteActive = useRecoilValue(isSoftDeleteActiveState); diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx index 80c5ecaefeb1..c5f120a6de63 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx @@ -8,7 +8,10 @@ import { AnimatedPlaceholderEmptyTitle, } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; +import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { Button } from '@/ui/input/button/components/Button'; +import { useContext } from 'react'; import { IconComponent } from 'twenty-ui'; type RecordTableEmptyStateDisplayProps = { @@ -28,6 +31,9 @@ export const RecordTableEmptyStateDisplay = ({ subTitle, title, }: RecordTableEmptyStateDisplayProps) => { + const { objectMetadataItem } = useContext(RecordTableContext); + const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem); + return ( @@ -37,12 +43,14 @@ export const RecordTableEmptyStateDisplay = ({ {subTitle} -