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, }; };