Upgrade relation picker (#8795)

- Rename all parts using the name "relation" to "record" when component
is only selecting record
- Remove the use of scope states in folder
- Rename entities to records

This PR prepares the use of the record picker in workflows
This commit is contained in:
Thomas Trompette
2024-11-28 18:08:39 +01:00
committed by GitHub
parent d73dc1a728
commit 83223eeae3
62 changed files with 585 additions and 687 deletions

View File

@ -29,7 +29,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
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 { 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 { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { prefillRecord } from '@/object-record/utils/prefillRecord'; import { prefillRecord } from '@/object-record/utils/prefillRecord';
const StyledSelectContainer = styled.div` const StyledSelectContainer = styled.div`
@ -52,7 +52,7 @@ export const ActivityTargetInlineCellEditMode = ({
activityObjectNameSingular, activityObjectNameSingular,
}: ActivityTargetInlineCellEditModeProps) => { }: ActivityTargetInlineCellEditModeProps) => {
const [isActivityInCreateMode] = useRecoilState(isActivityInCreateModeState); const [isActivityInCreateMode] = useRecoilState(isActivityInCreateModeState);
const relationPickerScopeId = `relation-picker-${activity.id}`; const recordPickerInstanceId = `record-picker-${activity.id}`;
const selectedTargetObjectIds = activityTargetWithTargetRecords.map( const selectedTargetObjectIds = activityTargetWithTargetRecords.map(
(activityTarget) => ({ (activityTarget) => ({
@ -101,7 +101,7 @@ export const ActivityTargetInlineCellEditMode = ({
const record = snapshot const record = snapshot
.getLoadable( .getLoadable(
objectRecordMultiSelectComponentFamilyState({ objectRecordMultiSelectComponentFamilyState({
scopeId: relationPickerScopeId, scopeId: recordPickerInstanceId,
familyKey: activityTarget.targetObject.id, familyKey: activityTarget.targetObject.id,
}), }),
) )
@ -124,7 +124,7 @@ export const ActivityTargetInlineCellEditMode = ({
[ [
activityTargetWithTargetRecords, activityTargetWithTargetRecords,
closeEditableField, closeEditableField,
relationPickerScopeId, recordPickerInstanceId,
setActivityFromStore, setActivityFromStore,
], ],
); );
@ -142,7 +142,7 @@ export const ActivityTargetInlineCellEditMode = ({
const previouslyCheckedRecordsIds = snapshot const previouslyCheckedRecordsIds = snapshot
.getLoadable( .getLoadable(
objectRecordMultiSelectCheckedRecordsIdsComponentState({ objectRecordMultiSelectCheckedRecordsIdsComponentState({
scopeId: relationPickerScopeId, scopeId: recordPickerInstanceId,
}), }),
) )
.getValue(); .getValue();
@ -153,7 +153,7 @@ export const ActivityTargetInlineCellEditMode = ({
const record = snapshot const record = snapshot
.getLoadable( .getLoadable(
objectRecordMultiSelectComponentFamilyState({ objectRecordMultiSelectComponentFamilyState({
scopeId: relationPickerScopeId, scopeId: recordPickerInstanceId,
familyKey: recordId, familyKey: recordId,
}), }),
) )
@ -167,7 +167,7 @@ export const ActivityTargetInlineCellEditMode = ({
set( set(
objectRecordMultiSelectCheckedRecordsIdsComponentState({ objectRecordMultiSelectCheckedRecordsIdsComponentState({
scopeId: relationPickerScopeId, scopeId: recordPickerInstanceId,
}), }),
(prev) => [...prev, recordId], (prev) => [...prev, recordId],
); );
@ -237,7 +237,7 @@ export const ActivityTargetInlineCellEditMode = ({
set( set(
objectRecordMultiSelectCheckedRecordsIdsComponentState({ objectRecordMultiSelectCheckedRecordsIdsComponentState({
scopeId: relationPickerScopeId, scopeId: recordPickerInstanceId,
}), }),
previouslyCheckedRecordsIds.filter((id) => id !== recordId), previouslyCheckedRecordsIds.filter((id) => id !== recordId),
); );
@ -273,7 +273,7 @@ export const ActivityTargetInlineCellEditMode = ({
deleteManyActivityTargets, deleteManyActivityTargets,
isActivityInCreateMode, isActivityInCreateMode,
objectMetadataItemActivityTarget, objectMetadataItemActivityTarget,
relationPickerScopeId, recordPickerInstanceId,
upsertActivity, upsertActivity,
activityObjectNameSingular, activityObjectNameSingular,
], ],
@ -281,7 +281,9 @@ export const ActivityTargetInlineCellEditMode = ({
return ( return (
<StyledSelectContainer> <StyledSelectContainer>
<RelationPickerScope relationPickerScopeId={relationPickerScopeId}> <RecordPickerComponentInstanceContext.Provider
value={{ instanceId: recordPickerInstanceId }}
>
<ActivityTargetObjectRecordEffect <ActivityTargetObjectRecordEffect
activityTargetWithTargetRecords={activityTargetWithTargetRecords} activityTargetWithTargetRecords={activityTargetWithTargetRecords}
/> />
@ -290,7 +292,7 @@ export const ActivityTargetInlineCellEditMode = ({
/> />
<ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect /> <ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect />
<MultiRecordSelect onSubmit={handleSubmit} onChange={handleChange} /> <MultiRecordSelect onSubmit={handleSubmit} onChange={handleChange} />
</RelationPickerScope> </RecordPickerComponentInstanceContext.Provider>
</StyledSelectContainer> </StyledSelectContainer>
); );
}; };

View File

@ -52,14 +52,14 @@ export const sortFavorites = (
if (isDefined(favorite[relationField.name])) { if (isDefined(favorite[relationField.name])) {
const relationObject = favorite[relationField.name]; const relationObject = favorite[relationField.name];
const relationObjectNameSingular = const objectNameSingular =
relationField.relationDefinition?.targetObjectMetadata relationField.relationDefinition?.targetObjectMetadata
.nameSingular ?? ''; .nameSingular ?? '';
const objectRecordIdentifier = const objectRecordIdentifier =
getObjectRecordIdentifierByNameSingular( getObjectRecordIdentifierByNameSingular(
relationObject, relationObject,
relationObjectNameSingular, objectNameSingular,
); );
return { return {
@ -75,7 +75,7 @@ export const sortFavorites = (
: '', : '',
workspaceMemberId: favorite.workspaceMemberId, workspaceMemberId: favorite.workspaceMemberId,
favoriteFolderId: favorite.favoriteFolderId, favoriteFolderId: favorite.favoriteFolderId,
objectNameSingular: relationObjectNameSingular, objectNameSingular: objectNameSingular,
} as ProcessedFavorite; } as ProcessedFavorite;
} }
} }

View File

@ -4,7 +4,7 @@ import { useRecoilValue } from 'recoil';
import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect'; import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect';
import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider'; import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader'; import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
export const ObjectMetadataItemsProvider = ({ export const ObjectMetadataItemsProvider = ({
@ -19,9 +19,11 @@ export const ObjectMetadataItemsProvider = ({
<ObjectMetadataItemsLoadEffect /> <ObjectMetadataItemsLoadEffect />
{shouldDisplayChildren ? ( {shouldDisplayChildren ? (
<PreComputedChipGeneratorsProvider> <PreComputedChipGeneratorsProvider>
<RelationPickerScope relationPickerScopeId="relation-picker"> <RecordPickerComponentInstanceContext.Provider
value={{ instanceId: 'record-picker' }}
>
{children} {children}
</RelationPickerScope> </RecordPickerComponentInstanceContext.Provider>
</PreComputedChipGeneratorsProvider> </PreComputedChipGeneratorsProvider>
) : ( ) : (
<UserOrMetadataLoader /> <UserOrMetadataLoader />

View File

@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { SingleRecordSelect } from '@/object-record/relation-picker/components/SingleRecordSelect';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
const StyledCompanyPickerContainer = styled.div` const StyledCompanyPickerContainer = styled.div`
@ -37,15 +37,15 @@ export const RecordBoardColumnNewOpportunity = ({
<> <>
{newRecord.isCreating && newRecord.position === position && ( {newRecord.isCreating && newRecord.position === position && (
<StyledCompanyPickerContainer> <StyledCompanyPickerContainer>
<SingleEntitySelect <SingleRecordSelect
disableBackgroundBlur disableBackgroundBlur
onCancel={() => handleCreateSuccess(position, columnId, false)} onCancel={() => handleCreateSuccess(position, columnId, false)}
onEntitySelected={(company) => onRecordSelected={(company) =>
company ? handleEntitySelect(position, company) : null company ? handleEntitySelect(position, company) : null
} }
relationObjectNameSingular={CoreObjectNameSingular.Company} objectNameSingular={CoreObjectNameSingular.Company}
relationPickerScopeId="relation-picker" recordPickerInstanceId="relation-picker"
selectedRelationRecordIds={[]} selectedRecordIds={[]}
/> />
</StyledCompanyPickerContainer> </StyledCompanyPickerContainer>
)} )}

View File

@ -1,8 +1,8 @@
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch'; import { useRecordSelectSearch } from '@/object-record/relation-picker/hooks/useRecordSelectSearch';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useCallback, useContext } from 'react'; import { useCallback, useContext } from 'react';
@ -20,8 +20,8 @@ export const useAddNewCard = () => {
const columnContext = useContext(RecordBoardColumnContext); const columnContext = useContext(RecordBoardColumnContext);
const { createOneRecord, selectFieldMetadataItem, objectMetadataItem } = const { createOneRecord, selectFieldMetadataItem, objectMetadataItem } =
useContext(RecordBoardContext); useContext(RecordBoardContext);
const { resetSearchFilter } = useEntitySelectSearch({ const { resetSearchFilter } = useRecordSelectSearch({
relationPickerScopeId: 'relation-picker', recordPickerInstanceId: 'record-picker',
}); });
const { const {
@ -71,7 +71,7 @@ export const useAddNewCard = () => {
labelValue: string, labelValue: string,
position: 'first' | 'last', position: 'first' | 'last',
isOpportunity: boolean, isOpportunity: boolean,
company?: EntityForSelect, company?: RecordForSelect,
) => { ) => {
if ( if (
(isOpportunity && company !== null) || (isOpportunity && company !== null) ||
@ -220,7 +220,7 @@ export const useAddNewCard = () => {
const handleEntitySelect = useCallback( const handleEntitySelect = useCallback(
( (
position: 'first' | 'last', position: 'first' | 'last',
company: EntityForSelect, company: RecordForSelect,
columnId?: string, columnId?: string,
) => { ) => {
const columnDefinitionId = getColumnDefinitionId(columnId); const columnDefinitionId = getColumnDefinitionId(columnId);

View File

@ -1,4 +1,4 @@
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
export type NewCard = { export type NewCard = {
@ -7,7 +7,7 @@ export type NewCard = {
isCreating: boolean; isCreating: boolean;
position: 'first' | 'last'; position: 'first' | 'last';
isOpportunity: boolean; isOpportunity: boolean;
company: EntityForSelect | null; company: RecordForSelect | null;
}; };
export const recordBoardNewRecordByColumnIdComponentFamilyState = export const recordBoardNewRecordByColumnIdComponentFamilyState =

View File

@ -24,7 +24,7 @@ import { isFieldRelationToOneValue } from '@/object-record/record-field/types/gu
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue'; import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray'; import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue'; import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue';
@ -141,7 +141,7 @@ export const usePersistField = () => {
); );
if (fieldIsRelationToOneObject) { if (fieldIsRelationToOneObject) {
const value = valueToPersist as EntityForSelect; const value = valueToPersist as RecordForSelect;
updateRecord?.({ updateRecord?.({
variables: { variables: {
where: { id: recordId }, where: { id: recordId },

View File

@ -14,7 +14,7 @@ export const RelationFromManyFieldDisplay = () => {
const { fieldName, objectMetadataNameSingular } = fieldDefinition.metadata; const { fieldName, objectMetadataNameSingular } = fieldDefinition.metadata;
const relationObjectNameSingular = const objectNameSingular =
fieldDefinition?.metadata.relationObjectMetadataNameSingular; fieldDefinition?.metadata.relationObjectMetadataNameSingular;
const { activityTargetObjectRecords } = useActivityTargetObjectRecords( const { activityTargetObjectRecords } = useActivityTargetObjectRecords(
@ -22,7 +22,7 @@ export const RelationFromManyFieldDisplay = () => {
fieldValue as NoteTarget[] | TaskTarget[], fieldValue as NoteTarget[] | TaskTarget[],
); );
if (!fieldValue || !relationObjectNameSingular) { if (!fieldValue || !objectNameSingular) {
return null; return null;
} }
@ -81,7 +81,7 @@ export const RelationFromManyFieldDisplay = () => {
.map((record) => ( .map((record) => (
<RecordChip <RecordChip
key={record.id} key={record.id}
objectNameSingular={relationObjectNameSingular} objectNameSingular={objectNameSingular}
record={record} record={record}
/> />
))} ))}

View File

@ -5,7 +5,7 @@ import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButto
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { FieldRelationValue } from '@/object-record/record-field/types/FieldMetadata'; import { FieldRelationValue } from '@/object-record/record-field/types/FieldMetadata';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldContext } from '../../contexts/FieldContext'; import { FieldContext } from '../../contexts/FieldContext';
@ -13,7 +13,7 @@ import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldRelation } from '../../types/guards/isFieldRelation'; import { isFieldRelation } from '../../types/guards/isFieldRelation';
export const useRelationField = < export const useRelationField = <
T extends EntityForSelect | EntityForSelect[], T extends RecordForSelect | RecordForSelect[],
>() => { >() => {
const { recordId, fieldDefinition, maxWidth } = useContext(FieldContext); const { recordId, fieldDefinition, maxWidth } = useContext(FieldContext);
const button = useGetButtonIcon(); const button = useGetButtonIcon();

View File

@ -9,7 +9,7 @@ import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEv
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect'; import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer'; import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
type RelationFromManyFieldInputProps = { type RelationFromManyFieldInputProps = {
@ -20,9 +20,9 @@ export const RelationFromManyFieldInput = ({
onSubmit, onSubmit,
}: RelationFromManyFieldInputProps) => { }: RelationFromManyFieldInputProps) => {
const { fieldDefinition, recordId } = useContext(FieldContext); const { fieldDefinition, recordId } = useContext(FieldContext);
const relationPickerScopeId = `relation-picker-${fieldDefinition.fieldMetadataId}`; const recordPickerInstanceId = `record-picker-${fieldDefinition.fieldMetadataId}`;
const { updateRelation } = useUpdateRelationFromManyFieldInput({ const { updateRelation } = useUpdateRelationFromManyFieldInput({
scopeId: relationPickerScopeId, scopeId: recordPickerInstanceId,
}); });
const handleSubmit = () => { const handleSubmit = () => {
@ -51,11 +51,13 @@ export const RelationFromManyFieldInput = ({
recordId, recordId,
}); });
const { dropdownPlacement } = useDropdown(relationPickerScopeId); const { dropdownPlacement } = useDropdown(recordPickerInstanceId);
return ( return (
<> <>
<RelationPickerScope relationPickerScopeId={relationPickerScopeId}> <RecordPickerComponentInstanceContext.Provider
value={{ instanceId: recordPickerInstanceId }}
>
<RelationFromManyFieldInputMultiRecordsEffect /> <RelationFromManyFieldInputMultiRecordsEffect />
<MultiRecordSelect <MultiRecordSelect
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -63,7 +65,7 @@ export const RelationFromManyFieldInput = ({
onCreate={createNewRecordAndOpenRightDrawer} onCreate={createNewRecordAndOpenRightDrawer}
dropdownPlacement={dropdownPlacement} dropdownPlacement={dropdownPlacement}
/> />
</RelationPickerScope> </RecordPickerComponentInstanceContext.Provider>
</> </>
); );
}; };

View File

@ -5,28 +5,28 @@ 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 { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions'; import { useRecordPickerRecordsOptions } from '@/object-record/relation-picker/hooks/useRecordPickerRecordsOptions';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const RelationFromManyFieldInputMultiRecordsEffect = () => { export const RelationFromManyFieldInputMultiRecordsEffect = () => {
const { fieldValue, fieldDefinition } = useRelationField<EntityForSelect[]>(); const { fieldValue, fieldDefinition } = useRelationField<RecordForSelect[]>();
const scopeId = useAvailableScopeIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RelationPickerScopeInternalContext, RecordPickerComponentInstanceContext,
); );
const { const {
objectRecordsIdsMultiSelectState, objectRecordsIdsMultiSelectState,
objectRecordMultiSelectCheckedRecordsIdsState, objectRecordMultiSelectCheckedRecordsIdsState,
recordMultiSelectIsLoadingState, recordMultiSelectIsLoadingState,
} = useObjectRecordMultiSelectScopedStates(scopeId); } = useObjectRecordMultiSelectScopedStates(instanceId);
const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] = const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] =
useRecoilState(objectRecordsIdsMultiSelectState); useRecoilState(objectRecordsIdsMultiSelectState);
const { entities } = useRelationPickerEntitiesOptions({ const { records } = useRecordPickerRecordsOptions({
relationObjectNameSingular: objectNameSingular:
fieldDefinition.metadata.relationObjectMetadataNameSingular, fieldDefinition.metadata.relationObjectMetadataNameSingular,
}); });
@ -41,7 +41,7 @@ export const RelationFromManyFieldInputMultiRecordsEffect = () => {
const allRecords = useMemo( const allRecords = useMemo(
() => [ () => [
...entities.entitiesToSelect.map((entity) => { ...records.recordsToSelect.map((entity) => {
const { record, ...recordIdentifier } = entity; const { record, ...recordIdentifier } = entity;
return { return {
objectMetadataItem: objectMetadataItem, objectMetadataItem: objectMetadataItem,
@ -50,7 +50,7 @@ export const RelationFromManyFieldInputMultiRecordsEffect = () => {
}; };
}), }),
], ],
[entities.entitiesToSelect, objectMetadataItem], [records.recordsToSelect, objectMetadataItem],
); );
const [ const [
@ -65,7 +65,7 @@ export const RelationFromManyFieldInputMultiRecordsEffect = () => {
const currentRecord = snapshot const currentRecord = snapshot
.getLoadable( .getLoadable(
objectRecordMultiSelectComponentFamilyState({ objectRecordMultiSelectComponentFamilyState({
scopeId: scopeId, scopeId: instanceId,
familyKey: newRecord.record.id, familyKey: newRecord.record.id,
}), }),
) )
@ -86,7 +86,7 @@ export const RelationFromManyFieldInputMultiRecordsEffect = () => {
) { ) {
set( set(
objectRecordMultiSelectComponentFamilyState({ objectRecordMultiSelectComponentFamilyState({
scopeId: scopeId, scopeId: instanceId,
familyKey: newRecordWithSelected.record.id, familyKey: newRecordWithSelected.record.id,
}), }),
newRecordWithSelected, newRecordWithSelected,
@ -94,7 +94,7 @@ export const RelationFromManyFieldInputMultiRecordsEffect = () => {
} }
} }
}, },
[objectRecordMultiSelectCheckedRecordsIds, scopeId], [objectRecordMultiSelectCheckedRecordsIds, instanceId],
); );
useEffect(() => { useEffect(() => {
@ -113,14 +113,14 @@ export const RelationFromManyFieldInputMultiRecordsEffect = () => {
useEffect(() => { useEffect(() => {
setObjectRecordMultiSelectCheckedRecordsIds( setObjectRecordMultiSelectCheckedRecordsIds(
fieldValue fieldValue
? fieldValue.map((fieldValueItem: EntityForSelect) => fieldValueItem.id) ? fieldValue.map((fieldValueItem: RecordForSelect) => fieldValueItem.id)
: [], : [],
); );
}, [fieldValue, setObjectRecordMultiSelectCheckedRecordsIds]); }, [fieldValue, setObjectRecordMultiSelectCheckedRecordsIds]);
useEffect(() => { useEffect(() => {
setRecordMultiSelectIsLoading(entities.loading); setRecordMultiSelectIsLoading(records.loading);
}, [entities.loading, setRecordMultiSelectIsLoading]); }, [records.loading, setRecordMultiSelectIsLoading]);
return <></>; return <></>;
}; };

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { RelationPicker } from '@/object-record/relation-picker/components/RelationPicker'; import { RelationPicker } from '@/object-record/relation-picker/components/RelationPicker';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { usePersistField } from '../../../hooks/usePersistField'; import { usePersistField } from '../../../hooks/usePersistField';
import { useRelationField } from '../../hooks/useRelationField'; import { useRelationField } from '../../hooks/useRelationField';
@ -24,11 +24,11 @@ export const RelationToOneFieldInput = ({
onCancel, onCancel,
}: RelationToOneFieldInputProps) => { }: RelationToOneFieldInputProps) => {
const { fieldDefinition, initialSearchValue, fieldValue } = const { fieldDefinition, initialSearchValue, fieldValue } =
useRelationField<EntityForSelect>(); useRelationField<RecordForSelect>();
const persistField = usePersistField(); const persistField = usePersistField();
const handleSubmit = (newEntity: EntityForSelect | null) => { const handleSubmit = (newEntity: RecordForSelect | null) => {
onSubmit?.(() => persistField(newEntity?.record ?? null)); onSubmit?.(() => persistField(newEntity?.record ?? null));
}; };

View File

@ -1,7 +1,7 @@
import { useClearField } from '@/object-record/record-field/hooks/useClearField'; import { useClearField } from '@/object-record/record-field/hooks/useClearField';
import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField'; import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList'; import { SINGLE_RECORD_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleRecordSelectBaseList';
import { SelectOption } from '@/spreadsheet-import/types'; import { SelectOption } from '@/spreadsheet-import/types';
import { SelectInput } from '@/ui/input/components/SelectInput'; import { SelectInput } from '@/ui/input/components/SelectInput';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -28,7 +28,7 @@ export const SelectFieldInput = ({
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]); const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]);
const { resetSelectedItem } = useSelectableList( const { resetSelectedItem } = useSelectableList(
SINGLE_ENTITY_SELECT_BASE_LIST, SINGLE_RECORD_SELECT_BASE_LIST,
); );
const clearField = useClearField(); const clearField = useClearField();
@ -65,7 +65,7 @@ export const SelectFieldInput = ({
return ( return (
<div ref={setSelectWrapperRef}> <div ref={setSelectWrapperRef}>
<SelectableList <SelectableList
selectableListId={SINGLE_ENTITY_SELECT_BASE_LIST} selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
selectableItemIdArray={optionIds} selectableItemIdArray={optionIds}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onEnter={(itemId) => { onEnter={(itemId) => {

View File

@ -13,7 +13,6 @@ import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldMetadataType } from '~/generated/graphql'; import { FieldMetadataType } from '~/generated/graphql';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
@ -26,6 +25,7 @@ import {
} from '~/testing/mock-data/users'; } from '~/testing/mock-data/users';
import { FieldContextProvider } from '@/object-record/record-field/meta-types/components/FieldContextProvider'; import { FieldContextProvider } from '@/object-record/record-field/meta-types/components/FieldContextProvider';
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { import {
RelationToOneFieldInput, RelationToOneFieldInput,
RelationToOneFieldInputProps, RelationToOneFieldInputProps,
@ -80,12 +80,12 @@ const RelationToOneFieldInputWithContext = ({
}} }}
recordId={recordId} recordId={recordId}
> >
<RelationPickerScope <RecordPickerComponentInstanceContext.Provider
relationPickerScopeId={'relation-to-one-field-input'} value={{ instanceId: 'relation-to-one-field-input' }}
> >
<RelationWorkspaceSetterEffect /> <RelationWorkspaceSetterEffect />
<RelationToOneFieldInput onSubmit={onSubmit} onCancel={onCancel} /> <RelationToOneFieldInput onSubmit={onSubmit} onCancel={onCancel} />
</RelationPickerScope> </RecordPickerComponentInstanceContext.Provider>
</FieldContextProvider> </FieldContextProvider>
<div data-testid="data-field-input-click-outside-div" /> <div data-testid="data-field-input-click-outside-div" />
</div> </div>

View File

@ -2,7 +2,7 @@ import { ThemeColor } from 'twenty-ui';
import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues'; import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues';
import { ZodHelperLiteral } from '@/object-record/record-field/types/ZodHelperLiteral'; import { ZodHelperLiteral } from '@/object-record/record-field/types/ZodHelperLiteral';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { RelationDefinitionType } from '~/generated-metadata/graphql'; import { RelationDefinitionType } from '~/generated-metadata/graphql';
import { CurrencyCode } from './CurrencyCode'; import { CurrencyCode } from './CurrencyCode';
@ -244,9 +244,9 @@ export type FieldRatingValue = (typeof RATING_VALUES)[number] | null;
export type FieldSelectValue = string | null; export type FieldSelectValue = string | null;
export type FieldMultiSelectValue = string[] | null; export type FieldMultiSelectValue = string[] | null;
export type FieldRelationToOneValue = EntityForSelect | null; export type FieldRelationToOneValue = RecordForSelect | null;
export type FieldRelationFromManyValue = EntityForSelect[] | []; export type FieldRelationFromManyValue = RecordForSelect[] | [];
export type FieldRelationValue< export type FieldRelationValue<
T extends FieldRelationToOneValue | FieldRelationFromManyValue, T extends FieldRelationToOneValue | FieldRelationFromManyValue,

View File

@ -18,11 +18,11 @@ import { RecordDetailSectionHeader } from '@/object-record/record-show/record-de
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect'; import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect';
import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch'; import { SingleRecordSelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleRecordSelectMenuItemsWithSearch';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer'; import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; import { useRecordPicker } from '@/object-record/relation-picker/hooks/useRecordPicker';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
@ -84,13 +84,13 @@ export const RecordDetailRelationSection = ({
const { closeDropdown, isDropdownOpen, dropdownPlacement } = const { closeDropdown, isDropdownOpen, dropdownPlacement } =
useDropdown(dropdownId); useDropdown(dropdownId);
const { setRelationPickerSearchFilter } = useRelationPicker({ const { setRecordPickerSearchFilter } = useRecordPicker({
relationPickerScopeId: dropdownId, recordPickerInstanceId: dropdownId,
}); });
const handleCloseRelationPickerDropdown = useCallback(() => { const handleCloseRelationPickerDropdown = useCallback(() => {
setRelationPickerSearchFilter(''); setRecordPickerSearchFilter('');
}, [setRelationPickerSearchFilter]); }, [setRecordPickerSearchFilter]);
const persistField = usePersistField(); const persistField = usePersistField();
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({ const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
@ -98,7 +98,7 @@ export const RecordDetailRelationSection = ({
}); });
const handleRelationPickerEntitySelected = ( const handleRelationPickerEntitySelected = (
selectedRelationEntity?: EntityForSelect, selectedRelationEntity?: RecordForSelect,
) => { ) => {
closeDropdown(); closeDropdown();
@ -193,16 +193,16 @@ export const RecordDetailRelationSection = ({
/> />
} }
dropdownComponents={ dropdownComponents={
<RelationPickerScope relationPickerScopeId={dropdownId}> <RecordPickerComponentInstanceContext.Provider
value={{ instanceId: dropdownId }}
>
{isToOneObject ? ( {isToOneObject ? (
<SingleEntitySelectMenuItemsWithSearch <SingleRecordSelectMenuItemsWithSearch
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
onEntitySelected={handleRelationPickerEntitySelected} onRecordSelected={handleRelationPickerEntitySelected}
selectedRelationRecordIds={relationRecordIds} selectedRecordIds={relationRecordIds}
relationObjectNameSingular={ objectNameSingular={relationObjectMetadataNameSingular}
relationObjectMetadataNameSingular recordPickerInstanceId={dropdownId}
}
relationPickerScopeId={dropdownId}
onCreate={createNewRecordAndOpenRightDrawer} onCreate={createNewRecordAndOpenRightDrawer}
dropdownPlacement={dropdownPlacement} dropdownPlacement={dropdownPlacement}
/> />
@ -217,7 +217,7 @@ export const RecordDetailRelationSection = ({
/> />
</> </>
)} )}
</RelationPickerScope> </RecordPickerComponentInstanceContext.Provider>
} }
dropdownHotkeyScope={{ dropdownHotkeyScope={{
scope: dropdownId, scope: dropdownId,

View File

@ -9,10 +9,10 @@ import {
import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates';
import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId'; import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
@ -20,13 +20,13 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
}: { }: {
selectedObjectRecordIds: SelectedObjectRecordId[]; selectedObjectRecordIds: SelectedObjectRecordId[];
}) => { }) => {
const scopeId = useAvailableScopeIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RelationPickerScopeInternalContext, RecordPickerComponentInstanceContext,
); );
const { const {
objectRecordsIdsMultiSelectState, objectRecordsIdsMultiSelectState,
objectRecordMultiSelectCheckedRecordsIdsState, objectRecordMultiSelectCheckedRecordsIdsState,
} = useObjectRecordMultiSelectScopedStates(scopeId); } = useObjectRecordMultiSelectScopedStates(instanceId);
const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] = const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] =
useRecoilState(objectRecordsIdsMultiSelectState); useRecoilState(objectRecordsIdsMultiSelectState);
@ -41,7 +41,7 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
const currentRecord = snapshot const currentRecord = snapshot
.getLoadable( .getLoadable(
objectRecordMultiSelectComponentFamilyState({ objectRecordMultiSelectComponentFamilyState({
scopeId: scopeId, scopeId: instanceId,
familyKey: newRecord.record.id, familyKey: newRecord.record.id,
}), }),
) )
@ -66,7 +66,7 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
) { ) {
set( set(
objectRecordMultiSelectComponentFamilyState({ objectRecordMultiSelectComponentFamilyState({
scopeId: scopeId, scopeId: instanceId,
familyKey: newRecordWithSelected.record.id, familyKey: newRecordWithSelected.record.id,
}), }),
newRecordWithSelected, newRecordWithSelected,
@ -74,12 +74,12 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
} }
} }
}, },
[objectRecordMultiSelectCheckedRecordsIdsState, scopeId], [objectRecordMultiSelectCheckedRecordsIdsState, instanceId],
); );
const matchesSearchFilterObjectRecords = useRecoilValue( const matchesSearchFilterObjectRecords = useRecoilValue(
objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({
scopeId, scopeId: instanceId,
}), }),
); );

View File

@ -1,35 +1,29 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState';
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect =
() => { () => {
const scopeId = useAvailableScopeIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RelationPickerScopeInternalContext, RecordPickerComponentInstanceContext,
); );
const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState( const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState(
objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({
scopeId, scopeId: instanceId,
}), }),
); );
const relationPickerScopedId = useAvailableScopeIdOrThrow( const recordPickerSearchFilter = useRecoilComponentValueV2(
RelationPickerScopeInternalContext, recordPickerSearchFilterComponentState,
); instanceId,
const { relationPickerSearchFilterState } = useRelationPickerScopedStates({
relationPickerScopedId,
});
const relationPickerSearchFilter = useRecoilValue(
relationPickerSearchFilterState,
); );
const { matchesSearchFilterObjectRecordsQueryResult } = const { matchesSearchFilterObjectRecordsQueryResult } =
@ -38,7 +32,7 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect =
CoreObjectNameSingular.Task, CoreObjectNameSingular.Task,
CoreObjectNameSingular.Note, CoreObjectNameSingular.Note,
], ],
searchFilterValue: relationPickerSearchFilter, searchFilterValue: recordPickerSearchFilter,
limit: 10, limit: 10,
}); });

View File

@ -2,8 +2,8 @@ import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useOb
import { MultipleObjectRecordOnClickOutsideEffect } from '@/object-record/relation-picker/components/MultipleObjectRecordOnClickOutsideEffect'; import { MultipleObjectRecordOnClickOutsideEffect } from '@/object-record/relation-picker/components/MultipleObjectRecordOnClickOutsideEffect';
import { MultipleObjectRecordSelectItem } from '@/object-record/relation-picker/components/MultipleObjectRecordSelectItem'; import { MultipleObjectRecordSelectItem } from '@/object-record/relation-picker/components/MultipleObjectRecordSelectItem';
import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId'; import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton'; import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -16,11 +16,13 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Placement } from '@floating-ui/react'; import { Placement } from '@floating-ui/react';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { IconPlus, isDefined } from 'twenty-ui'; import { IconPlus, isDefined } from 'twenty-ui';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -45,12 +47,12 @@ export const MultiRecordSelect = ({
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const relationPickerScopedId = useAvailableScopeIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RelationPickerScopeInternalContext, RecordPickerComponentInstanceContext,
); );
const { objectRecordsIdsMultiSelectState, recordMultiSelectIsLoadingState } = const { objectRecordsIdsMultiSelectState, recordMultiSelectIsLoadingState } =
useObjectRecordMultiSelectScopedStates(relationPickerScopedId); useObjectRecordMultiSelectScopedStates(instanceId);
const { resetSelectedItem } = useSelectableList( const { resetSelectedItem } = useSelectableList(
MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID, MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID,
@ -63,18 +65,18 @@ export const MultiRecordSelect = ({
objectRecordsIdsMultiSelectState, objectRecordsIdsMultiSelectState,
); );
const { relationPickerSearchFilterState } = useRelationPickerScopedStates({ const setSearchFilter = useSetRecoilComponentStateV2(
relationPickerScopedId, recordPickerSearchFilterComponentState,
}); instanceId,
);
const setSearchFilter = useSetRecoilState(relationPickerSearchFilterState); const recordPickerSearchFilter = useRecoilComponentValueV2(
const relationPickerSearchFilter = useRecoilValue( recordPickerSearchFilterComponentState,
relationPickerSearchFilterState, instanceId,
); );
useEffect(() => { useEffect(() => {
setHotkeyScope(relationPickerScopedId); setHotkeyScope(instanceId);
}, [setHotkeyScope, relationPickerScopedId]); }, [setHotkeyScope, instanceId]);
useScopedHotkeys( useScopedHotkeys(
Key.Escape, Key.Escape,
@ -83,7 +85,7 @@ export const MultiRecordSelect = ({
goBackToPreviousHotkeyScope(); goBackToPreviousHotkeyScope();
resetSelectedItem(); resetSelectedItem();
}, },
relationPickerScopedId, instanceId,
[onSubmit, goBackToPreviousHotkeyScope, resetSelectedItem], [onSubmit, goBackToPreviousHotkeyScope, resetSelectedItem],
); );
@ -99,7 +101,7 @@ export const MultiRecordSelect = ({
<SelectableList <SelectableList
selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID} selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID}
selectableItemIdArray={objectRecordsIdsMultiSelect} selectableItemIdArray={objectRecordsIdsMultiSelect}
hotkeyScope={relationPickerScopedId} hotkeyScope={instanceId}
onEnter={(selectedId) => { onEnter={(selectedId) => {
onChange?.(selectedId); onChange?.(selectedId);
resetSelectedItem(); resetSelectedItem();
@ -123,7 +125,7 @@ export const MultiRecordSelect = ({
const createNewButton = isDefined(onCreate) && ( const createNewButton = isDefined(onCreate) && (
<CreateNewButton <CreateNewButton
onClick={() => onCreate?.(relationPickerSearchFilter)} onClick={() => onCreate?.(recordPickerSearchFilter)}
LeftIcon={IconPlus} LeftIcon={IconPlus}
text="Add New" text="Add New"
/> />
@ -147,7 +149,7 @@ export const MultiRecordSelect = ({
)} )}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
{objectRecordsIdsMultiSelect?.length > 0 && results} {objectRecordsIdsMultiSelect?.length > 0 && results}
{recordMultiSelectIsLoading && !relationPickerSearchFilter && ( {recordMultiSelectIsLoading && !recordPickerSearchFilter && (
<> <>
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
@ -159,7 +161,7 @@ export const MultiRecordSelect = ({
</> </>
)} )}
<DropdownMenuSearchInput <DropdownMenuSearchInput
value={relationPickerSearchFilter} value={recordPickerSearchFilter}
onChange={handleFilterChange} onChange={handleFilterChange}
autoFocus autoFocus
/> />
@ -167,7 +169,7 @@ export const MultiRecordSelect = ({
isUndefinedOrNull(dropdownPlacement)) && ( isUndefinedOrNull(dropdownPlacement)) && (
<> <>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
{recordMultiSelectIsLoading && !relationPickerSearchFilter && ( {recordMultiSelectIsLoading && !recordPickerSearchFilter && (
<> <>
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />
<DropdownMenuSeparator /> <DropdownMenuSeparator />

View File

@ -4,10 +4,10 @@ import { Avatar, MenuItemMultiSelectAvatar } from 'twenty-ui';
import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates';
import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId'; import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const StyledSelectableItem = styled(SelectableItem)` export const StyledSelectableItem = styled(SelectableItem)`
@ -29,14 +29,14 @@ export const MultipleObjectRecordSelectItem = ({
const isSelectedByKeyboard = useRecoilValue( const isSelectedByKeyboard = useRecoilValue(
isSelectedItemIdSelector(objectRecordId), isSelectedItemIdSelector(objectRecordId),
); );
const scopeId = useAvailableScopeIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RelationPickerScopeInternalContext, RecordPickerComponentInstanceContext,
); );
const { const {
objectRecordMultiSelectFamilyState, objectRecordMultiSelectFamilyState,
objectRecordMultiSelectCheckedRecordsIdsState, objectRecordMultiSelectCheckedRecordsIdsState,
} = useObjectRecordMultiSelectScopedStates(scopeId); } = useObjectRecordMultiSelectScopedStates(instanceId);
const record = useRecoilValue( const record = useRecoilValue(
objectRecordMultiSelectFamilyState(objectRecordId), objectRecordMultiSelectFamilyState(objectRecordId),

View File

@ -6,16 +6,16 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
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/relation-picker/components/SearchPickerInitialValueEffect'; import { SearchPickerInitialValueEffect } from '@/object-record/relation-picker/components/SearchPickerInitialValueEffect';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { SingleRecordSelect } from '@/object-record/relation-picker/components/SingleRecordSelect';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer'; import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
export type RelationPickerProps = { export type RelationPickerProps = {
selectedRecordId?: string; selectedRecordId?: string;
onSubmit: (selectedEntity: EntityForSelect | null) => void; onSubmit: (selectedRecord: RecordForSelect | null) => void;
onCancel?: () => void; onCancel?: () => void;
width?: number; width?: number;
excludeRecordIds?: string[]; excludedRecordIds?: string[];
initialSearchFilter?: string | null; initialSearchFilter?: string | null;
fieldDefinition: FieldDefinition<FieldRelationMetadata>; fieldDefinition: FieldDefinition<FieldRelationMetadata>;
}; };
@ -24,16 +24,16 @@ export const RelationPicker = ({
selectedRecordId, selectedRecordId,
onSubmit, onSubmit,
onCancel, onCancel,
excludeRecordIds, excludedRecordIds,
width, width,
initialSearchFilter, initialSearchFilter,
fieldDefinition, fieldDefinition,
}: RelationPickerProps) => { }: RelationPickerProps) => {
const relationPickerScopeId = 'relation-picker'; const recordPickerInstanceId = 'relation-picker';
const handleEntitySelected = ( const handleRecordSelected = (
selectedEntity: EntityForSelect | null | undefined, selectedRecord: RecordForSelect | null | undefined,
) => onSubmit(selectedEntity ?? null); ) => onSubmit(selectedRecord ?? null);
const { objectMetadataItem: relationObjectMetadataItem } = const { objectMetadataItem: relationObjectMetadataItem } =
useObjectMetadataItem({ useObjectMetadataItem({
@ -60,21 +60,21 @@ export const RelationPicker = ({
<> <>
<SearchPickerInitialValueEffect <SearchPickerInitialValueEffect
initialValueForSearchFilter={initialSearchFilter} initialValueForSearchFilter={initialSearchFilter}
relationPickerScopeId={relationPickerScopeId} recordPickerInstanceId={recordPickerInstanceId}
/> />
<SingleEntitySelect <SingleRecordSelect
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
emptyLabel={'No ' + fieldDefinition.label} emptyLabel={'No ' + fieldDefinition.label}
onCancel={onCancel} onCancel={onCancel}
onCreate={createNewRecordAndOpenRightDrawer} onCreate={createNewRecordAndOpenRightDrawer}
onEntitySelected={handleEntitySelected} onRecordSelected={handleRecordSelected}
width={width} width={width}
relationObjectNameSingular={ objectNameSingular={
fieldDefinition.metadata.relationObjectMetadataNameSingular fieldDefinition.metadata.relationObjectMetadataNameSingular
} }
relationPickerScopeId={relationPickerScopeId} recordPickerInstanceId={recordPickerInstanceId}
selectedRelationRecordIds={selectedRecordId ? [selectedRecordId] : []} selectedRecordIds={selectedRecordId ? [selectedRecordId] : []}
excludedRelationRecordIds={excludeRecordIds} excludedRecordIds={excludedRecordIds}
/> />
</> </>
); );

View File

@ -1,25 +1,22 @@
import { getRelationPickerScopedStates } from '@/object-record/relation-picker/utils/getRelationPickerScopedStates'; import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
export const SearchPickerInitialValueEffect = ({ export const SearchPickerInitialValueEffect = ({
initialValueForSearchFilter, initialValueForSearchFilter,
relationPickerScopeId, recordPickerInstanceId,
}: { }: {
initialValueForSearchFilter?: string | null; initialValueForSearchFilter?: string | null;
relationPickerScopeId: string; recordPickerInstanceId: string;
}) => { }) => {
const { relationPickerSearchFilterState } = getRelationPickerScopedStates({ const setRecordPickerSearchFilter = useSetRecoilComponentStateV2(
relationPickerScopeId: relationPickerScopeId, recordPickerSearchFilterComponentState,
}); recordPickerInstanceId,
const setRelationPickerSearchFilter = useSetRecoilState(
relationPickerSearchFilterState,
); );
useEffect(() => { useEffect(() => {
setRelationPickerSearchFilter(initialValueForSearchFilter ?? ''); setRecordPickerSearchFilter(initialValueForSearchFilter ?? '');
}, [initialValueForSearchFilter, setRelationPickerSearchFilter]); }, [initialValueForSearchFilter, setRecordPickerSearchFilter]);
return <></>; return <></>;
}; };

View File

@ -2,14 +2,15 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Avatar, MenuItemSelectAvatar } from 'twenty-ui'; import { Avatar, MenuItemSelectAvatar } from 'twenty-ui';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { SINGLE_RECORD_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleRecordSelectBaseList';
import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
type SelectableMenuItemSelectProps = { type SelectableMenuItemSelectProps = {
entity: EntityForSelect; record: RecordForSelect;
onEntitySelected: (entitySelected?: EntityForSelect) => void; onRecordSelected: (recordSelected?: RecordForSelect) => void;
selectedEntity?: EntityForSelect; selectedRecord?: RecordForSelect;
}; };
const StyledSelectableItem = styled(SelectableItem)` const StyledSelectableItem = styled(SelectableItem)`
@ -17,31 +18,31 @@ const StyledSelectableItem = styled(SelectableItem)`
`; `;
export const SelectableMenuItemSelect = ({ export const SelectableMenuItemSelect = ({
entity, record,
onEntitySelected, onRecordSelected,
selectedEntity, selectedRecord,
}: SelectableMenuItemSelectProps) => { }: SelectableMenuItemSelectProps) => {
const { isSelectedItemIdSelector } = useSelectableList( const { isSelectedItemIdSelector } = useSelectableList(
'single-entity-select-base-list', SINGLE_RECORD_SELECT_BASE_LIST,
); );
const isSelectedItemId = useRecoilValue(isSelectedItemIdSelector(entity.id)); const isSelectedItemId = useRecoilValue(isSelectedItemIdSelector(record.id));
return ( return (
<StyledSelectableItem itemId={entity.id} key={entity.id}> <StyledSelectableItem itemId={record.id} key={record.id}>
<MenuItemSelectAvatar <MenuItemSelectAvatar
key={entity.id} key={record.id}
testId="menu-item" testId="menu-item"
onClick={() => onEntitySelected(entity)} onClick={() => onRecordSelected(record)}
text={entity.name} text={record.name}
selected={selectedEntity?.id === entity.id} selected={selectedRecord?.id === record.id}
hovered={isSelectedItemId} hovered={isSelectedItemId}
avatar={ avatar={
<Avatar <Avatar
avatarUrl={entity.avatarUrl} avatarUrl={record.avatarUrl}
placeholderColorSeed={entity.id} placeholderColorSeed={record.id}
placeholder={entity.name} placeholder={record.name}
size="md" size="md"
type={entity.avatarType ?? 'rounded'} type={record.avatarType ?? 'rounded'}
/> />
} }
/> />

View File

@ -1,114 +0,0 @@
import {
SingleEntitySelectMenuItems,
SingleEntitySelectMenuItemsProps,
} from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { Placement } from '@floating-ui/react';
import { IconPlus } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export type SingleEntitySelectMenuItemsWithSearchProps = {
excludedRelationRecordIds?: string[];
onCreate?: ((searchInput?: string) => void) | (() => void);
relationObjectNameSingular: string;
relationPickerScopeId?: string;
selectedRelationRecordIds: string[];
dropdownPlacement?: Placement | null;
} & Pick<
SingleEntitySelectMenuItemsProps,
| 'EmptyIcon'
| 'emptyLabel'
| 'onCancel'
| 'onEntitySelected'
| 'selectedEntity'
>;
export const SingleEntitySelectMenuItemsWithSearch = ({
EmptyIcon,
emptyLabel,
excludedRelationRecordIds,
onCancel,
onCreate,
onEntitySelected,
relationObjectNameSingular,
relationPickerScopeId = 'relation-picker',
selectedRelationRecordIds,
dropdownPlacement,
}: SingleEntitySelectMenuItemsWithSearchProps) => {
const { handleSearchFilterChange } = useEntitySelectSearch({
relationPickerScopeId,
});
const { entities, relationPickerSearchFilter } =
useRelationPickerEntitiesOptions({
relationObjectNameSingular,
selectedRelationRecordIds,
excludedRelationRecordIds,
});
const createNewButton = isDefined(onCreate) && (
<CreateNewButton
onClick={() => onCreate?.(relationPickerSearchFilter)}
LeftIcon={IconPlus}
text="Add New"
/>
);
const results = (
<SingleEntitySelectMenuItems
entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading}
selectedEntity={
entities.selectedEntities.length === 1
? entities.selectedEntities[0]
: undefined
}
shouldSelectEmptyOption={selectedRelationRecordIds?.length === 0}
hotkeyScope={relationPickerScopeId}
isFiltered={!!relationPickerSearchFilter}
{...{
EmptyIcon,
emptyLabel,
onCancel,
onEntitySelected,
}}
/>
);
return (
<>
{dropdownPlacement?.includes('end') && (
<>
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
{entities.entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
{entities.entitiesToSelect.length > 0 && results}
<DropdownMenuSeparator />
</>
)}
<DropdownMenuSearchInput onChange={handleSearchFilterChange} autoFocus />
{(dropdownPlacement?.includes('start') ||
isUndefinedOrNull(dropdownPlacement)) && (
<>
<DropdownMenuSeparator />
{entities.entitiesToSelect.length > 0 && results}
{entities.entitiesToSelect.length > 0 && isDefined(onCreate) && (
<DropdownMenuSeparator />
)}
{isDefined(onCreate) && (
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
)}
</>
)}
</>
);
};

View File

@ -1,31 +1,31 @@
import { useRef } from 'react'; import { useRef } from 'react';
import { import {
SingleEntitySelectMenuItemsWithSearch, SingleRecordSelectMenuItemsWithSearch,
SingleEntitySelectMenuItemsWithSearchProps, SingleRecordSelectMenuItemsWithSearchProps,
} from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch'; } from '@/object-record/relation-picker/components/SingleRecordSelectMenuItemsWithSearch';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export type SingleEntitySelectProps = { export type SingleRecordSelectProps = {
disableBackgroundBlur?: boolean; disableBackgroundBlur?: boolean;
width?: number; width?: number;
} & SingleEntitySelectMenuItemsWithSearchProps; } & SingleRecordSelectMenuItemsWithSearchProps;
export const SingleEntitySelect = ({ export const SingleRecordSelect = ({
disableBackgroundBlur = false, disableBackgroundBlur = false,
EmptyIcon, EmptyIcon,
emptyLabel, emptyLabel,
excludedRelationRecordIds, excludedRecordIds,
onCancel, onCancel,
onCreate, onCreate,
onEntitySelected, onRecordSelected,
relationObjectNameSingular, objectNameSingular,
relationPickerScopeId, recordPickerInstanceId,
selectedRelationRecordIds, selectedRecordIds,
width = 200, width = 200,
}: SingleEntitySelectProps) => { }: SingleRecordSelectProps) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
useListenClickOutside({ useListenClickOutside({
@ -50,17 +50,17 @@ export const SingleEntitySelect = ({
width={width} width={width}
data-select-disable data-select-disable
> >
<SingleEntitySelectMenuItemsWithSearch <SingleRecordSelectMenuItemsWithSearch
{...{ {...{
EmptyIcon, EmptyIcon,
emptyLabel, emptyLabel,
excludedRelationRecordIds, excludedRecordIds,
onCancel, onCancel,
onCreate, onCreate,
onEntitySelected, onRecordSelected,
relationObjectNameSingular, objectNameSingular,
relationPickerScopeId, recordPickerInstanceId,
selectedRelationRecordIds, selectedRecordIds,
}} }}
/> />
</DropdownMenu> </DropdownMenu>

View File

@ -5,7 +5,7 @@ import { Key } from 'ts-key-enum';
import { IconComponent, MenuItemSelect } from 'twenty-ui'; import { IconComponent, MenuItemSelect } from 'twenty-ui';
import { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect'; import { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect';
import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList'; import { SINGLE_RECORD_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleRecordSelectBaseList';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -13,44 +13,44 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { EntityForSelect } from '../types/EntityForSelect'; import { RecordForSelect } from '../types/RecordForSelect';
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
export type SingleEntitySelectMenuItemsProps = { export type SingleRecordSelectMenuItemsProps = {
EmptyIcon?: IconComponent; EmptyIcon?: IconComponent;
emptyLabel?: string; emptyLabel?: string;
entitiesToSelect: EntityForSelect[]; recordsToSelect: RecordForSelect[];
loading?: boolean; loading?: boolean;
onCancel?: () => void; onCancel?: () => void;
onEntitySelected: (entity?: EntityForSelect) => void; onRecordSelected: (entity?: RecordForSelect) => void;
selectedEntity?: EntityForSelect; selectedRecord?: RecordForSelect;
SelectAllIcon?: IconComponent; SelectAllIcon?: IconComponent;
selectAllLabel?: string; selectAllLabel?: string;
isAllEntitySelected?: boolean; isAllRecordsSelected?: boolean;
isAllEntitySelectShown?: boolean; isAllRecordsSelectShown?: boolean;
onAllEntitySelected?: () => void; onAllRecordsSelected?: () => void;
hotkeyScope?: string; hotkeyScope?: string;
isFiltered: boolean; isFiltered: boolean;
shouldSelectEmptyOption?: boolean; shouldSelectEmptyOption?: boolean;
}; };
export const SingleEntitySelectMenuItems = ({ export const SingleRecordSelectMenuItems = ({
EmptyIcon, EmptyIcon,
emptyLabel, emptyLabel,
entitiesToSelect, recordsToSelect,
loading, loading,
onCancel, onCancel,
onEntitySelected, onRecordSelected,
selectedEntity, selectedRecord,
SelectAllIcon, SelectAllIcon,
selectAllLabel, selectAllLabel,
isAllEntitySelected, isAllRecordsSelected,
isAllEntitySelectShown, isAllRecordsSelectShown,
onAllEntitySelected, onAllRecordsSelected,
hotkeyScope = RelationPickerHotkeyScope.RelationPicker, hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
isFiltered, isFiltered,
shouldSelectEmptyOption, shouldSelectEmptyOption,
}: SingleEntitySelectMenuItemsProps) => { }: SingleRecordSelectMenuItemsProps) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const selectNone = emptyLabel const selectNone = emptyLabel
@ -61,7 +61,7 @@ export const SingleEntitySelectMenuItems = ({
} }
: null; : null;
const selectAll = isAllEntitySelectShown const selectAll = isAllRecordsSelectShown
? { ? {
__typename: '', __typename: '',
id: 'select-all', id: 'select-all',
@ -69,18 +69,18 @@ export const SingleEntitySelectMenuItems = ({
} }
: null; : null;
const entitiesInDropdown = [ const recordsInDropdown = [
selectAll, selectAll,
selectNone, selectNone,
selectedEntity, selectedRecord,
...entitiesToSelect, ...recordsToSelect,
].filter( ].filter(
(entity): entity is EntityForSelect => (entity): entity is RecordForSelect =>
isDefined(entity) && isNonEmptyString(entity.name), isDefined(entity) && isNonEmptyString(entity.name),
); );
const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList( const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList(
SINGLE_ENTITY_SELECT_BASE_LIST, SINGLE_RECORD_SELECT_BASE_LIST,
); );
const isSelectedSelectNoneButton = useRecoilValue( const isSelectedSelectNoneButton = useRecoilValue(
@ -101,38 +101,38 @@ export const SingleEntitySelectMenuItems = ({
[onCancel, resetSelectedItem], [onCancel, resetSelectedItem],
); );
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id); const selectableItemIds = recordsInDropdown.map((entity) => entity.id);
return ( return (
<div ref={containerRef}> <div ref={containerRef}>
<SelectableList <SelectableList
selectableListId={SINGLE_ENTITY_SELECT_BASE_LIST} selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onEnter={(itemId) => { onEnter={(itemId) => {
const entityIndex = entitiesInDropdown.findIndex( const recordIndex = recordsInDropdown.findIndex(
(entity) => entity.id === itemId, (record) => record.id === itemId,
); );
onEntitySelected(entitiesInDropdown[entityIndex]); onRecordSelected(recordsInDropdown[recordIndex]);
resetSelectedItem(); resetSelectedItem();
}} }}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{loading && !isFiltered ? ( {loading && !isFiltered ? (
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />
) : entitiesInDropdown.length === 0 && ) : recordsInDropdown.length === 0 &&
!isAllEntitySelectShown && !isAllRecordsSelectShown &&
!loading ? ( !loading ? (
<></> <></>
) : ( ) : (
entitiesInDropdown?.map((entity) => { recordsInDropdown?.map((record) => {
switch (entity.id) { switch (record.id) {
case 'select-none': { case 'select-none': {
return ( return (
emptyLabel && ( emptyLabel && (
<MenuItemSelect <MenuItemSelect
key={entity.id} key={record.id}
onClick={() => onEntitySelected()} onClick={() => onRecordSelected()}
LeftIcon={EmptyIcon} LeftIcon={EmptyIcon}
text={emptyLabel} text={emptyLabel}
selected={shouldSelectEmptyOption === true} selected={shouldSelectEmptyOption === true}
@ -143,15 +143,15 @@ export const SingleEntitySelectMenuItems = ({
} }
case 'select-all': { case 'select-all': {
return ( return (
isAllEntitySelectShown && isAllRecordsSelectShown &&
selectAllLabel && selectAllLabel &&
onAllEntitySelected && ( onAllRecordsSelected && (
<MenuItemSelect <MenuItemSelect
key={entity.id} key={record.id}
onClick={() => onAllEntitySelected()} onClick={() => onAllRecordsSelected()}
LeftIcon={SelectAllIcon} LeftIcon={SelectAllIcon}
text={selectAllLabel} text={selectAllLabel}
selected={!!isAllEntitySelected} selected={!!isAllRecordsSelected}
hovered={isSelectedSelectAllButton} hovered={isSelectedSelectAllButton}
/> />
) )
@ -160,10 +160,10 @@ export const SingleEntitySelectMenuItems = ({
default: { default: {
return ( return (
<SelectableMenuItemSelect <SelectableMenuItemSelect
key={entity.id} key={record.id}
entity={entity} record={record}
onEntitySelected={onEntitySelected} onRecordSelected={onRecordSelected}
selectedEntity={selectedEntity} selectedRecord={selectedRecord}
/> />
); );
} }

View File

@ -0,0 +1,113 @@
import {
SingleRecordSelectMenuItems,
SingleRecordSelectMenuItemsProps,
} from '@/object-record/relation-picker/components/SingleRecordSelectMenuItems';
import { useRecordPickerRecordsOptions } from '@/object-record/relation-picker/hooks/useRecordPickerRecordsOptions';
import { useRecordSelectSearch } from '@/object-record/relation-picker/hooks/useRecordSelectSearch';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { Placement } from '@floating-ui/react';
import { IconPlus } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export type SingleRecordSelectMenuItemsWithSearchProps = {
excludedRecordIds?: string[];
onCreate?: ((searchInput?: string) => void) | (() => void);
objectNameSingular: string;
recordPickerInstanceId?: string;
selectedRecordIds: string[];
dropdownPlacement?: Placement | null;
} & Pick<
SingleRecordSelectMenuItemsProps,
| 'EmptyIcon'
| 'emptyLabel'
| 'onCancel'
| 'onRecordSelected'
| 'selectedRecord'
>;
export const SingleRecordSelectMenuItemsWithSearch = ({
EmptyIcon,
emptyLabel,
excludedRecordIds,
onCancel,
onCreate,
onRecordSelected,
objectNameSingular,
recordPickerInstanceId = 'record-picker',
selectedRecordIds,
dropdownPlacement,
}: SingleRecordSelectMenuItemsWithSearchProps) => {
const { handleSearchFilterChange } = useRecordSelectSearch({
recordPickerInstanceId,
});
const { records, recordPickerSearchFilter } = useRecordPickerRecordsOptions({
objectNameSingular,
selectedRecordIds,
excludedRecordIds,
});
const createNewButton = isDefined(onCreate) && (
<CreateNewButton
onClick={() => onCreate?.(recordPickerSearchFilter)}
LeftIcon={IconPlus}
text="Add New"
/>
);
const results = (
<SingleRecordSelectMenuItems
recordsToSelect={records.recordsToSelect}
loading={records.loading}
selectedRecord={
records.recordsToSelect.length === 1
? records.recordsToSelect[0]
: undefined
}
shouldSelectEmptyOption={selectedRecordIds?.length === 0}
hotkeyScope={recordPickerInstanceId}
isFiltered={!!recordPickerSearchFilter}
{...{
EmptyIcon,
emptyLabel,
onCancel,
onRecordSelected,
}}
/>
);
return (
<>
{dropdownPlacement?.includes('end') && (
<>
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
{records.recordsToSelect.length > 0 && <DropdownMenuSeparator />}
{records.recordsToSelect.length > 0 && results}
<DropdownMenuSeparator />
</>
)}
<DropdownMenuSearchInput onChange={handleSearchFilterChange} autoFocus />
{(dropdownPlacement?.includes('start') ||
isUndefinedOrNull(dropdownPlacement)) && (
<>
<DropdownMenuSeparator />
{records.recordsToSelect.length > 0 && results}
{records.recordsToSelect.length > 0 && isDefined(onCreate) && (
<DropdownMenuSeparator />
)}
{isDefined(onCreate) && (
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
)}
</>
)}
</>
);
};

View File

@ -5,18 +5,18 @@ 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 { RelationPickerDecorator } from '~/testing/decorators/RelationPickerDecorator'; 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';
import { sleep } from '~/utils/sleep'; import { sleep } from '~/utils/sleep';
import { EntityForSelect } from '../../types/EntityForSelect'; import { RecordForSelect } from '../../types/RecordForSelect';
import { SingleEntitySelect } from '../SingleEntitySelect'; import { SingleRecordSelect } from '../SingleRecordSelect';
const peopleMock = getPeopleMock(); const peopleMock = getPeopleMock();
const entities = peopleMock.map<EntityForSelect>((person) => ({ const records = peopleMock.map<RecordForSelect>((person) => ({
id: person.id, id: person.id,
name: person.name.firstName + ' ' + person.name.lastName, name: person.name.firstName + ' ' + person.name.lastName,
avatarUrl: 'https://picsum.photos/200', avatarUrl: 'https://picsum.photos/200',
@ -24,24 +24,24 @@ const entities = peopleMock.map<EntityForSelect>((person) => ({
record: { ...person, __typename: 'Person' }, record: { ...person, __typename: 'Person' },
})); }));
const meta: Meta<typeof SingleEntitySelect> = { const meta: Meta<typeof SingleRecordSelect> = {
title: 'UI/Input/RelationPicker/SingleEntitySelect', title: 'UI/Input/RelationPicker/SingleRecordSelect',
component: SingleEntitySelect, component: SingleRecordSelect,
decorators: [ decorators: [
ComponentDecorator, ComponentDecorator,
ComponentWithRecoilScopeDecorator, ComponentWithRecoilScopeDecorator,
RelationPickerDecorator, RecordPickerDecorator,
ObjectMetadataItemsDecorator, ObjectMetadataItemsDecorator,
SnackBarDecorator, SnackBarDecorator,
], ],
args: { args: {
relationObjectNameSingular: CoreObjectNameSingular.WorkspaceMember, objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
selectedRelationRecordIds: [], selectedRecordIds: [],
}, },
argTypes: { argTypes: {
selectedEntity: { selectedRecord: {
options: entities.map(({ name }) => name), options: records.map(({ name }) => name),
mapping: entities.reduce( mapping: records.reduce(
(result, entity) => ({ ...result, [entity.name]: entity }), (result, entity) => ({ ...result, [entity.name]: entity }),
{}, {},
), ),
@ -53,12 +53,12 @@ const meta: Meta<typeof SingleEntitySelect> = {
}; };
export default meta; export default meta;
type Story = StoryObj<typeof SingleEntitySelect>; type Story = StoryObj<typeof SingleRecordSelect>;
export const Default: Story = {}; export const Default: Story = {};
export const WithSelectedEntity: Story = { export const WithSelectedRecord: Story = {
args: { selectedEntity: entities[2] }, args: { selectedRecord: records[2] },
}; };
export const WithEmptyOption: Story = { export const WithEmptyOption: Story = {

View File

@ -1 +0,0 @@
export const SINGLE_ENTITY_SELECT_BASE_LIST = 'single-entity-select-base-list';

View File

@ -0,0 +1 @@
export const SINGLE_RECORD_SELECT_BASE_LIST = 'single-record-select-base-list';

View File

@ -1,46 +0,0 @@
import { ChangeEvent } from 'react';
import { act, renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
const scopeId = 'scopeId';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RelationPickerScopeInternalContext.Provider value={{ scopeId }}>
<RecoilRoot>{children}</RecoilRoot>
</RelationPickerScopeInternalContext.Provider>
);
describe('useEntitySelectSearch', () => {
it('should update searchFilter after change event', async () => {
const { result } = renderHook(
() => {
const entitySelectSearchHook = useEntitySelectSearch({
relationPickerScopeId: 'relation-picker',
});
const relationPickerScopedStatesHook = useRelationPickerScopedStates({
relationPickerScopedId: 'relation-picker',
});
const internallyStoredFilter = useRecoilValue(
relationPickerScopedStatesHook.relationPickerSearchFilterState,
);
return { entitySelectSearchHook, internallyStoredFilter };
},
{
wrapper: Wrapper,
},
);
const filter = 'value';
act(() => {
result.current.entitySelectSearchHook.handleSearchFilterChange({
currentTarget: { value: filter },
} as ChangeEvent<HTMLInputElement>);
});
expect(result.current.internallyStoredFilter).toBe(filter);
});
});

View File

@ -3,13 +3,13 @@ import { RecoilRoot } from 'recoil';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
const scopeId = 'scopeId'; const instanceId = 'instanceId';
const Wrapper = ({ children }: { children: React.ReactNode }) => ( const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RelationPickerScopeInternalContext.Provider value={{ scopeId }}> <RecordPickerComponentInstanceContext.Provider value={{ instanceId }}>
<RecoilRoot>{children}</RecoilRoot> <RecoilRoot>{children}</RecoilRoot>
</RelationPickerScopeInternalContext.Provider> </RecordPickerComponentInstanceContext.Provider>
); );
describe('useLimitPerMetadataItem', () => { describe('useLimitPerMetadataItem', () => {

View File

@ -3,14 +3,14 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const scopeId = 'scopeId'; const instanceId = 'instanceId';
const Wrapper = ({ children }: { children: React.ReactNode }) => ( const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RelationPickerScopeInternalContext.Provider value={{ scopeId }}> <RecordPickerComponentInstanceContext.Provider value={{ instanceId }}>
<RecoilRoot>{children}</RecoilRoot> <RecoilRoot>{children}</RecoilRoot>
</RelationPickerScopeInternalContext.Provider> </RecordPickerComponentInstanceContext.Provider>
); );
const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6'; const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6';

View File

@ -3,14 +3,14 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap'; import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const scopeId = 'scopeId'; const instanceId = 'instanceId';
const Wrapper = ({ children }: { children: React.ReactNode }) => ( const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RelationPickerScopeInternalContext.Provider value={{ scopeId }}> <RecordPickerComponentInstanceContext.Provider value={{ instanceId }}>
<RecoilRoot>{children}</RecoilRoot> <RecoilRoot>{children}</RecoilRoot>
</RelationPickerScopeInternalContext.Provider> </RecordPickerComponentInstanceContext.Provider>
); );
const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6'; const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6';

View File

@ -0,0 +1,45 @@
import { act, renderHook } from '@testing-library/react';
import { ChangeEvent } from 'react';
import { RecoilRoot } from 'recoil';
import { useRecordSelectSearch } from '@/object-record/relation-picker/hooks/useRecordSelectSearch';
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
const instanceId = 'instanceId';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecordPickerComponentInstanceContext.Provider value={{ instanceId }}>
<RecoilRoot>{children}</RecoilRoot>
</RecordPickerComponentInstanceContext.Provider>
);
describe('useRecordSelectSearch', () => {
it('should update searchFilter after change event', async () => {
const { result } = renderHook(
() => {
const recordSelectSearchHook = useRecordSelectSearch({
recordPickerInstanceId: instanceId,
});
const internallyStoredFilter = useRecoilComponentValueV2(
recordPickerSearchFilterComponentState,
instanceId,
);
return { recordSelectSearchHook, internallyStoredFilter };
},
{
wrapper: Wrapper,
},
);
const filter = 'value';
act(() => {
result.current.recordSelectSearchHook.handleSearchFilterChange({
currentTarget: { value: filter },
} as ChangeEvent<HTMLInputElement>);
});
expect(result.current.internallyStoredFilter).toBe(filter);
});
});

View File

@ -1,29 +0,0 @@
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { getRelationPickerScopedStates } from '@/object-record/relation-picker/utils/getRelationPickerScopedStates';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
export const useRelationPickerScopedStates = (args?: {
relationPickerScopedId?: string;
}) => {
const { relationPickerScopedId } = args ?? {};
const scopeId = useAvailableComponentInstanceIdOrThrow(
RecordTableComponentInstanceContext,
relationPickerScopedId,
);
const {
relationPickerSearchFilterState,
relationPickerPreselectedIdState,
searchQueryState,
} = getRelationPickerScopedStates({
relationPickerScopeId: scopeId,
});
return {
scopeId,
relationPickerSearchFilterState,
relationPickerPreselectedIdState,
searchQueryState,
};
};

View File

@ -0,0 +1,24 @@
import { recordPickerPreselectedIdComponentState } from '@/object-record/relation-picker/states/recordPickerPreselectedIdComponentState';
import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
export const useRecordPicker = ({
recordPickerInstanceId,
}: {
recordPickerInstanceId?: string;
}) => {
const setRecordPickerSearchFilter = useSetRecoilComponentStateV2(
recordPickerSearchFilterComponentState,
recordPickerInstanceId,
);
const setRecordPickerPreselectedId = useSetRecoilComponentStateV2(
recordPickerPreselectedIdComponentState,
recordPickerInstanceId,
);
return {
setRecordPickerSearchFilter,
setRecordPickerPreselectedId,
};
};

View File

@ -0,0 +1,26 @@
import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
import { useFilteredSearchRecordQuery } from '@/search/hooks/useFilteredSearchRecordQuery';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const useRecordPickerRecordsOptions = ({
objectNameSingular,
selectedRecordIds = [],
excludedRecordIds = [],
}: {
objectNameSingular: string;
selectedRecordIds?: string[];
excludedRecordIds?: string[];
}) => {
const recordPickerSearchFilter = useRecoilComponentValueV2(
recordPickerSearchFilterComponentState,
);
const records = useFilteredSearchRecordQuery({
searchFilter: recordPickerSearchFilter,
selectedIds: selectedRecordIds,
excludedRecordIds: excludedRecordIds,
objectNameSingular,
});
return { records, recordPickerSearchFilter };
};

View File

@ -1,17 +1,17 @@
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; import { useRecordPicker } from '@/object-record/relation-picker/hooks/useRecordPicker';
export const useEntitySelectSearch = ({ export const useRecordSelectSearch = ({
relationPickerScopeId, recordPickerInstanceId,
}: { }: {
relationPickerScopeId?: string; recordPickerInstanceId?: string;
} = {}) => { } = {}) => {
const { setRelationPickerSearchFilter, setRelationPickerPreselectedId } = const { setRecordPickerSearchFilter, setRecordPickerPreselectedId } =
useRelationPicker({ relationPickerScopeId }); useRecordPicker({ recordPickerInstanceId });
const debouncedSetSearchFilter = useDebouncedCallback( const debouncedSetSearchFilter = useDebouncedCallback(
setRelationPickerSearchFilter, setRecordPickerSearchFilter,
100, 100,
{ {
leading: true, leading: true,
@ -20,14 +20,14 @@ export const useEntitySelectSearch = ({
const resetSearchFilter = () => { const resetSearchFilter = () => {
debouncedSetSearchFilter(''); debouncedSetSearchFilter('');
setRelationPickerPreselectedId(''); setRecordPickerPreselectedId('');
}; };
const handleSearchFilterChange = ( const handleSearchFilterChange = (
event: React.ChangeEvent<HTMLInputElement>, event: React.ChangeEvent<HTMLInputElement>,
) => { ) => {
debouncedSetSearchFilter(event.currentTarget.value); debouncedSetSearchFilter(event.currentTarget.value);
setRelationPickerPreselectedId(''); setRecordPickerPreselectedId('');
}; };
return { return {

View File

@ -1,34 +0,0 @@
import { useSetRecoilState } from 'recoil';
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
type useRelationPickeProps = {
relationPickerScopeId?: string;
};
export const useRelationPicker = (props?: useRelationPickeProps) => {
const scopeId = useAvailableScopeIdOrThrow(
RelationPickerScopeInternalContext,
props?.relationPickerScopeId,
);
const { relationPickerSearchFilterState, relationPickerPreselectedIdState } =
useRelationPickerScopedStates({
relationPickerScopedId: scopeId,
});
const setRelationPickerSearchFilter = useSetRecoilState(
relationPickerSearchFilterState,
);
const setRelationPickerPreselectedId = useSetRecoilState(
relationPickerPreselectedIdState,
);
return {
setRelationPickerSearchFilter,
setRelationPickerPreselectedId,
};
};

View File

@ -1,36 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
export const useRelationPickerEntitiesOptions = ({
relationObjectNameSingular,
selectedRelationRecordIds = [],
excludedRelationRecordIds = [],
}: {
relationObjectNameSingular: string;
selectedRelationRecordIds?: string[];
excludedRelationRecordIds?: string[];
}) => {
const scopeId = useAvailableScopeIdOrThrow(
RelationPickerScopeInternalContext,
);
const { relationPickerSearchFilterState } = useRelationPickerScopedStates({
relationPickerScopedId: scopeId,
});
const relationPickerSearchFilter = useRecoilValue(
relationPickerSearchFilterState,
);
const entities = useFilteredSearchEntityQuery({
searchFilter: relationPickerSearchFilter,
selectedIds: selectedRelationRecordIds,
excludeRecordIds: excludedRelationRecordIds,
objectNameSingular: relationObjectNameSingular,
});
return { entities, relationPickerSearchFilter };
};

View File

@ -1,21 +0,0 @@
import { ReactNode } from 'react';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
type RelationPickerScopeProps = {
children: ReactNode;
relationPickerScopeId: string;
};
export const RelationPickerScope = ({
children,
relationPickerScopeId,
}: RelationPickerScopeProps) => {
return (
<RelationPickerScopeInternalContext.Provider
value={{ scopeId: relationPickerScopeId }}
>
{children}
</RelationPickerScopeInternalContext.Provider>
);
};

View File

@ -1,7 +0,0 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type RelationPickerScopeInternalContextProps = RecoilComponentStateKey;
export const RelationPickerScopeInternalContext =
createScopeInternalContext<RelationPickerScopeInternalContextProps>();

View File

@ -0,0 +1,4 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const RecordPickerComponentInstanceContext =
createComponentInstanceContext();

View File

@ -1,5 +0,0 @@
import { createContext } from 'react';
export const RelationPickerRecoilScopeContext = createContext<string | null>(
'relation-picker-context',
);

View File

@ -0,0 +1,10 @@
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const recordPickerPreselectedIdComponentState = createComponentStateV2<
string | undefined
>({
key: 'recordPickerPreselectedIdComponentState',
defaultValue: undefined,
componentInstanceContext: RecordPickerComponentInstanceContext,
});

View File

@ -0,0 +1,9 @@
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const recordPickerSearchFilterComponentState =
createComponentStateV2<string>({
key: 'recordPickerSearchFilterComponentState',
defaultValue: '',
componentInstanceContext: RecordPickerComponentInstanceContext,
});

View File

@ -1,8 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const relationPickerPreselectedIdScopedState = createComponentState<
string | undefined
>({
key: 'relationPickerPreselectedIdScopedState',
defaultValue: undefined,
});

View File

@ -1,7 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const relationPickerSearchFilterScopedState =
createComponentState<string>({
key: 'relationPickerSearchFilterScopedState',
defaultValue: '',
});

View File

@ -0,0 +1,10 @@
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { SearchQuery } from '@/object-record/relation-picker/types/SearchQuery';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const searchQueryComponentState =
createComponentStateV2<SearchQuery | null>({
key: 'searchQueryComponentState',
defaultValue: null,
componentInstanceContext: RecordPickerComponentInstanceContext,
});

View File

@ -1,7 +0,0 @@
import { SearchQuery } from '@/object-record/relation-picker/types/SearchQuery';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const searchQueryScopedState = createComponentState<SearchQuery | null>({
key: 'searchQueryScopedState',
defaultValue: null,
});

View File

@ -1,10 +0,0 @@
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
export type EntitiesForMultipleEntitySelect<
CustomEntityForSelect extends EntityForSelect,
> = {
selectedEntities: CustomEntityForSelect[];
filteredSelectedEntities: CustomEntityForSelect[];
entitiesToSelect: CustomEntityForSelect[];
loading: boolean;
};

View File

@ -1,6 +1,6 @@
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
export type EntityForSelect = ObjectRecordIdentifier & { export type RecordForSelect = ObjectRecordIdentifier & {
record: ObjectRecord; record: ObjectRecord;
}; };

View File

@ -0,0 +1,10 @@
import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
export type RecordsForMultipleRecordSelect<
CustomRecordForSelect extends RecordForSelect,
> = {
selectedRecords: CustomRecordForSelect[];
filteredSelectedRecords: CustomRecordForSelect[];
recordsToSelect: CustomRecordForSelect[];
loading: boolean;
};

View File

@ -1,31 +0,0 @@
import { relationPickerPreselectedIdScopedState } from '@/object-record/relation-picker/states/relationPickerPreselectedIdScopedState';
import { relationPickerSearchFilterScopedState } from '@/object-record/relation-picker/states/relationPickerSearchFilterScopedState';
import { searchQueryScopedState } from '@/object-record/relation-picker/states/searchQueryScopedState';
import { getScopedStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedStateDeprecated';
export const getRelationPickerScopedStates = ({
relationPickerScopeId,
}: {
relationPickerScopeId: string;
}) => {
const searchQueryState = getScopedStateDeprecated(
searchQueryScopedState,
relationPickerScopeId,
);
const relationPickerPreselectedIdState = getScopedStateDeprecated(
relationPickerPreselectedIdScopedState,
relationPickerScopeId,
);
const relationPickerSearchFilterState = getScopedStateDeprecated(
relationPickerSearchFilterScopedState,
relationPickerScopeId,
);
return {
relationPickerSearchFilterState,
relationPickerPreselectedIdState,
searchQueryState,
};
};

View File

@ -16,14 +16,14 @@ export const useRecordsForSelect = ({
sortOrder = 'AscNullsLast', sortOrder = 'AscNullsLast',
selectedIds, selectedIds,
limit, limit,
excludeRecordIds = [], excludedRecordIds = [],
objectNameSingular, objectNameSingular,
}: { }: {
searchFilterText: string; searchFilterText: string;
sortOrder?: OrderBy; sortOrder?: OrderBy;
selectedIds: string[]; selectedIds: string[];
limit?: number; limit?: number;
excludeRecordIds?: string[]; excludedRecordIds?: string[];
objectNameSingular: string; objectNameSingular: string;
}) => { }) => {
const { mapToObjectRecordIdentifier } = useMapToObjectRecordIdentifier({ const { mapToObjectRecordIdentifier } = useMapToObjectRecordIdentifier({
@ -91,7 +91,7 @@ export const useRecordsForSelect = ({
skip: !selectedIds.length, skip: !selectedIds.length,
}); });
const notFilterIds = [...selectedIds, ...excludeRecordIds]; const notFilterIds = [...selectedIds, ...excludedRecordIds];
const notFilter = notFilterIds.length const notFilter = notFilterIds.length
? { not: { id: { in: notFilterIds } } } ? { not: { id: { in: notFilterIds } } }
: undefined; : undefined;

View File

@ -47,13 +47,13 @@ export const getRecordChipGenerators = (
labelIdentifierFieldMetadataItem?.id === fieldMetadataItem.id; labelIdentifierFieldMetadataItem?.id === fieldMetadataItem.id;
const currentObjectNameSingular = objectMetadataItem.nameSingular; const currentObjectNameSingular = objectMetadataItem.nameSingular;
const fieldRelationObjectNameSingular = const fieldObjectNameSingular =
fieldMetadataItem.relationDefinition?.targetObjectMetadata fieldMetadataItem.relationDefinition?.targetObjectMetadata
.nameSingular ?? undefined; .nameSingular ?? undefined;
const objectNameSingularToFind = isLabelIdentifier const objectNameSingularToFind = isLabelIdentifier
? currentObjectNameSingular ? currentObjectNameSingular
: fieldRelationObjectNameSingular; : fieldObjectNameSingular;
const objectMetadataItemToUse = objectMetadataItems.find( const objectMetadataItemToUse = objectMetadataItems.find(
(objectMetadataItem) => (objectMetadataItem) =>

View File

@ -144,7 +144,7 @@ export const query = gql`
`; `;
export const variables = { export const variables = {
entitiesToSelect: { recordsToSelect: {
limit: 10, limit: 10,
filter: { filter: {
and: [ and: [
@ -154,7 +154,7 @@ export const variables = {
}, },
orderBy: [{ name: 'AscNullsLast' }], orderBy: [{ name: 'AscNullsLast' }],
}, },
filteredSelectedEntities: { filteredSelectedRecords: {
limit: 60, limit: 60,
filter: { filter: {
and: [ and: [

View File

@ -5,7 +5,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect'; import { RecordsForMultipleRecordSelect } from '@/object-record/relation-picker/types/RecordsForMultipleRecordSelect';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
@ -13,14 +13,14 @@ import {
query, query,
responseData, responseData,
variables, variables,
} from '../__mocks__/useFilteredSearchEntityQuery'; } from '../__mocks__/useFilteredSearchRecordQuery';
import { useFilteredSearchEntityQuery } from '../useFilteredSearchEntityQuery'; import { useFilteredSearchRecordQuery } from '../useFilteredSearchRecordQuery';
const mocks = [ const mocks = [
{ {
request: { request: {
query, query,
variables: variables.entitiesToSelect, variables: variables.recordsToSelect,
}, },
result: jest.fn(() => ({ result: jest.fn(() => ({
data: { data: {
@ -31,7 +31,7 @@ const mocks = [
{ {
request: { request: {
query, query,
variables: variables.filteredSelectedEntities, variables: variables.filteredSelectedRecords,
}, },
result: jest.fn(() => ({ result: jest.fn(() => ({
data: { data: {
@ -62,7 +62,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
</RecoilRoot> </RecoilRoot>
); );
describe('useFilteredSearchEntityQuery', () => { describe('useFilteredSearchRecordQuery', () => {
it('returns the correct result when everything is provided', async () => { it('returns the correct result when everything is provided', async () => {
const { result } = renderHook( const { result } = renderHook(
() => { () => {
@ -79,10 +79,10 @@ describe('useFilteredSearchEntityQuery', () => {
setMetadataItems(generatedMockObjectMetadataItems); setMetadataItems(generatedMockObjectMetadataItems);
return useFilteredSearchEntityQuery({ return useFilteredSearchRecordQuery({
selectedIds: ['1'], selectedIds: ['1'],
limit: 10, limit: 10,
excludeRecordIds: ['2'], excludedRecordIds: ['2'],
objectNameSingular: 'person', objectNameSingular: 'person',
searchFilter: 'Entity', searchFilter: 'Entity',
}); });
@ -90,10 +90,10 @@ describe('useFilteredSearchEntityQuery', () => {
{ wrapper: Wrapper }, { wrapper: Wrapper },
); );
const expectedResult: EntitiesForMultipleEntitySelect<any> = { const expectedResult: RecordsForMultipleRecordSelect<any> = {
selectedEntities: [], selectedRecords: [],
filteredSelectedEntities: [], filteredSelectedRecords: [],
entitiesToSelect: [], recordsToSelect: [],
loading: true, loading: true,
}; };

View File

@ -1,27 +1,27 @@
import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier'; import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier';
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit'; import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
import { useSearchRecords } from '@/object-record/hooks/useSearchRecords'; import { useSearchRecords } from '@/object-record/hooks/useSearchRecords';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RecordsForMultipleRecordSelect } from '@/object-record/relation-picker/types/RecordsForMultipleRecordSelect';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
// TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search // TODO: use this for all search queries, because we need selectedRecords and recordsToSelect each time we want to search
// Filtered entities to select are // Filtered entities to select are
export const useFilteredSearchEntityQuery = ({ export const useFilteredSearchRecordQuery = ({
selectedIds, selectedIds,
limit, limit,
excludeRecordIds = [], excludedRecordIds = [],
objectNameSingular, objectNameSingular,
searchFilter, searchFilter,
}: { }: {
selectedIds: string[]; selectedIds: string[];
limit?: number; limit?: number;
excludeRecordIds?: string[]; excludedRecordIds?: string[];
objectNameSingular: string; objectNameSingular: string;
searchFilter?: string; searchFilter?: string;
}): EntitiesForMultipleEntitySelect<EntityForSelect> => { }): RecordsForMultipleRecordSelect<RecordForSelect> => {
const { mapToObjectRecordIdentifier } = useMapToObjectRecordIdentifier({ const { mapToObjectRecordIdentifier } = useMapToObjectRecordIdentifier({
objectNameSingular, objectNameSingular,
}); });
@ -50,7 +50,7 @@ export const useFilteredSearchEntityQuery = ({
searchInput: searchFilter, searchInput: searchFilter,
}); });
const notFilterIds = [...selectedIds, ...excludeRecordIds]; const notFilterIds = [...selectedIds, ...excludedRecordIds];
const notFilter = notFilterIds.length const notFilter = notFilterIds.length
? { not: { id: { in: notFilterIds } } } ? { not: { id: { in: notFilterIds } } }
: undefined; : undefined;
@ -63,11 +63,11 @@ export const useFilteredSearchEntityQuery = ({
}); });
return { return {
selectedEntities: selectedRecords.map(mappingFunction).filter(isDefined), selectedRecords: selectedRecords.map(mappingFunction).filter(isDefined),
filteredSelectedEntities: filteredSelectedRecords filteredSelectedRecords: filteredSelectedRecords
.map(mappingFunction) .map(mappingFunction)
.filter(isDefined), .filter(isDefined),
entitiesToSelect: recordsToSelect.map(mappingFunction).filter(isDefined), recordsToSelect: recordsToSelect.map(mappingFunction).filter(isDefined),
loading: loading:
recordsToSelectLoading || recordsToSelectLoading ||
filteredSelectedRecordsLoading || filteredSelectedRecordsLoading ||

View File

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

View File

@ -1,9 +0,0 @@
import { Decorator } from '@storybook/react';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
export const RelationPickerDecorator: Decorator = (Story) => (
<RelationPickerScope relationPickerScopeId="relation-picker">
<Story />
</RelationPickerScope>
);