diff --git a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
index ad355fdf5..5163d3fb7 100644
--- a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
+++ b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx
@@ -9,6 +9,7 @@ import {
AvatarChip,
AvatarChipVariant,
ChipSize,
+ ChipVariant,
LinkAvatarChip,
} from 'twenty-ui/components';
import { isModifiedEvent } from 'twenty-ui/utilities';
@@ -56,6 +57,7 @@ export const RecordChip = ({
avatarType={recordChipData.avatarType}
avatarUrl={recordChipData.avatarUrl ?? ''}
className={className}
+ variant={ChipVariant.Static}
/>
);
}
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx
index a602bfa09..0c5768697 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx
@@ -9,6 +9,7 @@ export const ChipFieldDisplay = () => {
objectNameSingular,
labelIdentifierLink,
isLabelIdentifierCompact,
+ isReadOnly,
} = useChipFieldDisplay();
if (!isDefined(recordValue)) {
@@ -22,6 +23,7 @@ export const ChipFieldDisplay = () => {
size={ChipSize.Small}
to={labelIdentifierLink}
isLabelHidden={isLabelIdentifierCompact}
+ forceDisableClick={isReadOnly}
/>
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx
index 0dfefca8f..b76b5c7d2 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx
@@ -3,14 +3,19 @@ import { NoteTarget } from '@/activities/types/NoteTarget';
import { TaskTarget } from '@/activities/types/TaskTarget';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordChip } from '@/object-record/components/RecordChip';
+import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
import { useRelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRelationFromManyFieldDisplay';
+
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
+import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
+import { pascalCase } from '~/utils/string/pascalCase';
export const RelationFromManyFieldDisplay = () => {
const { fieldValue, fieldDefinition } = useRelationFromManyFieldDisplay();
const { isFocused } = useFieldFocus();
+ const { isReadOnly } = useContext(FieldContext);
const { fieldName, objectMetadataNameSingular } = fieldDefinition.metadata;
@@ -45,19 +50,36 @@ export const RelationFromManyFieldDisplay = () => {
: CoreObjectNameSingular.Task;
const relationFieldName = fieldName === 'noteTargets' ? 'note' : 'task';
+ const formattedRecords = fieldValue.map((record) => {
+ if (!isDefined(record[relationFieldName])) {
+ return {
+ ...record,
+ [relationFieldName]: {
+ id: 'fallback-id',
+ title: pascalCase(relationFieldName),
+ },
+ };
+ }
+ return record;
+ });
return (
- {fieldValue
- .map((record) =>
- isDefined(record) && isDefined(record[relationFieldName]) ? (
+ {formattedRecords
+ .map((record) => {
+ if (!isDefined(record)) {
+ return undefined;
+ }
+
+ return (
- ) : undefined,
- )
+ );
+ })
.filter(isDefined)}
);
@@ -69,6 +91,7 @@ export const RelationFromManyFieldDisplay = () => {
key={record.targetObject.id}
objectNameSingular={record.targetObjectMetadataItem.nameSingular}
record={record.targetObject}
+ forceDisableClick={isReadOnly}
/>
))}
@@ -81,6 +104,7 @@ export const RelationFromManyFieldDisplay = () => {
key={record.id}
objectNameSingular={objectNameSingular}
record={record}
+ forceDisableClick={isReadOnly}
/>
))}
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx
index d1edf4c61..79d08686b 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx
@@ -1,12 +1,16 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordChip } from '@/object-record/components/RecordChip';
+import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useRelationToOneFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRelationToOneFieldDisplay';
+import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const RelationToOneFieldDisplay = () => {
const { fieldValue, fieldDefinition, generateRecordChipData } =
useRelationToOneFieldDisplay();
+ const { isReadOnly } = useContext(FieldContext);
+
if (
!isDefined(fieldValue) ||
!isDefined(fieldDefinition?.metadata.relationObjectMetadataNameSingular)
@@ -24,7 +28,7 @@ export const RelationToOneFieldDisplay = () => {
key={recordChipData.recordId}
objectNameSingular={recordChipData.objectNameSingular}
record={fieldValue}
- forceDisableClick={isWorkspaceMemberFieldMetadataRelation}
+ forceDisableClick={isWorkspaceMemberFieldMetadataRelation || isReadOnly}
/>
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts
index 4ddfdd667..6b1edd0a9 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts
@@ -18,6 +18,7 @@ export const useChipFieldDisplay = () => {
isLabelIdentifier,
labelIdentifierLink,
isLabelIdentifierCompact,
+ isReadOnly,
} = useContext(FieldContext);
const { chipGeneratorPerObjectPerField } = useContext(
@@ -48,5 +49,6 @@ export const useChipFieldDisplay = () => {
isLabelIdentifier,
labelIdentifierLink,
isLabelIdentifierCompact,
+ isReadOnly,
};
};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useRelationFromManyFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useRelationFromManyFieldDisplay.ts
index 7b9f94ac6..27d026853 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useRelationFromManyFieldDisplay.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useRelationFromManyFieldDisplay.ts
@@ -8,10 +8,10 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth';
import { FieldMetadataType } from '~/generated-metadata/graphql';
+import { isDefined } from 'twenty-shared/utils';
import { FieldContext } from '../../contexts/FieldContext';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldRelation } from '../../types/guards/isFieldRelation';
-import { isDefined } from 'twenty-shared/utils';
export const useRelationFromManyFieldDisplay = () => {
const { recordId, fieldDefinition, maxWidth } = useContext(FieldContext);
diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx
index 92fe2e887..79bdb454f 100644
--- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx
@@ -17,6 +17,7 @@ import {
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import {
FieldMetadataType,
+ RelationDefinition,
RelationDefinitionType,
} from '~/generated-metadata/graphql';
type SettingsDataModelFieldRelationSettingsFormCardProps = {
@@ -80,12 +81,22 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({
const relationType = watchFormValue('relation.type', initialRelationType);
const relationTypeConfig = RELATION_TYPES[relationType];
+ const oppositeRelationType =
+ relationType === RelationDefinitionType.MANY_TO_ONE
+ ? RelationDefinitionType.ONE_TO_MANY
+ : RelationDefinitionType.MANY_TO_ONE;
+
return (
& {
id?: string;
name?: string;
@@ -31,7 +38,7 @@ export type SettingsDataModelFieldPreviewProps = {
};
const StyledFieldPreview = styled.div<{ shrink?: boolean }>`
- align-items: flex-start;
+ align-items: center;
background-color: ${({ theme }) => theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
@@ -95,7 +102,7 @@ export const SettingsDataModelFieldPreview = ({
fieldMetadataItem.name || `${fieldMetadataItem.type}-new-field`;
const recordId =
previewRecord?.id ??
- `${objectMetadataItem.nameSingular}-${fieldName}-preview`;
+ `${objectMetadataItem.nameSingular}-${fieldName}-${fieldMetadataItem.relationDefinition?.direction}-preview`;
return (
<>
@@ -104,7 +111,7 @@ export const SettingsDataModelFieldPreview = ({
instanceId: 'record-field-component-instance-id',
}}
>
- {previewRecord ? (
+ {isDefined(previewRecord) ? (
{fieldMetadataItem.type === FieldMetadataType.BOOLEAN ? (
diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx
index 8a377259b..3997720a0 100644
--- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx
@@ -92,14 +92,16 @@ describe('useFieldPreviewValue', () => {
);
// Then
- expect(result.current).toEqual({
- __typename: 'Person',
- id: '',
- name: {
- firstName: 'John',
- lastName: 'Doe',
+ expect(result.current).toEqual([
+ {
+ __typename: 'Person',
+ id: '',
+ name: {
+ firstName: 'John',
+ lastName: 'Doe',
+ },
},
- });
+ ]);
});
it("returns the field's preview value for a Select field", () => {
diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/useFieldPreviewValue.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/useFieldPreviewValue.ts
index 6c645dd0b..fbacbc235 100644
--- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/useFieldPreviewValue.ts
+++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/useFieldPreviewValue.ts
@@ -8,12 +8,15 @@ import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils
import { getMultiSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getMultiSelectFieldPreviewValue';
import { getPhonesFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getPhonesFieldPreviewValue';
import { getSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getSelectFieldPreviewValue';
-import { FieldMetadataType } from '~/generated-metadata/graphql';
+import {
+ FieldMetadataType,
+ RelationDefinitionType,
+} from '~/generated-metadata/graphql';
type UseFieldPreviewParams = {
fieldMetadataItem: Pick<
FieldMetadataItem,
- 'type' | 'options' | 'defaultValue'
+ 'type' | 'options' | 'defaultValue' | 'relationDefinition'
>;
relationObjectMetadataItem?: ObjectMetadataItem;
skip?: boolean;
@@ -43,7 +46,10 @@ export const useFieldPreviewValue = ({
case FieldMetadataType.CURRENCY:
return getCurrencyFieldPreviewValue({ fieldMetadataItem });
case FieldMetadataType.RELATION:
- return relationFieldPreviewValue;
+ return fieldMetadataItem.relationDefinition?.direction ===
+ RelationDefinitionType.MANY_TO_ONE
+ ? relationFieldPreviewValue
+ : [relationFieldPreviewValue];
case FieldMetadataType.SELECT:
return getSelectFieldPreviewValue({ fieldMetadataItem });
case FieldMetadataType.MULTI_SELECT:
diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/usePreviewRecord.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/usePreviewRecord.ts
index b84ba73e7..17c8daa2e 100644
--- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/usePreviewRecord.ts
+++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/usePreviewRecord.ts
@@ -1,12 +1,13 @@
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
+import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { pascalCase } from '~/utils/string/pascalCase';
-import { isDefined } from 'twenty-shared/utils';
type UsePreviewRecordParams = {
objectMetadataItem: Pick<
@@ -27,8 +28,15 @@ export const usePreviewRecord = ({
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
const skip = skipFromProps || !labelIdentifierFieldMetadataItem;
+ let recordGqlFields: Record | undefined = undefined;
+ if (objectMetadataItem.nameSingular === CoreObjectNameSingular.NoteTarget)
+ recordGqlFields = { id: true, note: true };
+ if (objectMetadataItem.nameSingular === CoreObjectNameSingular.TaskTarget)
+ recordGqlFields = { id: true, task: true };
+
const { records } = useFindManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
+ recordGqlFields,
limit: 1,
skip,
});
diff --git a/packages/twenty-ui/src/components/avatar-chip/AvatarChip.tsx b/packages/twenty-ui/src/components/avatar-chip/AvatarChip.tsx
index 8b2e96047..0b9fec5a0 100644
--- a/packages/twenty-ui/src/components/avatar-chip/AvatarChip.tsx
+++ b/packages/twenty-ui/src/components/avatar-chip/AvatarChip.tsx
@@ -14,10 +14,11 @@ export const AvatarChip = ({
maxWidth,
placeholderColorSeed,
size,
+ variant = ChipVariant.Transparent,
}: AvatarChipProps) => (
& {
+export type LinkAvatarChipProps = Omit<
+ AvatarChipsCommonProps,
+ 'clickable' | 'variant'
+> & {
to: string;
onClick?: LinkChipProps['onClick'];
variant?: AvatarChipVariant;
diff --git a/packages/twenty-ui/src/components/avatar-chip/types/AvatarChipsCommonProps.type.ts b/packages/twenty-ui/src/components/avatar-chip/types/AvatarChipsCommonProps.type.ts
index ae415e5af..9139a491a 100644
--- a/packages/twenty-ui/src/components/avatar-chip/types/AvatarChipsCommonProps.type.ts
+++ b/packages/twenty-ui/src/components/avatar-chip/types/AvatarChipsCommonProps.type.ts
@@ -1,8 +1,9 @@
import { AvatarChipsLeftComponentProps } from '@ui/components/avatar-chip/AvatarChipLeftComponent';
-import { ChipSize } from '@ui/components/chip/Chip';
+import { ChipSize, ChipVariant } from '@ui/components/chip/Chip';
export type AvatarChipsCommonProps = {
size?: ChipSize;
className?: string;
maxWidth?: number;
+ variant?: ChipVariant;
} & AvatarChipsLeftComponentProps;
diff --git a/packages/twenty-ui/src/components/chip/Chip.tsx b/packages/twenty-ui/src/components/chip/Chip.tsx
index 98103b08a..204c9e492 100644
--- a/packages/twenty-ui/src/components/chip/Chip.tsx
+++ b/packages/twenty-ui/src/components/chip/Chip.tsx
@@ -19,6 +19,7 @@ export enum ChipVariant {
Regular = 'regular',
Transparent = 'transparent',
Rounded = 'rounded',
+ Static = 'static',
}
export type ChipProps = {
@@ -89,7 +90,9 @@ const StyledContainer = withTheme(styled.div<
? theme.background.transparent.light
: variant === ChipVariant.Highlighted
? theme.background.transparent.medium
- : 'inherit'};
+ : variant === ChipVariant.Static
+ ? theme.background.transparent.light
+ : 'inherit'};
}
&:active {
@@ -98,11 +101,13 @@ const StyledContainer = withTheme(styled.div<
? theme.background.transparent.medium
: variant === ChipVariant.Highlighted
? theme.background.transparent.strong
- : 'inherit'};
+ : variant === ChipVariant.Static
+ ? theme.background.transparent.light
+ : 'inherit'};
}
background-color: ${({ theme, variant }) =>
- variant === ChipVariant.Highlighted
+ variant === ChipVariant.Highlighted || variant === ChipVariant.Static
? theme.background.transparent.light
: variant === ChipVariant.Rounded
? theme.background.transparent.lighter