feat: display label identifier table cell as chip with link to Record… (#3503)

* feat: display label identifier table cell as chip with link to RecordShowPage

Closes #3502

* Fix test

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thaïs
2024-01-17 13:44:36 -03:00
committed by GitHub
parent 4b7e42c38e
commit 2d929c3b91
32 changed files with 162 additions and 459 deletions

View File

@ -1,27 +0,0 @@
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '../flatMapAndSortEntityForSelectArrayByName';
describe('flatMapAndSortEntityForSelectArrayOfArrayByName', () => {
it('should return the correct value', () => {
const entityForSelectArray = [
[
{ id: 1, name: 'xRya' },
{ id: 2, name: 'BrcA' },
],
[
{ id: 3, name: 'aCxd' },
{ id: 4, name: 'kp7u' },
],
] as unknown as EntityForSelect[][];
const res =
flatMapAndSortEntityForSelectArrayOfArrayByName(entityForSelectArray);
expect(res).toHaveLength(4);
expect(res[0].id).toBe(3);
expect(res[1].id).toBe(2);
expect(res[2].id).toBe(4);
expect(res[3].id).toBe(1);
});
});

View File

@ -1,11 +0,0 @@
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
export const flatMapAndSortEntityForSelectArrayOfArrayByName = <
T extends EntityForSelect,
>(
entityForSelectArray: T[][],
) => {
const sortByName = (a: T, b: T) => a.name.localeCompare(b.name);
return entityForSelectArray.flatMap((entity) => entity).sort(sortByName);
};

View File

@ -1,99 +0,0 @@
import styled from '@emotion/styled';
import { v4 } from 'uuid';
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { Person } from '@/people/types/Person';
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
export const StyledInputContainer = styled.div`
background-color: transparent;
box-shadow: ${({ theme }) => theme.boxShadow.strong};
display: flex;
gap: ${({ theme }) => theme.spacing(0.5)};
width: ${({ theme }) => theme.spacing(62.5)};
& input,
div {
background-color: ${({ theme }) => theme.background.primary};
width: 100%;
}
div {
border-radius: ${({ theme }) => theme.spacing(1)};
overflow: hidden;
}
input {
display: flex;
flex-grow: 1;
padding: ${({ theme }) => theme.spacing(2)};
}
`;
type AddPersonToCompanyProps = {
companyId: string;
onEntitySelected: (entity?: EntityForSelect | undefined) => void;
closeDropdown?: () => void;
};
export const AddPersonToCompany = ({
companyId,
onEntitySelected,
closeDropdown,
}: AddPersonToCompanyProps) => {
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const handleEscape = () => {
goBackToPreviousHotkeyScope();
closeDropdown?.();
};
const { createOneRecord: createPerson } = useCreateOneRecord<Person>({
objectNameSingular: 'person',
});
const handleCreatePerson = async ({
firstValue,
secondValue,
}: FieldDoubleText) => {
if (!firstValue && !secondValue) return;
const person = await createPerson({
companyId,
id: v4(),
name: {
firstName: firstValue,
lastName: secondValue,
},
});
if (person) {
const entityForSelect: EntityForSelect = {
id: person.id,
name: person.name?.firstName ?? '',
avatarUrl: person.avatarUrl ?? '',
avatarType: 'rounded',
record: person,
};
onEntitySelected(entityForSelect);
}
goBackToPreviousHotkeyScope();
closeDropdown?.();
};
return (
<StyledInputContainer>
<DoubleTextInput
firstValue=""
secondValue=""
firstValuePlaceholder="First Name"
secondValuePlaceholder="Last Name"
onClickOutside={handleEscape}
onEnter={handleCreatePerson}
onEscape={handleEscape}
hotkeyScope={RelationPickerHotkeyScope.AddNew}
/>
</StyledInputContainer>
);
};

View File

@ -52,8 +52,7 @@ export const NewOpportunityButton = () => {
setIsCreatingCard(false); setIsCreatingCard(false);
}; };
const { relationPickerSearchFilter, identifiersMapper, searchQuery } = const { relationPickerSearchFilter, searchQuery } = useRelationPicker();
useRelationPicker();
const filteredSearchEntityResults = useFilteredSearchEntityQuery({ const filteredSearchEntityResults = useFilteredSearchEntityQuery({
filters: [ filters: [
@ -64,7 +63,6 @@ export const NewOpportunityButton = () => {
], ],
orderByField: 'createdAt', orderByField: 'createdAt',
selectedIds: [], selectedIds: [],
mappingFunction: (record: any) => identifiersMapper?.(record, 'company'),
objectNameSingular: CoreObjectNameSingular.Company, objectNameSingular: CoreObjectNameSingular.Company,
}); });

View File

@ -20,7 +20,7 @@ import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'
export type OpportunityPickerProps = { export type OpportunityPickerProps = {
companyId: string | null; companyId: string | null;
onSubmit: ( onSubmit: (
newCompanyId: EntityForSelect | null, newCompany: EntityForSelect | null,
newPipelineStepId: string | null, newPipelineStepId: string | null,
) => void; ) => void;
onCancel?: () => void; onCancel?: () => void;
@ -34,7 +34,7 @@ export const OpportunityPicker = ({
const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch(); const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
const { identifiersMapper, searchQuery } = useRelationPicker(); const { searchQuery } = useRelationPicker();
const filteredSearchEntityResults = useFilteredSearchEntityQuery({ const filteredSearchEntityResults = useFilteredSearchEntityQuery({
filters: [ filters: [
@ -45,7 +45,6 @@ export const OpportunityPicker = ({
], ],
orderByField: 'createdAt', orderByField: 'createdAt',
selectedIds: [], selectedIds: [],
mappingFunction: (record: any) => identifiersMapper?.(record, 'company'),
objectNameSingular: CoreObjectNameSingular.Company, objectNameSingular: CoreObjectNameSingular.Company,
}); });

View File

@ -1,11 +1,9 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { IdentifiersMapper } from '@/object-record/relation-picker/types/IdentifiersMapper';
import { getLogoUrlFromDomainName } from '~/utils';
export const ObjectMetadataItemsRelationPickerEffect = () => { export const ObjectMetadataItemsRelationPickerEffect = () => {
const { setIdentifiersMapper, setSearchQuery } = useRelationPicker({ const { setSearchQuery } = useRelationPicker({
relationPickerScopeId: 'relation-picker', relationPickerScopeId: 'relation-picker',
}); });
@ -21,62 +19,9 @@ export const ObjectMetadataItemsRelationPickerEffect = () => {
return ['name']; return ['name'];
}; };
const identifierMapper: IdentifiersMapper = (
record: any,
objectMetadataItemSingularName: string,
) => {
if (!record) {
return;
}
if (objectMetadataItemSingularName === 'company') {
return {
id: record.id,
name: record.name,
avatarUrl: getLogoUrlFromDomainName(record.domainName ?? ''),
avatarType: 'squared',
record: record,
};
}
if (
['workspaceMember', 'person'].includes(objectMetadataItemSingularName)
) {
return {
id: record.id,
name:
(record.name?.firstName ?? '') + ' ' + (record.name?.lastName ?? ''),
avatarUrl: record.avatarUrl,
avatarType: 'rounded',
record: record,
};
}
if (['opportunity'].includes(objectMetadataItemSingularName)) {
return {
id: record.id,
name: record?.company?.name ?? record.name,
avatarUrl: record.avatarUrl,
avatarType: 'rounded',
record: record,
};
}
return {
id: record.id,
name: record.name,
avatarUrl: record.avatarUrl,
avatarType: 'rounded',
record,
};
};
useEffect(() => { useEffect(() => {
setIdentifiersMapper(() => identifierMapper); setSearchQuery({ computeFilterFields });
setSearchQuery({ }, [setSearchQuery]);
computeFilterFields,
});
}, [setIdentifiersMapper, setSearchQuery]);
return <></>; return <></>;
}; };

View File

@ -1,16 +1,8 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
export const useMapToObjectRecordIdentifier = ({ export const useMapToObjectRecordIdentifier =
objectMetadataItem, ({ objectMetadataItem }: { objectMetadataItem: ObjectMetadataItem }) =>
}: { (record: ObjectRecord) =>
objectMetadataItem: ObjectMetadataItem; getObjectRecordIdentifier({ objectMetadataItem, record });
}): ((record: ObjectRecord) => ObjectRecordIdentifier) => {
return (record: ObjectRecord) =>
getObjectRecordIdentifier({
objectMetadataItem,
record,
});
};

View File

@ -14,34 +14,25 @@ export const getObjectRecordIdentifier = ({
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
record: ObjectRecord; record: ObjectRecord;
}): ObjectRecordIdentifier => { }): ObjectRecordIdentifier => {
switch (objectMetadataItem.nameSingular) { if (objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity) {
case CoreObjectNameSingular.Opportunity: return {
return { id: record.id,
id: record.id, name: record?.company?.name ?? record.name,
name: record?.company?.name ?? record.name, avatarUrl: record.avatarUrl,
avatarUrl: record.avatarUrl, avatarType: 'rounded',
avatarType: 'rounded', linkToShowPage: `/opportunities/${record.id}`,
linkToShowPage: `/opportunities/${record.id}`, };
};
} }
const labelIdentifierFieldMetadataItem = const labelIdentifierFieldMetadataItem =
getLabelIdentifierFieldMetadataItem(objectMetadataItem); getLabelIdentifierFieldMetadataItem(objectMetadataItem);
let labelIdentifierFieldValue = ''; const labelIdentifierFieldValue =
labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FullName
switch (labelIdentifierFieldMetadataItem?.type) { ? `${record.name?.firstName ?? ''} ${record.name?.lastName ?? ''}`
case FieldMetadataType.FullName: { : labelIdentifierFieldMetadataItem?.name
labelIdentifierFieldValue = `${record.name?.firstName ?? ''} ${ ? (record[labelIdentifierFieldMetadataItem.name] as string | number)
record.name?.lastName ?? ''
}`;
break;
}
default:
labelIdentifierFieldValue = labelIdentifierFieldMetadataItem
? record[labelIdentifierFieldMetadataItem.name]
: ''; : '';
}
const imageIdentifierFieldMetadata = objectMetadataItem.fields.find( const imageIdentifierFieldMetadata = objectMetadataItem.fields.find(
(field) => field.id === objectMetadataItem.imageIdentifierFieldMetadataId, (field) => field.id === objectMetadataItem.imageIdentifierFieldMetadataId,
@ -57,9 +48,9 @@ export const getObjectRecordIdentifier = ({
: 'rounded'; : 'rounded';
const avatarUrl = const avatarUrl =
objectMetadataItem.nameSingular === CoreObjectNameSingular.Company (objectMetadataItem.nameSingular === CoreObjectNameSingular.Company
? getLogoUrlFromDomainName(record['domainName'] ?? '') ? getLogoUrlFromDomainName(record['domainName'] ?? '')
: imageIdentifierFieldValue ?? null; : imageIdentifierFieldValue) ?? '';
const basePathToShowPage = getBasePathToShowPage({ const basePathToShowPage = getBasePathToShowPage({
objectMetadataItem, objectMetadataItem,
@ -69,7 +60,7 @@ export const getObjectRecordIdentifier = ({
return { return {
id: record.id, id: record.id,
name: labelIdentifierFieldValue, name: `${labelIdentifierFieldValue}`,
avatarUrl, avatarUrl,
avatarType, avatarType,
linkToShowPage, linkToShowPage,

View File

@ -21,7 +21,7 @@ export const RecordChip = ({ objectNameSingular, record }: RecordChipProps) => {
entityId={record.id} entityId={record.id}
name={objectRecordIdentifier.name} name={objectRecordIdentifier.name}
avatarType={objectRecordIdentifier.avatarType} avatarType={objectRecordIdentifier.avatarType}
avatarUrl={objectRecordIdentifier.avatarUrl ?? undefined} avatarUrl={objectRecordIdentifier.avatarUrl}
linkToEntity={objectRecordIdentifier.linkToShowPage} linkToEntity={objectRecordIdentifier.linkToShowPage}
/> />
); );

View File

@ -27,39 +27,33 @@ import { isFieldUuid } from '../types/guards/isFieldUuid';
export const FieldDisplay = () => { export const FieldDisplay = () => {
const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext); const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext);
if (
isLabelIdentifier && return isLabelIdentifier &&
(isFieldText(fieldDefinition) || isFieldFullName(fieldDefinition)) (isFieldText(fieldDefinition) ||
) { isFieldFullName(fieldDefinition) ||
return <ChipFieldDisplay />; isFieldNumber(fieldDefinition)) ? (
} <ChipFieldDisplay />
return ( ) : isFieldRelation(fieldDefinition) ? (
<> <RelationFieldDisplay />
{isFieldRelation(fieldDefinition) ? ( ) : isFieldText(fieldDefinition) ? (
<RelationFieldDisplay /> <TextFieldDisplay />
) : isFieldText(fieldDefinition) ? ( ) : isFieldUuid(fieldDefinition) ? (
<TextFieldDisplay /> <UuidFieldDisplay />
) : isFieldUuid(fieldDefinition) ? ( ) : isFieldEmail(fieldDefinition) ? (
<UuidFieldDisplay /> <EmailFieldDisplay />
) : isFieldEmail(fieldDefinition) ? ( ) : isFieldDateTime(fieldDefinition) ? (
<EmailFieldDisplay /> <DateFieldDisplay />
) : isFieldDateTime(fieldDefinition) ? ( ) : isFieldNumber(fieldDefinition) ? (
<DateFieldDisplay /> <NumberFieldDisplay />
) : isFieldNumber(fieldDefinition) ? ( ) : isFieldLink(fieldDefinition) ? (
<NumberFieldDisplay /> <LinkFieldDisplay />
) : isFieldLink(fieldDefinition) ? ( ) : isFieldCurrency(fieldDefinition) ? (
<LinkFieldDisplay /> <CurrencyFieldDisplay />
) : isFieldCurrency(fieldDefinition) ? ( ) : isFieldFullName(fieldDefinition) ? (
<CurrencyFieldDisplay /> <FullNameFieldDisplay />
) : isFieldFullName(fieldDefinition) ? ( ) : isFieldPhone(fieldDefinition) ? (
<FullNameFieldDisplay /> <PhoneFieldDisplay />
) : isFieldPhone(fieldDefinition) ? ( ) : isFieldSelect(fieldDefinition) ? (
<PhoneFieldDisplay /> <SelectFieldDisplay />
) : isFieldSelect(fieldDefinition) ? ( ) : null;
<SelectFieldDisplay />
) : (
<></>
)}
</>
);
}; };

View File

@ -1,25 +1,12 @@
import { RecordChip } from '@/object-record/components/RecordChip';
import { useChipField } from '@/object-record/field/meta-types/hooks/useChipField'; import { useChipField } from '@/object-record/field/meta-types/hooks/useChipField';
import { EntityChip } from '@/ui/display/chip/components/EntityChip';
export const ChipFieldDisplay = () => { export const ChipFieldDisplay = () => {
const { const { objectNameSingular, record } = useChipField();
record,
entityId,
identifiersMapper,
objectNameSingular,
basePathToShowPage,
} = useChipField();
// TODO: remove this and use ObjectRecordChip instead if (!record) return null;
const identifiers = identifiersMapper?.(record, objectNameSingular ?? '');
return ( return (
<EntityChip <RecordChip objectNameSingular={objectNameSingular || ''} record={record} />
name={identifiers?.name ?? ''}
avatarUrl={identifiers?.avatarUrl}
avatarType={identifiers?.avatarType}
entityId={entityId}
linkToEntity={basePathToShowPage + entityId}
/>
); );
}; };

View File

@ -1,30 +1,18 @@
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; import { RecordChip } from '@/object-record/components/RecordChip';
import { EntityChip } from '@/ui/display/chip/components/EntityChip';
import { useRelationField } from '../../hooks/useRelationField'; import { useRelationField } from '../../hooks/useRelationField';
export const RelationFieldDisplay = () => { export const RelationFieldDisplay = () => {
const { fieldValue, fieldDefinition } = useRelationField(); const { fieldValue, fieldDefinition } = useRelationField();
const { identifiersMapper } = useRelationPicker({ if (!fieldValue || !fieldDefinition) return null;
relationPickerScopeId: 'relation-picker',
});
if (!fieldValue || !fieldDefinition || !identifiersMapper) {
return <></>;
}
const objectIdentifiers = identifiersMapper(
fieldValue,
fieldDefinition.metadata.relationObjectMetadataNameSingular,
);
return ( return (
<EntityChip <RecordChip
entityId={fieldValue.id} objectNameSingular={
name={objectIdentifiers?.name ?? ''} fieldDefinition.metadata.relationObjectMetadataNameSingular
avatarUrl={objectIdentifiers?.avatarUrl} }
avatarType={objectIdentifiers?.avatarType} record={fieldValue}
/> />
); );
}; };

View File

@ -3,31 +3,25 @@ import { useRecoilValue } from 'recoil';
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState'; import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName'; import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
import { isFieldNumber } from '@/object-record/field/types/guards/isFieldNumber';
import { isFieldText } from '@/object-record/field/types/guards/isFieldText'; import { isFieldText } from '@/object-record/field/types/guards/isFieldText';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { FieldContext } from '../../contexts/FieldContext'; import { FieldContext } from '../../contexts/FieldContext';
export const useChipField = () => { export const useChipField = () => {
const { entityId, fieldDefinition, basePathToShowPage } = const { entityId, fieldDefinition } = useContext(FieldContext);
useContext(FieldContext);
const objectNameSingular = const objectNameSingular =
isFieldText(fieldDefinition) || isFieldFullName(fieldDefinition) isFieldText(fieldDefinition) ||
isFieldFullName(fieldDefinition) ||
isFieldNumber(fieldDefinition)
? fieldDefinition.metadata.objectMetadataNameSingular ? fieldDefinition.metadata.objectMetadataNameSingular
: undefined; : undefined;
const record = useRecoilValue<any | null>(entityFieldsFamilyState(entityId)); const record = useRecoilValue(entityFieldsFamilyState(entityId));
const { identifiersMapper } = useRelationPicker({
relationPickerScopeId: 'relation-picker',
});
return { return {
basePathToShowPage,
entityId,
objectNameSingular, objectNameSingular,
record, record,
identifiersMapper,
}; };
}; };

View File

@ -1,20 +1,28 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test'; import { expect, fn, userEvent, within } from '@storybook/test';
import { useSetRecoilState } from 'recoil';
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider'; import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useBooleanField } from '../../../hooks/useBooleanField';
import { import {
BooleanFieldInput, BooleanFieldInput,
BooleanFieldInputProps, BooleanFieldInputProps,
} from '../BooleanFieldInput'; } from '../BooleanFieldInput';
const BooleanFieldValueSetterEffect = ({ value }: { value: boolean }) => { const BooleanFieldValueSetterEffect = ({
const { setFieldValue } = useBooleanField(); value,
entityId,
}: {
value: boolean;
entityId: string;
}) => {
const setField = useSetRecoilState(entityFieldsFamilyState(entityId));
useEffect(() => { useEffect(() => {
setFieldValue(value); setField({ id: entityId, Boolean: value });
}, [setFieldValue, value]); }, [entityId, setField, value]);
return <></>; return <></>;
}; };
@ -42,7 +50,7 @@ const BooleanFieldInputWithContext = ({
}} }}
entityId={entityId} entityId={entityId}
> >
<BooleanFieldValueSetterEffect value={value} /> <BooleanFieldValueSetterEffect value={value} entityId={entityId ?? ''} />
<BooleanFieldInput onSubmit={onSubmit} testId="boolean-field-input" /> <BooleanFieldInput onSubmit={onSubmit} testId="boolean-field-input" />
</FieldContextProvider> </FieldContextProvider>
); );
@ -53,6 +61,7 @@ const meta: Meta = {
component: BooleanFieldInputWithContext, component: BooleanFieldInputWithContext,
args: { args: {
value: true, value: true,
entityId: 'id-1',
}, },
}; };

View File

@ -1,9 +1,8 @@
import { atomFamily } from 'recoil'; import { atomFamily } from 'recoil';
export const entityFieldsFamilyState = atomFamily< import { ObjectRecord } from '@/object-record/types/ObjectRecord';
Record<string, unknown> | null,
string export const entityFieldsFamilyState = atomFamily<ObjectRecord | null, string>({
>({
key: 'entityFieldsFamilyState', key: 'entityFieldsFamilyState',
default: null, default: null,
}); });

View File

@ -11,8 +11,7 @@ export const entityFieldsFamilySelector = selectorFamily({
set: set:
<T>({ fieldName, entityId }: { fieldName: string; entityId: string }) => <T>({ fieldName, entityId }: { fieldName: string; entityId: string }) =>
({ set }, newValue: T) => ({ set }, newValue: T) =>
set(entityFieldsFamilyState(entityId), (prevState) => ({ set(entityFieldsFamilyState(entityId), (prevState) =>
...prevState, prevState ? { ...prevState, [fieldName]: newValue } : null,
[fieldName]: newValue, ),
})),
}); });

View File

@ -125,7 +125,7 @@ export const RecordRelationFieldCardSection = () => {
const { relationPickerSearchFilter, setRelationPickerSearchFilter } = const { relationPickerSearchFilter, setRelationPickerSearchFilter } =
useRelationPicker({ relationPickerScopeId: dropdownId }); useRelationPicker({ relationPickerScopeId: dropdownId });
const { identifiersMapper, searchQuery } = useRelationPicker(); const { searchQuery } = useRelationPicker();
const entities = useFilteredSearchEntityQuery({ const entities = useFilteredSearchEntityQuery({
filters: [ filters: [
@ -138,8 +138,6 @@ export const RecordRelationFieldCardSection = () => {
}, },
], ],
orderByField: 'createdAt', orderByField: 'createdAt',
mappingFunction: (recordToMap) =>
identifiersMapper?.(recordToMap, relationObjectMetadataNameSingular),
selectedIds: relationRecordIds, selectedIds: relationRecordIds,
excludeEntityIds: relationRecordIds, excludeEntityIds: relationRecordIds,
objectNameSingular: relationObjectMetadataNameSingular, objectNameSingular: relationObjectMetadataNameSingular,

View File

@ -117,17 +117,13 @@ export const RecordTable = ({
recordTableScopeId={scopeId} recordTableScopeId={scopeId}
onColumnsChange={onColumnsChange} onColumnsChange={onColumnsChange}
> >
<> {!!objectNamePlural && (
{objectNamePlural ? ( <StyledTable ref={recordTableRef} className="entity-table-cell">
<StyledTable ref={recordTableRef} className="entity-table-cell"> <RecordTableHeader createRecord={createRecord} />
<RecordTableHeader createRecord={createRecord} /> <RecordTableBodyEffect objectNamePlural={objectNamePlural} />
<RecordTableBodyEffect objectNamePlural={objectNamePlural} /> <RecordTableBody objectNamePlural={objectNamePlural} />
<RecordTableBody objectNamePlural={objectNamePlural} /> </StyledTable>
</StyledTable> )}
) : (
<></>
)}
</>
</RecordTableScope> </RecordTableScope>
); );
}; };

View File

@ -1,8 +1,8 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
@ -26,9 +26,6 @@ export const RecordTableCellContainer = ({
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
const currentRowId = useContext(RowIdContext); const currentRowId = useContext(RowIdContext);
const { getObjectMetadataConfigState } = useRecordTableStates();
const objectMetadataConfig = useRecoilValue(getObjectMetadataConfigState());
const { setCurrentRowSelected } = useCurrentRowSelected(); const { setCurrentRowSelected } = useCurrentRowSelected();
@ -44,6 +41,11 @@ export const RecordTableCellContainer = ({
const columnDefinition = useContext(ColumnContext); const columnDefinition = useContext(ColumnContext);
const { basePathToShowPage, objectMetadataItem } = useObjectMetadataItem({
objectNameSingular:
columnDefinition?.metadata.objectMetadataNameSingular || '',
});
const updateRecord = useContext(RecordUpdateContext); const updateRecord = useContext(RecordUpdateContext);
if (!columnDefinition || !currentRowId) { if (!columnDefinition || !currentRowId) {
@ -65,16 +67,13 @@ export const RecordTableCellContainer = ({
fieldDefinition: columnDefinition, fieldDefinition: columnDefinition,
useUpdateRecord: () => [updateRecord, {}], useUpdateRecord: () => [updateRecord, {}],
hotkeyScope: customHotkeyScope, hotkeyScope: customHotkeyScope,
basePathToShowPage: objectMetadataConfig?.basePathToShowPage, basePathToShowPage,
isLabelIdentifier: isLabelIdentifierField({ isLabelIdentifier: isLabelIdentifierField({
fieldMetadataItem: { fieldMetadataItem: {
id: columnDefinition.fieldMetadataId, id: columnDefinition.fieldMetadataId,
name: columnDefinition.metadata.fieldName, name: columnDefinition.metadata.fieldName,
}, },
objectMetadataItem: { objectMetadataItem,
labelIdentifierFieldMetadataId:
objectMetadataConfig?.labelIdentifierFieldMetadataId,
},
}), }),
}} }}
> >

View File

@ -96,6 +96,6 @@ export const RecordTableCell = ({
/> />
} }
nonEditModeContent={<FieldDisplay />} nonEditModeContent={<FieldDisplay />}
></TableCellContainer> />
); );
}; };

View File

@ -1,6 +1,5 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { FieldDefinition } from '@/object-record/field/types/FieldDefinition'; import { FieldDefinition } from '@/object-record/field/types/FieldDefinition';
import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata'; import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
@ -31,7 +30,6 @@ export const RelationPicker = ({
const { const {
relationPickerSearchFilter, relationPickerSearchFilter,
setRelationPickerSearchFilter, setRelationPickerSearchFilter,
identifiersMapper,
searchQuery, searchQuery,
} = useRelationPicker({ relationPickerScopeId: 'relation-picker' }); } = useRelationPicker({ relationPickerScopeId: 'relation-picker' });
@ -39,12 +37,6 @@ export const RelationPicker = ({
setRelationPickerSearchFilter(initialSearchFilter ?? ''); setRelationPickerSearchFilter(initialSearchFilter ?? '');
}, [initialSearchFilter, setRelationPickerSearchFilter]); }, [initialSearchFilter, setRelationPickerSearchFilter]);
const { objectNameSingular: relationObjectNameSingular } =
useObjectNameSingularFromPlural({
objectNamePlural:
fieldDefinition.metadata.relationObjectMetadataNamePlural,
});
const entities = useFilteredSearchEntityQuery({ const entities = useFilteredSearchEntityQuery({
filters: [ filters: [
{ {
@ -56,18 +48,15 @@ export const RelationPicker = ({
}, },
], ],
orderByField: 'createdAt', orderByField: 'createdAt',
mappingFunction: (record: any) =>
identifiersMapper?.(
record,
fieldDefinition.metadata.relationObjectMetadataNameSingular,
),
selectedIds: recordId ? [recordId] : [], selectedIds: recordId ? [recordId] : [],
excludeEntityIds: excludeRecordIds, excludeEntityIds: excludeRecordIds,
objectNameSingular: relationObjectNameSingular, objectNameSingular:
fieldDefinition.metadata.relationObjectMetadataNameSingular,
}); });
const handleEntitySelected = (selectedEntity: any | null | undefined) => const handleEntitySelected = (
onSubmit(selectedEntity ?? null); selectedEntity: EntityForSelect | null | undefined,
) => onSubmit(selectedEntity ?? null);
return ( return (
<SingleEntitySelect <SingleEntitySelect

View File

@ -14,6 +14,8 @@ import { SingleEntitySelect } from '../SingleEntitySelect';
const entities = mockedPeopleData.map<EntityForSelect>((person) => ({ const entities = mockedPeopleData.map<EntityForSelect>((person) => ({
id: person.id, id: person.id,
name: person.name.firstName + ' ' + person.name.lastName, name: person.name.firstName + ' ' + person.name.lastName,
avatarUrl: person.avatarUrl,
avatarType: 'rounded',
record: person, record: person,
})); }));

View File

@ -13,7 +13,6 @@ export const useRelationPickerScopedStates = (args?: {
); );
const { const {
identifiersMapperState,
relationPickerSearchFilterState, relationPickerSearchFilterState,
relationPickerPreselectedIdState, relationPickerPreselectedIdState,
searchQueryState, searchQueryState,
@ -23,7 +22,6 @@ export const useRelationPickerScopedStates = (args?: {
return { return {
scopeId, scopeId,
identifiersMapperState,
relationPickerSearchFilterState, relationPickerSearchFilterState,
relationPickerPreselectedIdState, relationPickerPreselectedIdState,
searchQueryState, searchQueryState,

View File

@ -15,7 +15,6 @@ export const useRelationPicker = (props?: useRelationPickeProps) => {
); );
const { const {
identifiersMapperState,
searchQueryState, searchQueryState,
relationPickerSearchFilterState, relationPickerSearchFilterState,
relationPickerPreselectedIdState, relationPickerPreselectedIdState,
@ -23,10 +22,6 @@ export const useRelationPicker = (props?: useRelationPickeProps) => {
relationPickerScopedId: scopeId, relationPickerScopedId: scopeId,
}); });
const [identifiersMapper, setIdentifiersMapper] = useRecoilState(
identifiersMapperState,
);
const [searchQuery, setSearchQuery] = useRecoilState(searchQueryState); const [searchQuery, setSearchQuery] = useRecoilState(searchQueryState);
const [relationPickerSearchFilter, setRelationPickerSearchFilter] = const [relationPickerSearchFilter, setRelationPickerSearchFilter] =
@ -37,8 +32,6 @@ export const useRelationPicker = (props?: useRelationPickeProps) => {
return { return {
scopeId, scopeId,
identifiersMapper,
setIdentifiersMapper,
searchQuery, searchQuery,
setSearchQuery, setSearchQuery,
relationPickerSearchFilter, relationPickerSearchFilter,

View File

@ -1,8 +0,0 @@
import { IdentifiersMapper } from '@/object-record/relation-picker/types/IdentifiersMapper';
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
export const identifiersMapperScopedState =
createStateScopeMap<IdentifiersMapper | null>({
key: 'identifiersMapperScopedState',
defaultValue: null,
});

View File

@ -1,9 +1,4 @@
import { AvatarType } from '@/users/components/Avatar'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
export type EntityForSelect = { export type EntityForSelect = ObjectRecordIdentifier & { record: ObjectRecord };
id: string;
name: string;
avatarUrl?: string;
avatarType?: AvatarType;
record: any;
};

View File

@ -1,14 +0,0 @@
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;

View File

@ -1,4 +1,3 @@
import { identifiersMapperScopedState } from '@/object-record/relation-picker/states/identifiersMapperScopedState';
import { relationPickerPreselectedIdScopedState } from '@/object-record/relation-picker/states/relationPickerPreselectedIdScopedState'; import { relationPickerPreselectedIdScopedState } from '@/object-record/relation-picker/states/relationPickerPreselectedIdScopedState';
import { relationPickerSearchFilterScopedState } from '@/object-record/relation-picker/states/relationPickerSearchFilterScopedState'; import { relationPickerSearchFilterScopedState } from '@/object-record/relation-picker/states/relationPickerSearchFilterScopedState';
import { searchQueryScopedState } from '@/object-record/relation-picker/states/searchQueryScopedState'; import { searchQueryScopedState } from '@/object-record/relation-picker/states/searchQueryScopedState';
@ -9,11 +8,6 @@ export const getRelationPickerScopedStates = ({
}: { }: {
relationPickerScopeId: string; relationPickerScopeId: string;
}) => { }) => {
const identifiersMapperState = getScopedStateDeprecated(
identifiersMapperScopedState,
relationPickerScopeId,
);
const searchQueryState = getScopedStateDeprecated( const searchQueryState = getScopedStateDeprecated(
searchQueryScopedState, searchQueryScopedState,
relationPickerScopeId, relationPickerScopeId,
@ -30,7 +24,6 @@ export const getRelationPickerScopedStates = ({
); );
return { return {
identifiersMapperState,
relationPickerSearchFilterState, relationPickerSearchFilterState,
relationPickerPreselectedIdState, relationPickerPreselectedIdState,
searchQueryState, searchQueryState,

View File

@ -3,7 +3,7 @@ import { AvatarType } from '@/users/components/Avatar';
export type ObjectRecordIdentifier = { export type ObjectRecordIdentifier = {
id: string; id: string;
name: string; name: string;
avatarUrl?: string | null; avatarUrl: string;
avatarType?: AvatarType | null; avatarType?: AvatarType | null;
linkToShowPage?: string; linkToShowPage?: string;
}; };

View File

@ -85,10 +85,6 @@ describe('useFilteredSearchEntityQuery', () => {
filters: [{ fieldNames: ['name'], filter: 'Entity' }], filters: [{ fieldNames: ['name'], filter: 'Entity' }],
sortOrder: 'AscNullsLast', sortOrder: 'AscNullsLast',
selectedIds: ['1'], selectedIds: ['1'],
mappingFunction: (entity): any => ({
value: entity.id,
label: entity.name,
}),
limit: 10, limit: 10,
excludeEntityIds: ['2'], excludeEntityIds: ['2'],
objectNameSingular: 'person', objectNameSingular: 'person',

View File

@ -1,9 +1,11 @@
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderBy } from '@/object-metadata/types/OrderBy';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect'; import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { assertNotNull } from '~/utils/assert'; import { assertNotNull } from '~/utils/assert';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -19,7 +21,6 @@ export const useFilteredSearchEntityQuery = ({
filters, filters,
sortOrder = 'AscNullsLast', sortOrder = 'AscNullsLast',
selectedIds, selectedIds,
mappingFunction,
limit, limit,
excludeEntityIds = [], excludeEntityIds = [],
objectNameSingular, objectNameSingular,
@ -28,11 +29,18 @@ export const useFilteredSearchEntityQuery = ({
filters: SearchFilter[]; filters: SearchFilter[];
sortOrder?: OrderBy; sortOrder?: OrderBy;
selectedIds: string[]; selectedIds: string[];
mappingFunction: (entity: any) => EntityForSelect | undefined;
limit?: number; limit?: number;
excludeEntityIds?: string[]; excludeEntityIds?: string[];
objectNameSingular: string; objectNameSingular: string;
}): EntitiesForMultipleEntitySelect<EntityForSelect> => { }): EntitiesForMultipleEntitySelect<EntityForSelect> => {
const { mapToObjectRecordIdentifier } = useObjectMetadataItem({
objectNameSingular,
});
const mappingFunction = (record: ObjectRecord) => ({
...mapToObjectRecordIdentifier(record),
record,
});
const { loading: selectedRecordsLoading, records: selectedRecords } = const { loading: selectedRecordsLoading, records: selectedRecords } =
useFindManyRecords({ useFindManyRecords({
objectNameSingular, objectNameSingular,

View File

@ -47,34 +47,34 @@ export const EntityChip = ({
} }
}; };
return isNonEmptyString(name) ? ( return (
<Chip isNonEmptyString(name) && (
label={name} <Chip
variant={ label={name}
linkToEntity variant={
? variant === EntityChipVariant.Regular linkToEntity
? ChipVariant.Highlighted ? variant === EntityChipVariant.Regular
: ChipVariant.Regular ? ChipVariant.Highlighted
: ChipVariant.Transparent : ChipVariant.Regular
} : ChipVariant.Transparent
leftComponent={ }
LeftIcon ? ( leftComponent={
<LeftIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} /> LeftIcon ? (
) : ( <LeftIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
<Avatar ) : (
avatarUrl={avatarUrl} <Avatar
colorId={entityId} avatarUrl={avatarUrl}
placeholder={name} colorId={entityId}
size="sm" placeholder={name}
type={avatarType} size="sm"
/> type={avatarType}
) />
} )
clickable={!!linkToEntity} }
onClick={handleLinkClick} clickable={!!linkToEntity}
className={className} onClick={handleLinkClick}
/> className={className}
) : ( />
<></> )
); );
}; };