diff --git a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx index 09fdaf62b..2b0f2d052 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx +++ b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx @@ -13,6 +13,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { useFieldContext } from '@/object-record/hooks/useFieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider'; +import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly'; import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope'; import { RecordInlineCellContainer } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer'; import { RecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext'; @@ -23,7 +24,6 @@ type ActivityTargetsInlineCellProps = { activity: Task | Note; showLabel?: boolean; maxWidth?: number; - readonly?: boolean; activityObjectNameSingular: | CoreObjectNameSingular.Note | CoreObjectNameSingular.Task; @@ -33,7 +33,6 @@ export const ActivityTargetsInlineCell = ({ activity, showLabel = true, maxWidth, - readonly, activityObjectNameSingular, }: ActivityTargetsInlineCellProps) => { const { activityTargetObjectRecords } = @@ -43,6 +42,8 @@ export const ActivityTargetsInlineCell = ({ const { fieldDefinition } = useContext(FieldContext); + const isFieldReadOnly = useIsFieldValueReadOnly(); + useScopedHotkeys( Key.Escape, () => { @@ -76,7 +77,7 @@ export const ActivityTargetsInlineCell = ({ }, IconLabel: showLabel ? IconArrowUpRight : undefined, showLabel: showLabel, - readonly: readonly, + readonly: isFieldReadOnly, labelWidth: fieldDefinition?.labelWidth, editModeContent: ( )} diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx index ee4822006..588af20d9 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx @@ -134,7 +134,6 @@ export const TaskRow = ({ task }: { task: Task }) => { activity={task} showLabel={false} maxWidth={200} - readonly /> )} diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldValueReadOnly.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldValueReadOnly.test.tsx index a436b9753..274a251a0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldValueReadOnly.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldValueReadOnly.test.tsx @@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react'; import { ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { actorFieldDefinition, phonesFieldDefinition, @@ -17,34 +18,39 @@ import { JestRecordStoreSetter } from '~/testing/jest/JestRecordStoreSetter'; import { useIsFieldValueReadOnly } from '../useIsFieldValueReadOnly'; const recordId = 'recordId'; +const mockInstanceId = 'mock-instance-id'; const getWrapper = (fieldDefinition: FieldDefinition, isRecordDeleted: boolean) => ({ children }: { children: ReactNode }) => { return ( - - - + + - {children} - - - + + {children} + + + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts index c6e6c2bae..d6146718c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts @@ -1,9 +1,11 @@ import { useContext } from 'react'; +import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilValue } from 'recoil'; import { FieldContext } from '../contexts/FieldContext'; import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly'; @@ -17,6 +19,10 @@ export const useIsFieldValueReadOnly = () => { recordStoreFamilyState(recordId), ); + const contextStoreCurrentViewType = useRecoilComponentValueV2( + contextStoreCurrentViewTypeComponentState, + ); + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: metadata.objectMetadataNameSingular ?? '', }); @@ -30,5 +36,6 @@ export const useIsFieldValueReadOnly = () => { isObjectRemote: objectMetadataItem.isRemote, isRecordDeleted: recordFromStore?.deletedAt, hasObjectReadOnlyPermission, + contextStoreCurrentViewType, }); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueReadOnly.test.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueReadOnly.test.ts index 9744b3b8f..db435778f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueReadOnly.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueReadOnly.test.ts @@ -1,3 +1,4 @@ +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly'; import { FieldMetadataType } from '~/generated/graphql'; @@ -5,19 +6,37 @@ describe('isFieldValueReadOnly', () => { it('should return true if fieldName is noteTargets or taskTargets', () => { const result = isFieldValueReadOnly({ fieldName: 'noteTargets', + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(true); const result2 = isFieldValueReadOnly({ fieldName: 'taskTargets', + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result2).toBe(true); }); + it('should return true if fieldName is noteTargets or taskTargets but is not in table or kanban view', () => { + const result = isFieldValueReadOnly({ + fieldName: 'noteTargets', + contextStoreCurrentViewType: ContextStoreViewType.ShowPage, + }); + expect(result).toBe(false); + + const result2 = isFieldValueReadOnly({ + fieldName: 'taskTargets', + contextStoreCurrentViewType: ContextStoreViewType.ShowPage, + }); + + expect(result2).toBe(false); + }); + it('should return false if fieldName is not noteTargets or taskTargets', () => { const result = isFieldValueReadOnly({ fieldName: 'test', + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(false); @@ -26,6 +45,7 @@ describe('isFieldValueReadOnly', () => { it('should return true if isObjectRemote is true', () => { const result = isFieldValueReadOnly({ isObjectRemote: true, + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(true); @@ -34,6 +54,7 @@ describe('isFieldValueReadOnly', () => { it('should return false if isObjectRemote is false', () => { const result = isFieldValueReadOnly({ isObjectRemote: false, + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(false); @@ -42,6 +63,7 @@ describe('isFieldValueReadOnly', () => { it('should return true if isRecordDeleted is true', () => { const result = isFieldValueReadOnly({ isRecordDeleted: true, + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(true); @@ -50,6 +72,7 @@ describe('isFieldValueReadOnly', () => { it('should return false if isRecordDeleted is false', () => { const result = isFieldValueReadOnly({ isRecordDeleted: false, + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(false); @@ -59,6 +82,7 @@ describe('isFieldValueReadOnly', () => { const result = isFieldValueReadOnly({ objectNameSingular: 'workflow', fieldName: 'test', + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(true); @@ -68,6 +92,7 @@ describe('isFieldValueReadOnly', () => { const result = isFieldValueReadOnly({ objectNameSingular: 'Workflow', fieldName: 'name', + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(false); @@ -76,6 +101,7 @@ describe('isFieldValueReadOnly', () => { it('should return true if isWorkflowSubObjectMetadata is true', () => { const result = isFieldValueReadOnly({ objectNameSingular: 'workflowVersion', + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(true); @@ -84,6 +110,7 @@ describe('isFieldValueReadOnly', () => { it('should return true if fieldType is FieldMetadataType.ACTOR', () => { const result = isFieldValueReadOnly({ fieldType: FieldMetadataType.ACTOR, + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(true); @@ -92,6 +119,7 @@ describe('isFieldValueReadOnly', () => { it('should return true if fieldType is FieldMetadataType.RICH_TEXT', () => { const result = isFieldValueReadOnly({ fieldType: FieldMetadataType.RICH_TEXT, + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(true); @@ -100,13 +128,16 @@ describe('isFieldValueReadOnly', () => { it('should return false if fieldType is not FieldMetadataType.ACTOR or FieldMetadataType.RICH_TEXT', () => { const result = isFieldValueReadOnly({ fieldType: FieldMetadataType.TEXT, + contextStoreCurrentViewType: ContextStoreViewType.Table, }); expect(result).toBe(false); }); it('should return false if none of the conditions are met', () => { - const result = isFieldValueReadOnly({}); + const result = isFieldValueReadOnly({ + contextStoreCurrentViewType: ContextStoreViewType.Table, + }); expect(result).toBe(false); }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueReadOnly.ts index 41ce33a7f..acaa33b72 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueReadOnly.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueReadOnly.ts @@ -1,3 +1,4 @@ +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata'; import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor'; @@ -13,6 +14,7 @@ type isFieldValueReadOnlyParams = { isObjectRemote?: boolean; isRecordDeleted?: boolean; hasObjectReadOnlyPermission?: boolean; + contextStoreCurrentViewType: ContextStoreViewType | null; }; export const isFieldValueReadOnly = ({ @@ -22,8 +24,16 @@ export const isFieldValueReadOnly = ({ isObjectRemote = false, isRecordDeleted = false, hasObjectReadOnlyPermission = false, + contextStoreCurrentViewType, }: isFieldValueReadOnlyParams) => { - if (fieldName === 'noteTargets' || fieldName === 'taskTargets') { + const isTableViewOrKanbanView = + contextStoreCurrentViewType === ContextStoreViewType.Table || + contextStoreCurrentViewType === ContextStoreViewType.Kanban; + + const isTargetField = + fieldName === 'noteTargets' || fieldName === 'taskTargets'; + + if (isTableViewOrKanbanView && isTargetField) { return true; } diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx index ffbcd364d..bf580fc61 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx @@ -1,3 +1,4 @@ +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; @@ -53,6 +54,7 @@ export const SummaryCard = ({ const isReadOnly = isFieldValueReadOnly({ objectNameSingular, isRecordDeleted: recordFromStore?.isDeleted, + contextStoreCurrentViewType: ContextStoreViewType.ShowPage, }); const isCommandMenuV2Enabled = useIsFeatureEnabled( diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx index 7d212b39c..dc788344b 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx @@ -10,6 +10,7 @@ import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { getCompaniesMock } from '~/testing/mock-data/companies'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { allMockPersonRecords } from '~/testing/mock-data/people'; @@ -31,21 +32,25 @@ const meta: Meta = { component: RecordDetailRelationSection, decorators: [ (Story) => ( - name === 'people', - )!, - objectMetadataItem: mockedCompanyObjectMetadataItem, - }), - hotkeyScope: 'hotkey-scope', - }} + - - + name === 'people', + )!, + objectMetadataItem: mockedCompanyObjectMetadataItem, + }), + hotkeyScope: 'hotkey-scope', + }} + > + + + ), ComponentDecorator, ObjectMetadataItemsDecorator,