feat: add Relation Field Card plus button in Show Page (#3229)

Closes #3124
This commit is contained in:
Thaïs
2024-01-09 06:29:01 -03:00
committed by GitHub
parent dc94d26997
commit ed06cc0310
12 changed files with 306 additions and 209 deletions

View File

@ -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') ?? [],

View File

@ -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 = ({
/>
<DropdownMenuSeparator />
<RecoilScope>
<SingleEntitySelectBase
<SingleEntitySelectMenuItems
entitiesToSelect={filteredSearchEntityResults.entitiesToSelect}
loading={filteredSearchEntityResults.loading}
onCancel={onCancel}

View File

@ -95,7 +95,7 @@ export const usePersistField = () => {
where: { id: entityId },
updateOneRecordInput: {
[`${fieldName}Id`]: valueToPersist?.id ?? null,
[`${fieldName}`]: valueToPersist ?? null,
[fieldName]: valueToPersist ?? null,
},
},
});

View File

@ -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 }) =>
<T extends { id: string }, F extends keyof T>({
record,
fieldName,
}: {
record: T;
fieldName: F extends string ? F : never;
}) =>
set(
entityFieldsFamilySelector({ entityId: record.id, fieldName }),
(previousField) =>
isDeeplyEqual(previousField, record[fieldName])
? previousField
: record[fieldName],
),
[],
);

View File

@ -6,15 +6,10 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
// TODO: refactor with scoped state later
export const useUpsertRecordFromState = () =>
useRecoilCallback(
({ set, snapshot }) =>
<T extends { id: string }>(entity: T) => {
const currentEntity = snapshot
.getLoadable(entityFieldsFamilyState(entity.id))
.valueOrThrow();
if (!isDeeplyEqual(currentEntity, entity)) {
set(entityFieldsFamilyState(entity.id), entity);
}
},
({ set }) =>
<T extends { id: string }>(record: T) =>
set(entityFieldsFamilyState(record.id), (previousRecord) =>
isDeeplyEqual(previousRecord, record) ? previousRecord : record,
),
[],
);

View File

@ -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 (
<>
<SingleEntitySelectBase
entitiesToSelect={entitiesForSelect.entitiesToSelect}
selectedEntity={entitiesForSelect.selectedEntities[0]}
loading={entitiesForSelect.loading}
onEntitySelected={handleRecordSelected}
SelectAllIcon={filterDefinitionUsedInDropdown?.SelectAllIcon}
selectAllLabel={filterDefinitionUsedInDropdown?.selectAllLabel}
isAllEntitySelected={isAllEntitySelected}
isAllEntitySelectShown={isAllEntitySelectShown}
onAllEntitySelected={handleAllEntitySelectClick}
/>
</>
<SingleEntitySelectMenuItems
entitiesToSelect={entitiesForSelect.entitiesToSelect}
selectedEntity={entitiesForSelect.selectedEntities[0]}
loading={entitiesForSelect.loading}
onEntitySelected={handleRecordSelected}
SelectAllIcon={filterDefinitionUsedInDropdown?.SelectAllIcon}
selectAllLabel={filterDefinitionUsedInDropdown?.selectAllLabel}
isAllEntitySelected={isAllEntitySelected}
isAllEntitySelectShown={isAllEntitySelectShown}
onAllEntitySelected={handleAllEntitySelectClick}
/>
);
};

View File

@ -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<string, any>) | 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 (
<Section>
<StyledTitle>{fieldDefinition.label}</StyledTitle>
<StyledHeader isDropdownOpen={isDropdownOpen}>
<StyledTitle>{fieldDefinition.label}</StyledTitle>
<DropdownScope dropdownScopeId={dropdownScopeId}>
<StyledAddDropdown
dropdownPlacement="right-start"
onClose={handleCloseRelationPickerDropdown}
clickableComponent={
<LightIconButton
className="displayOnHover"
Icon={IconPlus}
accent="tertiary"
/>
}
dropdownComponents={
<SingleEntitySelectMenuItemsWithSearch
EmptyIcon={IconForbid}
entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading}
onEntitySelected={handleRelationPickerEntitySelected}
/>
}
dropdownHotkeyScope={{
scope: dropdownScopeId,
}}
/>
</DropdownScope>
</StyledHeader>
{!!relationRecords.length && (
<Card>
{relationRecords.map((relationRecord, index) => (
{relationRecords.slice(0, 5).map((relationRecord, index) => (
<RecordRelationFieldCardContent
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
divider={index < relationRecords.length - 1}

View File

@ -1,7 +1,5 @@
import { useEffect } from 'react';
import { useQuery } from '@apollo/client';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { FieldDefinition } from '@/object-record/field/types/FieldDefinition';
import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata';
@ -13,7 +11,7 @@ import { IconForbid } from '@/ui/display/icon';
export type RelationPickerProps = {
recordId?: string;
onSubmit: (newUser: EntityForSelect | null) => 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 (
<SingleEntitySelect
EmptyIcon={IconForbid}
emptyLabel={'No ' + fieldDefinition.label}
entitiesToSelect={records.entitiesToSelect}
loading={records.loading}
entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading}
onCancel={onCancel}
onEntitySelected={handleEntitySelected}
selectedEntity={records.selectedEntities[0]}
selectedEntity={entities.selectedEntities[0]}
width={width}
/>
);

View File

@ -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<HTMLDivElement>(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
>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleSearchFilterChange}
autoFocus
/>
<DropdownMenuSeparator />
<SingleEntitySelectBase
<SingleEntitySelectMenuItemsWithSearch
{...{
EmptyIcon,
emptyLabel,
@ -78,7 +52,6 @@ export const SingleEntitySelect = ({
onCreate,
onEntitySelected,
selectedEntity,
showCreateButton,
}}
/>
</DropdownMenu>

View File

@ -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<HTMLDivElement>(null);
const entitiesInDropdown = [selectedEntity, ...entitiesToSelect].filter(

View File

@ -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 (
<>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleSearchFilterChange}
autoFocus
/>
<DropdownMenuSeparator />
<SingleEntitySelectMenuItems
{...{
EmptyIcon,
emptyLabel,
entitiesToSelect,
loading,
onCancel,
onCreate,
onEntitySelected,
selectedEntity,
showCreateButton,
}}
/>
</>
);
};

View File

@ -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<any, any>,
) => QueryResult<any, any>;
orderByField: string;
filters: SearchFilter[];
sortOrder?: OrderBy;
@ -40,22 +33,11 @@ export const useFilteredSearchEntityQuery = ({
excludeEntityIds?: string[];
objectNameSingular: string;
}): EntitiesForMultipleEntitySelect<EntityForSelect> => {
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,
};
};