Activity as standard object (#6219)
In this PR I layout the first steps to migrate Activity to a traditional Standard objects Since this is a big transition, I'd rather split it into several deployments / PRs <img width="1512" alt="image" src="https://github.com/user-attachments/assets/012e2bbf-9d1b-4723-aaf6-269ef588b050"> --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: Faisal-imtiyaz123 <142205282+Faisal-imtiyaz123@users.noreply.github.com> Co-authored-by: Prateek Jain <prateekj1171998@gmail.com>
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
|
||||
@ -3,9 +3,7 @@ import { AvatarChip, AvatarChipVariant } from 'twenty-ui';
|
||||
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
|
||||
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { MouseEvent } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
||||
|
||||
export type RecordChipProps = {
|
||||
objectNameSingular: string;
|
||||
@ -20,31 +18,22 @@ export const RecordChip = ({
|
||||
className,
|
||||
variant,
|
||||
}: RecordChipProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { recordChipData } = useRecordChipData({
|
||||
objectNameSingular,
|
||||
record,
|
||||
});
|
||||
|
||||
const handleAvatarChipClick = (event: MouseEvent) => {
|
||||
const linkToShowPage = getLinkToShowPage(objectNameSingular, record);
|
||||
|
||||
if (isNonEmptyString(linkToShowPage)) {
|
||||
event.stopPropagation();
|
||||
navigate(linkToShowPage);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AvatarChip
|
||||
placeholderColorSeed={record.id}
|
||||
name={recordChipData.name}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
onClick={handleAvatarChipClick}
|
||||
className={className}
|
||||
variant={variant}
|
||||
/>
|
||||
<UndecoratedLink to={getLinkToShowPage(objectNameSingular, record)}>
|
||||
<AvatarChip
|
||||
placeholderColorSeed={record.id}
|
||||
name={recordChipData.name}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
className={className}
|
||||
variant={variant}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { print } from 'graphql';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||
import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery';
|
||||
|
||||
const expectedQueryTemplate = `
|
||||
mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
|
||||
@ -32,8 +33,7 @@ mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
|
||||
avatarUrl
|
||||
companyId
|
||||
}
|
||||
}
|
||||
`.replace(/\s/g, '');
|
||||
}`;
|
||||
|
||||
describe('useUpdateOneRecordMutation', () => {
|
||||
it('should return a valid createManyRecordsMutation', () => {
|
||||
@ -53,11 +53,10 @@ describe('useUpdateOneRecordMutation', () => {
|
||||
|
||||
expect(updateOneRecordMutation).toBeDefined();
|
||||
|
||||
const printedReceivedQuery = print(updateOneRecordMutation).replace(
|
||||
/\s/g,
|
||||
'',
|
||||
);
|
||||
const printedReceivedQuery = print(updateOneRecordMutation);
|
||||
|
||||
expect(printedReceivedQuery).toEqual(expectedQueryTemplate);
|
||||
expect(normalizeGQLQuery(printedReceivedQuery)).toEqual(
|
||||
normalizeGQLQuery(expectedQueryTemplate),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -96,8 +96,8 @@ export const useRecordActionBar = ({
|
||||
const { deleteTableData } = useDeleteTableData(baseTableDataParams);
|
||||
|
||||
const handleDeleteClick = useCallback(() => {
|
||||
deleteTableData();
|
||||
}, [deleteTableData]);
|
||||
deleteTableData(selectedRecordIds);
|
||||
}, [deleteTableData, selectedRecordIds]);
|
||||
|
||||
const handleExecuteQuickActionOnClick = useCallback(async () => {
|
||||
callback?.();
|
||||
|
||||
@ -4,7 +4,7 @@ import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/dis
|
||||
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
|
||||
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
|
||||
import { RelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay';
|
||||
|
||||
import { RichTextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RichTextFieldDisplay';
|
||||
import { isFieldIdentifierDisplay } from '@/object-record/record-field/meta-types/display/utils/isFieldIdentifierDisplay';
|
||||
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
||||
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
|
||||
@ -12,6 +12,7 @@ import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldL
|
||||
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
|
||||
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
|
||||
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
||||
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||
@ -93,5 +94,7 @@ export const FieldDisplay = () => {
|
||||
<BooleanFieldDisplay />
|
||||
) : isFieldRating(fieldDefinition) ? (
|
||||
<RatingFieldDisplay />
|
||||
) : isFieldRichText(fieldDefinition) ? (
|
||||
<RichTextFieldDisplay />
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -20,6 +20,8 @@ import { isFieldRelationToOneObject } from '@/object-record/record-field/types/g
|
||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||
|
||||
import { RichTextFieldInput } from '@/object-record/record-field/meta-types/input/components/RichTextFieldInput';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { BooleanFieldInput } from '../meta-types/input/components/BooleanFieldInput';
|
||||
import { CurrencyFieldInput } from '../meta-types/input/components/CurrencyFieldInput';
|
||||
@ -175,6 +177,8 @@ export const FieldInput = ({
|
||||
onTab={onTab}
|
||||
onShiftTab={onShiftTab}
|
||||
/>
|
||||
) : isFieldRichText(fieldDefinition) ? (
|
||||
<RichTextFieldInput />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
|
||||
export const useIsFieldReadOnly = () => {
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
return (
|
||||
fieldDefinition.type === FieldMetadataType.RichText ||
|
||||
fieldDefinition.metadata.fieldName === 'noteTargets' ||
|
||||
fieldDefinition.metadata.fieldName === 'taskTargets' // TODO: do something cleaner
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { useTextFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useTextFieldDisplay';
|
||||
import { getFirstNonEmptyLineOfRichText } from '@/ui/input/editor/utils/getFirstNonEmptyLineOfRichText';
|
||||
import { PartialBlock } from '@blocknote/core';
|
||||
|
||||
export const RichTextFieldDisplay = () => {
|
||||
const { fieldValue } = useTextFieldDisplay();
|
||||
const parsedField =
|
||||
fieldValue === '' ? null : (JSON.parse(fieldValue) as PartialBlock[]);
|
||||
|
||||
return <>{getFirstNonEmptyLineOfRichText(parsedField)}</>;
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
import { RichTextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RichTextFieldDisplay';
|
||||
|
||||
export const RichTextFieldInput = () => {
|
||||
return <RichTextFieldDisplay />;
|
||||
};
|
||||
@ -95,6 +95,11 @@ export type FieldRawJsonMetadata = {
|
||||
placeHolder: string;
|
||||
};
|
||||
|
||||
export type FieldRichTextMetadata = {
|
||||
objectMetadataNameSingular?: string;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type FieldDefinitionRelationType =
|
||||
| 'FROM_MANY_OBJECTS'
|
||||
| 'FROM_ONE_OBJECT'
|
||||
|
||||
@ -61,7 +61,9 @@ type AssertFieldMetadataFunction = <
|
||||
? FieldAddressMetadata
|
||||
: E extends 'RAW_JSON'
|
||||
? FieldRawJsonMetadata
|
||||
: never,
|
||||
: E extends 'RICH_TEXT'
|
||||
? FieldTextMetadata
|
||||
: never,
|
||||
>(
|
||||
fieldType: E,
|
||||
fieldTypeGuard: (
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldMetadata, FieldRichTextMetadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldRichText = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type'>,
|
||||
): field is FieldDefinition<FieldRichTextMetadata> =>
|
||||
field.type === FieldMetadataType.RichText;
|
||||
@ -23,6 +23,7 @@ import { isFieldPhone } from '@/object-record/record-field/types/guards/isFieldP
|
||||
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
|
||||
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||
@ -54,6 +55,7 @@ export const isFieldValueEmpty = ({
|
||||
isFieldBoolean(fieldDefinition) ||
|
||||
isFieldRelation(fieldDefinition) ||
|
||||
isFieldRawJson(fieldDefinition) ||
|
||||
isFieldRichText(fieldDefinition) ||
|
||||
isFieldPhone(fieldDefinition)
|
||||
) {
|
||||
return isValueEmpty(fieldValue);
|
||||
|
||||
@ -34,6 +34,18 @@ export const useRecordTableRecordGqlFields = ({
|
||||
),
|
||||
...identifierQueryFields,
|
||||
position: true,
|
||||
noteTargets: {
|
||||
note: {
|
||||
id: true,
|
||||
title: true,
|
||||
},
|
||||
},
|
||||
taskTargets: {
|
||||
task: {
|
||||
id: true,
|
||||
title: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return recordGqlFields;
|
||||
|
||||
@ -17,13 +17,10 @@ export const useDeleteTableData = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const {
|
||||
resetTableRowSelection,
|
||||
selectedRowIdsSelector,
|
||||
hasUserSelectedAllRowsState,
|
||||
} = useRecordTable({
|
||||
recordTableId: recordIndexId,
|
||||
});
|
||||
const { resetTableRowSelection, hasUserSelectedAllRowsState } =
|
||||
useRecordTable({
|
||||
recordTableId: recordIndexId,
|
||||
});
|
||||
|
||||
const tableRowIds = useRecoilValue(
|
||||
tableRowIdsComponentState({
|
||||
@ -36,18 +33,14 @@ export const useDeleteTableData = ({
|
||||
});
|
||||
const { favorites, deleteFavorite } = useFavorites();
|
||||
|
||||
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
|
||||
|
||||
const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState);
|
||||
|
||||
const deleteRecords = async () => {
|
||||
let recordIdsToDelete = selectedRowIds;
|
||||
|
||||
const deleteRecords = async (recordIdsToDelete: string[]) => {
|
||||
if (hasUserSelectedAllRows) {
|
||||
const allRecordIds = await fetchAllRecordIds();
|
||||
|
||||
const unselectedRecordIds = tableRowIds.filter(
|
||||
(recordId) => !selectedRowIds.includes(recordId),
|
||||
(recordId) => !recordIdsToDelete.includes(recordId),
|
||||
);
|
||||
|
||||
recordIdsToDelete = allRecordIds.filter(
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import groupBy from 'lodash.groupby';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { Note } from '@/activities/types/Note';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import {
|
||||
@ -62,7 +66,7 @@ export const RecordShowContainer = ({
|
||||
recordLoadingFamilyState(objectRecordId),
|
||||
);
|
||||
|
||||
const [recordFromStore] = useRecoilState(
|
||||
const [recordFromStore] = useRecoilState<any>(
|
||||
recordStoreFamilyState(objectRecordId),
|
||||
);
|
||||
|
||||
@ -131,90 +135,147 @@ export const RecordShowContainer = ({
|
||||
: 'inlineFieldMetadataItems',
|
||||
);
|
||||
|
||||
const inlineRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldMetadataItem.name === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldMetadataItem.name === 'taskTargets'),
|
||||
);
|
||||
|
||||
const boxedRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
objectNameSingular !== CoreObjectNameSingular.Note &&
|
||||
fieldMetadataItem.name !== 'noteTargets' &&
|
||||
objectNameSingular !== CoreObjectNameSingular.Task &&
|
||||
fieldMetadataItem.name !== 'taskTargets',
|
||||
);
|
||||
|
||||
const isReadOnly = objectMetadataItem.isRemote;
|
||||
const isMobile = useIsMobile() || isInRightDrawer;
|
||||
const isPrefetchLoading = useIsPrefetchLoading();
|
||||
|
||||
const summary = (
|
||||
const summaryCard = isDefined(recordFromStore) ? (
|
||||
<ShowPageSummaryCard
|
||||
id={objectRecordId}
|
||||
logoOrAvatar={recordIdentifier?.avatarUrl ?? ''}
|
||||
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
||||
date={recordFromStore.createdAt ?? ''}
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
title={
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
recoilScopeId:
|
||||
objectRecordId + labelIdentifierFieldMetadataItem?.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
type:
|
||||
labelIdentifierFieldMetadataItem?.type ||
|
||||
FieldMetadataType.Text,
|
||||
iconName: '',
|
||||
fieldMetadataId: labelIdentifierFieldMetadataItem?.id ?? '',
|
||||
label: labelIdentifierFieldMetadataItem?.label || '',
|
||||
metadata: {
|
||||
fieldName: labelIdentifierFieldMetadataItem?.name || '',
|
||||
objectMetadataNameSingular: objectNameSingular,
|
||||
},
|
||||
defaultValue: labelIdentifierFieldMetadataItem?.defaultValue,
|
||||
},
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
isCentered: true,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell readonly={isReadOnly} isCentered={true} />
|
||||
</FieldContext.Provider>
|
||||
}
|
||||
avatarType={recordIdentifier?.avatarType ?? 'rounded'}
|
||||
onUploadPicture={
|
||||
objectNameSingular === 'person' ? onUploadPicture : undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
const fieldsBox = (
|
||||
<>
|
||||
{isDefined(recordFromStore) && (
|
||||
<>
|
||||
<ShowPageSummaryCard
|
||||
id={objectRecordId}
|
||||
logoOrAvatar={recordIdentifier?.avatarUrl ?? ''}
|
||||
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
||||
date={recordFromStore.createdAt ?? ''}
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
title={
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
recoilScopeId:
|
||||
objectRecordId + labelIdentifierFieldMetadataItem?.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
type:
|
||||
labelIdentifierFieldMetadataItem?.type ||
|
||||
FieldMetadataType.Text,
|
||||
iconName: '',
|
||||
fieldMetadataId: labelIdentifierFieldMetadataItem?.id ?? '',
|
||||
label: labelIdentifierFieldMetadataItem?.label || '',
|
||||
metadata: {
|
||||
fieldName: labelIdentifierFieldMetadataItem?.name || '',
|
||||
objectMetadataNameSingular: objectNameSingular,
|
||||
},
|
||||
defaultValue:
|
||||
labelIdentifierFieldMetadataItem?.defaultValue,
|
||||
},
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
isCentered: true,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell readonly={isReadOnly} isCentered={true} />
|
||||
</FieldContext.Provider>
|
||||
}
|
||||
avatarType={recordIdentifier?.avatarType ?? 'rounded'}
|
||||
onUploadPicture={
|
||||
objectNameSingular === 'person' ? onUploadPicture : undefined
|
||||
}
|
||||
/>
|
||||
<PropertyBox>
|
||||
{isPrefetchLoading ? (
|
||||
<PropertyBoxSkeletonLoader />
|
||||
) : (
|
||||
inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={loading || recordLoading}
|
||||
readonly={isReadOnly}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))
|
||||
<>
|
||||
{inlineRelationFieldMetadataItems?.map(
|
||||
(fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<ActivityTargetsInlineCell
|
||||
activityObjectNameSingular={
|
||||
objectNameSingular as
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task
|
||||
}
|
||||
activity={recordFromStore as Task | Note}
|
||||
showLabel={true}
|
||||
maxWidth={200}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
)}
|
||||
{inlineFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={loading || recordLoading}
|
||||
readonly={isReadOnly}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</PropertyBox>
|
||||
<RecordDetailDuplicatesSection
|
||||
objectRecordId={objectRecordId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
{relationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||
{boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
@ -244,25 +305,23 @@ export const RecordShowContainer = ({
|
||||
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer forceMobile={isInRightDrawer}>
|
||||
{!isMobile && summary}
|
||||
{!isMobile && summaryCard}
|
||||
{!isMobile && fieldsBox}
|
||||
</ShowPageLeftContainer>
|
||||
{recordFromStore ? (
|
||||
<ShowPageRightContainer
|
||||
targetableObject={{
|
||||
id: objectRecordId,
|
||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
isRightDrawer={isInRightDrawer}
|
||||
summary={summary}
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<ShowPageRightContainer
|
||||
targetableObject={{
|
||||
id: objectRecordId,
|
||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
isInRightDrawer={isInRightDrawer}
|
||||
summaryCard={isMobile ? summaryCard : <></>}
|
||||
fieldsBox={fieldsBox}
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
);
|
||||
|
||||
@ -1,10 +1,46 @@
|
||||
import { generateActivityTargetMorphFieldKeys } from '@/activities/utils/generateActivityTargetMorphFieldKeys';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
|
||||
export const buildFindOneRecordForShowPageOperationSignature: RecordGqlOperationSignatureFactory =
|
||||
({ objectMetadataItem }: { objectMetadataItem: ObjectMetadataItem }) => ({
|
||||
({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
}) => ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {},
|
||||
fields: generateDepthOneRecordGqlFields({ objectMetadataItem }),
|
||||
fields: {
|
||||
...generateDepthOneRecordGqlFields({ objectMetadataItem }),
|
||||
...(objectMetadataItem.nameSingular === CoreObjectNameSingular.Task
|
||||
? {
|
||||
taskTargets: {
|
||||
id: true,
|
||||
__typename: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
note: true,
|
||||
noteId: true,
|
||||
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(objectMetadataItem.nameSingular === 'Note'
|
||||
? {
|
||||
noteTargets: {
|
||||
id: true,
|
||||
__typename: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
task: true,
|
||||
taskId: true,
|
||||
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
@ -6,6 +6,7 @@ import { useIcons } from 'twenty-ui';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@ -30,6 +31,7 @@ export const useRecordShowPage = (
|
||||
}
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({ objectNameSingular });
|
||||
const { favorites, createFavorite, deleteFavorite } = useFavorites();
|
||||
@ -39,7 +41,10 @@ export const useRecordShowPage = (
|
||||
const { getIcon } = useIcons();
|
||||
const headerIcon = getIcon(objectMetadataItem?.icon);
|
||||
const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE =
|
||||
buildFindOneRecordForShowPageOperationSignature({ objectMetadataItem });
|
||||
buildFindOneRecordForShowPageOperationSignature({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
const { record, loading } = useFindOneRecord({
|
||||
objectRecordId,
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
/* eslint-disable @nx/workspace-no-navigate-prefer-link */
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
@ -10,8 +12,6 @@ import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-s
|
||||
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
|
||||
import { buildIndexTablePageURL } from '@/object-record/record-table/utils/buildIndexTableURL';
|
||||
import { useQueryVariablesFromActiveFieldsOfViewOrDefaultView } from '@/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useRecordShowPagePagination = (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import qs from 'qs';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconForbid, IconPencil, IconPlus } from 'twenty-ui';
|
||||
|
||||
|
||||
@ -4,13 +4,11 @@ import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
|
||||
export const RecordTableCellFieldInput = () => {
|
||||
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
|
||||
useContext(RecordTableContext);
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
const { isReadOnly } = useContext(RecordTableRowContext);
|
||||
|
||||
const handleEnter: FieldInputEvent = (persistField) => {
|
||||
onUpsertRecord({
|
||||
@ -89,7 +87,7 @@ export const RecordTableCellFieldInput = () => {
|
||||
onShiftTab={handleShiftTab}
|
||||
onSubmit={handleSubmit}
|
||||
onTab={handleTab}
|
||||
isReadOnly={isReadOnly}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -22,6 +22,7 @@ import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
||||
|
||||
type RecordTableCellSoftFocusModeProps = {
|
||||
@ -35,7 +36,11 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
}: RecordTableCellSoftFocusModeProps) => {
|
||||
const { columnIndex } = useContext(RecordTableCellContext);
|
||||
const closeCurrentTableCell = useCloseCurrentTableCellInEditMode();
|
||||
const { isReadOnly } = useContext(RecordTableRowContext);
|
||||
const { isReadOnly: isRowReadOnly } = useContext(RecordTableRowContext);
|
||||
|
||||
const isFieldReadOnly = useIsFieldReadOnly();
|
||||
|
||||
const isCellReadOnly = isFieldReadOnly || isRowReadOnly;
|
||||
|
||||
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||
|
||||
@ -73,7 +78,7 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
if (!isFieldInputOnly) {
|
||||
if (!isFieldInputOnly && !isCellReadOnly) {
|
||||
openTableCell();
|
||||
} else {
|
||||
toggleEditOnlyInput();
|
||||
@ -111,7 +116,7 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isFieldInputOnly) {
|
||||
if (!isFieldInputOnly && !isCellReadOnly) {
|
||||
openTableCell();
|
||||
}
|
||||
};
|
||||
@ -143,7 +148,7 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
isDefined(buttonIcon) &&
|
||||
!editModeContentOnly &&
|
||||
(!isFirstColumn || !isEmpty) &&
|
||||
!isReadOnly;
|
||||
!isCellReadOnly;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -3,7 +3,9 @@ import { ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
@ -53,6 +55,8 @@ const updateOneRecordMock = jest.fn();
|
||||
createOneRecord: createOneRecordMock,
|
||||
});
|
||||
|
||||
const objectMetadataItems = getObjectMetadataItemsMock();
|
||||
|
||||
const Wrapper = ({
|
||||
children,
|
||||
pendingRecordIdMockedValue,
|
||||
@ -64,6 +68,7 @@ const Wrapper = ({
|
||||
}) => (
|
||||
<RecoilRoot
|
||||
initializeState={(snapshot) => {
|
||||
snapshot.set(objectMetadataItemsState, objectMetadataItems);
|
||||
snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue);
|
||||
snapshot.set(draftValueState, draftValueMockedValue);
|
||||
}}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
|
||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||
@ -26,6 +28,21 @@ export const useUpsertRecord = ({
|
||||
fieldName: string,
|
||||
recordTableId: string,
|
||||
) => {
|
||||
const objectMetadataItems = snapshot
|
||||
.getLoadable(objectMetadataItemsState)
|
||||
.getValue();
|
||||
|
||||
const foundObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
if (!foundObjectMetadataItem) {
|
||||
throw new Error('Object metadata item cannot be found');
|
||||
}
|
||||
|
||||
const labelIdentifierFieldMetadataItem =
|
||||
getLabelIdentifierFieldMetadataItem(foundObjectMetadataItem);
|
||||
|
||||
const tableScopeId = getScopeIdFromComponentId(recordTableId);
|
||||
|
||||
const recordTablePendingRecordIdState = extractComponentState(
|
||||
@ -51,14 +68,14 @@ export const useUpsertRecord = ({
|
||||
if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) {
|
||||
createOneRecord({
|
||||
id: recordTablePendingRecordId,
|
||||
name: draftValue,
|
||||
[labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue,
|
||||
position: 'first',
|
||||
});
|
||||
} else if (!recordTablePendingRecordId) {
|
||||
persistField();
|
||||
}
|
||||
},
|
||||
[createOneRecord],
|
||||
[createOneRecord, objectNameSingular],
|
||||
);
|
||||
|
||||
return { upsertRecord };
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
} from 'recoil';
|
||||
|
||||
import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState';
|
||||
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
|
||||
import {
|
||||
@ -48,9 +49,14 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({
|
||||
const relationPickerSearchFilter = useRecoilValue(
|
||||
relationPickerSearchFilterState,
|
||||
);
|
||||
|
||||
const { filteredSelectedObjectRecords, loading, objectRecordsToSelect } =
|
||||
useMultiObjectSearch({
|
||||
searchFilterValue: relationPickerSearchFilter,
|
||||
excludedObjects: [
|
||||
CoreObjectNameSingular.Task,
|
||||
CoreObjectNameSingular.Note,
|
||||
],
|
||||
selectedObjectRecordIds,
|
||||
excludedObjectRecordIds: [],
|
||||
limit: 10,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery';
|
||||
import { useMultiObjectSearchMatchesSearchFilterAndToSelectQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery';
|
||||
@ -30,11 +31,13 @@ export const useMultiObjectSearch = ({
|
||||
selectedObjectRecordIds,
|
||||
limit,
|
||||
excludedObjectRecordIds = [],
|
||||
excludedObjects,
|
||||
}: {
|
||||
searchFilterValue: string;
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
limit?: number;
|
||||
excludedObjectRecordIds?: SelectedObjectRecordId[];
|
||||
excludedObjects?: CoreObjectNameSingular[];
|
||||
}): MultiObjectSearch => {
|
||||
const { selectedObjectRecords, selectedObjectRecordsLoading } =
|
||||
useMultiObjectSearchSelectedItemsQuery({
|
||||
@ -54,6 +57,7 @@ export const useMultiObjectSearch = ({
|
||||
toSelectAndMatchesSearchFilterObjectRecords,
|
||||
toSelectAndMatchesSearchFilterObjectRecordsLoading,
|
||||
} = useMultiObjectSearchMatchesSearchFilterAndToSelectQuery({
|
||||
excludedObjects,
|
||||
excludedObjectRecordIds,
|
||||
searchFilterValue,
|
||||
selectedObjectRecordIds,
|
||||
|
||||
@ -2,6 +2,7 @@ import { useQuery } from '@apollo/client';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
|
||||
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
|
||||
import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem';
|
||||
@ -21,17 +22,21 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
|
||||
excludedObjectRecordIds,
|
||||
searchFilterValue,
|
||||
limit,
|
||||
excludedObjects,
|
||||
}: {
|
||||
selectedObjectRecordIds: SelectedObjectRecordId[];
|
||||
excludedObjectRecordIds: SelectedObjectRecordId[];
|
||||
searchFilterValue: string;
|
||||
limit?: number;
|
||||
excludedObjects?: CoreObjectNameSingular[];
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const selectableObjectMetadataItems = objectMetadataItems.filter(
|
||||
({ isSystem, isRemote }) => !isSystem && !isRemote,
|
||||
);
|
||||
const selectableObjectMetadataItems = objectMetadataItems
|
||||
.filter(({ isSystem, isRemote }) => !isSystem && !isRemote)
|
||||
.filter(({ nameSingular }) => {
|
||||
return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular);
|
||||
});
|
||||
|
||||
const { searchFilterPerMetadataItemNameSingular } =
|
||||
useSearchFilterPerMetadataItem({
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
export const getObjectFilterFields = (objectSingleName: string) => {
|
||||
if (objectSingleName === 'company') {
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
if (['workspaceMember', 'person'].includes(objectSingleName)) {
|
||||
return ['name.firstName', 'name.lastName'];
|
||||
}
|
||||
|
||||
@ -84,6 +84,9 @@ export const generateEmptyFieldValue = (
|
||||
case FieldMetadataType.RawJson: {
|
||||
return null;
|
||||
}
|
||||
case FieldMetadataType.RichText: {
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unhandled FieldMetadataType');
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||
import {
|
||||
@ -7,9 +8,11 @@ import {
|
||||
|
||||
export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
if (
|
||||
[FieldMetadataType.Uuid, FieldMetadataType.Position].includes(
|
||||
fieldMetadataItem.type,
|
||||
)
|
||||
[
|
||||
FieldMetadataType.Uuid,
|
||||
FieldMetadataType.Position,
|
||||
FieldMetadataType.RichText,
|
||||
].includes(fieldMetadataItem.type)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -22,6 +25,25 @@ export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata ??
|
||||
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata;
|
||||
|
||||
// Hack to display targets on Notes and Tasks
|
||||
if (
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata?.nameSingular ===
|
||||
CoreObjectNameSingular.NoteTarget &&
|
||||
fieldMetadataItem.relationDefinition?.sourceObjectMetadata
|
||||
.nameSingular === CoreObjectNameSingular.Note
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata?.nameSingular ===
|
||||
CoreObjectNameSingular.TaskTarget &&
|
||||
fieldMetadataItem.relationDefinition?.sourceObjectMetadata
|
||||
.nameSingular === CoreObjectNameSingular.Task
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!relationMetadata ||
|
||||
// TODO: Many to many relations are not supported yet.
|
||||
|
||||
@ -36,7 +36,7 @@ export const sanitizeRecordInput = ({
|
||||
(field) => field.name === relationIdFieldName,
|
||||
);
|
||||
|
||||
return relationIdFieldMetadataItem
|
||||
return relationIdFieldMetadataItem && fieldValue?.id
|
||||
? [relationIdFieldName, fieldValue?.id ?? null]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user