Introduce a RelationPicker component with a RelationPickerScope (#2617)
Refactor mainIdentifier into scope componetn
This commit is contained in:
@ -1,9 +1,18 @@
|
|||||||
|
import { ObjectMetadataItemsRelationPickerEffect } from '@/object-metadata/components/ObjectMetadataItemsRelationPickerEffect';
|
||||||
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
|
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
|
||||||
|
import { RelationPickerScope } from '@/ui/input/components/internal/relation-picker/scopes/RelationPickerScope';
|
||||||
|
|
||||||
export const ObjectMetadataItemsProvider = ({
|
export const ObjectMetadataItemsProvider = ({
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren) => {
|
}: React.PropsWithChildren) => {
|
||||||
const { loading } = useFindManyObjectMetadataItems();
|
const { loading } = useFindManyObjectMetadataItems();
|
||||||
|
|
||||||
return loading ? <></> : <>{children}</>;
|
return loading ? (
|
||||||
|
<></>
|
||||||
|
) : (
|
||||||
|
<RelationPickerScope relationPickerScopeId="relation-picker">
|
||||||
|
<ObjectMetadataItemsRelationPickerEffect />
|
||||||
|
{children}
|
||||||
|
</RelationPickerScope>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 <></>;
|
||||||
|
};
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { AvatarType } from '@/users/components/Avatar';
|
|
||||||
import { Nullable } from '~/types/Nullable';
|
import { Nullable } from '~/types/Nullable';
|
||||||
|
|
||||||
export const useObjectMainIdentifier = (
|
export const useObjectMainIdentifier = (
|
||||||
@ -9,39 +8,14 @@ export const useObjectMainIdentifier = (
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelIdentifierFieldPaths = ['person', 'workspaceMember'].includes(
|
const labelIdentifierFieldMetadataId = objectMetadataItem.fields.find(
|
||||||
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(
|
|
||||||
({ name }) => name === 'name',
|
({ name }) => name === 'name',
|
||||||
)?.id;
|
)?.id;
|
||||||
|
|
||||||
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
|
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labelIdentifierFieldPaths,
|
labelIdentifierFieldMetadataId,
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
mainIdentifierFieldMetadataId,
|
|
||||||
basePathToShowPage,
|
basePathToShowPage,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
||||||
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
||||||
import { AvatarType } from '@/users/components/Avatar';
|
|
||||||
|
|
||||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||||
|
|
||||||
@ -17,27 +16,6 @@ export const formatFieldMetadataItemAsColumnDefinition = ({
|
|||||||
const relationObjectMetadataItem =
|
const relationObjectMetadataItem =
|
||||||
field.toRelationMetadata?.fromObjectMetadata;
|
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 {
|
return {
|
||||||
position,
|
position,
|
||||||
fieldMetadataId: field.id,
|
fieldMetadataId: field.id,
|
||||||
@ -47,15 +25,10 @@ export const formatFieldMetadataItemAsColumnDefinition = ({
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: field.name,
|
fieldName: field.name,
|
||||||
placeHolder: field.label,
|
placeHolder: field.label,
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
relationType: parseFieldRelationType(field),
|
relationType: parseFieldRelationType(field),
|
||||||
searchFields: ['name'],
|
|
||||||
objectMetadataNamePlural: relationObjectMetadataItem?.namePlural ?? '',
|
|
||||||
objectMetadataNameSingular:
|
objectMetadataNameSingular:
|
||||||
relationObjectMetadataItem?.nameSingular ?? '',
|
relationObjectMetadataItem?.nameSingular ?? '',
|
||||||
|
objectMetadataNamePlural: relationObjectMetadataItem?.namePlural ?? '',
|
||||||
},
|
},
|
||||||
iconName: field.icon ?? 'Icon123',
|
iconName: field.icon ?? 'Icon123',
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
|
|||||||
@ -21,14 +21,8 @@ export const RecordTableEffect = () => {
|
|||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const { basePathToShowPage, labelIdentifierFieldMetadataId } =
|
||||||
basePathToShowPage,
|
useObjectMainIdentifier(objectMetadataItem);
|
||||||
mainIdentifierFieldMetadataId,
|
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
} = useObjectMainIdentifier(objectMetadataItem);
|
|
||||||
|
|
||||||
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
||||||
useComputeDefinitionsFromFieldMetadata(objectMetadataItem);
|
useComputeDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||||
@ -43,25 +37,17 @@ export const RecordTableEffect = () => {
|
|||||||
} = useView();
|
} = useView();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (basePathToShowPage && mainIdentifierFieldMetadataId) {
|
if (basePathToShowPage && labelIdentifierFieldMetadataId) {
|
||||||
setObjectMetadataConfig?.({
|
setObjectMetadataConfig?.({
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
basePathToShowPage,
|
basePathToShowPage,
|
||||||
mainIdentifierFieldMetadataId,
|
labelIdentifierFieldMetadataId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
basePathToShowPage,
|
basePathToShowPage,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
mainIdentifierFieldMetadataId,
|
labelIdentifierFieldMetadataId,
|
||||||
setObjectMetadataConfig,
|
setObjectMetadataConfig,
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
|||||||
import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects';
|
import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects';
|
||||||
import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
|
import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
|
import { assertNotNull } from '~/utils/assert';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
type SearchFilter = { fieldNames: string[]; filter: string | number };
|
type SearchFilter = { fieldNames: string[]; filter: string | number };
|
||||||
@ -36,7 +37,7 @@ export const useFilteredSearchEntityQuery = ({
|
|||||||
filters: SearchFilter[];
|
filters: SearchFilter[];
|
||||||
sortOrder?: OrderBy;
|
sortOrder?: OrderBy;
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
mappingFunction: (entity: any) => EntityForSelect;
|
mappingFunction: (entity: any) => EntityForSelect | undefined;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
excludeEntityIds?: string[];
|
excludeEntityIds?: string[];
|
||||||
objectNamePlural: string;
|
objectNamePlural: string;
|
||||||
@ -139,15 +140,21 @@ export const useFilteredSearchEntityQuery = ({
|
|||||||
selectedEntities: mapPaginatedObjectsToObjects({
|
selectedEntities: mapPaginatedObjectsToObjects({
|
||||||
objectNamePlural: objectNamePlural,
|
objectNamePlural: objectNamePlural,
|
||||||
pagedObjects: selectedEntitiesData,
|
pagedObjects: selectedEntitiesData,
|
||||||
}).map(mappingFunction),
|
})
|
||||||
|
.map(mappingFunction)
|
||||||
|
.filter(assertNotNull),
|
||||||
filteredSelectedEntities: mapPaginatedObjectsToObjects({
|
filteredSelectedEntities: mapPaginatedObjectsToObjects({
|
||||||
objectNamePlural: objectNamePlural,
|
objectNamePlural: objectNamePlural,
|
||||||
pagedObjects: filteredSelectedEntitiesData,
|
pagedObjects: filteredSelectedEntitiesData,
|
||||||
}).map(mappingFunction),
|
})
|
||||||
|
.map(mappingFunction)
|
||||||
|
.filter(assertNotNull),
|
||||||
entitiesToSelect: mapPaginatedObjectsToObjects({
|
entitiesToSelect: mapPaginatedObjectsToObjects({
|
||||||
objectNamePlural: objectNamePlural,
|
objectNamePlural: objectNamePlural,
|
||||||
pagedObjects: entitiesToSelectData,
|
pagedObjects: entitiesToSelectData,
|
||||||
}).map(mappingFunction),
|
})
|
||||||
|
.map(mappingFunction)
|
||||||
|
.filter(assertNotNull),
|
||||||
loading:
|
loading:
|
||||||
entitiesToSelectLoading ||
|
entitiesToSelectLoading ||
|
||||||
filteredSelectedEntitiesLoading ||
|
filteredSelectedEntitiesLoading ||
|
||||||
|
|||||||
@ -90,13 +90,7 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
objectMetadataId,
|
objectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const { defaultValue: relationDefaultValue } = useRelationFieldPreview({
|
||||||
defaultValue: relationDefaultValue,
|
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
} = useRelationFieldPreview({
|
|
||||||
relationObjectMetadataId,
|
relationObjectMetadataId,
|
||||||
skipDefaultValue:
|
skipDefaultValue:
|
||||||
fieldMetadata.type !== FieldMetadataType.Relation || hasValue,
|
fieldMetadata.type !== FieldMetadataType.Relation || hasValue,
|
||||||
@ -107,15 +101,6 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
? relationDefaultValue
|
? relationDefaultValue
|
||||||
: dataTypes[fieldMetadata.type].defaultValue;
|
: dataTypes[fieldMetadata.type].defaultValue;
|
||||||
|
|
||||||
if (
|
|
||||||
!labelIdentifierFieldPaths ||
|
|
||||||
!imageIdentifierUrlField ||
|
|
||||||
!imageIdentifierUrlPrefix ||
|
|
||||||
!imageIdentifierFormat
|
|
||||||
) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer className={className}>
|
<StyledContainer className={className}>
|
||||||
<StyledObjectSummary>
|
<StyledObjectSummary>
|
||||||
@ -160,10 +145,6 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
label: fieldMetadata.label,
|
label: fieldMetadata.label,
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName,
|
fieldName,
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hotkeyScope: 'field-preview',
|
hotkeyScope: 'field-preview',
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useObjectMainIdentifier } from '@/object-metadata/hooks/useObjectMainIdentifier';
|
|
||||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||||
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||||
|
|
||||||
@ -20,18 +19,7 @@ export const useRelationFieldPreview = ({
|
|||||||
skip: skipDefaultValue || !relationObjectMetadataItem,
|
skip: skipDefaultValue || !relationObjectMetadataItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
} = useObjectMainIdentifier(relationObjectMetadataItem);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultValue: relationObjects?.[0],
|
defaultValue: relationObjects?.[0],
|
||||||
labelIdentifierFieldPaths,
|
|
||||||
imageIdentifierUrlField,
|
|
||||||
imageIdentifierUrlPrefix,
|
|
||||||
imageIdentifierFormat,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -42,18 +42,25 @@ export const RelationPicker = ({
|
|||||||
|
|
||||||
const useFindManyQuery = (options: any) => useQuery(findManyQuery, options);
|
const useFindManyQuery = (options: any) => useQuery(findManyQuery, options);
|
||||||
|
|
||||||
const { mapToObjectIdentifiers } = useRelationField();
|
const { identifiersMapper, searchQuery } = useRelationField();
|
||||||
|
|
||||||
const workspaceMembers = useFilteredSearchEntityQuery({
|
const workspaceMembers = useFilteredSearchEntityQuery({
|
||||||
queryHook: useFindManyQuery,
|
queryHook: useFindManyQuery,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
fieldNames: fieldDefinition.metadata.searchFields,
|
fieldNames:
|
||||||
|
searchQuery?.filterFields?.(
|
||||||
|
fieldDefinition.metadata.objectMetadataNameSingular,
|
||||||
|
) ?? [],
|
||||||
filter: relationPickerSearchFilter,
|
filter: relationPickerSearchFilter,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
orderByField: 'createdAt',
|
orderByField: 'createdAt',
|
||||||
mappingFunction: mapToObjectIdentifiers,
|
mappingFunction: (record: any) =>
|
||||||
|
identifiersMapper?.(
|
||||||
|
record,
|
||||||
|
fieldDefinition.metadata.objectMetadataNameSingular,
|
||||||
|
),
|
||||||
selectedIds: recordId ? [recordId] : [],
|
selectedIds: recordId ? [recordId] : [],
|
||||||
objectNamePlural: fieldDefinition.metadata.objectMetadataNamePlural,
|
objectNamePlural: fieldDefinition.metadata.objectMetadataNamePlural,
|
||||||
});
|
});
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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 (
|
||||||
|
<RelationPickerScopeInternalContext.Provider
|
||||||
|
value={{ scopeId: relationPickerScopeId }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RelationPickerScopeInternalContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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<RelationPickerScopeInternalContextProps>();
|
||||||
@ -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<IdentifiersMapper | null>({
|
||||||
|
key: 'identifiersMapperScopedState',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
@ -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<SearchQuery | null>({
|
||||||
|
key: 'searchQueryScopedState',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
@ -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;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export type SearchQuery = {
|
||||||
|
filterFields: (relationPickerType: string) => string[];
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,9 +1,7 @@
|
|||||||
import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey';
|
import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey';
|
||||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||||
|
|
||||||
type DropdownScopeInternalContextProps = ScopedStateKey & {
|
type DropdownScopeInternalContextProps = ScopedStateKey;
|
||||||
test?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DropdownScopeInternalContext =
|
export const DropdownScopeInternalContext =
|
||||||
createScopeInternalContext<DropdownScopeInternalContextProps>();
|
createScopeInternalContext<DropdownScopeInternalContextProps>();
|
||||||
|
|||||||
@ -5,20 +5,23 @@ import { useRelationField } from '../../hooks/useRelationField';
|
|||||||
export const RelationFieldDisplay = () => {
|
export const RelationFieldDisplay = () => {
|
||||||
const { fieldValue, fieldDefinition } = useRelationField();
|
const { fieldValue, fieldDefinition } = useRelationField();
|
||||||
|
|
||||||
const { mapToObjectIdentifiers } = useRelationField();
|
const { identifiersMapper } = useRelationField();
|
||||||
|
|
||||||
if (!fieldValue || !fieldDefinition) {
|
if (!fieldValue || !fieldDefinition || !identifiersMapper) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectIdentifiers = mapToObjectIdentifiers(fieldValue);
|
const objectIdentifiers = identifiersMapper(
|
||||||
|
fieldValue,
|
||||||
|
fieldDefinition.metadata.objectMetadataNameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityChip
|
<EntityChip
|
||||||
entityId={fieldValue.id}
|
entityId={fieldValue.id}
|
||||||
name={objectIdentifiers.name}
|
name={objectIdentifiers?.name ?? ''}
|
||||||
avatarUrl={objectIdentifiers.avatarUrl}
|
avatarUrl={objectIdentifiers?.avatarUrl}
|
||||||
avatarType={objectIdentifiers.avatarType}
|
avatarType={objectIdentifiers?.avatarType}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker';
|
||||||
|
|
||||||
import { FieldContext } from '../../contexts/FieldContext';
|
import { FieldContext } from '../../contexts/FieldContext';
|
||||||
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
|
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
|
||||||
import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
|
import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
|
||||||
@ -30,35 +32,7 @@ export const useRelationField = () => {
|
|||||||
|
|
||||||
const initialValue = fieldInitialValue?.isEmpty ? null : fieldValue;
|
const initialValue = fieldInitialValue?.isEmpty ? null : fieldValue;
|
||||||
|
|
||||||
const mapToObjectIdentifiers = (record: any) => {
|
const { identifiersMapper, searchQuery } = useRelationPicker();
|
||||||
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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
@ -66,6 +40,7 @@ export const useRelationField = () => {
|
|||||||
initialValue,
|
initialValue,
|
||||||
initialSearchValue,
|
initialSearchValue,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
mapToObjectIdentifiers,
|
searchQuery,
|
||||||
|
identifiersMapper,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import styled from '@emotion/styled';
|
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 { 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 { usePersistField } from '../../../hooks/usePersistField';
|
||||||
import { useRelationField } from '../../hooks/useRelationField';
|
import { useRelationField } from '../../hooks/useRelationField';
|
||||||
|
|||||||
@ -64,11 +64,6 @@ export type FieldRelationMetadata = {
|
|||||||
fieldName: string;
|
fieldName: string;
|
||||||
useEditButton?: boolean;
|
useEditButton?: boolean;
|
||||||
relationType?: FieldDefinitionRelationType;
|
relationType?: FieldDefinitionRelationType;
|
||||||
labelIdentifierFieldPaths: string[];
|
|
||||||
imageIdentifierUrlField: string;
|
|
||||||
imageIdentifierUrlPrefix: string;
|
|
||||||
imageIdentifierFormat: 'squared' | 'rounded';
|
|
||||||
searchFields: string[];
|
|
||||||
objectMetadataNameSingular: string;
|
objectMetadataNameSingular: string;
|
||||||
objectMetadataNamePlural: string;
|
objectMetadataNamePlural: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => {
|
|||||||
hotkeyScope: customHotkeyScope,
|
hotkeyScope: customHotkeyScope,
|
||||||
isMainIdentifier:
|
isMainIdentifier:
|
||||||
columnDefinition.fieldMetadataId ===
|
columnDefinition.fieldMetadataId ===
|
||||||
objectMetadataConfig?.mainIdentifierFieldMetadataId,
|
objectMetadataConfig?.labelIdentifierFieldMetadataId,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableCell customHotkeyScope={{ scope: customHotkeyScope }} />
|
<TableCell customHotkeyScope={{ scope: customHotkeyScope }} />
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import { AvatarType } from '@/users/components/Avatar';
|
|
||||||
|
|
||||||
export type ObjectMetadataConfig = {
|
export type ObjectMetadataConfig = {
|
||||||
mainIdentifierFieldMetadataId: string;
|
labelIdentifierFieldMetadataId: string;
|
||||||
labelIdentifierFieldPaths: string[];
|
|
||||||
imageIdentifierUrlField: string;
|
|
||||||
imageIdentifierUrlPrefix: string;
|
|
||||||
imageIdentifierFormat: AvatarType;
|
|
||||||
basePathToShowPage: string;
|
basePathToShowPage: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user