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:
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 =
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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'}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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>
|
||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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 = {
|
||||||
@ -1 +0,0 @@
|
|||||||
export const SINGLE_ENTITY_SELECT_BASE_LIST = 'single-entity-select-base-list';
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export const SINGLE_RECORD_SELECT_BASE_LIST = 'single-record-select-base-list';
|
||||||
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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', () => {
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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 };
|
||||||
|
};
|
||||||
@ -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 {
|
||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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 };
|
|
||||||
};
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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>();
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||||
|
|
||||||
|
export const RecordPickerComponentInstanceContext =
|
||||||
|
createComponentInstanceContext();
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export const RelationPickerRecoilScopeContext = createContext<string | null>(
|
|
||||||
'relation-picker-context',
|
|
||||||
);
|
|
||||||
@ -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,
|
||||||
|
});
|
||||||
@ -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,
|
||||||
|
});
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
|
||||||
|
|
||||||
export const relationPickerPreselectedIdScopedState = createComponentState<
|
|
||||||
string | undefined
|
|
||||||
>({
|
|
||||||
key: 'relationPickerPreselectedIdScopedState',
|
|
||||||
defaultValue: undefined,
|
|
||||||
});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
|
||||||
|
|
||||||
export const relationPickerSearchFilterScopedState =
|
|
||||||
createComponentState<string>({
|
|
||||||
key: 'relationPickerSearchFilterScopedState',
|
|
||||||
defaultValue: '',
|
|
||||||
});
|
|
||||||
@ -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,
|
||||||
|
});
|
||||||
@ -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,
|
|
||||||
});
|
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
@ -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;
|
||||||
};
|
};
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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) =>
|
||||||
|
|||||||
@ -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: [
|
||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -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 ||
|
||||||
@ -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>
|
||||||
|
);
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
Reference in New Issue
Block a user