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:
Charles Bochet
2025-02-27 09:56:03 +01:00
committed by GitHub
parent aa25a80171
commit d246010c5c
28 changed files with 207 additions and 149 deletions

View File

@ -3,7 +3,10 @@ import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity'; import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { ActivityTargetInlineCellEditModeMultiRecordsEffect } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditModeMultiRecordsEffect';
import { ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect';
import { ActivityTargetObjectRecordEffect } from '@/activities/inline-cell/components/ActivityTargetObjectRecordEffect'; import { ActivityTargetObjectRecordEffect } from '@/activities/inline-cell/components/ActivityTargetObjectRecordEffect';
import { MultipleObjectRecordOnClickOutsideEffect } from '@/activities/inline-cell/components/MultipleObjectRecordOnClickOutsideEffect';
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState'; import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
import { ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject'; import { ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject';
import { Note } from '@/activities/types/Note'; import { Note } from '@/activities/types/Note';
@ -24,12 +27,10 @@ import {
objectRecordMultiSelectComponentFamilyState, objectRecordMultiSelectComponentFamilyState,
} from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
import { ActivityTargetInlineCellEditModeMultiRecordsEffect } from '@/object-record/record-picker-morph-legacy/components/ActivityTargetInlineCellEditModeMultiRecordsEffect';
import { ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect } from '@/object-record/record-picker-morph-legacy/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect';
import { MultipleRecordPicker } from '@/object-record/record-picker/components/MultipleRecordPicker'; import { MultipleRecordPicker } from '@/object-record/record-picker/components/MultipleRecordPicker';
import { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { prefillRecord } from '@/object-record/utils/prefillRecord'; import { prefillRecord } from '@/object-record/utils/prefillRecord';
import { useRef } from 'react';
type ActivityTargetInlineCellEditModeProps = { type ActivityTargetInlineCellEditModeProps = {
activity: Task | Note; activity: Task | Note;
@ -257,20 +258,31 @@ export const ActivityTargetInlineCellEditMode = ({
], ],
); );
const containerRef = useRef<HTMLDivElement>(null);
return ( return (
<> <>
<RecordPickerComponentInstanceContext.Provider <ActivityTargetObjectRecordEffect
value={{ instanceId: recordPickerInstanceId }} activityTargetWithTargetRecords={activityTargetWithTargetRecords}
> />
<ActivityTargetObjectRecordEffect <ActivityTargetInlineCellEditModeMultiRecordsEffect
activityTargetWithTargetRecords={activityTargetWithTargetRecords} recordPickerInstanceId={recordPickerInstanceId}
selectedObjectRecordIds={selectedTargetObjectIds}
/>
<ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect
recordPickerInstanceId={recordPickerInstanceId}
/>
<MultipleObjectRecordOnClickOutsideEffect
containerRef={containerRef}
onClickOutside={closeEditableField}
/>
<div ref={containerRef}>
<MultipleRecordPicker
onSubmit={handleSubmit}
onChange={handleChange}
componentInstanceId={recordPickerInstanceId}
/> />
<ActivityTargetInlineCellEditModeMultiRecordsEffect </div>
selectedObjectRecordIds={selectedTargetObjectIds}
/>
<ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect />
<MultipleRecordPicker onSubmit={handleSubmit} onChange={handleChange} />
</RecordPickerComponentInstanceContext.Provider>
</> </>
); );
}; };

View File

@ -17,12 +17,15 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
// Todo: this effect should be deprecated to use sync hooks // Todo: this effect should be deprecated to use sync hooks
export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
recordPickerInstanceId,
selectedObjectRecordIds, selectedObjectRecordIds,
}: { }: {
recordPickerInstanceId: string;
selectedObjectRecordIds: SelectedObjectRecordId[]; selectedObjectRecordIds: SelectedObjectRecordId[];
}) => { }) => {
const instanceId = useAvailableComponentInstanceIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RecordPickerComponentInstanceContext, RecordPickerComponentInstanceContext,
recordPickerInstanceId,
); );
const { const {
objectRecordsIdsMultiSelectState, objectRecordsIdsMultiSelectState,

View File

@ -0,0 +1,51 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/activities/inline-cell/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
import { useMultiObjectSearch } from '@/activities/inline-cell/hooks/useMultiObjectSearch';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState';
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 = ({
recordPickerInstanceId,
}: {
recordPickerInstanceId: string;
}) => {
const instanceId = useAvailableComponentInstanceIdOrThrow(
RecordPickerComponentInstanceContext,
recordPickerInstanceId,
);
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 <></>;
};

View File

@ -5,6 +5,7 @@ import { IconArrowUpRight, IconPencil } from 'twenty-ui';
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips'; import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode'; import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode';
import { useOpenActivityTargetInlineCellEditMode } from '@/activities/inline-cell/hooks/useOpenActivityTargetInlineCellEditMode';
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope'; import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
import { Note } from '@/activities/types/Note'; import { Note } from '@/activities/types/Note';
import { Task } from '@/activities/types/Task'; import { Task } from '@/activities/types/Task';
@ -59,6 +60,9 @@ export const ActivityTargetsInlineCell = ({
overridenIsFieldEmpty: activityTargetObjectRecords.length === 0, overridenIsFieldEmpty: activityTargetObjectRecords.length === 0,
}); });
const { openActivityTargetInlineCellEditMode } =
useOpenActivityTargetInlineCellEditMode();
return ( return (
<RecordFieldInputScope recordFieldInputScopeId={activity?.id ?? ''}> <RecordFieldInputScope recordFieldInputScopeId={activity?.id ?? ''}>
<FieldFocusContextProvider> <FieldFocusContextProvider>
@ -90,6 +94,11 @@ export const ActivityTargetsInlineCell = ({
maxWidth={maxWidth} maxWidth={maxWidth}
/> />
), ),
onOpenEditMode: () => {
openActivityTargetInlineCellEditMode({
recordPickerInstanceId: `record-picker-${activity.id}`,
});
},
}} }}
> >
<RecordInlineCellContainer /> <RecordInlineCellContainer />

View File

@ -1,8 +1,8 @@
import { act, renderHook } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react';
import { RecoilRoot, useSetRecoilState } from 'recoil'; import { RecoilRoot, useSetRecoilState } from 'recoil';
import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/activities/inline-cell/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; 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 { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';

View File

@ -1,8 +1,8 @@
import { act, renderHook } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react';
import { RecoilRoot, useSetRecoilState } from 'recoil'; import { RecoilRoot, useSetRecoilState } from 'recoil';
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/activities/inline-cell/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; 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 { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';

View File

@ -3,15 +3,10 @@ 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 { MultiObjectRecordQueryResult } from '@/object-record/multiple-objects/types/MultiObjectRecordQueryResult';
import { formatMultiObjectRecordSearchResults } from '@/object-record/record-picker-morph-legacy/utils/formatMultiObjectRecordSearchResults'; import { formatMultiObjectRecordSearchResults } from '@/object-record/multiple-objects/utils/formatMultiObjectRecordSearchResults';
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export type MultiObjectRecordQueryResult = {
[namePlural: string]: RecordGqlConnection;
};
export const useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray = export const useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray =
({ ({
multiObjectRecordsQueryResult, multiObjectRecordsQueryResult,

View File

@ -6,7 +6,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery';
import { MultiObjectRecordQueryResult } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { MultiObjectRecordQueryResult } from '@/object-record/multiple-objects/types/MultiObjectRecordQueryResult';
import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';

View File

@ -3,8 +3,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 { MultiObjectRecordQueryResult } from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { MultiObjectRecordQueryResult } from '@/object-record/multiple-objects/types/MultiObjectRecordQueryResult';
import { formatMultiObjectRecordSearchResults } from '@/object-record/record-picker-morph-legacy/utils/formatMultiObjectRecordSearchResults'; import { formatMultiObjectRecordSearchResults } from '@/object-record/multiple-objects/utils/formatMultiObjectRecordSearchResults';
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';

View File

@ -2,14 +2,12 @@ import { gql, useQuery } from '@apollo/client';
import { isNonEmptyArray } from '@sniptt/guards'; import { isNonEmptyArray } from '@sniptt/guards';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/activities/inline-cell/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
import { useLimitPerMetadataItem } from '@/object-metadata/hooks/useLimitPerMetadataItem'; import { useLimitPerMetadataItem } from '@/object-metadata/hooks/useLimitPerMetadataItem';
import { useOrderByFieldPerMetadataItem } from '@/object-metadata/hooks/useOrderByFieldPerMetadataItem'; import { useOrderByFieldPerMetadataItem } from '@/object-metadata/hooks/useOrderByFieldPerMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
import { import { MultiObjectRecordQueryResult } from '@/object-record/multiple-objects/types/MultiObjectRecordQueryResult';
MultiObjectRecordQueryResult,
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray,
} from '@/object-record/record-picker-morph-legacy/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId'; import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId';
import { capitalize, isDefined } from 'twenty-shared'; import { capitalize, isDefined } from 'twenty-shared';

View File

@ -0,0 +1,14 @@
type OpenActivityTargetInlineCellEditModeProps = {
recordPickerInstanceId: string;
};
export const useOpenActivityTargetInlineCellEditMode = () => {
const openActivityTargetInlineCellEditMode = ({
recordPickerInstanceId,
}: OpenActivityTargetInlineCellEditModeProps) => {
// eslint-disable-next-line no-console
console.log('openActivityTargetInlineCellEditMode', recordPickerInstanceId);
};
return { openActivityTargetInlineCellEditMode };
};

View File

@ -5,7 +5,7 @@ import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { useCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables'; import { useCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables';
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; 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 = ({ export const useCombinedFindManyRecords = ({
operationSignatures, operationSignatures,

View File

@ -4,7 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; 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 = ({ export const useCombinedGetTotalCount = ({
objectMetadataItems, objectMetadataItems,

View File

@ -0,0 +1,5 @@
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
export type MultiObjectRecordQueryResult = {
[namePlural: string]: RecordGqlConnection;
};

View File

@ -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 = ( export const formatMultiObjectRecordSearchResults = (
searchResults: MultiObjectRecordQueryResult | undefined | null, searchResults: MultiObjectRecordQueryResult | undefined | null,

View File

@ -33,6 +33,8 @@ export type GenericFieldContextType = {
overridenIsFieldEmpty?: boolean; overridenIsFieldEmpty?: boolean;
displayedMaxRows?: number; displayedMaxRows?: number;
isDisplayModeFixHeight?: boolean; isDisplayModeFixHeight?: boolean;
onOpenEditMode?: () => void;
onCloseEditMode?: () => void;
}; };
export const FieldContext = createContext<GenericFieldContextType>( export const FieldContext = createContext<GenericFieldContextType>(

View File

@ -57,6 +57,7 @@ export const RelationFromManyFieldInput = ({
> >
<RelationFromManyFieldInputMultiRecordsEffect /> <RelationFromManyFieldInputMultiRecordsEffect />
<MultipleRecordPicker <MultipleRecordPicker
componentInstanceId={recordPickerInstanceId}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onChange={updateRelation} onChange={updateRelation}
onCreate={createNewRecordAndOpenRightDrawer} onCreate={createNewRecordAndOpenRightDrawer}

View File

@ -3,11 +3,11 @@ import { IconForbid } from 'twenty-ui';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; 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 { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
import { RelationPickerHotkeyScope } from '@/object-record/record-field/meta-types/input/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/object-record/record-field/meta-types/input/types/RelationPickerHotkeyScope';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; 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 { SingleRecordPicker } from '@/object-record/record-picker/components/SingleRecordPicker';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/types/SingleRecordPickerRecord';
@ -59,7 +59,7 @@ export const RelationPicker = ({
return ( return (
<> <>
<SearchPickerInitialValueEffect <RelationPickerInitialValueEffect
initialValueForSearchFilter={initialSearchFilter} initialValueForSearchFilter={initialSearchFilter}
recordPickerInstanceId={recordPickerInstanceId} recordPickerInstanceId={recordPickerInstanceId}
/> />

View File

@ -3,7 +3,7 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
import { useEffect } from 'react'; import { useEffect } from 'react';
// Todo: this effect should be deprecated to use sync hooks // Todo: this effect should be deprecated to use sync hooks
export const SearchPickerInitialValueEffect = ({ export const RelationPickerInitialValueEffect = ({
initialValueForSearchFilter, initialValueForSearchFilter,
recordPickerInstanceId, recordPickerInstanceId,
}: { }: {

View File

@ -31,8 +31,14 @@ type RecordInlineCellProps = {
}; };
export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => { export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
const { fieldDefinition, recordId, isCentered, isDisplayModeFixHeight } = const {
useContext(FieldContext); fieldDefinition,
recordId,
isCentered,
isDisplayModeFixHeight,
onOpenEditMode,
onCloseEditMode,
} = useContext(FieldContext);
const buttonIcon = useGetButtonIcon(); const buttonIcon = useGetButtonIcon();
const isFieldInputOnly = useIsFieldInputOnly(); const isFieldInputOnly = useIsFieldInputOnly();
@ -129,6 +135,8 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
isDisplayModeFixHeight: isDisplayModeFixHeight, isDisplayModeFixHeight: isDisplayModeFixHeight,
editModeContentOnly: isFieldInputOnly, editModeContentOnly: isFieldInputOnly,
loading: loading, loading: loading,
onOpenEditMode,
onCloseEditMode,
}; };
return ( return (

View File

@ -17,6 +17,8 @@ export type RecordInlineCellContextProps = {
disableHoverEffect?: boolean; disableHoverEffect?: boolean;
loading?: boolean; loading?: boolean;
isCentered?: boolean; isCentered?: boolean;
onOpenEditMode?: () => void;
onCloseEditMode?: () => void;
}; };
const defaultRecordInlineCellContextProp: RecordInlineCellContextProps = { const defaultRecordInlineCellContextProp: RecordInlineCellContextProps = {
@ -34,6 +36,8 @@ const defaultRecordInlineCellContextProp: RecordInlineCellContextProps = {
disableHoverEffect: false, disableHoverEffect: false,
loading: false, loading: false,
isCentered: false, isCentered: false,
onOpenEditMode: undefined,
onCloseEditMode: undefined,
}; };
export const RecordInlineCellContext = export const RecordInlineCellContext =

View File

@ -7,6 +7,7 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2'; 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 { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
@ -24,6 +25,8 @@ export const useInlineCell = () => {
isInlineCellInEditModeScopedState(recoilScopeId), isInlineCellInEditModeScopedState(recoilScopeId),
); );
const { onOpenEditMode, onCloseEditMode } = useRecordInlineCellContext();
const { setActiveDropdownFocusIdAndMemorizePrevious } = const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious(); useSetActiveDropdownFocusIdAndMemorizePrevious();
const { goBackToPreviousDropdownFocusId } = const { goBackToPreviousDropdownFocusId } =
@ -37,6 +40,7 @@ export const useInlineCell = () => {
const initFieldInputDraftValue = useInitDraftValueV2(); const initFieldInputDraftValue = useInitDraftValueV2();
const closeInlineCell = () => { const closeInlineCell = () => {
onCloseEditMode?.();
setIsInlineCellInEditMode(false); setIsInlineCellInEditMode(false);
goBackToPreviousHotkeyScope(); goBackToPreviousHotkeyScope();
@ -45,6 +49,7 @@ export const useInlineCell = () => {
}; };
const openInlineCell = (customEditHotkeyScopeForField?: HotkeyScope) => { const openInlineCell = (customEditHotkeyScopeForField?: HotkeyScope) => {
onOpenEditMode?.();
setIsInlineCellInEditMode(true); setIsInlineCellInEditMode(true);
initFieldInputDraftValue({ recordId, fieldDefinition }); initFieldInputDraftValue({ recordId, fieldDefinition });

View File

@ -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 <></>;
};

View File

@ -33,22 +33,27 @@ export const StyledSelectableItem = styled(SelectableItem)`
width: 100%; width: 100%;
`; `;
type MultipleRecordPickerProps = {
onChange?: (changedRecordForSelectId: string) => void;
onSubmit?: () => void;
onCreate?: ((searchInput?: string) => void) | (() => void);
dropdownPlacement?: Placement | null;
componentInstanceId: string;
};
export const MultipleRecordPicker = ({ export const MultipleRecordPicker = ({
onChange, onChange,
onSubmit, onSubmit,
onCreate, onCreate,
dropdownPlacement, dropdownPlacement,
}: { componentInstanceId,
onChange?: (changedRecordForSelectId: string) => void; }: MultipleRecordPickerProps) => {
onSubmit?: () => void;
onCreate?: ((searchInput?: string) => void) | (() => void);
dropdownPlacement?: Placement | null;
}) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const instanceId = useAvailableComponentInstanceIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RecordPickerComponentInstanceContext, RecordPickerComponentInstanceContext,
componentInstanceId,
); );
const { objectRecordsIdsMultiSelectState, recordMultiSelectIsLoadingState } = const { objectRecordsIdsMultiSelectState, recordMultiSelectIsLoadingState } =
@ -143,49 +148,57 @@ export const MultipleRecordPicker = ({
); );
return ( return (
<DropdownMenu ref={containerRef} data-select-disable width={200}> <RecordPickerComponentInstanceContext.Provider
{dropdownPlacement?.includes('end') && ( value={{ instanceId: componentInstanceId }}
<> >
{isDefined(onCreate) && !hasObjectReadOnlyPermission && ( <DropdownMenu ref={containerRef} data-select-disable width={200}>
<DropdownMenuItemsContainer scrollable={false}> {dropdownPlacement?.includes('end') && (
{createNewButton} <>
</DropdownMenuItemsContainer> {isDefined(onCreate) && !hasObjectReadOnlyPermission && (
)} <DropdownMenuItemsContainer scrollable={false}>
<DropdownMenuSeparator /> {createNewButton}
{objectRecordsIdsMultiSelect?.length > 0 && results} </DropdownMenuItemsContainer>
{recordMultiSelectIsLoading && !recordPickerSearchFilter && ( )}
<> <DropdownMenuSeparator />
<DropdownMenuSkeletonItem /> {objectRecordsIdsMultiSelect?.length > 0 && results}
{recordMultiSelectIsLoading && !recordPickerSearchFilter && (
<>
<DropdownMenuSkeletonItem />
<DropdownMenuSeparator />
</>
)}
{objectRecordsIdsMultiSelect?.length > 0 && (
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> )}
)} </>
{objectRecordsIdsMultiSelect?.length > 0 && <DropdownMenuSeparator />} )}
</> <DropdownMenuSearchInput
)} value={recordPickerSearchFilter}
<DropdownMenuSearchInput onChange={handleFilterChange}
value={recordPickerSearchFilter} autoFocus
onChange={handleFilterChange} />
autoFocus {(dropdownPlacement?.includes('start') ||
/> isUndefinedOrNull(dropdownPlacement)) && (
{(dropdownPlacement?.includes('start') || <>
isUndefinedOrNull(dropdownPlacement)) && ( <DropdownMenuSeparator />
<> {recordMultiSelectIsLoading && !recordPickerSearchFilter && (
<DropdownMenuSeparator /> <>
{recordMultiSelectIsLoading && !recordPickerSearchFilter && ( <DropdownMenuSkeletonItem />
<> <DropdownMenuSeparator />
<DropdownMenuSkeletonItem /> </>
)}
{objectRecordsIdsMultiSelect?.length > 0 && results}
{objectRecordsIdsMultiSelect?.length > 0 && (
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> )}
)} {isDefined(onCreate) && (
{objectRecordsIdsMultiSelect?.length > 0 && results} <DropdownMenuItemsContainer scrollable={false}>
{objectRecordsIdsMultiSelect?.length > 0 && <DropdownMenuSeparator />} {createNewButton}
{isDefined(onCreate) && ( </DropdownMenuItemsContainer>
<DropdownMenuItemsContainer scrollable={false}> )}
{createNewButton} </>
</DropdownMenuItemsContainer> )}
)} </DropdownMenu>
</> </RecordPickerComponentInstanceContext.Provider>
)}
</DropdownMenu>
); );
}; };

View File

@ -5,7 +5,6 @@ import { ComponentDecorator, IconUserCircle } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { RecordPickerDecorator } from '~/testing/decorators/RecordPickerDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getPeopleMock } from '~/testing/mock-data/people'; import { getPeopleMock } from '~/testing/mock-data/people';
@ -30,7 +29,6 @@ const meta: Meta<typeof SingleRecordPicker> = {
decorators: [ decorators: [
ComponentDecorator, ComponentDecorator,
ComponentWithRecoilScopeDecorator, ComponentWithRecoilScopeDecorator,
RecordPickerDecorator,
ObjectMetadataItemsDecorator, ObjectMetadataItemsDecorator,
SnackBarDecorator, SnackBarDecorator,
], ],

View File

@ -219,6 +219,7 @@ export const RecordDetailRelationSection = ({
<> <>
<RelationFromManyFieldInputMultiRecordsEffect /> <RelationFromManyFieldInputMultiRecordsEffect />
<MultipleRecordPicker <MultipleRecordPicker
componentInstanceId={dropdownId}
onCreate={() => { onCreate={() => {
closeDropdown(); closeDropdown();
createNewRecordAndOpenRightDrawer?.(); createNewRecordAndOpenRightDrawer?.();

View File

@ -1,10 +0,0 @@
import { RecordPickerComponentInstanceContext } from '@/object-record/record-picker/states/contexts/RecordPickerComponentInstanceContext';
import { Decorator } from '@storybook/react';
export const RecordPickerDecorator: Decorator = (Story) => (
<RecordPickerComponentInstanceContext.Provider
value={{ instanceId: 'record-picker-decorator-instance-id' }}
>
<Story />
</RecordPickerComponentInstanceContext.Provider>
);