diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/__mocks__/fieldDefinitions.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/__mocks__/fieldDefinitions.ts index c4f33907fe..1e7cd68250 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/__mocks__/fieldDefinitions.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/__mocks__/fieldDefinitions.ts @@ -4,11 +4,12 @@ import { FieldActorMetadata, FieldFullNameMetadata, FieldLinksMetadata, + FieldMorphRelationMetadata, FieldRatingMetadata, FieldSelectMetadata, FieldTextMetadata, } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; export const fieldMetadataId = 'fieldMetadataId'; @@ -129,3 +130,19 @@ export const linksFieldDefinition: FieldDefinition = { settings: null, }, }; + +export const morphRelationFieldDefinition: FieldDefinition = + { + fieldMetadataId, + label: 'Attachments', + iconName: 'IconLink', + type: FieldMetadataType.MORPH_RELATION, + defaultValue: [], + metadata: { + fieldName: 'attachments', + objectMetadataNameSingular: 'company', + morphRelations: [], + relationType: RelationType.ONE_TO_MANY, + settings: null, + }, + }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useIsFieldEmpty.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useIsFieldEmpty.ts index 6899f0ef81..b7a2352070 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useIsFieldEmpty.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useIsFieldEmpty.ts @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { isFieldValueEmpty } from '@/object-record/record-field/ui/utils/isFieldValueEmpty'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { isDefined } from 'twenty-shared/utils'; import { FieldContext } from '../contexts/FieldContext'; @@ -13,6 +13,7 @@ export const useIsFieldEmpty = () => { const fieldValue = useRecordFieldValue( recordId, fieldDefinition?.metadata?.fieldName ?? '', + fieldDefinition, ); if (isDefined(overridenIsFieldEmpty)) { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/MorphRelationOneToManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/MorphRelationOneToManyFieldDisplay.tsx index ccd923a82a..64d75cdab9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/MorphRelationOneToManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/MorphRelationOneToManyFieldDisplay.tsx @@ -14,7 +14,15 @@ export const MorphRelationOneToManyFieldDisplay = () => { const { isFocused } = useFieldFocus(); const { disableChipClick, triggerEvent } = useContext(FieldContext); - if (!morphValuesWithObjectNameSingular) { + if (!isDefined(morphValuesWithObjectNameSingular)) { + return null; + } + const areMorphValuesWithObjectNameSingularEmpty = + morphValuesWithObjectNameSingular.every( + (morphValueWithObjectNameSingular) => + morphValueWithObjectNameSingular.value.length === 0, + ); + if (areMorphValuesWithObjectNameSingularEmpty) { return null; } diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts index bb58857ca9..e439c5ba8f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts @@ -1,9 +1,9 @@ import { useContext } from 'react'; import { type FieldActorValue } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { AuthContext } from '@/auth/contexts/AuthContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { isDefined } from 'twenty-shared/utils'; import { type WorkspaceMember } from '~/generated-metadata/graphql'; import { FieldContext } from '../../contexts/FieldContext'; @@ -24,6 +24,7 @@ export const useActorFieldDisplay = (): ActorFieldDisplayValue | undefined => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); if (!isDefined(fieldValue)) { return undefined; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts index 5fbe5dacd3..b2f7efaa55 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts @@ -1,7 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; import { type FieldAddressValue } from '../../types/FieldMetadata'; @@ -13,6 +12,7 @@ export const useAddressFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts index 7c314607a8..0121ffa06c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts @@ -4,7 +4,8 @@ import { type FieldArrayMetadata, type FieldArrayValue, } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; + import { useContext } from 'react'; export const useArrayFieldDisplay = () => { @@ -15,6 +16,7 @@ export const useArrayFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts index 1639d424b1..20b5d94064 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts @@ -1,7 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useBooleanFieldDisplay = () => { @@ -12,6 +11,7 @@ export const useBooleanFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts index a27c039e7a..afe51cedb0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts @@ -1,9 +1,8 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldCurrency } from '@/object-record/record-field/ui/types/guards/isFieldCurrency'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldMetadataType } from 'twenty-shared/types'; import { FieldContext } from '../../contexts/FieldContext'; import { type FieldCurrencyValue } from '../../types/FieldMetadata'; @@ -22,6 +21,7 @@ export const useCurrencyFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts index 25b0f044c8..5fe63e4a0c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts @@ -1,9 +1,8 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; import { type FieldDateMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useDateFieldDisplay = () => { @@ -14,6 +13,7 @@ export const useDateFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts index abe9969a85..12576441d7 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts @@ -1,9 +1,8 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; import { type FieldDateTimeMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useDateTimeFieldDisplay = () => { @@ -14,6 +13,7 @@ export const useDateTimeFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts index 94d0d5950d..f675c29b0b 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts @@ -1,8 +1,7 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - import { type FieldEmailsValue } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useEmailsFieldDisplay = () => { @@ -13,6 +12,7 @@ export const useEmailsFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts index 1c14ce3c5a..7b4a5deaa0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts @@ -1,8 +1,8 @@ import { useContext } from 'react'; import { type FieldFullNameValue } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useFullNameFieldDisplay = () => { @@ -13,6 +13,7 @@ export const useFullNameFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts index bce59daed1..a8a3f0e0ca 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts @@ -1,9 +1,9 @@ import { useContext } from 'react'; import { type FieldJsonValue } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { useFormattedJsonFieldValue } from '@/object-record/record-field/ui/meta-types/hooks/useFormattedJsonFieldValue'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useJsonFieldDisplay = () => { @@ -14,6 +14,7 @@ export const useJsonFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); const formattedFieldValue = useFormattedJsonFieldValue({ diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts index 9ea9c9c3e1..e6729754ac 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts @@ -1,8 +1,8 @@ import { useContext } from 'react'; import { type FieldLinksValue } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useLinksFieldDisplay = () => { @@ -13,6 +13,7 @@ export const useLinksFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts index 9e29f809bc..03fc10956b 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts @@ -7,8 +7,8 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; -import { recordStoreMorphOneToManyValueWithObjectNameFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector'; -import { useRecoilValue } from 'recoil'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { type ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FieldContext } from '../../contexts/FieldContext'; import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; @@ -32,12 +32,12 @@ export const useMorphRelationFromManyFieldDisplay = () => { const button = fieldDefinition.editButtonIcon; - const morphValuesWithObjectNameSingular = useRecoilValue( - recordStoreMorphOneToManyValueWithObjectNameFamilySelector({ - recordId, - morphRelations: fieldDefinition.metadata.morphRelations, - }), - ); + const morphValuesWithObjectNameSingular = useRecordFieldValue< + { + objectNameSingular: string; + value: ObjectRecord; + }[] + >(recordId, fieldDefinition.metadata.fieldName, fieldDefinition); const maxWidthForField = isDefined(button) && isDefined(maxWidth) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts index e80115c24c..99134159eb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts @@ -11,8 +11,9 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; -import { recordStoreMorphManyToOneValueWithObjectNameFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector'; -import { useRecoilValue } from 'recoil'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; + +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isDefined } from 'twenty-shared/utils'; export const useMorphRelationToOneFieldDisplay = () => { @@ -33,12 +34,10 @@ export const useMorphRelationToOneFieldDisplay = () => { const button = fieldDefinition.editButtonIcon; - const morphFieldValueWithObjectName = useRecoilValue( - recordStoreMorphManyToOneValueWithObjectNameFamilySelector({ - recordId, - morphRelations: fieldDefinition.metadata.morphRelations, - }), - ); + const morphFieldValueWithObjectName = useRecordFieldValue<{ + objectNameSingular: string; + value: ObjectRecord; + }>(recordId, fieldDefinition.metadata.fieldName, fieldDefinition); if (!isDefined(morphFieldValueWithObjectName)) { return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts index bad9955ca3..6fc10d044c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts @@ -6,7 +6,7 @@ import { type FieldMultiSelectMetadata, type FieldMultiSelectValue, } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; export const useMultiSelectFieldDisplay = () => { const { recordId, fieldDefinition } = useContext(FieldContext); @@ -16,6 +16,7 @@ export const useMultiSelectFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts index 232749405d..aabb6a2e52 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts @@ -1,8 +1,8 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; import { isFieldNumber } from '../../types/guards/isFieldNumber'; @@ -13,7 +13,11 @@ export const useNumberFieldDisplay = () => { assertFieldMetadata(FieldMetadataType.NUMBER, isFieldNumber, fieldDefinition); const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue(recordId, fieldName); + const fieldValue = useRecordFieldValue( + recordId, + fieldName, + fieldDefinition, + ); return { fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts index f082e67824..948525fdfc 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts @@ -1,8 +1,8 @@ import { useContext } from 'react'; import { type FieldPhonesValue } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const usePhonesFieldDisplay = () => { @@ -13,6 +13,7 @@ export const usePhonesFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts index c1dd7e25ae..e668bb1827 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts @@ -1,7 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { type FieldRatingValue } from 'twenty-shared/types'; import { FieldContext } from '../../contexts/FieldContext'; @@ -10,7 +9,11 @@ export const useRatingFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue(recordId, fieldName); + const fieldValue = useRecordFieldValue( + recordId, + fieldName, + fieldDefinition, + ); const rating = fieldValue ?? null; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts index c80cf606d6..ceaed52261 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts @@ -3,11 +3,12 @@ import { useContext } from 'react'; import { PreComputedChipGeneratorsContext } from '@/object-metadata/contexts/PreComputedChipGeneratorsContext'; import { generateDefaultRecordChipData } from '@/object-metadata/utils/generateDefaultRecordChipData'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; + import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { isDefined } from 'twenty-shared/utils'; import { FieldContext } from '../../contexts/FieldContext'; import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; @@ -37,6 +38,7 @@ export const useRelationFromManyFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); const maxWidthForField = diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts index c8ea860abb..257ea7ce07 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts @@ -2,7 +2,7 @@ import { isNonEmptyString } from '@sniptt/guards'; import { useContext } from 'react'; import { PreComputedChipGeneratorsContext } from '@/object-metadata/contexts/PreComputedChipGeneratorsContext'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; + import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -11,6 +11,7 @@ import { generateDefaultRecordChipData } from '@/object-metadata/utils/generateD import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { isDefined } from 'twenty-shared/utils'; export const useRelationToOneFieldDisplay = () => { @@ -37,6 +38,7 @@ export const useRelationToOneFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); const maxWidthForField = diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts index 23fae39690..fee3db2eb2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts @@ -1,10 +1,9 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - import { type FieldRichTextValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import type { PartialBlock } from '@blocknote/core'; import { isDefined, parseJson } from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -24,6 +23,7 @@ export const useRichTextFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); const fieldValueParsed = isDefined(fieldValue) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts index cf6ce974f1..4873d06996 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts @@ -1,10 +1,9 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - import { type FieldRichTextV2Value } from '@/object-record/record-field/ui/types/FieldMetadata'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldContext } from '../../contexts/FieldContext'; @@ -22,6 +21,7 @@ export const useRichTextV2FieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts index 1f8c68de6c..8fd0d5862f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts @@ -1,8 +1,8 @@ import { useContext } from 'react'; import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; import { type FieldSelectMetadata, @@ -17,6 +17,7 @@ export const useSelectFieldDisplay = () => { const fieldValue = useRecordFieldValue( recordId, fieldName, + fieldDefinition, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts index 30f10e93cd..bd4f7f6368 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts @@ -1,7 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - +import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; import { FieldContext } from '../../contexts/FieldContext'; export const useTextFieldDisplay = () => { @@ -11,7 +10,11 @@ export const useTextFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; const fieldValue = - useRecordFieldValue(recordId, fieldName) ?? ''; + useRecordFieldValue( + recordId, + fieldName, + fieldDefinition, + ) ?? ''; return { fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/isFieldValueEmpty.test.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/isFieldValueEmpty.test.ts index 173fa58416..f13c63dca6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/isFieldValueEmpty.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/isFieldValueEmpty.test.ts @@ -3,6 +3,7 @@ import { fieldMetadataId, fullNameFieldDefinition, linksFieldDefinition, + morphRelationFieldDefinition, relationFieldDefinition, selectFieldDefinition, } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions'; @@ -213,4 +214,25 @@ describe('isFieldValueEmpty', () => { }), ).toBe(false); }); + + it('should return correct value for morph relation field', () => { + expect( + isFieldValueEmpty({ + fieldDefinition: morphRelationFieldDefinition, + fieldValue: null, + }), + ).toBe(true); + expect( + isFieldValueEmpty({ + fieldDefinition: morphRelationFieldDefinition, + fieldValue: [{ value: null }, { value: [] }], + }), + ).toBe(true); + expect( + isFieldValueEmpty({ + fieldDefinition: morphRelationFieldDefinition, + fieldValue: [{ value: [{ id: '123' }] }], + }), + ).toBe(false); + }); }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/isFieldValueEmpty.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/isFieldValueEmpty.ts index 84cbf8f794..ac8cc9ce32 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/isFieldValueEmpty.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/isFieldValueEmpty.ts @@ -82,9 +82,23 @@ export const isFieldValueEmpty = ({ } if (isFieldMorphRelation(fieldDefinition)) { - return isArray(fieldValue) - ? !isNonEmptyArray(fieldValue) - : isValueEmpty(fieldValue); + if (!isArray(fieldValue)) { + return isValueEmpty(fieldValue); + } + + const areValuesEmpty = fieldValue + .filter(isDefined) + .every((fieldValueWithObjectNameSingular) => { + if ('value' in fieldValueWithObjectNameSingular) { + const value = fieldValueWithObjectNameSingular?.value; + if (!isArray(value)) { + return isValueEmpty(value); + } + return !isNonEmptyArray(value); + } + return true; + }); + return areValuesEmpty; } if (isFieldMultiSelect(fieldDefinition) || isFieldArray(fieldDefinition)) { diff --git a/packages/twenty-front/src/modules/object-record/record-store/contexts/RecordFieldValueSelectorContext.tsx b/packages/twenty-front/src/modules/object-record/record-store/contexts/RecordFieldValueSelectorContext.tsx deleted file mode 100644 index 5fed46cb3b..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-store/contexts/RecordFieldValueSelectorContext.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; -import { useRecoilValue } from 'recoil'; - -export const useRecordFieldValue = ( - recordId: string, - fieldName: string, -) => { - const recordFieldValue = useRecoilValue( - recordStoreFamilySelector({ - recordId, - fieldName, - }), - ); - - return recordFieldValue as T | undefined; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-store/hooks/useRecordFieldValue.tsx b/packages/twenty-front/src/modules/object-record/record-store/hooks/useRecordFieldValue.tsx new file mode 100644 index 0000000000..8b41142c07 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-store/hooks/useRecordFieldValue.tsx @@ -0,0 +1,23 @@ +import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; +import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { recordStoreFieldValueSelector } from '@/object-record/record-store/states/selectors/recordStoreFieldValueSelector'; +import { useRecoilValue } from 'recoil'; + +export const useRecordFieldValue = ( + recordId: string, + fieldName: string, + fieldDefinition: Pick, 'type' | 'metadata'>, +) => { + const recordFieldValue = useRecoilValue( + recordStoreFieldValueSelector({ + recordId, + fieldName, + fieldDefinition: { + type: fieldDefinition.type, + metadata: fieldDefinition.metadata, + }, + }), + ) as T | undefined; + + return recordFieldValue; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelector.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelector.ts new file mode 100644 index 0000000000..dc969cd74e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelector.ts @@ -0,0 +1,84 @@ +import { selectorFamily } from 'recoil'; + +import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; +import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { RelationType, type ObjectRecord } from 'twenty-shared/types'; +import { + computeMorphRelationFieldName, + CustomError, + isDefined, +} from 'twenty-shared/utils'; + +export const recordStoreFieldValueSelector = selectorFamily({ + key: 'recordStoreFieldValueSelector', + get: + ({ + recordId, + fieldName, + fieldDefinition, + }: { + recordId: string; + fieldName: string; + fieldDefinition: Pick< + FieldDefinition, + 'type' | 'metadata' + >; + }) => + ({ get }) => { + if (!isFieldMorphRelation(fieldDefinition)) { + return get(recordStoreFamilyState(recordId))?.[fieldName]; + } + + const morphRelations = fieldDefinition.metadata.morphRelations; + + if (!Array.isArray(morphRelations) || morphRelations.length === 0) { + throw new CustomError( + 'No morph relations found', + 'NO_MORPH_RELATIONS_FOUND', + ); + } + + const morphValuesWithObjectName = morphRelations.map((morphRelation) => { + const computedFieldName = computeMorphRelationFieldName({ + fieldName: morphRelation.sourceFieldMetadata.name, + relationType: morphRelation.type, + targetObjectMetadataNameSingular: + morphRelation.targetObjectMetadata.nameSingular, + targetObjectMetadataNamePlural: + morphRelation.targetObjectMetadata.namePlural, + }); + return { + objectNameSingular: morphRelation.targetObjectMetadata.nameSingular, + value: get(recordStoreFamilyState(recordId))?.[computedFieldName], + }; + }); + + const relationType = morphRelations[0].type; + + if (relationType === RelationType.ONE_TO_MANY) { + return morphValuesWithObjectName as { + objectNameSingular: string; + value: ObjectRecord[]; + }[]; + } + + if (relationType === RelationType.MANY_TO_ONE) { + const morphValueFiltered = morphValuesWithObjectName.filter( + (morphValue) => isDefined(morphValue.value), + ); + return morphValueFiltered.length > 0 + ? (morphValueFiltered[0] as { + objectNameSingular: string; + value: ObjectRecord; + }) + : null; + } + + throw new CustomError( + `Unknown relation type: ${relationType}`, + 'UNKNOWN_RELATION_TYPE', + ); + }, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector.ts deleted file mode 100644 index 7977ec7ca9..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { selectorFamily } from 'recoil'; - -import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; - -export const recordStoreMorphManyToOneValueWithObjectNameFamilySelector = - selectorFamily({ - key: 'recordStoreMorphManyToOneValueWithObjectNameFamilySelector', - get: - ({ - recordId, - morphRelations, - }: { - recordId: string; - morphRelations: FieldMetadataItemRelation[]; - }) => - ({ get }) => { - const morphValuesWithObjectName = morphRelations.map( - (morphRelation) => { - const fieldName = computeMorphRelationFieldName({ - fieldName: morphRelation.sourceFieldMetadata.name, - relationType: morphRelation.type, - targetObjectMetadataNameSingular: - morphRelation.targetObjectMetadata.nameSingular, - targetObjectMetadataNamePlural: - morphRelation.targetObjectMetadata.namePlural, - }); - return { - objectNameSingular: - morphRelation.targetObjectMetadata.nameSingular, - value: get(recordStoreFamilyState(recordId))?.[ - fieldName - ] as ObjectRecord, - }; - }, - ); - - const morphValueFiltered = morphValuesWithObjectName.filter( - (morphValue) => isDefined(morphValue.value), - ); - return morphValueFiltered.length > 0 ? morphValueFiltered[0] : null; - }, - });