Feat/activities custom objects (#3213)
* WIP * WIP - MultiObjectSearch * WIP * WIP * Finished working version * Fix * Fixed and cleaned * Fix * Disabled files and emails for custom objects * Cleaned console.log * Fixed attachment * Fixed * fix lint --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { EntityChip } from '@/ui/display/chip/components/EntityChip';
|
||||
|
||||
export type RecordChipProps = {
|
||||
objectNameSingular: string;
|
||||
record: ObjectRecord;
|
||||
};
|
||||
|
||||
export const RecordChip = ({ objectNameSingular, record }: RecordChipProps) => {
|
||||
const { mapToObjectRecordIdentifier } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const objectRecordIdentifier = mapToObjectRecordIdentifier(record);
|
||||
|
||||
return (
|
||||
<EntityChip
|
||||
entityId={record.id}
|
||||
name={objectRecordIdentifier.name}
|
||||
avatarType={objectRecordIdentifier.avatarType}
|
||||
avatarUrl={objectRecordIdentifier.avatarUrl ?? undefined}
|
||||
linkToEntity={objectRecordIdentifier.linkToShowPage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -77,13 +77,6 @@ export const RecordShowPage = () => {
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const objectMetadataType =
|
||||
objectMetadataItem?.nameSingular === 'company'
|
||||
? 'Company'
|
||||
: objectMetadataItem?.nameSingular === 'person'
|
||||
? 'Person'
|
||||
: 'Custom';
|
||||
|
||||
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
@ -171,7 +164,7 @@ export const RecordShowPage = () => {
|
||||
hasBackButton
|
||||
Icon={IconBuildingSkyscraper}
|
||||
>
|
||||
{record && objectMetadataType !== 'Custom' && (
|
||||
{record && (
|
||||
<>
|
||||
<PageFavoriteButton
|
||||
isFavorite={isFavorite}
|
||||
@ -181,7 +174,7 @@ export const RecordShowPage = () => {
|
||||
key="add"
|
||||
entity={{
|
||||
id: record.id,
|
||||
type: objectMetadataType,
|
||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||
}}
|
||||
/>
|
||||
<ShowPageMoreButton
|
||||
@ -275,15 +268,9 @@ export const RecordShowPage = () => {
|
||||
)}
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
entity={{
|
||||
id: record?.id || '',
|
||||
// TODO: refacto
|
||||
type:
|
||||
objectMetadataItem?.nameSingular === 'company'
|
||||
? 'Company'
|
||||
: objectMetadataItem?.nameSingular === 'person'
|
||||
? 'Person'
|
||||
: 'Custom',
|
||||
targetableObject={{
|
||||
id: record?.id ?? '',
|
||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
|
||||
@ -10,6 +10,7 @@ export const ChipFieldDisplay = () => {
|
||||
basePathToShowPage,
|
||||
} = useChipField();
|
||||
|
||||
// TODO: remove this and use ObjectRecordChip instead
|
||||
const identifiers = identifiersMapper?.(record, objectNameSingular ?? '');
|
||||
|
||||
return (
|
||||
|
||||
@ -5,11 +5,10 @@ import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimis
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { useGenerateEmptyRecord } from '@/object-record/hooks/useGenerateEmptyRecord';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useCreateManyRecords = <
|
||||
T extends Record<string, unknown> & { id: string },
|
||||
>({
|
||||
export const useCreateManyRecords = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
}: ObjectMetadataItemIdentifier) => {
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect({
|
||||
@ -27,17 +26,16 @@ export const useCreateManyRecords = <
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createManyRecords = async (data: Record<string, any>[]) => {
|
||||
const createManyRecords = async (data: Partial<T>[]) => {
|
||||
const withIds = data.map((record) => ({
|
||||
...record,
|
||||
id: (record.id as string) ?? v4(),
|
||||
}));
|
||||
|
||||
withIds.forEach((record) => {
|
||||
const emptyRecord: Record<string, unknown> | undefined =
|
||||
generateEmptyRecord({
|
||||
id: record.id,
|
||||
});
|
||||
const emptyRecord: T | undefined = generateEmptyRecord({
|
||||
id: record.id,
|
||||
} as T);
|
||||
|
||||
if (emptyRecord) {
|
||||
triggerOptimisticEffects({
|
||||
|
||||
@ -34,7 +34,7 @@ export const useCreateOneRecord = <T>({
|
||||
const createOneRecord = async (input: Record<string, any>) => {
|
||||
const recordId = v4();
|
||||
|
||||
const generatedEmptyRecord = generateEmptyRecord<Record<string, unknown>>({
|
||||
const generatedEmptyRecord = generateEmptyRecord({
|
||||
id: recordId,
|
||||
createdAt: new Date().toISOString(),
|
||||
...input,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue';
|
||||
|
||||
export const useGenerateEmptyRecord = ({
|
||||
@ -7,11 +8,11 @@ export const useGenerateEmptyRecord = ({
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
// Todo fix typing once we generate the return base on Metadata
|
||||
const generateEmptyRecord = <T>(input: Partial<T> & { id: string }) => {
|
||||
const generateEmptyRecord = <T extends ObjectRecord>(input: T) => {
|
||||
// Todo replace this by runtime typing
|
||||
const validatedInput = input as { id: string } & { [key: string]: any };
|
||||
const validatedInput = input as T;
|
||||
|
||||
const emptyRecord = {} as Record<string, any>;
|
||||
const emptyRecord = {} as any;
|
||||
|
||||
for (const fieldMetadataItem of objectMetadataItem.fields) {
|
||||
emptyRecord[fieldMetadataItem.name] =
|
||||
@ -19,7 +20,7 @@ export const useGenerateEmptyRecord = ({
|
||||
generateEmptyFieldValue(fieldMetadataItem);
|
||||
}
|
||||
|
||||
return emptyRecord;
|
||||
return emptyRecord as T;
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({
|
||||
objectMetadataItems,
|
||||
depth,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
depth?: number;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
|
||||
const capitalizedObjectNameSingulars = objectMetadataItems.map(
|
||||
({ nameSingular }) => capitalize(nameSingular),
|
||||
);
|
||||
|
||||
const filterPerMetadataItemArray = capitalizedObjectNameSingulars
|
||||
.map(
|
||||
(capitalizedObjectNameSingular) =>
|
||||
`$filter${capitalizedObjectNameSingular}: ${capitalizedObjectNameSingular}FilterInput`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
const orderByPerMetadataItemArray = capitalizedObjectNameSingulars
|
||||
.map(
|
||||
(capitalizedObjectNameSingular) =>
|
||||
`$orderBy${capitalizedObjectNameSingular}: ${capitalizedObjectNameSingular}OrderByInput`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
const lastCursorPerMetadataItemArray = capitalizedObjectNameSingulars
|
||||
.map(
|
||||
(capitalizedObjectNameSingular) =>
|
||||
`$lastCursor${capitalizedObjectNameSingular}: String`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
const limitPerMetadataItemArray = capitalizedObjectNameSingulars
|
||||
.map(
|
||||
(capitalizedObjectNameSingular) =>
|
||||
`$limit${capitalizedObjectNameSingular}: Float = 5`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
return gql`
|
||||
query FindManyRecordsMultipleMetadataItems(
|
||||
${filterPerMetadataItemArray},
|
||||
${orderByPerMetadataItemArray},
|
||||
${lastCursorPerMetadataItemArray},
|
||||
${limitPerMetadataItemArray}
|
||||
) {
|
||||
${objectMetadataItems
|
||||
.map(
|
||||
({ namePlural, nameSingular, fields }) =>
|
||||
`${namePlural}(filter: $filter${capitalize(
|
||||
nameSingular,
|
||||
)}, orderBy: $orderBy${capitalize(
|
||||
nameSingular,
|
||||
)}, first: $limit${capitalize(
|
||||
nameSingular,
|
||||
)}, after: $lastCursor${capitalize(nameSingular)}){
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
${fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field, depth))
|
||||
.join('\n')}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}`,
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -1,7 +1,6 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { EMPTY_QUERY } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
@ -14,10 +13,6 @@ export const useGenerateFindManyRecordsQuery = ({
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_QUERY;
|
||||
}
|
||||
|
||||
return gql`
|
||||
query FindMany${capitalize(
|
||||
objectMetadataItem.namePlural,
|
||||
|
||||
@ -67,13 +67,6 @@ export const useRecordTableContextMenuEntries = (
|
||||
|
||||
const { createFavorite, favorites, deleteFavorite } = useFavorites();
|
||||
|
||||
const objectMetadataType =
|
||||
objectNameSingular === 'company'
|
||||
? 'Company'
|
||||
: objectNameSingular === 'person'
|
||||
? 'Person'
|
||||
: 'Custom';
|
||||
|
||||
const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => {
|
||||
const selectedRowIds = injectSelectorSnapshotValueWithRecordTableScopeId(
|
||||
snapshot,
|
||||
@ -212,14 +205,14 @@ export const useRecordTableContextMenuEntries = (
|
||||
label: 'Task',
|
||||
Icon: IconCheckbox,
|
||||
onClick: () => {
|
||||
openCreateActivityDrawer('Task', objectMetadataType);
|
||||
openCreateActivityDrawer('Task', objectNameSingular);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Note',
|
||||
Icon: IconNotes,
|
||||
onClick: () => {
|
||||
openCreateActivityDrawer('Note', objectMetadataType);
|
||||
openCreateActivityDrawer('Note', objectNameSingular);
|
||||
},
|
||||
},
|
||||
...(dataExecuteQuickActionOnmentEnabled
|
||||
|
||||
@ -0,0 +1,212 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
ObjectRecordForSelect,
|
||||
SelectedObjectRecordId,
|
||||
useMultiObjectSearch,
|
||||
} from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
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 { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export type EntitiesForMultipleObjectRecordSelect = {
|
||||
filteredSelectedObjectRecords: ObjectRecordForSelect[];
|
||||
objectRecordsToSelect: ObjectRecordForSelect[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export const MultipleObjectRecordSelect = ({
|
||||
onChange,
|
||||
onSubmit,
|
||||
selectedObjectRecordIds,
|
||||
}: {
|
||||
onChange?: (
|
||||
changedRecordForSelect: ObjectRecordForSelect,
|
||||
newSelectedValue: boolean,
|
||||
) => void;
|
||||
onCancel?: (objectRecordsForSelect: ObjectRecordForSelect[]) => void;
|
||||
onSubmit?: (objectRecordsForSelect: ObjectRecordForSelect[]) => void;
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [searchFilter, setSearchFilter] = useState<string>('');
|
||||
|
||||
const {
|
||||
filteredSelectedObjectRecords,
|
||||
loading,
|
||||
objectRecordsToSelect,
|
||||
selectedObjectRecords,
|
||||
} = useMultiObjectSearch({
|
||||
searchFilterValue: searchFilter,
|
||||
selectedObjectRecordIds,
|
||||
excludedObjectRecordIds: [],
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
const selectedObjectRecordsForSelect = useMemo(
|
||||
() =>
|
||||
selectedObjectRecords.filter((selectedObjectRecord) =>
|
||||
selectedObjectRecordIds.some(
|
||||
(selectedObjectRecordId) =>
|
||||
selectedObjectRecordId.id ===
|
||||
selectedObjectRecord.recordIdentifier.id,
|
||||
),
|
||||
),
|
||||
[selectedObjectRecords, selectedObjectRecordIds],
|
||||
);
|
||||
|
||||
const [internalSelectedRecords, setInternalSelectedRecords] = useState<
|
||||
ObjectRecordForSelect[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setInternalSelectedRecords(selectedObjectRecordsForSelect);
|
||||
}
|
||||
}, [selectedObjectRecordsForSelect, loading]);
|
||||
|
||||
const debouncedSetSearchFilter = debounce(setSearchFilter, 100, {
|
||||
leading: true,
|
||||
});
|
||||
|
||||
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
debouncedSetSearchFilter(event.currentTarget.value);
|
||||
};
|
||||
|
||||
const handleSelectChange = (
|
||||
changedRecordForSelect: ObjectRecordForSelect,
|
||||
newSelectedValue: boolean,
|
||||
) => {
|
||||
const newSelectedRecords = newSelectedValue
|
||||
? [...internalSelectedRecords, changedRecordForSelect]
|
||||
: internalSelectedRecords.filter(
|
||||
(selectedRecord) =>
|
||||
selectedRecord.record.id !== changedRecordForSelect.record.id,
|
||||
);
|
||||
|
||||
setInternalSelectedRecords(newSelectedRecords);
|
||||
|
||||
onChange?.(changedRecordForSelect, newSelectedValue);
|
||||
};
|
||||
|
||||
const entitiesInDropdown = useMemo(
|
||||
() =>
|
||||
[
|
||||
...(filteredSelectedObjectRecords ?? []),
|
||||
...(objectRecordsToSelect ?? []),
|
||||
].filter((entity) => isNonEmptyString(entity.recordIdentifier.id)),
|
||||
[filteredSelectedObjectRecords, objectRecordsToSelect],
|
||||
);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [containerRef],
|
||||
callback: (event) => {
|
||||
event.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
onSubmit?.(internalSelectedRecords);
|
||||
},
|
||||
});
|
||||
|
||||
const selectableItemIds = entitiesInDropdown.map(
|
||||
(entity) => entity.record.id,
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu ref={containerRef} data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{loading ? (
|
||||
<MenuItem text="Loading..." />
|
||||
) : (
|
||||
<>
|
||||
<SelectableList
|
||||
selectableListId="multiple-entity-select-list"
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
onEnter={(recordId) => {
|
||||
const recordIsSelected = internalSelectedRecords?.some(
|
||||
(selectedRecord) => selectedRecord.record.id === recordId,
|
||||
);
|
||||
|
||||
const correspondingRecordForSelect = entitiesInDropdown?.find(
|
||||
(entity) => entity.record.id === recordId,
|
||||
);
|
||||
|
||||
if (correspondingRecordForSelect) {
|
||||
handleSelectChange(
|
||||
correspondingRecordForSelect,
|
||||
!recordIsSelected,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{entitiesInDropdown?.map((objectRecordForSelect) => (
|
||||
<StyledSelectableItem
|
||||
itemId={objectRecordForSelect.record.id}
|
||||
key={objectRecordForSelect.record.id + v4()}
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
selected={internalSelectedRecords?.some(
|
||||
(selectedRecord) => {
|
||||
return (
|
||||
selectedRecord.record.id ===
|
||||
objectRecordForSelect.record.id
|
||||
);
|
||||
},
|
||||
)}
|
||||
onSelectChange={(newCheckedValue) =>
|
||||
handleSelectChange(objectRecordForSelect, newCheckedValue)
|
||||
}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={
|
||||
objectRecordForSelect.recordIdentifier.avatarUrl
|
||||
}
|
||||
colorId={objectRecordForSelect.record.id}
|
||||
placeholder={
|
||||
objectRecordForSelect.recordIdentifier.name
|
||||
}
|
||||
size="md"
|
||||
type={
|
||||
objectRecordForSelect.recordIdentifier.avatarType ??
|
||||
'rounded'
|
||||
}
|
||||
/>
|
||||
}
|
||||
text={objectRecordForSelect.recordIdentifier.name}
|
||||
/>
|
||||
</StyledSelectableItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
{entitiesInDropdown?.length === 0 && <MenuItem text="No result" />}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useLimitPerMetadataItem = ({
|
||||
objectMetadataItems,
|
||||
limit = DEFAULT_SEARCH_REQUEST_LIMIT,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
limit?: number;
|
||||
}) => {
|
||||
const limitPerMetadataItem = Object.fromEntries(
|
||||
objectMetadataItems
|
||||
.map(({ nameSingular }) => {
|
||||
return [`limit${capitalize(nameSingular)}`, limit];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
return {
|
||||
limitPerMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export type MultiObjectRecordQueryResult = {
|
||||
[namePlural: string]: ObjectRecordConnection;
|
||||
};
|
||||
|
||||
export const useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray =
|
||||
({
|
||||
multiObjectRecordsQueryResult,
|
||||
}: {
|
||||
multiObjectRecordsQueryResult:
|
||||
| MultiObjectRecordQueryResult
|
||||
| null
|
||||
| undefined;
|
||||
}) => {
|
||||
const objectMetadataItemsByNamePluralMap = useRecoilValue(
|
||||
objectMetadataItemsByNamePluralMapSelector,
|
||||
);
|
||||
|
||||
const objectRecordForSelectArray = useMemo(() => {
|
||||
return Object.entries(multiObjectRecordsQueryResult ?? {}).flatMap(
|
||||
([namePlural, objectRecordConnection]) => {
|
||||
const objectMetadataItem =
|
||||
objectMetadataItemsByNamePluralMap.get(namePlural);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) return [];
|
||||
|
||||
return objectRecordConnection.edges.map(({ node }) => ({
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
recordIdentifier: getObjectRecordIdentifier({
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
}),
|
||||
})) as ObjectRecordForSelect[];
|
||||
},
|
||||
);
|
||||
}, [multiObjectRecordsQueryResult, objectMetadataItemsByNamePluralMap]);
|
||||
|
||||
return {
|
||||
objectRecordForSelectArray,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,72 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery';
|
||||
import { useMultiObjectSearchMatchesSearchFilterAndToSelectQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery';
|
||||
import { useMultiObjectSearchSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
|
||||
|
||||
export const DEFAULT_SEARCH_REQUEST_LIMIT = 5;
|
||||
|
||||
export type ObjectRecordForSelect = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
record: ObjectRecord;
|
||||
recordIdentifier: ObjectRecordIdentifier;
|
||||
};
|
||||
|
||||
export type SelectedObjectRecordId = {
|
||||
objectNameSingular: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type MultiObjectSearch = {
|
||||
selectedObjectRecords: ObjectRecordForSelect[];
|
||||
filteredSelectedObjectRecords: ObjectRecordForSelect[];
|
||||
objectRecordsToSelect: ObjectRecordForSelect[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export const useMultiObjectSearch = ({
|
||||
searchFilterValue,
|
||||
selectedObjectRecordIds,
|
||||
limit,
|
||||
excludedObjectRecordIds = [],
|
||||
}: {
|
||||
searchFilterValue: string;
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
limit?: number;
|
||||
excludedObjectRecordIds?: SelectedObjectRecordId[];
|
||||
}): MultiObjectSearch => {
|
||||
const { selectedObjectRecords, selectedObjectRecordsLoading } =
|
||||
useMultiObjectSearchSelectedItemsQuery({
|
||||
selectedObjectRecordIds,
|
||||
});
|
||||
|
||||
const {
|
||||
selectedAndMatchesSearchFilterObjectRecords,
|
||||
selectedAndMatchesSearchFilterObjectRecordsLoading,
|
||||
} = useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery({
|
||||
searchFilterValue,
|
||||
selectedObjectRecordIds,
|
||||
limit,
|
||||
});
|
||||
|
||||
const {
|
||||
toSelectAndMatchesSearchFilterObjectRecords,
|
||||
toSelectAndMatchesSearchFilterObjectRecordsLoading,
|
||||
} = useMultiObjectSearchMatchesSearchFilterAndToSelectQuery({
|
||||
excludedObjectRecordIds,
|
||||
searchFilterValue,
|
||||
selectedObjectRecordIds,
|
||||
limit,
|
||||
});
|
||||
|
||||
return {
|
||||
selectedObjectRecords,
|
||||
filteredSelectedObjectRecords: selectedAndMatchesSearchFilterObjectRecords,
|
||||
objectRecordsToSelect: toSelectAndMatchesSearchFilterObjectRecords,
|
||||
loading:
|
||||
selectedAndMatchesSearchFilterObjectRecordsLoading ||
|
||||
toSelectAndMatchesSearchFilterObjectRecordsLoading ||
|
||||
selectedObjectRecordsLoading,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,113 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useGenerateFindManyRecordsForMultipleMetadataItemsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery';
|
||||
import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem';
|
||||
import {
|
||||
MultiObjectRecordQueryResult,
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray,
|
||||
} from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||
import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem';
|
||||
import { useSearchFilterPerMetadataItem } from '@/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({
|
||||
selectedObjectRecordIds,
|
||||
searchFilterValue,
|
||||
limit,
|
||||
}: {
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
searchFilterValue: string;
|
||||
limit?: number;
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const { searchFilterPerMetadataItemNameSingular } =
|
||||
useSearchFilterPerMetadataItem({
|
||||
objectMetadataItems,
|
||||
searchFilterValue,
|
||||
});
|
||||
|
||||
const objectMetadataItemsUsedInSelectedIdsQuery = objectMetadataItems.filter(
|
||||
({ nameSingular }) => {
|
||||
return selectedObjectRecordIds.some(({ objectNameSingular }) => {
|
||||
return objectNameSingular === nameSingular;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const selectedAndMatchesSearchFilterTextFilterPerMetadataItem =
|
||||
Object.fromEntries(
|
||||
objectMetadataItems
|
||||
.map(({ nameSingular }) => {
|
||||
const selectedIds = selectedObjectRecordIds
|
||||
.filter(
|
||||
({ objectNameSingular }) => objectNameSingular === nameSingular,
|
||||
)
|
||||
.map(({ id }) => id);
|
||||
|
||||
if (!isNonEmptyArray(selectedIds)) return null;
|
||||
|
||||
const searchFilter =
|
||||
searchFilterPerMetadataItemNameSingular[nameSingular] ?? {};
|
||||
|
||||
return [
|
||||
`filter${capitalize(nameSingular)}`,
|
||||
{
|
||||
and: [
|
||||
{
|
||||
...searchFilter,
|
||||
},
|
||||
{
|
||||
id: {
|
||||
in: selectedIds,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
const { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
});
|
||||
|
||||
const { limitPerMetadataItem } = useLimitPerMetadataItem({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
limit,
|
||||
});
|
||||
|
||||
const multiSelectQueryForSelectedIds =
|
||||
useGenerateFindManyRecordsForMultipleMetadataItemsQuery({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
});
|
||||
|
||||
const {
|
||||
loading: selectedAndMatchesSearchFilterObjectRecordsLoading,
|
||||
data: selectedAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
} = useQuery<MultiObjectRecordQueryResult>(multiSelectQueryForSelectedIds, {
|
||||
variables: {
|
||||
...selectedAndMatchesSearchFilterTextFilterPerMetadataItem,
|
||||
...orderByFieldPerMetadataItem,
|
||||
...limitPerMetadataItem,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
objectRecordForSelectArray: selectedAndMatchesSearchFilterObjectRecords,
|
||||
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult:
|
||||
selectedAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
});
|
||||
|
||||
return {
|
||||
selectedAndMatchesSearchFilterObjectRecordsLoading,
|
||||
selectedAndMatchesSearchFilterObjectRecords,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,130 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useGenerateFindManyRecordsForMultipleMetadataItemsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery';
|
||||
import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem';
|
||||
import {
|
||||
MultiObjectRecordQueryResult,
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray,
|
||||
} from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||
import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem';
|
||||
import { useSearchFilterPerMetadataItem } from '@/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
|
||||
selectedObjectRecordIds,
|
||||
excludedObjectRecordIds,
|
||||
searchFilterValue,
|
||||
limit,
|
||||
}: {
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
excludedObjectRecordIds: SelectedObjectRecordId[];
|
||||
searchFilterValue: string;
|
||||
limit?: number;
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const nonSystemObjectMetadataItems = objectMetadataItems.filter(
|
||||
({ nameSingular, isSystem }) =>
|
||||
!isSystem && nameSingular !== CoreObjectNameSingular.Opportunity,
|
||||
);
|
||||
|
||||
const { searchFilterPerMetadataItemNameSingular } =
|
||||
useSearchFilterPerMetadataItem({
|
||||
objectMetadataItems: nonSystemObjectMetadataItems,
|
||||
searchFilterValue,
|
||||
});
|
||||
|
||||
const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem =
|
||||
Object.fromEntries(
|
||||
nonSystemObjectMetadataItems
|
||||
.map(({ nameSingular }) => {
|
||||
const selectedIds = selectedObjectRecordIds
|
||||
.filter(
|
||||
({ objectNameSingular }) => objectNameSingular === nameSingular,
|
||||
)
|
||||
.map(({ id }) => id);
|
||||
|
||||
const excludedIds = excludedObjectRecordIds
|
||||
.filter(
|
||||
({ objectNameSingular }) => objectNameSingular === nameSingular,
|
||||
)
|
||||
.map(({ id }) => id);
|
||||
|
||||
const searchFilter =
|
||||
searchFilterPerMetadataItemNameSingular[nameSingular] ?? {};
|
||||
|
||||
const excludedIdsUnion = [...selectedIds, ...excludedIds];
|
||||
|
||||
const noFilter =
|
||||
!isNonEmptyArray(excludedIdsUnion) &&
|
||||
isDeeplyEqual(searchFilter, {});
|
||||
|
||||
return [
|
||||
`filter${capitalize(nameSingular)}`,
|
||||
!noFilter
|
||||
? {
|
||||
and: [
|
||||
{
|
||||
...searchFilter,
|
||||
},
|
||||
isNonEmptyArray(excludedIdsUnion)
|
||||
? {
|
||||
not: {
|
||||
id: {
|
||||
in: [...selectedIds, ...excludedIds],
|
||||
},
|
||||
},
|
||||
}
|
||||
: {},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
const { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({
|
||||
objectMetadataItems: nonSystemObjectMetadataItems,
|
||||
});
|
||||
|
||||
const { limitPerMetadataItem } = useLimitPerMetadataItem({
|
||||
objectMetadataItems: nonSystemObjectMetadataItems,
|
||||
limit,
|
||||
});
|
||||
|
||||
const multiSelectQuery =
|
||||
useGenerateFindManyRecordsForMultipleMetadataItemsQuery({
|
||||
objectMetadataItems: nonSystemObjectMetadataItems,
|
||||
});
|
||||
|
||||
const {
|
||||
loading: toSelectAndMatchesSearchFilterObjectRecordsLoading,
|
||||
data: toSelectAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
} = useQuery<MultiObjectRecordQueryResult>(multiSelectQuery, {
|
||||
variables: {
|
||||
...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem,
|
||||
...orderByFieldPerMetadataItem,
|
||||
...limitPerMetadataItem,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords,
|
||||
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult:
|
||||
toSelectAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
});
|
||||
|
||||
return {
|
||||
toSelectAndMatchesSearchFilterObjectRecordsLoading,
|
||||
toSelectAndMatchesSearchFilterObjectRecords,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,88 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useGenerateFindManyRecordsForMultipleMetadataItemsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery';
|
||||
import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem';
|
||||
import {
|
||||
MultiObjectRecordQueryResult,
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray,
|
||||
} from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||
import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useMultiObjectSearchSelectedItemsQuery = ({
|
||||
selectedObjectRecordIds,
|
||||
}: {
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItemsUsedInSelectedIdsQuery = objectMetadataItems.filter(
|
||||
({ nameSingular }) => {
|
||||
return selectedObjectRecordIds.some(({ objectNameSingular }) => {
|
||||
return objectNameSingular === nameSingular;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const selectedIdFilterPerMetadataItem = Object.fromEntries(
|
||||
objectMetadataItemsUsedInSelectedIdsQuery
|
||||
.map(({ nameSingular }) => {
|
||||
const selectedIds = selectedObjectRecordIds
|
||||
.filter(
|
||||
({ objectNameSingular }) => objectNameSingular === nameSingular,
|
||||
)
|
||||
.map(({ id }) => id);
|
||||
|
||||
if (!isNonEmptyArray(selectedIds)) return null;
|
||||
|
||||
return [
|
||||
`filter${capitalize(nameSingular)}`,
|
||||
{
|
||||
id: {
|
||||
in: selectedIds,
|
||||
},
|
||||
},
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
const { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
});
|
||||
|
||||
const { limitPerMetadataItem } = useLimitPerMetadataItem({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
});
|
||||
|
||||
const multiSelectQueryForSelectedIds =
|
||||
useGenerateFindManyRecordsForMultipleMetadataItemsQuery({
|
||||
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
|
||||
});
|
||||
|
||||
const {
|
||||
loading: selectedObjectRecordsLoading,
|
||||
data: selectedObjectRecordsQueryResult,
|
||||
} = useQuery<MultiObjectRecordQueryResult>(multiSelectQueryForSelectedIds, {
|
||||
variables: {
|
||||
...selectedIdFilterPerMetadataItem,
|
||||
...orderByFieldPerMetadataItem,
|
||||
...limitPerMetadataItem,
|
||||
},
|
||||
});
|
||||
|
||||
const { objectRecordForSelectArray: selectedObjectRecords } =
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult: selectedObjectRecordsQueryResult,
|
||||
});
|
||||
|
||||
return {
|
||||
selectedObjectRecordsLoading,
|
||||
selectedObjectRecords,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectOrderByField } from '@/object-metadata/utils/getObjectOrderByField';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useOrderByFieldPerMetadataItem = ({
|
||||
objectMetadataItems,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
}) => {
|
||||
const orderByFieldPerMetadataItem = Object.fromEntries(
|
||||
objectMetadataItems
|
||||
.map((objectMetadataItem) => {
|
||||
const orderByField = getObjectOrderByField(objectMetadataItem);
|
||||
|
||||
return [
|
||||
`orderBy${capitalize(objectMetadataItem.nameSingular)}`,
|
||||
{
|
||||
...orderByField,
|
||||
},
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
return {
|
||||
orderByFieldPerMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,67 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useSearchFilterPerMetadataItem = ({
|
||||
objectMetadataItems,
|
||||
searchFilterValue,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
searchFilterValue: string;
|
||||
}) => {
|
||||
const searchFilterPerMetadataItemNameSingular =
|
||||
Object.fromEntries<ObjectRecordQueryFilter>(
|
||||
objectMetadataItems
|
||||
.map((objectMetadataItem) => {
|
||||
if (!isNonEmptyString(searchFilterValue)) return null;
|
||||
|
||||
const labelIdentifierFieldMetadataItem =
|
||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
|
||||
|
||||
let searchFilter: ObjectRecordQueryFilter = {};
|
||||
|
||||
if (labelIdentifierFieldMetadataItem) {
|
||||
switch (labelIdentifierFieldMetadataItem.type) {
|
||||
case FieldMetadataType.FullName: {
|
||||
searchFilter = {
|
||||
or: [
|
||||
{
|
||||
[labelIdentifierFieldMetadataItem.name]: {
|
||||
firstName: {
|
||||
ilike: `%${searchFilterValue}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
[labelIdentifierFieldMetadataItem.name]: {
|
||||
lastName: {
|
||||
ilike: `%${searchFilterValue}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
searchFilter = {
|
||||
[labelIdentifierFieldMetadataItem.name]: {
|
||||
ilike: `%${searchFilterValue}%`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return [objectMetadataItem.nameSingular, searchFilter] as const;
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
return {
|
||||
searchFilterPerMetadataItemNameSingular,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export type ObjectRecord = Record<string, any> & { id: string };
|
||||
@ -0,0 +1,11 @@
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
|
||||
export type ObjectRecordConnection = {
|
||||
edges: ObjectRecordEdge[];
|
||||
pageInfo: {
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
startCursor?: string;
|
||||
endCursor?: string;
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export type ObjectRecordEdge = {
|
||||
node: ObjectRecord;
|
||||
cursor: string;
|
||||
};
|
||||
@ -5,5 +5,6 @@ export type ObjectRecordIdentifier = {
|
||||
name: string;
|
||||
avatarUrl?: string | null;
|
||||
avatarType?: AvatarType | null;
|
||||
linkToEntity?: string;
|
||||
linkToShowPage?: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user