diff --git a/packages/twenty-front/src/modules/companies/components/NewOpportunityButton.tsx b/packages/twenty-front/src/modules/companies/components/NewOpportunityButton.tsx
index 13597fa8a..8a9596e91 100644
--- a/packages/twenty-front/src/modules/companies/components/NewOpportunityButton.tsx
+++ b/packages/twenty-front/src/modules/companies/components/NewOpportunityButton.tsx
@@ -1,7 +1,5 @@
import { useCallback, useContext, useState } from 'react';
-import { useQuery } from '@apollo/client';
-import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { NewButton } from '@/object-record/record-board/components/NewButton';
import { BoardColumnContext } from '@/object-record/record-board/contexts/BoardColumnContext';
@@ -54,18 +52,10 @@ export const NewOpportunityButton = () => {
setIsCreatingCard(false);
};
- const { relationPickerSearchFilter } = useRelationPicker();
-
- // TODO: refactor useFilteredSearchEntityQuery
- const { findManyRecordsQuery } = useObjectMetadataItem({
- objectNameSingular: CoreObjectNameSingular.Company,
- });
- const useFindManyQuery = (options: any) =>
- useQuery(findManyRecordsQuery, options);
- const { identifiersMapper, searchQuery } = useRelationPicker();
+ const { relationPickerSearchFilter, identifiersMapper, searchQuery } =
+ useRelationPicker();
const filteredSearchEntityResults = useFilteredSearchEntityQuery({
- queryHook: useFindManyQuery,
filters: [
{
fieldNames: searchQuery?.computeFilterFields?.('company') ?? [],
diff --git a/packages/twenty-front/src/modules/companies/components/OpportunityPicker.tsx b/packages/twenty-front/src/modules/companies/components/OpportunityPicker.tsx
index a5fc2908a..2d121f643 100644
--- a/packages/twenty-front/src/modules/companies/components/OpportunityPicker.tsx
+++ b/packages/twenty-front/src/modules/companies/components/OpportunityPicker.tsx
@@ -1,10 +1,8 @@
import { useEffect, useMemo, useRef, useState } from 'react';
-import { useQuery } from '@apollo/client';
import { useRecoilValue } from 'recoil';
-import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
-import { SingleEntitySelectBase } from '@/object-record/relation-picker/components/SingleEntitySelectBase';
+import { SingleEntitySelectMenuItems } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
@@ -36,16 +34,9 @@ export const OpportunityPicker = ({
const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
- // TODO: refactor useFilteredSearchEntityQuery
- const { findManyRecordsQuery: findManyCompanies } = useObjectMetadataItem({
- objectNameSingular: CoreObjectNameSingular.Company,
- });
- const useFindManyQuery = (options: any) =>
- useQuery(findManyCompanies, options);
const { identifiersMapper, searchQuery } = useRelationPicker();
const filteredSearchEntityResults = useFilteredSearchEntityQuery({
- queryHook: useFindManyQuery,
filters: [
{
fieldNames: searchQuery?.computeFilterFields?.('company') ?? [],
@@ -127,7 +118,7 @@ export const OpportunityPicker = ({
/>
- {
where: { id: entityId },
updateOneRecordInput: {
[`${fieldName}Id`]: valueToPersist?.id ?? null,
- [`${fieldName}`]: valueToPersist ?? null,
+ [fieldName]: valueToPersist ?? null,
},
},
});
diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpsertRecordFieldFromState.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpsertRecordFieldFromState.ts
new file mode 100644
index 000000000..eb460d0ea
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/hooks/useUpsertRecordFieldFromState.ts
@@ -0,0 +1,24 @@
+import { useRecoilCallback } from 'recoil';
+
+import { entityFieldsFamilySelector } from '@/object-record/field/states/selectors/entityFieldsFamilySelector';
+import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
+
+export const useUpsertRecordFieldFromState = () =>
+ useRecoilCallback(
+ ({ set }) =>
+ ({
+ record,
+ fieldName,
+ }: {
+ record: T;
+ fieldName: F extends string ? F : never;
+ }) =>
+ set(
+ entityFieldsFamilySelector({ entityId: record.id, fieldName }),
+ (previousField) =>
+ isDeeplyEqual(previousField, record[fieldName])
+ ? previousField
+ : record[fieldName],
+ ),
+ [],
+ );
diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpsertRecordFromState.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpsertRecordFromState.ts
index 27ec737ce..38aee3205 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/useUpsertRecordFromState.ts
+++ b/packages/twenty-front/src/modules/object-record/hooks/useUpsertRecordFromState.ts
@@ -6,15 +6,10 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
// TODO: refactor with scoped state later
export const useUpsertRecordFromState = () =>
useRecoilCallback(
- ({ set, snapshot }) =>
- (entity: T) => {
- const currentEntity = snapshot
- .getLoadable(entityFieldsFamilyState(entity.id))
- .valueOrThrow();
-
- if (!isDeeplyEqual(currentEntity, entity)) {
- set(entityFieldsFamilyState(entity.id), entity);
- }
- },
+ ({ set }) =>
+ (record: T) =>
+ set(entityFieldsFamilyState(record.id), (previousRecord) =>
+ isDeeplyEqual(previousRecord, record) ? previousRecord : record,
+ ),
[],
);
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect.tsx
index 6b69caa98..1b5a6bb7b 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect.tsx
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect';
-import { SingleEntitySelectBase } from '@/object-record/relation-picker/components/SingleEntitySelectBase';
+import { SingleEntitySelectMenuItems } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
@@ -99,18 +99,16 @@ export const ObjectFilterDropdownEntitySearchSelect = ({
]);
return (
- <>
-
- >
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx b/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx
index 481c59ff3..87392588a 100644
--- a/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx
@@ -1,31 +1,73 @@
-import { useContext, useEffect, useMemo } from 'react';
+import { useCallback, useContext, useEffect, useMemo } from 'react';
+import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
+import { usePersistField } from '@/object-record/field/hooks/usePersistField';
+import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { entityFieldsFamilySelector } from '@/object-record/field/states/selectors/entityFieldsFamilySelector';
import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
+import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { useUpsertRecordFromState } from '@/object-record/hooks/useUpsertRecordFromState';
import { RecordRelationFieldCardContent } from '@/object-record/record-relation-card/components/RecordRelationFieldCardContent';
+import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
+import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
+import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
+import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
+import { IconForbid, IconPlus } from '@/ui/display/icon';
+import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Card } from '@/ui/layout/card/components/Card';
+import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
+import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
+import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { Section } from '@/ui/layout/section/components/Section';
+const StyledAddDropdown = styled(Dropdown)`
+ margin-left: auto;
+`;
+
+const StyledHeader = styled.header<{ isDropdownOpen?: boolean }>`
+ align-items: center;
+ display: flex;
+ margin-bottom: ${({ theme }) => theme.spacing(2)};
+
+ ${({ isDropdownOpen, theme }) =>
+ isDropdownOpen
+ ? ''
+ : css`
+ .displayOnHover {
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity ${theme.animation.duration.instant}s ease;
+ }
+ `}
+
+ &:hover {
+ .displayOnHover {
+ opacity: 1;
+ pointer-events: auto;
+ }
+ }
+`;
+
const StyledTitle = styled.div`
font-weight: ${({ theme }) => theme.font.weight.medium};
- margin-bottom: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(0, 1)};
`;
export const RecordRelationFieldCardSection = () => {
const { entityId, fieldDefinition } = useContext(FieldContext);
const {
+ fieldName,
relationFieldMetadataId,
relationObjectMetadataNameSingular,
relationType,
} = fieldDefinition.metadata as FieldRelationMetadata;
+ const record = useRecoilValue(entityFieldsFamilyState(entityId));
const {
labelIdentifierFieldMetadata: relationLabelIdentifierFieldMetadata,
@@ -40,16 +82,11 @@ export const RecordRelationFieldCardSection = () => {
const fieldValue = useRecoilValue<
({ id: string } & Record) | null
- >(
- entityFieldsFamilySelector({
- entityId,
- fieldName: fieldDefinition.metadata.fieldName,
- }),
- );
+ >(entityFieldsFamilySelector({ entityId, fieldName }));
const isToOneObject = relationType === 'TO_ONE_OBJECT';
- const { record: recordFromFieldValue } = useFindOneRecord({
+ const { record: relationRecordFromFieldValue } = useFindOneRecord({
objectNameSingular: relationObjectMetadataNameSingular,
objectRecordId: fieldValue?.id,
skip: !relationLabelIdentifierFieldMetadata || !isToOneObject,
@@ -58,9 +95,8 @@ export const RecordRelationFieldCardSection = () => {
// ONE_TO_MANY records cannot be retrieved from the field value,
// as the record's field is an empty "Connection" object.
// TODO: maybe the backend could return an array of related records instead?
- const { records } = useFindManyRecords({
+ const { records: relationRecordsFromQuery } = useFindManyRecords({
objectNameSingular: relationObjectMetadataNameSingular,
- limit: 5,
filter: {
// TODO: this won't work for MANY_TO_MANY relations.
[`${relationFieldMetadataItem?.name}Id`]: {
@@ -74,28 +110,120 @@ export const RecordRelationFieldCardSection = () => {
});
const relationRecords = useMemo(
- () => (recordFromFieldValue ? [recordFromFieldValue] : records),
- [recordFromFieldValue, records],
+ () =>
+ relationRecordFromFieldValue
+ ? [relationRecordFromFieldValue]
+ : relationRecordsFromQuery,
+ [relationRecordFromFieldValue, relationRecordsFromQuery],
+ );
+ const relationRecordIds = useMemo(
+ () => relationRecords.map(({ id }) => id),
+ [relationRecords],
);
const upsertRecordFromState = useUpsertRecordFromState();
useEffect(() => {
- if (!relationRecords.length) return;
-
relationRecords.forEach((relationRecord) =>
upsertRecordFromState(relationRecord),
);
}, [relationRecords, upsertRecordFromState]);
+ const dropdownScopeId = `record-field-card-relation-picker-${fieldDefinition.label}`;
+
+ const { closeDropdown, isDropdownOpen } = useDropdown(dropdownScopeId);
+
+ const {
+ identifiersMapper,
+ relationPickerSearchFilter,
+ searchQuery,
+ setRelationPickerSearchFilter,
+ } = useRelationPicker();
+
+ const entities = useFilteredSearchEntityQuery({
+ filters: [
+ {
+ fieldNames:
+ searchQuery?.computeFilterFields?.(
+ relationObjectMetadataNameSingular,
+ ) ?? [],
+ filter: relationPickerSearchFilter,
+ },
+ ],
+ orderByField: 'createdAt',
+ mappingFunction: (recordToMap: any) =>
+ identifiersMapper?.(recordToMap, relationObjectMetadataNameSingular),
+ selectedIds: relationRecordIds,
+ excludeEntityIds: relationRecordIds,
+ objectNameSingular: relationObjectMetadataNameSingular,
+ });
+
+ const handleCloseRelationPickerDropdown = useCallback(() => {
+ setRelationPickerSearchFilter('');
+ }, [setRelationPickerSearchFilter]);
+
+ const persistField = usePersistField();
+ const { updateOneRecord } = useUpdateOneRecord({
+ objectNameSingular: relationObjectMetadataNameSingular,
+ });
+
+ const handleRelationPickerEntitySelected = (
+ selectedRelationEntity?: EntityForSelect,
+ ) => {
+ closeDropdown();
+
+ if (!selectedRelationEntity?.id) return;
+
+ if (isToOneObject) {
+ persistField(selectedRelationEntity.record);
+ return;
+ }
+
+ if (!relationFieldMetadataItem?.name) return;
+
+ updateOneRecord({
+ idToUpdate: selectedRelationEntity.id,
+ updateOneRecordInput: {
+ [`${relationFieldMetadataItem.name}Id`]: entityId,
+ [relationFieldMetadataItem.name]: record,
+ },
+ });
+ };
+
if (!relationLabelIdentifierFieldMetadata) return null;
return (
- {fieldDefinition.label}
+
+ {fieldDefinition.label}
+
+
+ }
+ dropdownComponents={
+
+ }
+ dropdownHotkeyScope={{
+ scope: dropdownScopeId,
+ }}
+ />
+
+
{!!relationRecords.length && (
- {relationRecords.map((relationRecord, index) => (
+ {relationRecords.slice(0, 5).map((relationRecord, index) => (
void;
+ onSubmit: (selectedEntity: EntityForSelect | null) => void;
onCancel?: () => void;
width?: number;
excludeRecordIds?: string[];
@@ -30,32 +28,24 @@ export const RelationPicker = ({
initialSearchFilter,
fieldDefinition,
}: RelationPickerProps) => {
- const { relationPickerSearchFilter, setRelationPickerSearchFilter } =
- useRelationPicker();
+ const {
+ relationPickerSearchFilter,
+ setRelationPickerSearchFilter,
+ identifiersMapper,
+ searchQuery,
+ } = useRelationPicker();
useEffect(() => {
setRelationPickerSearchFilter(initialSearchFilter ?? '');
}, [initialSearchFilter, setRelationPickerSearchFilter]);
- // TODO: refactor useFilteredSearchEntityQuery
- const { findManyRecordsQuery } = useObjectMetadataItem({
- objectNameSingular:
- fieldDefinition.metadata.relationObjectMetadataNameSingular,
- });
-
- const useFindManyQuery = (options: any) =>
- useQuery(findManyRecordsQuery, options);
-
- const { identifiersMapper, searchQuery } = useRelationPicker();
-
const { objectNameSingular: relationObjectNameSingular } =
useObjectNameSingularFromPlural({
objectNamePlural:
fieldDefinition.metadata.relationObjectMetadataNamePlural,
});
- const records = useFilteredSearchEntityQuery({
- queryHook: useFindManyQuery,
+ const entities = useFilteredSearchEntityQuery({
filters: [
{
fieldNames:
@@ -76,19 +66,18 @@ export const RelationPicker = ({
objectNameSingular: relationObjectNameSingular,
});
- const handleEntitySelected = async (selectedUser: any | null | undefined) => {
- onSubmit(selectedUser ?? null);
- };
+ const handleEntitySelected = (selectedEntity: any | null | undefined) =>
+ onSubmit(selectedEntity ?? null);
return (
);
diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx
index 403044053..1b3bc1092 100644
--- a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx
+++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelect.tsx
@@ -1,32 +1,16 @@
import { useRef } from 'react';
-import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
-import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
-import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
-import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
-import { isDefined } from '~/utils/isDefined';
-
-import { useEntitySelectSearch } from '../hooks/useEntitySelectSearch';
-
import {
- SingleEntitySelectBase,
- SingleEntitySelectBaseProps,
-} from './SingleEntitySelectBase';
+ SingleEntitySelectMenuItemsWithSearch,
+ SingleEntitySelectMenuItemsWithSearchProps,
+} from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
+import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
+import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
export type SingleEntitySelectProps = {
disableBackgroundBlur?: boolean;
- onCreate?: () => void;
width?: number;
-} & Pick<
- SingleEntitySelectBaseProps,
- | 'EmptyIcon'
- | 'emptyLabel'
- | 'entitiesToSelect'
- | 'loading'
- | 'onCancel'
- | 'onEntitySelected'
- | 'selectedEntity'
->;
+} & SingleEntitySelectMenuItemsWithSearchProps;
export const SingleEntitySelect = ({
EmptyIcon,
@@ -42,10 +26,6 @@ export const SingleEntitySelect = ({
}: SingleEntitySelectProps) => {
const containerRef = useRef(null);
- const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
-
- const showCreateButton = isDefined(onCreate) && searchFilter !== '';
-
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
@@ -62,13 +42,7 @@ export const SingleEntitySelect = ({
width={width}
data-select-disable
>
-
-
-
diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectBase.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItems.tsx
similarity index 92%
rename from packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectBase.tsx
rename to packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItems.tsx
index f33134efc..88352c0b6 100644
--- a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectBase.tsx
+++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItems.tsx
@@ -5,6 +5,8 @@ import { Key } from 'ts-key-enum';
import { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect';
import { IconPlus } from '@/ui/display/icon';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
+import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
+import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@@ -13,12 +15,10 @@ import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSel
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { assertNotNull } from '~/utils/assert';
-import { CreateNewButton } from '../../../ui/input/relation-picker/components/CreateNewButton';
-import { DropdownMenuSkeletonItem } from '../../../ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { EntityForSelect } from '../types/EntityForSelect';
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
-export type SingleEntitySelectBaseProps = {
+export type SingleEntitySelectMenuItemsProps = {
EmptyIcon?: IconComponent;
emptyLabel?: string;
entitiesToSelect: EntityForSelect[];
@@ -35,7 +35,7 @@ export type SingleEntitySelectBaseProps = {
onAllEntitySelected?: () => void;
};
-export const SingleEntitySelectBase = ({
+export const SingleEntitySelectMenuItems = ({
EmptyIcon,
emptyLabel,
entitiesToSelect,
@@ -50,7 +50,7 @@ export const SingleEntitySelectBase = ({
isAllEntitySelected,
isAllEntitySelectShown,
onAllEntitySelected,
-}: SingleEntitySelectBaseProps) => {
+}: SingleEntitySelectMenuItemsProps) => {
const containerRef = useRef(null);
const entitiesInDropdown = [selectedEntity, ...entitiesToSelect].filter(
diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch.tsx
new file mode 100644
index 000000000..ddbb450f0
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch.tsx
@@ -0,0 +1,61 @@
+import {
+ SingleEntitySelectMenuItems,
+ SingleEntitySelectMenuItemsProps,
+} from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
+import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
+import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
+import { isDefined } from '~/utils/isDefined';
+
+import { useEntitySelectSearch } from '../hooks/useEntitySelectSearch';
+
+export type SingleEntitySelectMenuItemsWithSearchProps = {
+ onCreate?: () => void;
+} & Pick<
+ SingleEntitySelectMenuItemsProps,
+ | 'EmptyIcon'
+ | 'emptyLabel'
+ | 'entitiesToSelect'
+ | 'loading'
+ | 'onCancel'
+ | 'onEntitySelected'
+ | 'selectedEntity'
+>;
+
+export const SingleEntitySelectMenuItemsWithSearch = ({
+ EmptyIcon,
+ emptyLabel,
+ entitiesToSelect,
+ loading,
+ onCancel,
+ onCreate,
+ onEntitySelected,
+ selectedEntity,
+}: SingleEntitySelectMenuItemsWithSearchProps) => {
+ const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
+
+ const showCreateButton = isDefined(onCreate) && searchFilter !== '';
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts b/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts
index 8d900e747..e1095871e 100644
--- a/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts
+++ b/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts
@@ -1,11 +1,9 @@
-import { QueryHookOptions, QueryResult } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards';
-import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { OrderBy } from '@/object-metadata/types/OrderBy';
+import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
-import { mapPaginatedRecordsToRecords } from '@/object-record/utils/mapPaginatedRecordsToRecords';
import { assertNotNull } from '~/utils/assert';
import { isDefined } from '~/utils/isDefined';
@@ -16,9 +14,7 @@ export const DEFAULT_SEARCH_REQUEST_LIMIT = 60;
// TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search
// Filtered entities to select are
-// TODO: replace query hooks by useFindManyRecords
export const useFilteredSearchEntityQuery = ({
- queryHook,
orderByField,
filters,
sortOrder = 'AscNullsLast',
@@ -28,9 +24,6 @@ export const useFilteredSearchEntityQuery = ({
excludeEntityIds = [],
objectNameSingular,
}: {
- queryHook: (
- queryOptions?: QueryHookOptions,
- ) => QueryResult;
orderByField: string;
filters: SearchFilter[];
sortOrder?: OrderBy;
@@ -40,22 +33,11 @@ export const useFilteredSearchEntityQuery = ({
excludeEntityIds?: string[];
objectNameSingular: string;
}): EntitiesForMultipleEntitySelect => {
- const { objectMetadataItem } = useObjectMetadataItem({
- objectNameSingular,
- });
-
- const { loading: selectedEntitiesLoading, data: selectedEntitiesData } =
- queryHook({
- variables: {
- filter: {
- id: {
- in: selectedIds,
- },
- },
- orderBy: {
- [orderByField]: sortOrder,
- },
- } as any,
+ const { loading: selectedRecordsLoading, records: selectedRecords } =
+ useFindManyRecords({
+ objectNameSingular,
+ filter: { id: { in: selectedIds } },
+ orderBy: { [orderByField]: sortOrder },
});
const searchFilter = filters
@@ -90,74 +72,40 @@ export const useFilteredSearchEntityQuery = ({
.filter(isDefined);
const {
- loading: filteredSelectedEntitiesLoading,
- data: filteredSelectedEntitiesData,
- } = queryHook({
- variables: {
- filter: {
- and: [
- {
- and: searchFilter,
- },
- {
- id: {
- in: selectedIds,
- },
- },
- ],
- },
- orderBy: {
- [orderByField]: sortOrder,
- },
- } as any,
+ loading: filteredSelectedRecordsLoading,
+ records: filteredSelectedRecords,
+ } = useFindManyRecords({
+ objectNameSingular,
+ filter: { and: [{ and: searchFilter }, { id: { in: selectedIds } }] },
+ orderBy: { [orderByField]: sortOrder },
});
- const { loading: entitiesToSelectLoading, data: entitiesToSelectData } =
- queryHook({
- variables: {
- filter: {
- and: [
- {
- and: searchFilter,
- },
- {
- not: {
- id: {
- in: [...selectedIds, ...excludeEntityIds],
- },
- },
- },
- ],
- },
- limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT,
- orderBy: {
- [orderByField]: sortOrder,
- },
- } as any,
+ const { loading: recordsToSelectLoading, records: recordsToSelect } =
+ useFindManyRecords({
+ objectNameSingular,
+ filter: {
+ and: [
+ { and: searchFilter },
+ { not: { id: { in: [...selectedIds, ...excludeEntityIds] } } },
+ ],
+ },
+ limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT,
+ orderBy: { [orderByField]: sortOrder },
});
return {
- selectedEntities: mapPaginatedRecordsToRecords({
- objectNamePlural: objectMetadataItem.namePlural,
- pagedRecords: selectedEntitiesData,
- })
+ selectedEntities: selectedRecords
.map(mappingFunction)
.filter(assertNotNull),
- filteredSelectedEntities: mapPaginatedRecordsToRecords({
- objectNamePlural: objectMetadataItem.namePlural,
- pagedRecords: filteredSelectedEntitiesData,
- })
+ filteredSelectedEntities: filteredSelectedRecords
.map(mappingFunction)
.filter(assertNotNull),
- entitiesToSelect: mapPaginatedRecordsToRecords({
- objectNamePlural: objectMetadataItem.namePlural,
- pagedRecords: entitiesToSelectData,
- })
+ entitiesToSelect: recordsToSelect
.map(mappingFunction)
.filter(assertNotNull),
loading:
- entitiesToSelectLoading ||
- filteredSelectedEntitiesLoading ||
- selectedEntitiesLoading,
+ recordsToSelectLoading ||
+ filteredSelectedRecordsLoading ||
+ selectedRecordsLoading,
};
};