Simplify multi-object picker logic with search (#8010)
Simplifying the logic around multi-object pickers and search by getting rid of the behaviour that keeped selected elements even when they did not match the search filter (eg keeping selected record "Brian Chesky" in dropdown even when search input is "Qonto"). This allows us to simplify the fetch queries around the search to only do one query. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -27,6 +27,7 @@ import {
|
|||||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { ActivityTargetInlineCellEditModeMultiRecordsEffect } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect';
|
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 { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect';
|
||||||
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
|
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
|
||||||
import { prefillRecord } from '@/object-record/utils/prefillRecord';
|
import { prefillRecord } from '@/object-record/utils/prefillRecord';
|
||||||
@ -287,6 +288,7 @@ export const ActivityTargetInlineCellEditMode = ({
|
|||||||
<ActivityTargetInlineCellEditModeMultiRecordsEffect
|
<ActivityTargetInlineCellEditModeMultiRecordsEffect
|
||||||
selectedObjectRecordIds={selectedTargetObjectIds}
|
selectedObjectRecordIds={selectedTargetObjectIds}
|
||||||
/>
|
/>
|
||||||
|
<ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect />
|
||||||
<MultiRecordSelect onSubmit={handleSubmit} onChange={handleChange} />
|
<MultiRecordSelect onSubmit={handleSubmit} onChange={handleChange} />
|
||||||
</RelationPickerScope>
|
</RelationPickerScope>
|
||||||
</StyledSelectContainer>
|
</StyledSelectContainer>
|
||||||
|
|||||||
@ -5,10 +5,10 @@ import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useOb
|
|||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField';
|
import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField';
|
||||||
import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
|
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 { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions';
|
||||||
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
|
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
|
||||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
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 { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
|
|||||||
@ -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';
|
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
|
||||||
|
|
||||||
export type ObjectRecordAndSelected = ObjectRecordForSelect & {
|
export type ObjectRecordAndSelected = ObjectRecordForSelect & {
|
||||||
|
|||||||
@ -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<ObjectRecordForSelect[]>({
|
||||||
|
key: 'objectRecordMultiSelectMatchesFilterRecordsIdsComponentState',
|
||||||
|
defaultValue: [],
|
||||||
|
});
|
||||||
@ -7,15 +7,11 @@ import {
|
|||||||
} from 'recoil';
|
} from 'recoil';
|
||||||
|
|
||||||
import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates';
|
import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
|
import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
|
||||||
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
|
import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState';
|
||||||
import {
|
|
||||||
ObjectRecordForSelect,
|
|
||||||
SelectedObjectRecordId,
|
|
||||||
useMultiObjectSearch,
|
|
||||||
} from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
|
||||||
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
|
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 { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
@ -30,43 +26,14 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
|
|||||||
const {
|
const {
|
||||||
objectRecordsIdsMultiSelectState,
|
objectRecordsIdsMultiSelectState,
|
||||||
objectRecordMultiSelectCheckedRecordsIdsState,
|
objectRecordMultiSelectCheckedRecordsIdsState,
|
||||||
recordMultiSelectIsLoadingState,
|
|
||||||
} = useObjectRecordMultiSelectScopedStates(scopeId);
|
} = useObjectRecordMultiSelectScopedStates(scopeId);
|
||||||
const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] =
|
const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] =
|
||||||
useRecoilState(objectRecordsIdsMultiSelectState);
|
useRecoilState(objectRecordsIdsMultiSelectState);
|
||||||
|
|
||||||
const setRecordMultiSelectIsLoading = useSetRecoilState(
|
const setObjectRecordMultiSelectCheckedRecordsIds = useSetRecoilState(
|
||||||
recordMultiSelectIsLoadingState,
|
objectRecordMultiSelectCheckedRecordsIdsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const relationPickerScopedId = useAvailableScopeIdOrThrow(
|
|
||||||
RelationPickerScopeInternalContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { relationPickerSearchFilterState } = useRelationPickerScopedStates({
|
|
||||||
relationPickerScopedId,
|
|
||||||
});
|
|
||||||
const relationPickerSearchFilter = useRecoilValue(
|
|
||||||
relationPickerSearchFilterState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { filteredSelectedObjectRecords, loading, objectRecordsToSelect } =
|
|
||||||
useMultiObjectSearch({
|
|
||||||
searchFilterValue: relationPickerSearchFilter,
|
|
||||||
excludedObjects: [
|
|
||||||
CoreObjectNameSingular.Task,
|
|
||||||
CoreObjectNameSingular.Note,
|
|
||||||
],
|
|
||||||
selectedObjectRecordIds,
|
|
||||||
excludedObjectRecordIds: [],
|
|
||||||
limit: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [
|
|
||||||
objectRecordMultiSelectCheckedRecordsIds,
|
|
||||||
setObjectRecordMultiSelectCheckedRecordsIds,
|
|
||||||
] = useRecoilState(objectRecordMultiSelectCheckedRecordsIdsState);
|
|
||||||
|
|
||||||
const updateRecords = useRecoilCallback(
|
const updateRecords = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
(newRecords: ObjectRecordForSelect[]) => {
|
(newRecords: ObjectRecordForSelect[]) => {
|
||||||
@ -80,6 +47,10 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
|
|||||||
)
|
)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
|
const objectRecordMultiSelectCheckedRecordsIds = snapshot
|
||||||
|
.getLoadable(objectRecordMultiSelectCheckedRecordsIdsState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
const newRecordWithSelected = {
|
const newRecordWithSelected = {
|
||||||
...newRecord,
|
...newRecord,
|
||||||
selected: objectRecordMultiSelectCheckedRecordsIds.some(
|
selected: objectRecordMultiSelectCheckedRecordsIds.some(
|
||||||
@ -103,23 +74,25 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[objectRecordMultiSelectCheckedRecordsIds, scopeId],
|
[objectRecordMultiSelectCheckedRecordsIdsState, scopeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const matchesSearchFilterObjectRecords = useRecoilValue(
|
||||||
|
objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({
|
||||||
|
scopeId,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const allRecords = [
|
const allRecords = matchesSearchFilterObjectRecords ?? [];
|
||||||
...(filteredSelectedObjectRecords ?? []),
|
|
||||||
...(objectRecordsToSelect ?? []),
|
|
||||||
];
|
|
||||||
updateRecords(allRecords);
|
updateRecords(allRecords);
|
||||||
const allRecordsIds = allRecords.map((record) => record.record.id);
|
const allRecordsIds = allRecords.map((record) => record.record.id);
|
||||||
if (!isDeeplyEqual(allRecordsIds, objectRecordsIdsMultiSelect)) {
|
if (!isDeeplyEqual(allRecordsIds, objectRecordsIdsMultiSelect)) {
|
||||||
setObjectRecordsIdsMultiSelect(allRecordsIds);
|
setObjectRecordsIdsMultiSelect(allRecordsIds);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
filteredSelectedObjectRecords,
|
matchesSearchFilterObjectRecords,
|
||||||
objectRecordsIdsMultiSelect,
|
objectRecordsIdsMultiSelect,
|
||||||
objectRecordsToSelect,
|
|
||||||
setObjectRecordsIdsMultiSelect,
|
setObjectRecordsIdsMultiSelect,
|
||||||
updateRecords,
|
updateRecords,
|
||||||
]);
|
]);
|
||||||
@ -130,9 +103,5 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
|
|||||||
);
|
);
|
||||||
}, [selectedObjectRecordIds, setObjectRecordMultiSelectCheckedRecordsIds]);
|
}, [selectedObjectRecordIds, setObjectRecordMultiSelectCheckedRecordsIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRecordMultiSelectIsLoading(loading);
|
|
||||||
}, [loading, setRecordMultiSelectIsLoading]);
|
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 <></>;
|
||||||
|
};
|
||||||
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -4,7 +4,8 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector';
|
import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector';
|
||||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||||
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
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';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export type MultiObjectRecordQueryResult = {
|
export type MultiObjectRecordQueryResult = {
|
||||||
@ -24,25 +25,34 @@ export const useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArr
|
|||||||
objectMetadataItemsByNamePluralMapSelector,
|
objectMetadataItemsByNamePluralMapSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formattedMultiObjectRecordsQueryResult = useMemo(() => {
|
||||||
|
return formatMultiObjectRecordSearchResults(
|
||||||
|
multiObjectRecordsQueryResult,
|
||||||
|
);
|
||||||
|
}, [multiObjectRecordsQueryResult]);
|
||||||
|
|
||||||
const objectRecordForSelectArray = useMemo(() => {
|
const objectRecordForSelectArray = useMemo(() => {
|
||||||
return Object.entries(multiObjectRecordsQueryResult ?? {}).flatMap(
|
return Object.entries(
|
||||||
([namePlural, objectRecordConnection]) => {
|
formattedMultiObjectRecordsQueryResult ?? {},
|
||||||
const objectMetadataItem =
|
).flatMap(([namePlural, objectRecordConnection]) => {
|
||||||
objectMetadataItemsByNamePluralMap.get(namePlural);
|
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,
|
objectMetadataItem,
|
||||||
record: node,
|
record: node,
|
||||||
recordIdentifier: getObjectRecordIdentifier({
|
}),
|
||||||
objectMetadataItem,
|
})) as ObjectRecordForSelect[];
|
||||||
record: node,
|
});
|
||||||
}),
|
}, [
|
||||||
})) as ObjectRecordForSelect[];
|
formattedMultiObjectRecordsQueryResult,
|
||||||
},
|
objectMetadataItemsByNamePluralMap,
|
||||||
);
|
]);
|
||||||
}, [multiObjectRecordsQueryResult, objectMetadataItemsByNamePluralMap]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objectRecordForSelectArray,
|
objectRecordForSelectArray,
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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<MultiObjectRecordQueryResult>(
|
|
||||||
multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY,
|
|
||||||
{
|
|
||||||
variables: {
|
|
||||||
search: searchFilterValue,
|
|
||||||
...selectedAndMatchesSearchFilterTextFilterPerMetadataItem,
|
|
||||||
...limitPerMetadataItem,
|
|
||||||
},
|
|
||||||
skip: !isDefined(multiSelectSearchQueryForSelectedIds),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
objectRecordForSelectArray: selectedAndMatchesSearchFilterObjectRecords,
|
|
||||||
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
|
||||||
multiObjectRecordsQueryResult: formatSearchResults(
|
|
||||||
selectedAndMatchesSearchFilterObjectRecordsQueryResult,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedAndMatchesSearchFilterObjectRecordsLoading,
|
|
||||||
selectedAndMatchesSearchFilterObjectRecords,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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<MultiObjectRecordQueryResult>(multiSelectQuery ?? EMPTY_QUERY, {
|
|
||||||
variables: {
|
|
||||||
search: searchFilterValue,
|
|
||||||
...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem,
|
|
||||||
...limitPerMetadataItem,
|
|
||||||
},
|
|
||||||
skip: !isDefined(multiSelectQuery),
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords,
|
|
||||||
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
|
||||||
multiObjectRecordsQueryResult: formatSearchResults(
|
|
||||||
toSelectAndMatchesSearchFilterObjectRecordsQueryResult,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
toSelectAndMatchesSearchFilterObjectRecordsLoading,
|
|
||||||
toSelectAndMatchesSearchFilterObjectRecords,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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<MultiObjectRecordQueryResult>(
|
||||||
|
multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY,
|
||||||
|
{
|
||||||
|
variables: {
|
||||||
|
search: searchFilterValue,
|
||||||
|
...limitPerMetadataItem,
|
||||||
|
},
|
||||||
|
skip: !isDefined(multiSelectSearchQueryForSelectedIds),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { objectRecordForSelectArray: matchesSearchFilterObjectRecords } =
|
||||||
|
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||||
|
multiObjectRecordsQueryResult:
|
||||||
|
matchesSearchFilterObjectRecordsQueryResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
matchesSearchFilterObjectRecordsLoading,
|
||||||
|
matchesSearchFilterObjectRecords,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -9,8 +9,8 @@ import {
|
|||||||
MultiObjectRecordQueryResult,
|
MultiObjectRecordQueryResult,
|
||||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray,
|
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray,
|
||||||
} from '@/object-record/relation-picker/hooks/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 { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem';
|
||||||
|
import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { capitalize } from '~/utils/string/capitalize';
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
};
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export type SelectedObjectRecordId = {
|
||||||
|
objectNameSingular: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user