RecordPicker refactoring part 3: remove effects (#10505)
This PR is part 3 of RecordPicker refactoring. It aims to remove all effects from: - (low level layer) SingleRecordPicker, MultipleRecordPicker - (higher level layer) RelationPicker, RelationToOneInput, RelationFromManyInput, ActivityTargetInput... In this PR, I'm re-grouping Effects in ActivityTarget section and creating a hook that will be called on inlineCellOpen
This commit is contained in:
@ -5,7 +5,7 @@ import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
|
||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||
import { useCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables';
|
||||
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/multiple-objects/types/MultiObjectRecordQueryResult';
|
||||
|
||||
export const useCombinedFindManyRecords = ({
|
||||
operationSignatures,
|
||||
|
||||
@ -4,7 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
|
||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/multiple-objects/types/MultiObjectRecordQueryResult';
|
||||
|
||||
export const useCombinedGetTotalCount = ({
|
||||
objectMetadataItems,
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
||||
|
||||
export type MultiObjectRecordQueryResult = {
|
||||
[namePlural: string]: RecordGqlConnection;
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/multiple-objects/types/MultiObjectRecordQueryResult';
|
||||
|
||||
export const formatMultiObjectRecordSearchResults = (
|
||||
searchResults: MultiObjectRecordQueryResult | undefined | null,
|
||||
@ -33,6 +33,8 @@ export type GenericFieldContextType = {
|
||||
overridenIsFieldEmpty?: boolean;
|
||||
displayedMaxRows?: number;
|
||||
isDisplayModeFixHeight?: boolean;
|
||||
onOpenEditMode?: () => void;
|
||||
onCloseEditMode?: () => void;
|
||||
};
|
||||
|
||||
export const FieldContext = createContext<GenericFieldContextType>(
|
||||
|
||||
@ -57,6 +57,7 @@ export const RelationFromManyFieldInput = ({
|
||||
>
|
||||
<RelationFromManyFieldInputMultiRecordsEffect />
|
||||
<MultipleRecordPicker
|
||||
componentInstanceId={recordPickerInstanceId}
|
||||
onSubmit={handleSubmit}
|
||||
onChange={updateRelation}
|
||||
onCreate={createNewRecordAndOpenRightDrawer}
|
||||
|
||||
@ -3,11 +3,11 @@ import { IconForbid } from 'twenty-ui';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { RelationPickerInitialValueEffect } from '@/object-record/record-field/meta-types/input/components/RelationPickerInitialValueEffect';
|
||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/record-field/meta-types/input/types/RelationPickerHotkeyScope';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { SearchPickerInitialValueEffect } from '@/object-record/record-picker-morph-legacy/components/SearchPickerInitialValueEffect';
|
||||
import { SingleRecordPicker } from '@/object-record/record-picker/components/SingleRecordPicker';
|
||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/types/SingleRecordPickerRecord';
|
||||
|
||||
@ -59,7 +59,7 @@ export const RelationPicker = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchPickerInitialValueEffect
|
||||
<RelationPickerInitialValueEffect
|
||||
initialValueForSearchFilter={initialSearchFilter}
|
||||
recordPickerInstanceId={recordPickerInstanceId}
|
||||
/>
|
||||
|
||||
@ -3,7 +3,7 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
|
||||
import { useEffect } from 'react';
|
||||
|
||||
// Todo: this effect should be deprecated to use sync hooks
|
||||
export const SearchPickerInitialValueEffect = ({
|
||||
export const RelationPickerInitialValueEffect = ({
|
||||
initialValueForSearchFilter,
|
||||
recordPickerInstanceId,
|
||||
}: {
|
||||
@ -31,8 +31,14 @@ type RecordInlineCellProps = {
|
||||
};
|
||||
|
||||
export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
||||
const { fieldDefinition, recordId, isCentered, isDisplayModeFixHeight } =
|
||||
useContext(FieldContext);
|
||||
const {
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
isCentered,
|
||||
isDisplayModeFixHeight,
|
||||
onOpenEditMode,
|
||||
onCloseEditMode,
|
||||
} = useContext(FieldContext);
|
||||
const buttonIcon = useGetButtonIcon();
|
||||
|
||||
const isFieldInputOnly = useIsFieldInputOnly();
|
||||
@ -129,6 +135,8 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
||||
isDisplayModeFixHeight: isDisplayModeFixHeight,
|
||||
editModeContentOnly: isFieldInputOnly,
|
||||
loading: loading,
|
||||
onOpenEditMode,
|
||||
onCloseEditMode,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -17,6 +17,8 @@ export type RecordInlineCellContextProps = {
|
||||
disableHoverEffect?: boolean;
|
||||
loading?: boolean;
|
||||
isCentered?: boolean;
|
||||
onOpenEditMode?: () => void;
|
||||
onCloseEditMode?: () => void;
|
||||
};
|
||||
|
||||
const defaultRecordInlineCellContextProp: RecordInlineCellContextProps = {
|
||||
@ -34,6 +36,8 @@ const defaultRecordInlineCellContextProp: RecordInlineCellContextProps = {
|
||||
disableHoverEffect: false,
|
||||
loading: false,
|
||||
isCentered: false,
|
||||
onOpenEditMode: undefined,
|
||||
onCloseEditMode: undefined,
|
||||
};
|
||||
|
||||
export const RecordInlineCellContext =
|
||||
|
||||
@ -7,6 +7,7 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2';
|
||||
import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
@ -24,6 +25,8 @@ export const useInlineCell = () => {
|
||||
isInlineCellInEditModeScopedState(recoilScopeId),
|
||||
);
|
||||
|
||||
const { onOpenEditMode, onCloseEditMode } = useRecordInlineCellContext();
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
@ -37,6 +40,7 @@ export const useInlineCell = () => {
|
||||
const initFieldInputDraftValue = useInitDraftValueV2();
|
||||
|
||||
const closeInlineCell = () => {
|
||||
onCloseEditMode?.();
|
||||
setIsInlineCellInEditMode(false);
|
||||
|
||||
goBackToPreviousHotkeyScope();
|
||||
@ -45,6 +49,7 @@ export const useInlineCell = () => {
|
||||
};
|
||||
|
||||
const openInlineCell = (customEditHotkeyScopeForField?: HotkeyScope) => {
|
||||
onOpenEditMode?.();
|
||||
setIsInlineCellInEditMode(true);
|
||||
initFieldInputDraftValue({ recordId, fieldDefinition });
|
||||
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
useRecoilCallback,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
useSetRecoilState,
|
||||
} from 'recoil';
|
||||
|
||||
import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates';
|
||||
import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
|
||||
import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState';
|
||||
import { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
|
||||
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
|
||||
import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
// Todo: this effect should be deprecated to use sync hooks
|
||||
export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
|
||||
selectedObjectRecordIds,
|
||||
}: {
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
}) => {
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordPickerComponentInstanceContext,
|
||||
);
|
||||
const {
|
||||
objectRecordsIdsMultiSelectState,
|
||||
objectRecordMultiSelectCheckedRecordsIdsState,
|
||||
} = useObjectRecordMultiSelectScopedStates(instanceId);
|
||||
const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] =
|
||||
useRecoilState(objectRecordsIdsMultiSelectState);
|
||||
|
||||
const setObjectRecordMultiSelectCheckedRecordsIds = useSetRecoilState(
|
||||
objectRecordMultiSelectCheckedRecordsIdsState,
|
||||
);
|
||||
|
||||
const updateRecords = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(newRecords: ObjectRecordForSelect[]) => {
|
||||
for (const newRecord of newRecords) {
|
||||
const currentRecord = snapshot
|
||||
.getLoadable(
|
||||
objectRecordMultiSelectComponentFamilyState({
|
||||
scopeId: instanceId,
|
||||
familyKey: newRecord.record.id,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const objectRecordMultiSelectCheckedRecordsIds = snapshot
|
||||
.getLoadable(objectRecordMultiSelectCheckedRecordsIdsState)
|
||||
.getValue();
|
||||
|
||||
const newRecordWithSelected = {
|
||||
...newRecord,
|
||||
selected: objectRecordMultiSelectCheckedRecordsIds.some(
|
||||
(checkedRecordId) => checkedRecordId === newRecord.record.id,
|
||||
),
|
||||
};
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
newRecordWithSelected.selected,
|
||||
currentRecord?.selected,
|
||||
)
|
||||
) {
|
||||
set(
|
||||
objectRecordMultiSelectComponentFamilyState({
|
||||
scopeId: instanceId,
|
||||
familyKey: newRecordWithSelected.record.id,
|
||||
}),
|
||||
newRecordWithSelected,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[objectRecordMultiSelectCheckedRecordsIdsState, instanceId],
|
||||
);
|
||||
|
||||
const matchesSearchFilterObjectRecords = useRecoilValue(
|
||||
objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({
|
||||
scopeId: instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const allRecords = matchesSearchFilterObjectRecords ?? [];
|
||||
updateRecords(allRecords);
|
||||
const allRecordsIds = allRecords.map((record) => record.record.id);
|
||||
if (!isDeeplyEqual(allRecordsIds, objectRecordsIdsMultiSelect)) {
|
||||
setObjectRecordsIdsMultiSelect(allRecordsIds);
|
||||
}
|
||||
}, [
|
||||
matchesSearchFilterObjectRecords,
|
||||
objectRecordsIdsMultiSelect,
|
||||
setObjectRecordsIdsMultiSelect,
|
||||
updateRecords,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setObjectRecordMultiSelectCheckedRecordsIds(
|
||||
selectedObjectRecordIds.map((rec) => rec.id),
|
||||
);
|
||||
}, [selectedObjectRecordIds, setObjectRecordMultiSelectCheckedRecordsIds]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -1,51 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState';
|
||||
import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { useMultiObjectSearch } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectSearch';
|
||||
import { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
|
||||
import { recordPickerSearchFilterComponentState } from '@/object-record/record-picker/states/recordPickerSearchFilterComponentState';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
// Todo: this effect should be deprecated to use sync hooks
|
||||
export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect =
|
||||
() => {
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordPickerComponentInstanceContext,
|
||||
);
|
||||
const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState(
|
||||
objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({
|
||||
scopeId: instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const recordPickerSearchFilter = useRecoilComponentValueV2(
|
||||
recordPickerSearchFilterComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const { matchesSearchFilterObjectRecordsQueryResult } =
|
||||
useMultiObjectSearch({
|
||||
excludedObjects: [
|
||||
CoreObjectNameSingular.Task,
|
||||
CoreObjectNameSingular.Note,
|
||||
],
|
||||
searchFilterValue: recordPickerSearchFilter,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
const { objectRecordForSelectArray } =
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult:
|
||||
matchesSearchFilterObjectRecordsQueryResult,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setRecordMultiSelectMatchesFilterRecords(objectRecordForSelectArray);
|
||||
}, [setRecordMultiSelectMatchesFilterRecords, objectRecordForSelectArray]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -1,40 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { RECORD_PICKER_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-picker/constants/RecordPickerClickOutsideListenerId';
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
// Todo: this effect should be deprecated to use sync hooks
|
||||
export const MultipleObjectRecordOnClickOutsideEffect = ({
|
||||
containerRef,
|
||||
onClickOutside,
|
||||
}: {
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
onClickOutside: () => void;
|
||||
}) => {
|
||||
const { toggleClickOutsideListener: toggleRightDrawerClickOustideListener } =
|
||||
useClickOutsideListener(RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID);
|
||||
|
||||
useEffect(() => {
|
||||
toggleRightDrawerClickOustideListener(false);
|
||||
|
||||
return () => {
|
||||
toggleRightDrawerClickOustideListener(true);
|
||||
};
|
||||
}, [toggleRightDrawerClickOustideListener]);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [containerRef],
|
||||
callback: (event) => {
|
||||
event.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
onClickOutside();
|
||||
},
|
||||
listenerId: RECORD_PICKER_CLICK_OUTSIDE_LISTENER_ID,
|
||||
});
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -1,95 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
|
||||
const instanceId = 'instanceId';
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecordPickerComponentInstanceContext.Provider value={{ instanceId }}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RecordPickerComponentInstanceContext.Provider>
|
||||
);
|
||||
|
||||
const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6';
|
||||
const personId = 'ab091fd9-1b81-4dfd-bfdb-564ffee032a2';
|
||||
|
||||
describe('useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray', () => {
|
||||
it('should return object formatted from objectMetadataItemsState', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
return {
|
||||
formattedRecord:
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray(
|
||||
{
|
||||
multiObjectRecordsQueryResult: {
|
||||
opportunities: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: opportunityId,
|
||||
pointOfContactId:
|
||||
'e992bda7-d797-4e12-af04-9b427f42244c',
|
||||
updatedAt: '2023-11-30T11:13:15.308Z',
|
||||
createdAt: '2023-11-30T11:13:15.308Z',
|
||||
__typename: 'Opportunity',
|
||||
},
|
||||
cursor: 'cursor',
|
||||
__typename: 'OpportunityEdge',
|
||||
},
|
||||
],
|
||||
pageInfo: {},
|
||||
},
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: personId,
|
||||
updatedAt: '2023-11-30T11:13:15.308Z',
|
||||
createdAt: '2023-11-30T11:13:15.308Z',
|
||||
__typename: 'Person',
|
||||
},
|
||||
cursor: 'cursor',
|
||||
__typename: 'PersonEdge',
|
||||
},
|
||||
],
|
||||
pageInfo: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
setObjectMetadata: useSetRecoilState(objectMetadataItemsState),
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
act(() => {
|
||||
result.current.setObjectMetadata(generatedMockObjectMetadataItems);
|
||||
});
|
||||
|
||||
expect(
|
||||
result.current.formattedRecord.objectRecordForSelectArray.length,
|
||||
).toBe(2);
|
||||
|
||||
const [opportunityRecordForSelect, personRecordForSelect] =
|
||||
result.current.formattedRecord.objectRecordForSelectArray;
|
||||
|
||||
expect(opportunityRecordForSelect.objectMetadataItem.namePlural).toBe(
|
||||
'opportunities',
|
||||
);
|
||||
expect(opportunityRecordForSelect.record.id).toBe(opportunityId);
|
||||
expect(opportunityRecordForSelect.recordIdentifier.linkToShowPage).toBe(
|
||||
`/object/opportunity/${opportunityId}`,
|
||||
);
|
||||
|
||||
expect(personRecordForSelect.objectMetadataItem.namePlural).toBe('people');
|
||||
expect(personRecordForSelect.record.id).toBe(personId);
|
||||
expect(personRecordForSelect.recordIdentifier.linkToShowPage).toBe(
|
||||
`/object/person/${personId}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,97 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
|
||||
import { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
|
||||
const instanceId = 'instanceId';
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecordPickerComponentInstanceContext.Provider value={{ instanceId }}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RecordPickerComponentInstanceContext.Provider>
|
||||
);
|
||||
|
||||
const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6';
|
||||
const personId = 'ab091fd9-1b81-4dfd-bfdb-564ffee032a2';
|
||||
|
||||
describe('useMultiObjectRecordsQueryResultFormattedAsObjectRecordsMap', () => {
|
||||
it('should return object formatted from objectMetadataItemsState', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
return {
|
||||
formattedRecord:
|
||||
useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap({
|
||||
multiObjectRecordsQueryResult: {
|
||||
opportunities: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: opportunityId,
|
||||
pointOfContactId:
|
||||
'e992bda7-d797-4e12-af04-9b427f42244c',
|
||||
updatedAt: '2023-11-30T11:13:15.308Z',
|
||||
createdAt: '2023-11-30T11:13:15.308Z',
|
||||
__typename: 'Opportunity',
|
||||
},
|
||||
cursor: 'cursor',
|
||||
__typename: 'OpportunityEdge',
|
||||
},
|
||||
],
|
||||
pageInfo: {},
|
||||
},
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: personId,
|
||||
updatedAt: '2023-11-30T11:13:15.308Z',
|
||||
createdAt: '2023-11-30T11:13:15.308Z',
|
||||
__typename: 'Person',
|
||||
},
|
||||
cursor: 'cursor',
|
||||
__typename: 'PersonEdge',
|
||||
},
|
||||
],
|
||||
pageInfo: {},
|
||||
},
|
||||
},
|
||||
}),
|
||||
setObjectMetadata: useSetRecoilState(objectMetadataItemsState),
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
act(() => {
|
||||
result.current.setObjectMetadata(generatedMockObjectMetadataItems);
|
||||
});
|
||||
|
||||
expect(
|
||||
Object.values(result.current.formattedRecord.objectRecordsMap).flat()
|
||||
.length,
|
||||
).toBe(2);
|
||||
|
||||
const opportunityObjectRecords =
|
||||
result.current.formattedRecord.objectRecordsMap.opportunities;
|
||||
|
||||
const personObjectRecords =
|
||||
result.current.formattedRecord.objectRecordsMap.people;
|
||||
|
||||
expect(opportunityObjectRecords[0].objectMetadataItem.namePlural).toBe(
|
||||
'opportunities',
|
||||
);
|
||||
expect(opportunityObjectRecords[0].record.id).toBe(opportunityId);
|
||||
expect(opportunityObjectRecords[0].recordIdentifier.linkToShowPage).toBe(
|
||||
`/object/opportunity/${opportunityId}`,
|
||||
);
|
||||
|
||||
expect(personObjectRecords[0].objectMetadataItem.namePlural).toBe('people');
|
||||
expect(personObjectRecords[0].record.id).toBe(personId);
|
||||
expect(personObjectRecords[0].recordIdentifier.linkToShowPage).toBe(
|
||||
`/object/person/${personId}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,60 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
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 { formatMultiObjectRecordSearchResults } from '@/object-record/record-picker-morph-legacy/utils/formatMultiObjectRecordSearchResults';
|
||||
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export type MultiObjectRecordQueryResult = {
|
||||
[namePlural: string]: RecordGqlConnection;
|
||||
};
|
||||
|
||||
export const useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray =
|
||||
({
|
||||
multiObjectRecordsQueryResult,
|
||||
}: {
|
||||
multiObjectRecordsQueryResult:
|
||||
| MultiObjectRecordQueryResult
|
||||
| null
|
||||
| undefined;
|
||||
}) => {
|
||||
const objectMetadataItemsByNamePluralMap = useRecoilValue(
|
||||
objectMetadataItemsByNamePluralMapSelector,
|
||||
);
|
||||
|
||||
const formattedMultiObjectRecordsQueryResult = useMemo(() => {
|
||||
return formatMultiObjectRecordSearchResults(
|
||||
multiObjectRecordsQueryResult,
|
||||
);
|
||||
}, [multiObjectRecordsQueryResult]);
|
||||
|
||||
const objectRecordForSelectArray = useMemo(() => {
|
||||
return Object.entries(
|
||||
formattedMultiObjectRecordsQueryResult ?? {},
|
||||
).flatMap(([namePlural, objectRecordConnection]) => {
|
||||
const objectMetadataItem =
|
||||
objectMetadataItemsByNamePluralMap.get(namePlural);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) return [];
|
||||
|
||||
return objectRecordConnection.edges.map(({ node }) => ({
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
recordIdentifier: getObjectRecordIdentifier({
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
}),
|
||||
})) as ObjectRecordForSelect[];
|
||||
});
|
||||
}, [
|
||||
formattedMultiObjectRecordsQueryResult,
|
||||
objectMetadataItemsByNamePluralMap,
|
||||
]);
|
||||
|
||||
return {
|
||||
objectRecordForSelectArray,
|
||||
};
|
||||
};
|
||||
@ -1,66 +0,0 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useLimitPerMetadataItem } from '@/object-metadata/hooks/useLimitPerMetadataItem';
|
||||
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 { MultiObjectRecordQueryResult } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useMultiObjectSearch = ({
|
||||
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),
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
matchesSearchFilterObjectRecordsLoading,
|
||||
matchesSearchFilterObjectRecordsQueryResult,
|
||||
};
|
||||
};
|
||||
@ -1,61 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { formatMultiObjectRecordSearchResults } from '@/object-record/record-picker-morph-legacy/utils/formatMultiObjectRecordSearchResults';
|
||||
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap = ({
|
||||
multiObjectRecordsQueryResult,
|
||||
}: {
|
||||
multiObjectRecordsQueryResult:
|
||||
| MultiObjectRecordQueryResult
|
||||
| null
|
||||
| undefined;
|
||||
}) => {
|
||||
const objectMetadataItemsByNamePluralMap = useRecoilValue(
|
||||
objectMetadataItemsByNamePluralMapSelector,
|
||||
);
|
||||
|
||||
const formattedMultiObjectRecordsQueryResult = useMemo(() => {
|
||||
return formatMultiObjectRecordSearchResults(multiObjectRecordsQueryResult);
|
||||
}, [multiObjectRecordsQueryResult]);
|
||||
|
||||
const objectRecordsMap = useMemo(() => {
|
||||
const recordsByNamePlural: { [key: string]: ObjectRecordForSelect[] } = {};
|
||||
Object.entries(formattedMultiObjectRecordsQueryResult ?? {}).forEach(
|
||||
([namePlural, objectRecordConnection]) => {
|
||||
const objectMetadataItem =
|
||||
objectMetadataItemsByNamePluralMap.get(namePlural);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) return [];
|
||||
if (!isDefined(recordsByNamePlural[namePlural])) {
|
||||
recordsByNamePlural[namePlural] = [];
|
||||
}
|
||||
|
||||
objectRecordConnection.edges.forEach(({ node }) => {
|
||||
const record = {
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
recordIdentifier: getObjectRecordIdentifier({
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
}),
|
||||
} as ObjectRecordForSelect;
|
||||
recordsByNamePlural[namePlural].push(record);
|
||||
});
|
||||
},
|
||||
);
|
||||
return recordsByNamePlural;
|
||||
}, [
|
||||
formattedMultiObjectRecordsQueryResult,
|
||||
objectMetadataItemsByNamePluralMap,
|
||||
]);
|
||||
|
||||
return {
|
||||
objectRecordsMap,
|
||||
};
|
||||
};
|
||||
@ -1,102 +0,0 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useLimitPerMetadataItem } from '@/object-metadata/hooks/useLimitPerMetadataItem';
|
||||
import { useOrderByFieldPerMetadataItem } from '@/object-metadata/hooks/useOrderByFieldPerMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
|
||||
import {
|
||||
MultiObjectRecordQueryResult,
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray,
|
||||
} from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId';
|
||||
import { capitalize, isDefined } from 'twenty-shared';
|
||||
|
||||
export const EMPTY_QUERY = gql`
|
||||
query Empty {
|
||||
__typename
|
||||
}
|
||||
`;
|
||||
|
||||
export const useMultiObjectSearchSelectedItemsQuery = ({
|
||||
selectedObjectRecordIds,
|
||||
}: {
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItemsUsedInSelectedIdsQuery = objectMetadataItems.filter(
|
||||
({ nameSingular }) => {
|
||||
return selectedObjectRecordIds.some(({ objectNameSingular }) => {
|
||||
return objectNameSingular === nameSingular;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const selectedIdFilterPerMetadataItem = Object.fromEntries(
|
||||
objectMetadataItemsUsedInSelectedIdsQuery
|
||||
.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 { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
});
|
||||
|
||||
const { limitPerMetadataItem } = useLimitPerMetadataItem({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
});
|
||||
|
||||
const multiSelectQueryForSelectedIds =
|
||||
useGenerateCombinedFindManyRecordsQuery({
|
||||
operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map(
|
||||
(objectMetadataItem) => ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const {
|
||||
loading: selectedObjectRecordsLoading,
|
||||
data: selectedObjectRecordsQueryResult,
|
||||
} = useQuery<MultiObjectRecordQueryResult>(
|
||||
multiSelectQueryForSelectedIds ?? EMPTY_QUERY,
|
||||
{
|
||||
variables: {
|
||||
...selectedIdFilterPerMetadataItem,
|
||||
...orderByFieldPerMetadataItem,
|
||||
...limitPerMetadataItem,
|
||||
},
|
||||
skip: !isDefined(multiSelectQueryForSelectedIds),
|
||||
},
|
||||
);
|
||||
|
||||
const { objectRecordForSelectArray: selectedObjectRecords } =
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult: selectedObjectRecordsQueryResult,
|
||||
});
|
||||
|
||||
return {
|
||||
selectedObjectRecordsLoading,
|
||||
selectedObjectRecords,
|
||||
};
|
||||
};
|
||||
@ -33,22 +33,27 @@ export const StyledSelectableItem = styled(SelectableItem)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type MultipleRecordPickerProps = {
|
||||
onChange?: (changedRecordForSelectId: string) => void;
|
||||
onSubmit?: () => void;
|
||||
onCreate?: ((searchInput?: string) => void) | (() => void);
|
||||
dropdownPlacement?: Placement | null;
|
||||
componentInstanceId: string;
|
||||
};
|
||||
|
||||
export const MultipleRecordPicker = ({
|
||||
onChange,
|
||||
onSubmit,
|
||||
onCreate,
|
||||
dropdownPlacement,
|
||||
}: {
|
||||
onChange?: (changedRecordForSelectId: string) => void;
|
||||
onSubmit?: () => void;
|
||||
onCreate?: ((searchInput?: string) => void) | (() => void);
|
||||
dropdownPlacement?: Placement | null;
|
||||
}) => {
|
||||
componentInstanceId,
|
||||
}: MultipleRecordPickerProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
|
||||
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordPickerComponentInstanceContext,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const { objectRecordsIdsMultiSelectState, recordMultiSelectIsLoadingState } =
|
||||
@ -143,49 +148,57 @@ export const MultipleRecordPicker = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu ref={containerRef} data-select-disable width={200}>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && results}
|
||||
{recordMultiSelectIsLoading && !recordPickerSearchFilter && (
|
||||
<>
|
||||
<DropdownMenuSkeletonItem />
|
||||
<RecordPickerComponentInstanceContext.Provider
|
||||
value={{ instanceId: componentInstanceId }}
|
||||
>
|
||||
<DropdownMenu ref={containerRef} data-select-disable width={200}>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && results}
|
||||
{recordMultiSelectIsLoading && !recordPickerSearchFilter && (
|
||||
<>
|
||||
<DropdownMenuSkeletonItem />
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && (
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && <DropdownMenuSeparator />}
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSearchInput
|
||||
value={recordPickerSearchFilter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
/>
|
||||
{(dropdownPlacement?.includes('start') ||
|
||||
isUndefinedOrNull(dropdownPlacement)) && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
{recordMultiSelectIsLoading && !recordPickerSearchFilter && (
|
||||
<>
|
||||
<DropdownMenuSkeletonItem />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSearchInput
|
||||
value={recordPickerSearchFilter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
/>
|
||||
{(dropdownPlacement?.includes('start') ||
|
||||
isUndefinedOrNull(dropdownPlacement)) && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
{recordMultiSelectIsLoading && !recordPickerSearchFilter && (
|
||||
<>
|
||||
<DropdownMenuSkeletonItem />
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && results}
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && (
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && results}
|
||||
{objectRecordsIdsMultiSelect?.length > 0 && <DropdownMenuSeparator />}
|
||||
{isDefined(onCreate) && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
)}
|
||||
{isDefined(onCreate) && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</RecordPickerComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,7 +5,6 @@ import { ComponentDecorator, IconUserCircle } from 'twenty-ui';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { RecordPickerDecorator } from '~/testing/decorators/RecordPickerDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
@ -30,7 +29,6 @@ const meta: Meta<typeof SingleRecordPicker> = {
|
||||
decorators: [
|
||||
ComponentDecorator,
|
||||
ComponentWithRecoilScopeDecorator,
|
||||
RecordPickerDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
SnackBarDecorator,
|
||||
],
|
||||
|
||||
@ -219,6 +219,7 @@ export const RecordDetailRelationSection = ({
|
||||
<>
|
||||
<RelationFromManyFieldInputMultiRecordsEffect />
|
||||
<MultipleRecordPicker
|
||||
componentInstanceId={dropdownId}
|
||||
onCreate={() => {
|
||||
closeDropdown();
|
||||
createNewRecordAndOpenRightDrawer?.();
|
||||
|
||||
Reference in New Issue
Block a user