diff --git a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx index 5b9d62b6cd7a..7719f4576bed 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx +++ b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx @@ -27,6 +27,7 @@ import { import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ActivityTargetInlineCellEditModeMultiRecordsEffect } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect'; +import { ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect'; import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect'; import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope'; import { prefillRecord } from '@/object-record/utils/prefillRecord'; @@ -287,6 +288,7 @@ export const ActivityTargetInlineCellEditMode = ({ + diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx index 6f42a7fbad2e..866edfdada99 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx @@ -5,10 +5,10 @@ import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useOb import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField'; import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; -import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts index 0e15c962e11b..e4150ae029c6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts @@ -1,4 +1,4 @@ -import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; export type ObjectRecordAndSelected = ObjectRecordForSelect & { diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts new file mode 100644 index 000000000000..bfaebeaa86fa --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts @@ -0,0 +1,8 @@ +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const objectRecordMultiSelectMatchesFilterRecordsIdsComponentState = + createComponentState({ + key: 'objectRecordMultiSelectMatchesFilterRecordsIdsComponentState', + defaultValue: [], + }); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx index 425877c20e15..26981f3c0396 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx @@ -7,15 +7,11 @@ import { } from 'recoil'; import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; -import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; -import { - ObjectRecordForSelect, - SelectedObjectRecordId, - useMultiObjectSearch, -} from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; +import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -30,43 +26,14 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ const { objectRecordsIdsMultiSelectState, objectRecordMultiSelectCheckedRecordsIdsState, - recordMultiSelectIsLoadingState, } = useObjectRecordMultiSelectScopedStates(scopeId); const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] = useRecoilState(objectRecordsIdsMultiSelectState); - const setRecordMultiSelectIsLoading = useSetRecoilState( - recordMultiSelectIsLoadingState, - ); - - const relationPickerScopedId = useAvailableScopeIdOrThrow( - RelationPickerScopeInternalContext, - ); - - const { relationPickerSearchFilterState } = useRelationPickerScopedStates({ - relationPickerScopedId, - }); - const relationPickerSearchFilter = useRecoilValue( - relationPickerSearchFilterState, + const setObjectRecordMultiSelectCheckedRecordsIds = useSetRecoilState( + objectRecordMultiSelectCheckedRecordsIdsState, ); - const { filteredSelectedObjectRecords, loading, objectRecordsToSelect } = - useMultiObjectSearch({ - searchFilterValue: relationPickerSearchFilter, - excludedObjects: [ - CoreObjectNameSingular.Task, - CoreObjectNameSingular.Note, - ], - selectedObjectRecordIds, - excludedObjectRecordIds: [], - limit: 10, - }); - - const [ - objectRecordMultiSelectCheckedRecordsIds, - setObjectRecordMultiSelectCheckedRecordsIds, - ] = useRecoilState(objectRecordMultiSelectCheckedRecordsIdsState); - const updateRecords = useRecoilCallback( ({ snapshot, set }) => (newRecords: ObjectRecordForSelect[]) => { @@ -80,6 +47,10 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ ) .getValue(); + const objectRecordMultiSelectCheckedRecordsIds = snapshot + .getLoadable(objectRecordMultiSelectCheckedRecordsIdsState) + .getValue(); + const newRecordWithSelected = { ...newRecord, selected: objectRecordMultiSelectCheckedRecordsIds.some( @@ -103,23 +74,25 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ } } }, - [objectRecordMultiSelectCheckedRecordsIds, scopeId], + [objectRecordMultiSelectCheckedRecordsIdsState, scopeId], + ); + + const matchesSearchFilterObjectRecords = useRecoilValue( + objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ + scopeId, + }), ); useEffect(() => { - const allRecords = [ - ...(filteredSelectedObjectRecords ?? []), - ...(objectRecordsToSelect ?? []), - ]; + const allRecords = matchesSearchFilterObjectRecords ?? []; updateRecords(allRecords); const allRecordsIds = allRecords.map((record) => record.record.id); if (!isDeeplyEqual(allRecordsIds, objectRecordsIdsMultiSelect)) { setObjectRecordsIdsMultiSelect(allRecordsIds); } }, [ - filteredSelectedObjectRecords, + matchesSearchFilterObjectRecords, objectRecordsIdsMultiSelect, - objectRecordsToSelect, setObjectRecordsIdsMultiSelect, updateRecords, ]); @@ -130,9 +103,5 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ ); }, [selectedObjectRecordIds, setObjectRecordMultiSelectCheckedRecordsIds]); - useEffect(() => { - setRecordMultiSelectIsLoading(loading); - }, [loading, setRecordMultiSelectIsLoading]); - return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx new file mode 100644 index 000000000000..c216122c148c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx @@ -0,0 +1,54 @@ +import { useEffect } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; + +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; +import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; +import { useMultiObjectSearchMatchesSearchFilterQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery'; +import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; + +export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = + () => { + const scopeId = useAvailableScopeIdOrThrow( + RelationPickerScopeInternalContext, + ); + + const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState( + objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ + scopeId, + }), + ); + + const relationPickerScopedId = useAvailableScopeIdOrThrow( + RelationPickerScopeInternalContext, + ); + + const { relationPickerSearchFilterState } = useRelationPickerScopedStates({ + relationPickerScopedId, + }); + const relationPickerSearchFilter = useRecoilValue( + relationPickerSearchFilterState, + ); + + const { matchesSearchFilterObjectRecords } = + useMultiObjectSearchMatchesSearchFilterQuery({ + excludedObjects: [ + CoreObjectNameSingular.Task, + CoreObjectNameSingular.Note, + ], + searchFilterValue: relationPickerSearchFilter, + limit: 10, + }); + + useEffect(() => { + setRecordMultiSelectMatchesFilterRecords( + matchesSearchFilterObjectRecords, + ); + }, [ + setRecordMultiSelectMatchesFilterRecords, + matchesSearchFilterObjectRecords, + ]); + + return <>; + }; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx deleted file mode 100644 index 0f27bd796e54..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { - MultiObjectSearch, - ObjectRecordForSelect, - SelectedObjectRecordId, - useMultiObjectSearch, -} from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; -import { useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; -import { useMultiObjectSearchMatchesSearchFilterAndToSelectQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery'; -import { useMultiObjectSearchSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery'; -import { renderHook } from '@testing-library/react'; -import { FieldMetadataType } from '~/generated/graphql'; - -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery', -); -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery', -); -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery', -); - -const objectData: ObjectMetadataItem[] = [ - { - createdAt: 'createdAt', - id: 'id', - isActive: true, - isCustom: true, - isSystem: false, - isRemote: false, - labelPlural: 'labelPlural', - labelSingular: 'labelSingular', - namePlural: 'namePlural', - nameSingular: 'nameSingular', - isLabelSyncedWithName: false, - updatedAt: 'updatedAt', - fields: [ - { - id: 'f6a0a73a-5ee6-442e-b764-39b682471240', - name: 'id', - label: 'id', - type: FieldMetadataType.Uuid, - createdAt: '2024-01-01T00:00:00.000Z', - updatedAt: '2024-01-01T00:00:00.000Z', - isActive: true, - }, - ], - indexMetadatas: [], - }, -]; - -describe('useMultiObjectSearch', () => { - const selectedObjectRecordIds: SelectedObjectRecordId[] = [ - { objectNameSingular: 'object1', id: '1' }, - { objectNameSingular: 'object2', id: '2' }, - ]; - const searchFilterValue = 'searchValue'; - const limit = 5; - const excludedObjectRecordIds: SelectedObjectRecordId[] = [ - { objectNameSingular: 'object3', id: '3' }, - { objectNameSingular: 'object4', id: '4' }, - ]; - const excludedObjects: CoreObjectNameSingular[] = []; - - const selectedObjectRecords: ObjectRecordForSelect[] = [ - { - objectMetadataItem: objectData[0], - record: { - __typename: 'ObjectRecord', - id: '1', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - }, - recordIdentifier: { - id: '1', - name: 'name', - }, - }, - ]; - const selectedObjectRecordsLoading = false; - - const selectedAndMatchesSearchFilterObjectRecords: ObjectRecordForSelect[] = - []; - const selectedAndMatchesSearchFilterObjectRecordsLoading = false; - - const toSelectAndMatchesSearchFilterObjectRecords: ObjectRecordForSelect[] = - []; - const toSelectAndMatchesSearchFilterObjectRecordsLoading = false; - - beforeEach(() => { - (useMultiObjectSearchSelectedItemsQuery as jest.Mock).mockReturnValue({ - selectedObjectRecords, - selectedObjectRecordsLoading, - }); - - ( - useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery as jest.Mock - ).mockReturnValue({ - selectedAndMatchesSearchFilterObjectRecords, - selectedAndMatchesSearchFilterObjectRecordsLoading, - }); - - ( - useMultiObjectSearchMatchesSearchFilterAndToSelectQuery as jest.Mock - ).mockReturnValue({ - toSelectAndMatchesSearchFilterObjectRecords, - toSelectAndMatchesSearchFilterObjectRecordsLoading, - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should return the correct object records and loading state', () => { - const { result } = renderHook(() => - useMultiObjectSearch({ - searchFilterValue, - selectedObjectRecordIds, - limit, - excludedObjectRecordIds, - excludedObjects, - }), - ); - - const expected: MultiObjectSearch = { - selectedObjectRecords, - filteredSelectedObjectRecords: - selectedAndMatchesSearchFilterObjectRecords, - objectRecordsToSelect: toSelectAndMatchesSearchFilterObjectRecords, - loading: - selectedAndMatchesSearchFilterObjectRecordsLoading || - toSelectAndMatchesSearchFilterObjectRecordsLoading || - selectedObjectRecordsLoading, - }; - - expect(result.current).toEqual(expected); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts index a1047478a7ba..ebac778c44f9 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts @@ -4,7 +4,8 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection'; -import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { formatMultiObjectRecordSearchResults } from '@/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { isDefined } from '~/utils/isDefined'; export type MultiObjectRecordQueryResult = { @@ -24,25 +25,34 @@ export const useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArr objectMetadataItemsByNamePluralMapSelector, ); + const formattedMultiObjectRecordsQueryResult = useMemo(() => { + return formatMultiObjectRecordSearchResults( + multiObjectRecordsQueryResult, + ); + }, [multiObjectRecordsQueryResult]); + const objectRecordForSelectArray = useMemo(() => { - return Object.entries(multiObjectRecordsQueryResult ?? {}).flatMap( - ([namePlural, objectRecordConnection]) => { - const objectMetadataItem = - objectMetadataItemsByNamePluralMap.get(namePlural); + return Object.entries( + formattedMultiObjectRecordsQueryResult ?? {}, + ).flatMap(([namePlural, objectRecordConnection]) => { + const objectMetadataItem = + objectMetadataItemsByNamePluralMap.get(namePlural); - if (!isDefined(objectMetadataItem)) return []; + if (!isDefined(objectMetadataItem)) return []; - return objectRecordConnection.edges.map(({ node }) => ({ + return objectRecordConnection.edges.map(({ node }) => ({ + objectMetadataItem, + record: node, + recordIdentifier: getObjectRecordIdentifier({ objectMetadataItem, record: node, - recordIdentifier: getObjectRecordIdentifier({ - objectMetadataItem, - record: node, - }), - })) as ObjectRecordForSelect[]; - }, - ); - }, [multiObjectRecordsQueryResult, objectMetadataItemsByNamePluralMap]); + }), + })) as ObjectRecordForSelect[]; + }); + }, [ + formattedMultiObjectRecordsQueryResult, + objectMetadataItemsByNamePluralMap, + ]); return { objectRecordForSelectArray, diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearch.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearch.ts deleted file mode 100644 index 8651e7f428f8..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearch.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; -import { useMultiObjectSearchMatchesSearchFilterAndToSelectQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery'; -import { useMultiObjectSearchSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; - -export const MULTI_OBJECT_SEARCH_REQUEST_LIMIT = 5; - -export type ObjectRecordForSelect = { - objectMetadataItem: ObjectMetadataItem; - record: ObjectRecord; - recordIdentifier: ObjectRecordIdentifier; -}; - -export type SelectedObjectRecordId = { - objectNameSingular: string; - id: string; -}; - -export type MultiObjectSearch = { - selectedObjectRecords: ObjectRecordForSelect[]; - filteredSelectedObjectRecords: ObjectRecordForSelect[]; - objectRecordsToSelect: ObjectRecordForSelect[]; - loading: boolean; -}; - -export const useMultiObjectSearch = ({ - searchFilterValue, - selectedObjectRecordIds, - limit, - excludedObjectRecordIds = [], - excludedObjects, -}: { - searchFilterValue: string; - selectedObjectRecordIds: SelectedObjectRecordId[]; - limit?: number; - excludedObjectRecordIds?: SelectedObjectRecordId[]; - excludedObjects?: CoreObjectNameSingular[]; -}): MultiObjectSearch => { - const { selectedObjectRecords, selectedObjectRecordsLoading } = - useMultiObjectSearchSelectedItemsQuery({ - selectedObjectRecordIds, - }); - - const { - selectedAndMatchesSearchFilterObjectRecords, - selectedAndMatchesSearchFilterObjectRecordsLoading, - } = useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery({ - searchFilterValue, - selectedObjectRecordIds, - limit, - }); - - const { - toSelectAndMatchesSearchFilterObjectRecords, - toSelectAndMatchesSearchFilterObjectRecordsLoading, - } = useMultiObjectSearchMatchesSearchFilterAndToSelectQuery({ - excludedObjects, - excludedObjectRecordIds, - searchFilterValue, - selectedObjectRecordIds, - limit, - }); - - return { - selectedObjectRecords, - filteredSelectedObjectRecords: selectedAndMatchesSearchFilterObjectRecords, - objectRecordsToSelect: toSelectAndMatchesSearchFilterObjectRecords, - loading: - selectedAndMatchesSearchFilterObjectRecordsLoading || - toSelectAndMatchesSearchFilterObjectRecordsLoading || - selectedObjectRecordsLoading, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts deleted file mode 100644 index b69ef1f40c6d..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { isNonEmptyArray } from '@sniptt/guards'; -import { useRecoilValue } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; -import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; -import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; -import { - MultiObjectRecordQueryResult, - useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, -} from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; -import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; -import { useMemo } from 'react'; -import { isDefined } from '~/utils/isDefined'; -import { capitalize } from '~/utils/string/capitalize'; - -export const formatSearchResults = ( - searchResults: MultiObjectRecordQueryResult | undefined, -): MultiObjectRecordQueryResult => { - if (!searchResults) { - return {}; - } - - return Object.entries(searchResults).reduce((acc, [key, value]) => { - let newKey = key.replace(/^search/, ''); - newKey = newKey.charAt(0).toLowerCase() + newKey.slice(1); - acc[newKey] = value; - return acc; - }, {} as MultiObjectRecordQueryResult); -}; - -export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ - selectedObjectRecordIds, - searchFilterValue, - limit, -}: { - selectedObjectRecordIds: SelectedObjectRecordId[]; - searchFilterValue: string; - limit?: number; -}) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - - const objectMetadataItemsUsedInSelectedIdsQuery = useMemo( - () => - objectMetadataItems.filter(({ nameSingular }) => { - return selectedObjectRecordIds.some(({ objectNameSingular }) => { - return objectNameSingular === nameSingular; - }); - }), - [objectMetadataItems, selectedObjectRecordIds], - ); - - const selectedAndMatchesSearchFilterTextFilterPerMetadataItem = - Object.fromEntries( - objectMetadataItems - .map(({ nameSingular }) => { - const selectedIds = selectedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - if (!isNonEmptyArray(selectedIds)) return null; - - return [ - `filter${capitalize(nameSingular)}`, - { - id: { - in: selectedIds, - }, - }, - ]; - }) - .filter(isDefined), - ); - - const { limitPerMetadataItem } = useLimitPerMetadataItem({ - objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery, - limit, - }); - - const multiSelectSearchQueryForSelectedIds = - useGenerateCombinedSearchRecordsQuery({ - operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map( - (objectMetadataItem) => ({ - objectNameSingular: objectMetadataItem.nameSingular, - variables: {}, - }), - ), - }); - - const { - loading: selectedAndMatchesSearchFilterObjectRecordsLoading, - data: selectedAndMatchesSearchFilterObjectRecordsQueryResult, - } = useQuery( - multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY, - { - variables: { - search: searchFilterValue, - ...selectedAndMatchesSearchFilterTextFilterPerMetadataItem, - ...limitPerMetadataItem, - }, - skip: !isDefined(multiSelectSearchQueryForSelectedIds), - }, - ); - - const { - objectRecordForSelectArray: selectedAndMatchesSearchFilterObjectRecords, - } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: formatSearchResults( - selectedAndMatchesSearchFilterObjectRecordsQueryResult, - ), - }); - - return { - selectedAndMatchesSearchFilterObjectRecordsLoading, - selectedAndMatchesSearchFilterObjectRecords, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts deleted file mode 100644 index c3150cd44ca8..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { useRecoilValue } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; -import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; -import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; -import { - MultiObjectRecordQueryResult, - useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, -} from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; -import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; -import { formatSearchResults } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; -import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; -import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; -import { isDefined } from '~/utils/isDefined'; -import { capitalize } from '~/utils/string/capitalize'; - -export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ - selectedObjectRecordIds, - excludedObjectRecordIds, - searchFilterValue, - limit, - excludedObjects, -}: { - selectedObjectRecordIds: SelectedObjectRecordId[]; - excludedObjectRecordIds: SelectedObjectRecordId[]; - searchFilterValue: string; - limit?: number; - excludedObjects?: CoreObjectNameSingular[]; -}) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - - const selectableObjectMetadataItems = objectMetadataItems - .filter(({ isSystem, isRemote }) => !isSystem && !isRemote) - .filter(({ nameSingular }) => { - return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); - }) - .filter((object) => - isObjectMetadataItemSearchableInCombinedRequest(object), - ); - - const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem = - Object.fromEntries( - selectableObjectMetadataItems - .map(({ nameSingular }) => { - const selectedIds = selectedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - const excludedIds = excludedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - const excludedIdsUnion = [...selectedIds, ...excludedIds]; - const excludedIdsFilter = excludedIdsUnion.length - ? { not: { id: { in: excludedIdsUnion } } } - : undefined; - - return [ - `filter${capitalize(nameSingular)}`, - makeAndFilterVariables([excludedIdsFilter]), - ]; - }) - .filter(isDefined), - ); - const { limitPerMetadataItem } = useLimitPerMetadataItem({ - objectMetadataItems: selectableObjectMetadataItems, - limit, - }); - - const multiSelectQuery = useGenerateCombinedSearchRecordsQuery({ - operationSignatures: selectableObjectMetadataItems.map( - (objectMetadataItem) => ({ - objectNameSingular: objectMetadataItem.nameSingular, - variables: {}, - }), - ), - }); - - const { - loading: toSelectAndMatchesSearchFilterObjectRecordsLoading, - data: toSelectAndMatchesSearchFilterObjectRecordsQueryResult, - } = useQuery(multiSelectQuery ?? EMPTY_QUERY, { - variables: { - search: searchFilterValue, - ...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem, - ...limitPerMetadataItem, - }, - skip: !isDefined(multiSelectQuery), - }); - - const { - objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords, - } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: formatSearchResults( - toSelectAndMatchesSearchFilterObjectRecordsQueryResult, - ), - }); - - return { - toSelectAndMatchesSearchFilterObjectRecordsLoading, - toSelectAndMatchesSearchFilterObjectRecords, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts new file mode 100644 index 000000000000..a8bdfec35419 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts @@ -0,0 +1,75 @@ +import { useQuery } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; + +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; +import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; +import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; +import { + MultiObjectRecordQueryResult, + useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, +} from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; +import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; +import { isDefined } from '~/utils/isDefined'; + +export const useMultiObjectSearchMatchesSearchFilterQuery = ({ + searchFilterValue, + limit, + excludedObjects, +}: { + searchFilterValue: string; + limit?: number; + excludedObjects?: CoreObjectNameSingular[]; +}) => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const selectableObjectMetadataItems = objectMetadataItems + .filter(({ isSystem, isRemote }) => !isSystem && !isRemote) + .filter(({ nameSingular }) => { + return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); + }) + .filter((objectMetadataItem) => + isObjectMetadataItemSearchableInCombinedRequest(objectMetadataItem), + ); + + const { limitPerMetadataItem } = useLimitPerMetadataItem({ + objectMetadataItems, + limit, + }); + + const multiSelectSearchQueryForSelectedIds = + useGenerateCombinedSearchRecordsQuery({ + operationSignatures: selectableObjectMetadataItems.map( + (objectMetadataItem) => ({ + objectNameSingular: objectMetadataItem.nameSingular, + variables: {}, + }), + ), + }); + + const { + loading: matchesSearchFilterObjectRecordsLoading, + data: matchesSearchFilterObjectRecordsQueryResult, + } = useQuery( + multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY, + { + variables: { + search: searchFilterValue, + ...limitPerMetadataItem, + }, + skip: !isDefined(multiSelectSearchQueryForSelectedIds), + }, + ); + + const { objectRecordForSelectArray: matchesSearchFilterObjectRecords } = + useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ + multiObjectRecordsQueryResult: + matchesSearchFilterObjectRecordsQueryResult, + }); + + return { + matchesSearchFilterObjectRecordsLoading, + matchesSearchFilterObjectRecords, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts index 5656cb77f5b9..a4fd29ddcbb5 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts @@ -9,8 +9,8 @@ import { MultiObjectRecordQueryResult, useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; -import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem'; +import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId'; import { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults.ts b/packages/twenty-front/src/modules/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults.ts new file mode 100644 index 000000000000..318f12ab7548 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults.ts @@ -0,0 +1,16 @@ +import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; + +export const formatMultiObjectRecordSearchResults = ( + searchResults: MultiObjectRecordQueryResult | undefined | null, +): MultiObjectRecordQueryResult => { + if (!searchResults) { + return {}; + } + + return Object.entries(searchResults).reduce((acc, [key, value]) => { + let newKey = key.replace(/^search/, ''); + newKey = newKey.charAt(0).toLowerCase() + newKey.slice(1); + acc[newKey] = value; + return acc; + }, {} as MultiObjectRecordQueryResult); +}; diff --git a/packages/twenty-front/src/modules/object-record/types/ObjectRecordForSelect.ts b/packages/twenty-front/src/modules/object-record/types/ObjectRecordForSelect.ts new file mode 100644 index 000000000000..a1266fb6f702 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/ObjectRecordForSelect.ts @@ -0,0 +1,9 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; + +export type ObjectRecordForSelect = { + objectMetadataItem: ObjectMetadataItem; + record: ObjectRecord; + recordIdentifier: ObjectRecordIdentifier; +}; diff --git a/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts b/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts new file mode 100644 index 000000000000..2c9bb2353892 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts @@ -0,0 +1,4 @@ +export type SelectedObjectRecordId = { + objectNameSingular: string; + id: string; +};