diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx index 93b84a8cc..d36382078 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx @@ -16,7 +16,12 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker'; +import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch'; +import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useRecoilCallback } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; type RelationFromManyFieldInputProps = { onSubmit?: FieldInputEvent; @@ -86,6 +91,55 @@ export const RelationFromManyFieldInput = ({ recordFieldInputLayoutDirectionComponentState, ); + const multipleRecordPickerPickableMorphItemsCallbackState = + useRecoilComponentCallbackStateV2( + multipleRecordPickerPickableMorphItemsComponentState, + recordPickerInstanceId, + ); + const { performSearch: multipleRecordPickerPerformSearch } = + useMultipleRecordPickerPerformSearch(); + + const handleCreateNew = useRecoilCallback( + ({ snapshot, set }) => + async (searchInput?: string) => { + const newRecordId = + await createNewRecordAndOpenRightDrawer?.(searchInput); + + if (!isDefined(newRecordId)) { + return; + } + + const multipleRecordPickerPickableMorphItems = snapshot + .getLoadable(multipleRecordPickerPickableMorphItemsCallbackState) + .getValue(); + + const newMorphItems = multipleRecordPickerPickableMorphItems.concat({ + recordId: newRecordId, + objectMetadataId: relationObjectMetadataItem.id, + isSelected: true, + isMatchingSearchFilter: true, + }); + + set(multipleRecordPickerPickableMorphItemsCallbackState, newMorphItems); + + multipleRecordPickerPerformSearch({ + multipleRecordPickerInstanceId: recordPickerInstanceId, + forceSearchFilter: searchInput, + forceSearchableObjectMetadataItems: [relationObjectMetadataItem], + forcePickableMorphItems: newMorphItems, + }); + }, + [ + createNewRecordAndOpenRightDrawer, + relationObjectMetadataItem, + recordPickerInstanceId, + multipleRecordPickerPickableMorphItemsCallbackState, + multipleRecordPickerPerformSearch, + ], + ); + + const canCreateNew = !isRelationFromActivityTargets; + return ( { + const newRecordId = await createNewRecordAndOpenRightDrawer?.(searchInput); + + if (isDefined(newRecordId)) { + setSingleRecordPickerSelectedId(newRecordId); + } + }; + if (isLoading) { return <>; } @@ -73,7 +89,7 @@ export const RelationToOneFieldInput = ({ EmptyIcon={IconForbid} emptyLabel={'No ' + fieldDefinition.label} onCancel={onCancel} - onCreate={createNewRecordAndOpenRightDrawer} + onCreate={handleCreateNew} onRecordSelected={handleRecordSelected} objectNameSingular={ fieldDefinition.metadata.relationObjectMetadataNameSingular diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts index 17ec38204..852f12abf 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer.ts @@ -1,6 +1,7 @@ import { useSetRecoilState } from 'recoil'; import { v4 } from 'uuid'; +import { SEARCH_QUERY } from '@/command-menu/graphql/queries/search'; import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; @@ -9,6 +10,8 @@ import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; +import { useApolloClient } from '@apollo/client'; +import { getOperationName } from '@apollo/client/utilities'; import { isDefined } from 'twenty-shared/utils'; import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql'; @@ -18,6 +21,7 @@ type RecordDetailRelationSectionProps = { relationFieldMetadataItem?: FieldMetadataItem; recordId: string; }; + export const useAddNewRecordAndOpenRightDrawer = ({ relationObjectMetadataNameSingular, relationObjectMetadataItem, @@ -41,6 +45,8 @@ export const useAddNewRecordAndOpenRightDrawer = ({ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); + const apolloClient = useApolloClient(); + if ( relationObjectMetadataNameSingular === 'workspaceMember' || !isDefined( @@ -103,10 +109,16 @@ export const useAddNewRecordAndOpenRightDrawer = ({ setViewableRecordId(newRecordId); setViewableRecordNameSingular(relationObjectMetadataNameSingular); + apolloClient.refetchQueries({ + include: [getOperationName(SEARCH_QUERY) ?? ''], + }); + openRecordInCommandMenu({ recordId: newRecordId, objectNameSingular: relationObjectMetadataNameSingular, }); + + return newRecordId; }, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPicker.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPicker.tsx index ce16d779c..0634fc44f 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPicker.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPicker.tsx @@ -6,6 +6,7 @@ import { } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch'; import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext'; import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState'; +import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; @@ -43,10 +44,12 @@ export const SingleRecordPicker = ({ onCancel?.(); }; - const handleCreateNew = (searchInput?: string | undefined) => { - onCreate?.(searchInput); - + const handleRecordSelected = ( + selectedRecord?: SingleRecordPickerRecord | undefined, + ) => { setRecordPickerSearchFilter(''); + + onRecordSelected?.(selectedRecord); }; useListenClickOutside({ @@ -78,8 +81,8 @@ export const SingleRecordPicker = ({ emptyLabel, excludedRecordIds, onCancel: handleCancel, - onCreate: handleCreateNew, - onRecordSelected, + onCreate, + onRecordSelected: handleRecordSelected, objectNameSingular, layoutDirection, }} diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx index 2c5d4d438..01e1b7452 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx @@ -76,6 +76,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({ const searchHasNoResults = isNonEmptyString(recordPickerSearchFilter) && records.recordsToSelect.length === 0 && + records.filteredSelectedRecords.length === 0 && !records.loading; const handleCreateNew = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts index 2b6a421c0..16aa4a28d 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts @@ -40,7 +40,6 @@ export const useSingleRecordPickerSearch = ( event: React.ChangeEvent, ) => { debouncedSetSearchFilter(event.currentTarget.value); - setRecordPickerSelectedId(undefined); }; return { diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdown.tsx index 2cc0fb26f..4e9682e50 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdown.tsx @@ -1,34 +1,12 @@ -import { useCallback, useContext } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useContext } from 'react'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly'; import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly'; -import { usePersistField } from '@/object-record/record-field/hooks/usePersistField'; -import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; -import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; -import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker'; -import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch'; -import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; -import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState'; -import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; -import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker'; -import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState'; -import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState'; -import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; -import { getRecordFieldCardRelationPickerDropdownId } from '@/object-record/record-show/utils/getRecordFieldCardRelationPickerDropdownId'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; -import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { IconForbid, IconPencil, IconPlus } from 'twenty-ui/display'; -import { LightIconButton } from 'twenty-ui/input'; +import { RecordDetailRelationSectionDropdownToMany } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany'; +import { RecordDetailRelationSectionDropdownToOne } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne'; import { RelationType } from '~/generated-metadata/graphql'; type RecordDetailRelationSectionDropdownProps = { @@ -38,17 +16,13 @@ type RecordDetailRelationSectionDropdownProps = { export const RecordDetailRelationSectionDropdown = ({ loading, }: RecordDetailRelationSectionDropdownProps) => { - const { recordId, fieldDefinition } = useContext(FieldContext); + const { fieldDefinition, recordId } = useContext(FieldContext); const { - fieldName, - relationFieldMetadataId, - relationObjectMetadataNameSingular, relationType, objectMetadataNameSingular, + relationObjectMetadataNameSingular, } = fieldDefinition.metadata as FieldRelationMetadata; - const record = useRecoilValue(recordStoreFamilyState(recordId)); - const { objectMetadataItem: recordObjectMetadataItem } = useObjectMetadataItem({ objectNameSingular: objectMetadataNameSingular ?? '', @@ -59,99 +33,10 @@ export const RecordDetailRelationSectionDropdown = ({ objectNameSingular: relationObjectMetadataNameSingular, }); - const relationFieldMetadataItem = relationObjectMetadataItem.fields.find( - ({ id }) => id === relationFieldMetadataId, - ); - - const fieldValue = useRecoilValue< - ({ id: string } & Record) | ObjectRecord[] | null - >(recordStoreFamilySelector({ recordId, fieldName })); - // TODO: use new relation type const isToOneObject = relationType === RelationType.MANY_TO_ONE; const isToManyObjects = relationType === RelationType.ONE_TO_MANY; - const relationRecords: ObjectRecord[] = - fieldValue && isToOneObject - ? [fieldValue as ObjectRecord] - : ((fieldValue as ObjectRecord[]) ?? []); - - const dropdownId = getRecordFieldCardRelationPickerDropdownId({ - fieldDefinition, - recordId, - }); - - const { closeDropdown, dropdownPlacement } = useDropdown(dropdownId); - - const setMultipleRecordPickerSearchFilter = useSetRecoilComponentStateV2( - multipleRecordPickerSearchFilterComponentState, - dropdownId, - ); - - const setMultipleRecordPickerPickableMorphItems = - useSetRecoilComponentStateV2( - multipleRecordPickerPickableMorphItemsComponentState, - dropdownId, - ); - - const setMultipleRecordPickerSearchableObjectMetadataItems = - useSetRecoilComponentStateV2( - multipleRecordPickerSearchableObjectMetadataItemsComponentState, - dropdownId, - ); - - const { performSearch: multipleRecordPickerPerformSearch } = - useMultipleRecordPickerPerformSearch(); - - const setSingleRecordPickerSearchFilter = useSetRecoilComponentStateV2( - singleRecordPickerSearchFilterComponentState, - dropdownId, - ); - - const setSingleRecordPickerSelectedId = useSetRecoilComponentStateV2( - singleRecordPickerSelectedIdComponentState, - dropdownId, - ); - - const handleCloseRelationPickerDropdown = useCallback(() => { - setMultipleRecordPickerSearchFilter(''); - }, [setMultipleRecordPickerSearchFilter]); - - const persistField = usePersistField(); - const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({ - objectNameSingular: relationObjectMetadataNameSingular, - }); - - const handleRelationPickerEntitySelected = ( - selectedRelationEntity?: SingleRecordPickerRecord, - ) => { - closeDropdown(); - - if (!selectedRelationEntity?.id || !relationFieldMetadataItem?.name) return; - - if (isToOneObject) { - persistField(selectedRelationEntity.record); - return; - } - - updateOneRelationRecord({ - idToUpdate: selectedRelationEntity.id, - updateOneRecordInput: { - [relationFieldMetadataItem.name]: record, - }, - }); - }; - - const { updateRelation } = useUpdateRelationFromManyFieldInput(); - - const { createNewRecordAndOpenRightDrawer } = - useAddNewRecordAndOpenRightDrawer({ - relationObjectMetadataNameSingular, - relationObjectMetadataItem, - relationFieldMetadataItem, - recordId, - }); - const isRecordReadOnly = useIsRecordReadOnly({ recordId, objectMetadataId: isToOneObject @@ -166,93 +51,11 @@ export const RecordDetailRelationSectionDropdown = ({ if (loading || isFieldReadOnly) return null; - const handleOpenRelationPickerDropdown = () => { - if (isToOneObject) { - setSingleRecordPickerSearchFilter(''); - if (relationRecords.length > 0) { - setSingleRecordPickerSelectedId(relationRecords[0].id); - } - } - - if (isToManyObjects) { - setMultipleRecordPickerSearchableObjectMetadataItems([ - relationObjectMetadataItem, - ]); - setMultipleRecordPickerSearchFilter(''); - setMultipleRecordPickerPickableMorphItems( - relationRecords.map((record) => ({ - recordId: record.id, - objectMetadataId: relationObjectMetadataItem.id, - isSelected: true, - isMatchingSearchFilter: true, - })), - ); - - multipleRecordPickerPerformSearch({ - multipleRecordPickerInstanceId: dropdownId, - forceSearchFilter: '', - forceSearchableObjectMetadataItems: [relationObjectMetadataItem], - forcePickableMorphItems: relationRecords.map((record) => ({ - recordId: record.id, - objectMetadataId: relationObjectMetadataItem.id, - isSelected: true, - isMatchingSearchFilter: true, - })), - }); - } - }; - - return ( - - - } - dropdownComponents={ - isToOneObject ? ( - - ) : ( - { - closeDropdown(); - createNewRecordAndOpenRightDrawer?.(); - }} - onChange={updateRelation} - onSubmit={closeDropdown} - onClickOutside={closeDropdown} - layoutDirection={ - dropdownPlacement?.includes('end') - ? 'search-bar-on-bottom' - : 'search-bar-on-top' - } - /> - ) - } - /> - - ); + if (isToOneObject) { + return ; + } else if (isToManyObjects) { + return ; + } else { + return null; + } }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany.tsx new file mode 100644 index 000000000..979635b2a --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany.tsx @@ -0,0 +1,153 @@ +import { useCallback, useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; +import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; +import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput'; +import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker'; +import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch'; +import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; +import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState'; +import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; +import { getRecordFieldCardRelationPickerDropdownId } from '@/object-record/record-show/utils/getRecordFieldCardRelationPickerDropdownId'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { IconPlus } from 'twenty-ui/display'; +import { LightIconButton } from 'twenty-ui/input'; + +export const RecordDetailRelationSectionDropdownToMany = () => { + const { recordId, fieldDefinition } = useContext(FieldContext); + const { + fieldName, + relationFieldMetadataId, + relationObjectMetadataNameSingular, + } = fieldDefinition.metadata as FieldRelationMetadata; + + const { objectMetadataItem: relationObjectMetadataItem } = + useObjectMetadataItem({ + objectNameSingular: relationObjectMetadataNameSingular, + }); + + const relationFieldMetadataItem = relationObjectMetadataItem.fields.find( + ({ id }) => id === relationFieldMetadataId, + ); + + const fieldValue = useRecoilValue< + ({ id: string } & Record) | ObjectRecord[] | null + >(recordStoreFamilySelector({ recordId, fieldName })); + + const relationRecords: ObjectRecord[] = (fieldValue as ObjectRecord[]) ?? []; + + const dropdownId = getRecordFieldCardRelationPickerDropdownId({ + fieldDefinition, + recordId, + }); + + const { closeDropdown, dropdownPlacement } = useDropdown(dropdownId); + + const setMultipleRecordPickerSearchFilter = useSetRecoilComponentStateV2( + multipleRecordPickerSearchFilterComponentState, + dropdownId, + ); + + const setMultipleRecordPickerPickableMorphItems = + useSetRecoilComponentStateV2( + multipleRecordPickerPickableMorphItemsComponentState, + dropdownId, + ); + + const setMultipleRecordPickerSearchableObjectMetadataItems = + useSetRecoilComponentStateV2( + multipleRecordPickerSearchableObjectMetadataItemsComponentState, + dropdownId, + ); + + const { performSearch: multipleRecordPickerPerformSearch } = + useMultipleRecordPickerPerformSearch(); + + const handleCloseRelationPickerDropdown = useCallback(() => { + setMultipleRecordPickerSearchFilter(''); + }, [setMultipleRecordPickerSearchFilter]); + + const { updateRelation } = useUpdateRelationFromManyFieldInput(); + + const { createNewRecordAndOpenRightDrawer } = + useAddNewRecordAndOpenRightDrawer({ + relationObjectMetadataNameSingular, + relationObjectMetadataItem, + relationFieldMetadataItem, + recordId, + }); + + const handleOpenRelationPickerDropdown = () => { + setMultipleRecordPickerSearchableObjectMetadataItems([ + relationObjectMetadataItem, + ]); + setMultipleRecordPickerSearchFilter(''); + setMultipleRecordPickerPickableMorphItems( + relationRecords.map((record) => ({ + recordId: record.id, + objectMetadataId: relationObjectMetadataItem.id, + isSelected: true, + isMatchingSearchFilter: true, + })), + ); + + multipleRecordPickerPerformSearch({ + multipleRecordPickerInstanceId: dropdownId, + forceSearchFilter: '', + forceSearchableObjectMetadataItems: [relationObjectMetadataItem], + forcePickableMorphItems: relationRecords.map((record) => ({ + recordId: record.id, + objectMetadataId: relationObjectMetadataItem.id, + isSelected: true, + isMatchingSearchFilter: true, + })), + }); + }; + + const handleCreateNew = (searchString?: string) => { + closeDropdown(); + + createNewRecordAndOpenRightDrawer?.(searchString); + }; + + return ( + + + } + dropdownComponents={ + + } + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne.tsx new file mode 100644 index 000000000..f6f5c0589 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne.tsx @@ -0,0 +1,141 @@ +import { useCallback, useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; +import { usePersistField } from '@/object-record/record-field/hooks/usePersistField'; +import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; +import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker'; +import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState'; +import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState'; +import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; +import { getRecordFieldCardRelationPickerDropdownId } from '@/object-record/record-show/utils/getRecordFieldCardRelationPickerDropdownId'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { IconForbid, IconPencil } from 'twenty-ui/display'; +import { LightIconButton } from 'twenty-ui/input'; + +export const RecordDetailRelationSectionDropdownToOne = () => { + const { recordId, fieldDefinition } = useContext(FieldContext); + const { + fieldName, + relationFieldMetadataId, + relationObjectMetadataNameSingular, + } = fieldDefinition.metadata as FieldRelationMetadata; + + const { objectMetadataItem: relationObjectMetadataItem } = + useObjectMetadataItem({ + objectNameSingular: relationObjectMetadataNameSingular, + }); + + const relationFieldMetadataItem = relationObjectMetadataItem.fields.find( + ({ id }) => id === relationFieldMetadataId, + ); + + const fieldValue = useRecoilValue< + ({ id: string } & Record) | ObjectRecord[] | null + >(recordStoreFamilySelector({ recordId, fieldName })); + + const relationRecords: ObjectRecord[] = fieldValue + ? [fieldValue as ObjectRecord] + : []; + + const dropdownId = getRecordFieldCardRelationPickerDropdownId({ + fieldDefinition, + recordId, + }); + + const { closeDropdown, dropdownPlacement } = useDropdown(dropdownId); + + const setSingleRecordPickerSearchFilter = useSetRecoilComponentStateV2( + singleRecordPickerSearchFilterComponentState, + dropdownId, + ); + + const setSingleRecordPickerSelectedId = useSetRecoilComponentStateV2( + singleRecordPickerSelectedIdComponentState, + dropdownId, + ); + + const handleCloseRelationPickerDropdown = useCallback(() => { + setSingleRecordPickerSearchFilter(''); + }, [setSingleRecordPickerSearchFilter]); + + const persistField = usePersistField(); + + const handleRelationPickerEntitySelected = ( + selectedRelationEntity?: SingleRecordPickerRecord, + ) => { + closeDropdown(); + + if (!selectedRelationEntity?.id || !relationFieldMetadataItem?.name) return; + + persistField(selectedRelationEntity.record); + }; + + const { createNewRecordAndOpenRightDrawer } = + useAddNewRecordAndOpenRightDrawer({ + relationObjectMetadataNameSingular, + relationObjectMetadataItem, + relationFieldMetadataItem, + recordId, + }); + + const handleOpenRelationPickerDropdown = () => { + setSingleRecordPickerSearchFilter(''); + if (relationRecords.length > 0) { + setSingleRecordPickerSelectedId(relationRecords[0]?.id); + } + }; + + const handleCreateNew = (searchString?: string) => { + closeDropdown(); + + createNewRecordAndOpenRightDrawer?.(searchString); + }; + + const shouldAllowCreateNew = + relationObjectMetadataNameSingular !== + CoreObjectNameSingular.WorkspaceMember; + + return ( + + + } + dropdownComponents={ + + } + /> + + ); +};