From f97d25d986f48615c0e0db7f95348bb120954902 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 21 Nov 2023 16:09:02 +0100 Subject: [PATCH] Introduce a RelationPicker component with a RelationPickerScope (#2617) Refactor mainIdentifier into scope componetn --- .../ObjectMetadataItemsProvider.tsx | 11 +++- ...bjectMetadataItemsRelationPickerEffect.tsx | 51 +++++++++++++++++++ .../hooks/useObjectMainIdentifier.ts | 30 +---------- ...rmatFieldMetadataItemAsColumnDefinition.ts | 29 +---------- .../components/RecordTableEffect.tsx | 24 ++------- .../hooks/useFilteredSearchEntityQuery.ts | 15 ++++-- .../components/SettingsObjectFieldPreview.tsx | 21 +------- .../hooks/useRelationFieldPreview.ts | 12 ----- .../relation-picker}/RelationPicker.tsx | 13 +++-- .../internal/useRelationPickerScopedStates.ts | 28 ++++++++++ .../hooks/useRelationPicker.ts | 35 +++++++++++++ .../scopes/RelationPickerScope.tsx | 21 ++++++++ .../RelationPickerScopeInternalContext.ts | 7 +++ .../states/identifiersMapperScopedState.ts | 8 +++ .../states/searchQueryScopedState.ts | 7 +++ .../types/IdentifiersMapper.tsx | 14 +++++ .../relation-picker/types/SearchQuery.tsx | 3 ++ .../utils/getRelationPickerScopedStates.ts | 24 +++++++++ .../DropdownScopeInternalContext.ts | 4 +- .../components/RelationFieldDisplay.tsx | 15 +++--- .../meta-types/hooks/useRelationField.ts | 35 ++----------- .../input/components/RelationFieldInput.tsx | 2 +- .../ui/object/field/types/FieldMetadata.ts | 5 -- .../components/RecordTableCell.tsx | 2 +- .../types/ObjectMetadataConfig.ts | 8 +-- 25 files changed, 256 insertions(+), 168 deletions(-) create mode 100644 front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx rename front/src/modules/ui/{object/field/meta-types/input/components/internal => input/components/internal/relation-picker}/RelationPicker.tsx (88%) create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/hooks/internal/useRelationPickerScopedStates.ts create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/hooks/useRelationPicker.ts create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/scopes/RelationPickerScope.tsx create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext.ts create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/states/identifiersMapperScopedState.ts create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/states/searchQueryScopedState.ts create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/types/IdentifiersMapper.tsx create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/types/SearchQuery.tsx create mode 100644 front/src/modules/ui/input/components/internal/relation-picker/utils/getRelationPickerScopedStates.ts diff --git a/front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx b/front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx index b4d55bcb5..749a5dfd6 100644 --- a/front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx +++ b/front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx @@ -1,9 +1,18 @@ +import { ObjectMetadataItemsRelationPickerEffect } from '@/object-metadata/components/ObjectMetadataItemsRelationPickerEffect'; import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems'; +import { RelationPickerScope } from '@/ui/input/components/internal/relation-picker/scopes/RelationPickerScope'; export const ObjectMetadataItemsProvider = ({ children, }: React.PropsWithChildren) => { const { loading } = useFindManyObjectMetadataItems(); - return loading ? <> : <>{children}; + return loading ? ( + <> + ) : ( + + + {children} + + ); }; diff --git a/front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx b/front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx new file mode 100644 index 000000000..ee7ff6ebd --- /dev/null +++ b/front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx @@ -0,0 +1,51 @@ +import { useEffect } from 'react'; + +import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker'; +import { IdentifiersMapper } from '@/ui/input/components/internal/relation-picker/types/IdentifiersMapper'; + +export const ObjectMetadataItemsRelationPickerEffect = () => { + const { setIdentifiersMapper } = useRelationPicker(); + + const identifierMapper: IdentifiersMapper = ( + record: any, + objectMetadataItemSingularName: string, + ) => { + if (!record) { + return; + } + + if (objectMetadataItemSingularName === 'company') { + return { + id: record.id, + name: record.name, + avatarUrl: record.avatarUrl, + avatarType: 'squared', + record: record, + }; + } + + if (objectMetadataItemSingularName === 'workspaceMember') { + return { + id: record.id, + name: record.name.firstName + ' ' + record.name.lastName, + avatarUrl: record.avatarUrl, + avatarType: 'rounded', + record: record, + }; + } + + return { + id: record.id, + name: record.name, + avatarUrl: record.avatarUrl, + avatarType: 'rounded', + record, + }; + }; + + useEffect(() => { + setIdentifiersMapper(() => identifierMapper); + }, [setIdentifiersMapper]); + + return <>; +}; diff --git a/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts b/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts index dd4121f51..d30f477cc 100644 --- a/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts +++ b/front/src/modules/object-metadata/hooks/useObjectMainIdentifier.ts @@ -1,5 +1,4 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { AvatarType } from '@/users/components/Avatar'; import { Nullable } from '~/types/Nullable'; export const useObjectMainIdentifier = ( @@ -9,39 +8,14 @@ export const useObjectMainIdentifier = ( return {}; } - const labelIdentifierFieldPaths = ['person', 'workspaceMember'].includes( - objectMetadataItem.nameSingular, - ) - ? ['name.firstName', 'name.lastName'] - : ['name']; - const imageIdentifierFormat: AvatarType = ['company'].includes( - objectMetadataItem.nameSingular, - ) - ? 'squared' - : 'rounded'; - const imageIdentifierUrlPrefix = ['company'].includes( - objectMetadataItem.nameSingular, - ) - ? 'https://favicon.twenty.com/' - : ''; - const imageIdentifierUrlField = ['company'].includes( - objectMetadataItem.nameSingular, - ) - ? 'domainName' - : 'avatarUrl'; - - const mainIdentifierFieldMetadataId = objectMetadataItem.fields.find( + const labelIdentifierFieldMetadataId = objectMetadataItem.fields.find( ({ name }) => name === 'name', )?.id; const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`; return { - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, - mainIdentifierFieldMetadataId, + labelIdentifierFieldMetadataId, basePathToShowPage, }; }; diff --git a/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts b/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts index d5ee3859b..5e5ceabe8 100644 --- a/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts +++ b/front/src/modules/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition.ts @@ -1,7 +1,6 @@ import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType'; import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata'; import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition'; -import { AvatarType } from '@/users/components/Avatar'; import { FieldMetadataItem } from '../types/FieldMetadataItem'; @@ -17,27 +16,6 @@ export const formatFieldMetadataItemAsColumnDefinition = ({ const relationObjectMetadataItem = field.toRelationMetadata?.fromObjectMetadata; - const labelIdentifierFieldPaths = ['person', 'workspaceMember'].includes( - relationObjectMetadataItem?.nameSingular ?? '', - ) - ? ['name.firstName', 'name.lastName'] - : ['name']; - const imageIdentifierFormat: AvatarType = ['company'].includes( - relationObjectMetadataItem?.nameSingular ?? '', - ) - ? 'squared' - : 'rounded'; - const imageIdentifierUrlPrefix = ['company'].includes( - relationObjectMetadataItem?.nameSingular ?? '', - ) - ? 'https://favicon.twenty.com/' - : ''; - const imageIdentifierUrlField = ['company'].includes( - relationObjectMetadataItem?.nameSingular ?? '', - ) - ? 'domainName' - : 'avatarUrl'; - return { position, fieldMetadataId: field.id, @@ -47,15 +25,10 @@ export const formatFieldMetadataItemAsColumnDefinition = ({ metadata: { fieldName: field.name, placeHolder: field.label, - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, relationType: parseFieldRelationType(field), - searchFields: ['name'], - objectMetadataNamePlural: relationObjectMetadataItem?.namePlural ?? '', objectMetadataNameSingular: relationObjectMetadataItem?.nameSingular ?? '', + objectMetadataNamePlural: relationObjectMetadataItem?.namePlural ?? '', }, iconName: field.icon ?? 'Icon123', isVisible: true, diff --git a/front/src/modules/object-record/components/RecordTableEffect.tsx b/front/src/modules/object-record/components/RecordTableEffect.tsx index da3f7db08..aec52c93a 100644 --- a/front/src/modules/object-record/components/RecordTableEffect.tsx +++ b/front/src/modules/object-record/components/RecordTableEffect.tsx @@ -21,14 +21,8 @@ export const RecordTableEffect = () => { objectNamePlural, }); - const { - basePathToShowPage, - mainIdentifierFieldMetadataId, - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, - } = useObjectMainIdentifier(objectMetadataItem); + const { basePathToShowPage, labelIdentifierFieldMetadataId } = + useObjectMainIdentifier(objectMetadataItem); const { columnDefinitions, filterDefinitions, sortDefinitions } = useComputeDefinitionsFromFieldMetadata(objectMetadataItem); @@ -43,25 +37,17 @@ export const RecordTableEffect = () => { } = useView(); useEffect(() => { - if (basePathToShowPage && mainIdentifierFieldMetadataId) { + if (basePathToShowPage && labelIdentifierFieldMetadataId) { setObjectMetadataConfig?.({ - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, basePathToShowPage, - mainIdentifierFieldMetadataId, + labelIdentifierFieldMetadataId, }); } }, [ basePathToShowPage, objectMetadataItem, - mainIdentifierFieldMetadataId, + labelIdentifierFieldMetadataId, setObjectMetadataConfig, - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, ]); useEffect(() => { diff --git a/front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts b/front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts index e01ea17f4..3344cd8f2 100644 --- a/front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts +++ b/front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts @@ -4,6 +4,7 @@ import { isNonEmptyString } from '@sniptt/guards'; import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects'; import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; +import { assertNotNull } from '~/utils/assert'; import { isDefined } from '~/utils/isDefined'; type SearchFilter = { fieldNames: string[]; filter: string | number }; @@ -36,7 +37,7 @@ export const useFilteredSearchEntityQuery = ({ filters: SearchFilter[]; sortOrder?: OrderBy; selectedIds: string[]; - mappingFunction: (entity: any) => EntityForSelect; + mappingFunction: (entity: any) => EntityForSelect | undefined; limit?: number; excludeEntityIds?: string[]; objectNamePlural: string; @@ -139,15 +140,21 @@ export const useFilteredSearchEntityQuery = ({ selectedEntities: mapPaginatedObjectsToObjects({ objectNamePlural: objectNamePlural, pagedObjects: selectedEntitiesData, - }).map(mappingFunction), + }) + .map(mappingFunction) + .filter(assertNotNull), filteredSelectedEntities: mapPaginatedObjectsToObjects({ objectNamePlural: objectNamePlural, pagedObjects: filteredSelectedEntitiesData, - }).map(mappingFunction), + }) + .map(mappingFunction) + .filter(assertNotNull), entitiesToSelect: mapPaginatedObjectsToObjects({ objectNamePlural: objectNamePlural, pagedObjects: entitiesToSelectData, - }).map(mappingFunction), + }) + .map(mappingFunction) + .filter(assertNotNull), loading: entitiesToSelectLoading || filteredSelectedEntitiesLoading || diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx index bd71de588..f542751b3 100644 --- a/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx +++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx @@ -90,13 +90,7 @@ export const SettingsObjectFieldPreview = ({ objectMetadataId, }); - const { - defaultValue: relationDefaultValue, - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, - } = useRelationFieldPreview({ + const { defaultValue: relationDefaultValue } = useRelationFieldPreview({ relationObjectMetadataId, skipDefaultValue: fieldMetadata.type !== FieldMetadataType.Relation || hasValue, @@ -107,15 +101,6 @@ export const SettingsObjectFieldPreview = ({ ? relationDefaultValue : dataTypes[fieldMetadata.type].defaultValue; - if ( - !labelIdentifierFieldPaths || - !imageIdentifierUrlField || - !imageIdentifierUrlPrefix || - !imageIdentifierFormat - ) { - return <>; - } - return ( @@ -160,10 +145,6 @@ export const SettingsObjectFieldPreview = ({ label: fieldMetadata.label, metadata: { fieldName, - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, }, }, hotkeyScope: 'field-preview', diff --git a/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts b/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts index f0300587d..9b297c3cb 100644 --- a/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts +++ b/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts @@ -1,4 +1,3 @@ -import { useObjectMainIdentifier } from '@/object-metadata/hooks/useObjectMainIdentifier'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; @@ -20,18 +19,7 @@ export const useRelationFieldPreview = ({ skip: skipDefaultValue || !relationObjectMetadataItem, }); - const { - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, - } = useObjectMainIdentifier(relationObjectMetadataItem); - return { defaultValue: relationObjects?.[0], - labelIdentifierFieldPaths, - imageIdentifierUrlField, - imageIdentifierUrlPrefix, - imageIdentifierFormat, }; }; diff --git a/front/src/modules/ui/object/field/meta-types/input/components/internal/RelationPicker.tsx b/front/src/modules/ui/input/components/internal/relation-picker/RelationPicker.tsx similarity index 88% rename from front/src/modules/ui/object/field/meta-types/input/components/internal/RelationPicker.tsx rename to front/src/modules/ui/input/components/internal/relation-picker/RelationPicker.tsx index 716731d1d..ce0d76798 100644 --- a/front/src/modules/ui/object/field/meta-types/input/components/internal/RelationPicker.tsx +++ b/front/src/modules/ui/input/components/internal/relation-picker/RelationPicker.tsx @@ -42,18 +42,25 @@ export const RelationPicker = ({ const useFindManyQuery = (options: any) => useQuery(findManyQuery, options); - const { mapToObjectIdentifiers } = useRelationField(); + const { identifiersMapper, searchQuery } = useRelationField(); const workspaceMembers = useFilteredSearchEntityQuery({ queryHook: useFindManyQuery, filters: [ { - fieldNames: fieldDefinition.metadata.searchFields, + fieldNames: + searchQuery?.filterFields?.( + fieldDefinition.metadata.objectMetadataNameSingular, + ) ?? [], filter: relationPickerSearchFilter, }, ], orderByField: 'createdAt', - mappingFunction: mapToObjectIdentifiers, + mappingFunction: (record: any) => + identifiersMapper?.( + record, + fieldDefinition.metadata.objectMetadataNameSingular, + ), selectedIds: recordId ? [recordId] : [], objectNamePlural: fieldDefinition.metadata.objectMetadataNamePlural, }); diff --git a/front/src/modules/ui/input/components/internal/relation-picker/hooks/internal/useRelationPickerScopedStates.ts b/front/src/modules/ui/input/components/internal/relation-picker/hooks/internal/useRelationPickerScopedStates.ts new file mode 100644 index 000000000..ba3212444 --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/hooks/internal/useRelationPickerScopedStates.ts @@ -0,0 +1,28 @@ +import { getRelationPickerScopedStates } from '@/ui/input/components/internal/relation-picker/utils/getRelationPickerScopedStates'; +import { RecordTableScopeInternalContext } from '@/ui/object/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; + +export const useRelationPickerScopedStates = (args?: { + relationPickerScopedId?: string; +}) => { + const { relationPickerScopedId } = args ?? {}; + + const scopeId = useAvailableScopeIdOrThrow( + RecordTableScopeInternalContext, + relationPickerScopedId, + ); + + const { identifiersMapperState } = getRelationPickerScopedStates({ + relationPickerScopeId: scopeId, + }); + + const { searchQueryState } = getRelationPickerScopedStates({ + relationPickerScopeId: scopeId, + }); + + return { + scopeId, + identifiersMapperState, + searchQueryState, + }; +}; diff --git a/front/src/modules/ui/input/components/internal/relation-picker/hooks/useRelationPicker.ts b/front/src/modules/ui/input/components/internal/relation-picker/hooks/useRelationPicker.ts new file mode 100644 index 000000000..6308f61f4 --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/hooks/useRelationPicker.ts @@ -0,0 +1,35 @@ +import { useRecoilState } from 'recoil'; + +import { useRelationPickerScopedStates } from '@/ui/input/components/internal/relation-picker/hooks/internal/useRelationPickerScopedStates'; +import { RelationPickerScopeInternalContext } from '@/ui/input/components/internal/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; + +type useRelationPickeProps = { + relationPickerScopeId?: string; +}; + +export const useRelationPicker = (props?: useRelationPickeProps) => { + const scopeId = useAvailableScopeIdOrThrow( + RelationPickerScopeInternalContext, + props?.relationPickerScopeId, + ); + + const { identifiersMapperState, searchQueryState } = + useRelationPickerScopedStates({ + relationPickerScopedId: scopeId, + }); + + const [identifiersMapper, setIdentifiersMapper] = useRecoilState( + identifiersMapperState, + ); + + const [searchQuery, setSearchQuery] = useRecoilState(searchQueryState); + + return { + scopeId, + identifiersMapper, + setIdentifiersMapper, + searchQuery, + setSearchQuery, + }; +}; diff --git a/front/src/modules/ui/input/components/internal/relation-picker/scopes/RelationPickerScope.tsx b/front/src/modules/ui/input/components/internal/relation-picker/scopes/RelationPickerScope.tsx new file mode 100644 index 000000000..6a3e4b7ca --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/scopes/RelationPickerScope.tsx @@ -0,0 +1,21 @@ +import { ReactNode } from 'react'; + +import { RelationPickerScopeInternalContext } from '@/ui/input/components/internal/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; + +type RelationPickerScopeProps = { + children: ReactNode; + relationPickerScopeId: string; +}; + +export const RelationPickerScope = ({ + children, + relationPickerScopeId, +}: RelationPickerScopeProps) => { + return ( + + {children} + + ); +}; diff --git a/front/src/modules/ui/input/components/internal/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext.ts b/front/src/modules/ui/input/components/internal/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext.ts new file mode 100644 index 000000000..6458f6065 --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext.ts @@ -0,0 +1,7 @@ +import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey'; +import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext'; + +type RelationPickerScopeInternalContextProps = ScopedStateKey; + +export const RelationPickerScopeInternalContext = + createScopeInternalContext(); diff --git a/front/src/modules/ui/input/components/internal/relation-picker/states/identifiersMapperScopedState.ts b/front/src/modules/ui/input/components/internal/relation-picker/states/identifiersMapperScopedState.ts new file mode 100644 index 000000000..c01353ff7 --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/states/identifiersMapperScopedState.ts @@ -0,0 +1,8 @@ +import { IdentifiersMapper } from '@/ui/input/components/internal/relation-picker/types/IdentifiersMapper'; +import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; + +export const identifiersMapperScopedState = + createScopedState({ + key: 'identifiersMapperScopedState', + defaultValue: null, + }); diff --git a/front/src/modules/ui/input/components/internal/relation-picker/states/searchQueryScopedState.ts b/front/src/modules/ui/input/components/internal/relation-picker/states/searchQueryScopedState.ts new file mode 100644 index 000000000..54982f92d --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/states/searchQueryScopedState.ts @@ -0,0 +1,7 @@ +import { SearchQuery } from '@/ui/input/components/internal/relation-picker/types/SearchQuery'; +import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; + +export const searchQueryScopedState = createScopedState({ + key: 'searchQueryScopedState', + defaultValue: null, +}); diff --git a/front/src/modules/ui/input/components/internal/relation-picker/types/IdentifiersMapper.tsx b/front/src/modules/ui/input/components/internal/relation-picker/types/IdentifiersMapper.tsx new file mode 100644 index 000000000..3a477df0e --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/types/IdentifiersMapper.tsx @@ -0,0 +1,14 @@ +import { AvatarType } from '@/users/components/Avatar'; + +type RecordMappedToIdentifiers = { + id: string; + name: string; + avatarUrl?: string; + avatarType: AvatarType; + record: any; +}; + +export type IdentifiersMapper = ( + record: any, + relationPickerType: string, +) => RecordMappedToIdentifiers | undefined; diff --git a/front/src/modules/ui/input/components/internal/relation-picker/types/SearchQuery.tsx b/front/src/modules/ui/input/components/internal/relation-picker/types/SearchQuery.tsx new file mode 100644 index 000000000..e34cb9f56 --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/types/SearchQuery.tsx @@ -0,0 +1,3 @@ +export type SearchQuery = { + filterFields: (relationPickerType: string) => string[]; +}; diff --git a/front/src/modules/ui/input/components/internal/relation-picker/utils/getRelationPickerScopedStates.ts b/front/src/modules/ui/input/components/internal/relation-picker/utils/getRelationPickerScopedStates.ts new file mode 100644 index 000000000..228f4c2a6 --- /dev/null +++ b/front/src/modules/ui/input/components/internal/relation-picker/utils/getRelationPickerScopedStates.ts @@ -0,0 +1,24 @@ +import { identifiersMapperScopedState } from '@/ui/input/components/internal/relation-picker/states/identifiersMapperScopedState'; +import { searchQueryScopedState } from '@/ui/input/components/internal/relation-picker/states/searchQueryScopedState'; +import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState'; + +export const getRelationPickerScopedStates = ({ + relationPickerScopeId, +}: { + relationPickerScopeId: string; +}) => { + const identifiersMapperState = getScopedState( + identifiersMapperScopedState, + relationPickerScopeId, + ); + + const searchQueryState = getScopedState( + searchQueryScopedState, + relationPickerScopeId, + ); + + return { + identifiersMapperState, + searchQueryState, + }; +}; diff --git a/front/src/modules/ui/layout/dropdown/scopes/scope-internal-context/DropdownScopeInternalContext.ts b/front/src/modules/ui/layout/dropdown/scopes/scope-internal-context/DropdownScopeInternalContext.ts index 7dbd1ce36..de5e7d3f1 100644 --- a/front/src/modules/ui/layout/dropdown/scopes/scope-internal-context/DropdownScopeInternalContext.ts +++ b/front/src/modules/ui/layout/dropdown/scopes/scope-internal-context/DropdownScopeInternalContext.ts @@ -1,9 +1,7 @@ import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey'; import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext'; -type DropdownScopeInternalContextProps = ScopedStateKey & { - test?: string; -}; +type DropdownScopeInternalContextProps = ScopedStateKey; export const DropdownScopeInternalContext = createScopeInternalContext(); diff --git a/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx b/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx index 414f52e10..5d52a0a28 100644 --- a/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx +++ b/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx @@ -5,20 +5,23 @@ import { useRelationField } from '../../hooks/useRelationField'; export const RelationFieldDisplay = () => { const { fieldValue, fieldDefinition } = useRelationField(); - const { mapToObjectIdentifiers } = useRelationField(); + const { identifiersMapper } = useRelationField(); - if (!fieldValue || !fieldDefinition) { + if (!fieldValue || !fieldDefinition || !identifiersMapper) { return <>; } - const objectIdentifiers = mapToObjectIdentifiers(fieldValue); + const objectIdentifiers = identifiersMapper( + fieldValue, + fieldDefinition.metadata.objectMetadataNameSingular, + ); return ( ); }; diff --git a/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts b/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts index 6eeaef7b4..47541b1b4 100644 --- a/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts +++ b/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts @@ -1,6 +1,8 @@ import { useContext } from 'react'; import { useRecoilState } from 'recoil'; +import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker'; + import { FieldContext } from '../../contexts/FieldContext'; import { useFieldInitialValue } from '../../hooks/useFieldInitialValue'; import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; @@ -30,35 +32,7 @@ export const useRelationField = () => { const initialValue = fieldInitialValue?.isEmpty ? null : fieldValue; - const mapToObjectIdentifiers = (record: any) => { - let name = ''; - for (const fieldPath of fieldDefinition.metadata - .labelIdentifierFieldPaths) { - const fieldPathParts = fieldPath.split('.'); - - if (fieldPathParts.length === 1) { - name += record[fieldPathParts[0]]; - } else if (fieldPathParts.length === 2) { - name += record[fieldPathParts[0]][fieldPathParts[1]] + ' '; - } else { - throw new Error( - `Invalid field path ${fieldPath}. Relation picker only supports field paths with 1 or 2 parts.`, - ); - } - } - - const avatarUrl = record[fieldDefinition.metadata.imageIdentifierUrlField]; - return { - id: record.id, - name: name.trimEnd(), - avatarUrl: avatarUrl - ? fieldDefinition.metadata.imageIdentifierUrlPrefix + - record[fieldDefinition.metadata.imageIdentifierUrlField] - : '', - avatarType: fieldDefinition.metadata.imageIdentifierFormat, - record: record, - }; - }; + const { identifiersMapper, searchQuery } = useRelationPicker(); return { fieldDefinition, @@ -66,6 +40,7 @@ export const useRelationField = () => { initialValue, initialSearchValue, setFieldValue, - mapToObjectIdentifiers, + searchQuery, + identifiersMapper, }; }; diff --git a/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx b/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx index afca7f2c3..76f43c537 100644 --- a/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx +++ b/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx @@ -1,8 +1,8 @@ import { useEffect } from 'react'; import styled from '@emotion/styled'; +import { RelationPicker } from '@/ui/input/components/internal/relation-picker/RelationPicker'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; -import { RelationPicker } from '@/ui/object/field/meta-types/input/components/internal/RelationPicker'; import { usePersistField } from '../../../hooks/usePersistField'; import { useRelationField } from '../../hooks/useRelationField'; diff --git a/front/src/modules/ui/object/field/types/FieldMetadata.ts b/front/src/modules/ui/object/field/types/FieldMetadata.ts index 24559560c..fef641964 100644 --- a/front/src/modules/ui/object/field/types/FieldMetadata.ts +++ b/front/src/modules/ui/object/field/types/FieldMetadata.ts @@ -64,11 +64,6 @@ export type FieldRelationMetadata = { fieldName: string; useEditButton?: boolean; relationType?: FieldDefinitionRelationType; - labelIdentifierFieldPaths: string[]; - imageIdentifierUrlField: string; - imageIdentifierUrlPrefix: string; - imageIdentifierFormat: 'squared' | 'rounded'; - searchFields: string[]; objectMetadataNameSingular: string; objectMetadataNamePlural: string; }; diff --git a/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx b/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx index 1181f2870..87ae6aff3 100644 --- a/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx +++ b/front/src/modules/ui/object/record-table/components/RecordTableCell.tsx @@ -62,7 +62,7 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => { hotkeyScope: customHotkeyScope, isMainIdentifier: columnDefinition.fieldMetadataId === - objectMetadataConfig?.mainIdentifierFieldMetadataId, + objectMetadataConfig?.labelIdentifierFieldMetadataId, }} > diff --git a/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts b/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts index d12811ae7..97efb00b5 100644 --- a/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts +++ b/front/src/modules/ui/object/record-table/types/ObjectMetadataConfig.ts @@ -1,10 +1,4 @@ -import { AvatarType } from '@/users/components/Avatar'; - export type ObjectMetadataConfig = { - mainIdentifierFieldMetadataId: string; - labelIdentifierFieldPaths: string[]; - imageIdentifierUrlField: string; - imageIdentifierUrlPrefix: string; - imageIdentifierFormat: AvatarType; + labelIdentifierFieldMetadataId: string; basePathToShowPage: string; };