diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNameCellSecondaryLabel.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNameCellSecondaryLabel.tsx
new file mode 100644
index 0000000000..4d705607cf
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/components/SettingsNameCellSecondaryLabel.tsx
@@ -0,0 +1,34 @@
+import { styled } from '@linaria/react';
+import { type ReactNode } from 'react';
+
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+const StyledSettingsNameCellSecondaryLabel = styled.span`
+ color: ${themeCssVariables.font.color.light};
+ flex: 0 999 auto;
+ font-size: ${themeCssVariables.font.size.md};
+ font-weight: ${themeCssVariables.font.weight.regular};
+ min-width: 48px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &::before {
+ content: '·';
+ margin-right: ${themeCssVariables.spacing[1]};
+ }
+`;
+
+type SettingsNameCellSecondaryLabelProps = {
+ children: ReactNode;
+ title?: string;
+};
+
+export const SettingsNameCellSecondaryLabel = ({
+ children,
+ title,
+}: SettingsNameCellSecondaryLabelProps) => (
+
+ {children}
+
+);
diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx
index 7772f15c07..6e650fa722 100644
--- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx
@@ -6,6 +6,7 @@ import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifie
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
+import { SettingsNameCellSecondaryLabel } from '@/settings/components/SettingsNameCellSecondaryLabel';
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
@@ -56,21 +57,6 @@ const StyledNameLabel = styled.div`
white-space: nowrap;
`;
-const StyledInactiveLabel = styled.span`
- color: ${themeCssVariables.font.color.extraLight};
- flex: 0 999 auto;
- font-size: ${themeCssVariables.font.size.sm};
- min-width: 48px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- &::before {
- content: '·';
- margin-right: ${themeCssVariables.spacing[1]};
- }
-`;
-
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
@@ -159,9 +145,9 @@ export const SettingsObjectFieldItemTableRow = ({
if (!isFieldTypeSupported) return null;
- const isRelatedObjectLinkable =
- isDefined(relationObjectMetadataItem?.namePlural) &&
- !relationObjectMetadataItem.isSystem;
+ const isRelatedObjectLinkable = isDefined(
+ relationObjectMetadataItem?.namePlural,
+ );
const morphRelationCount = fieldMetadataItem.morphRelations?.length;
const morphRelationLabel =
@@ -199,7 +185,9 @@ export const SettingsObjectFieldItemTableRow = ({
{fieldMetadataItem.label}
{!fieldMetadataItem.isActive && (
- {t`Deactivated`}
+
+ {t`Deactivated`}
+
)}
diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx
index a7d0e479b4..63acf16434 100644
--- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx
@@ -6,6 +6,7 @@ import { ObjectMetadataIcon } from '@/object-metadata/components/ObjectMetadataI
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
+import { SettingsNameCellSecondaryLabel } from '@/settings/components/SettingsNameCellSecondaryLabel';
import {
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
StyledActionTableCell,
@@ -37,21 +38,6 @@ const StyledNameLabel = styled.div`
white-space: nowrap;
`;
-const StyledInactiveLabel = styled.span`
- color: ${themeCssVariables.font.color.extraLight};
- flex: 0 999 auto;
- font-size: ${themeCssVariables.font.size.sm};
- min-width: 48px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- &::before {
- content: '·';
- margin-right: ${themeCssVariables.spacing[1]};
- }
-`;
-
export const SettingsObjectMetadataItemTableRow = ({
action,
objectMetadataItem,
@@ -79,7 +65,9 @@ export const SettingsObjectMetadataItemTableRow = ({
{objectMetadataItem.labelPlural}
{!objectMetadataItem.isActive && (
- {t`Deactivated`}
+
+ {t`Deactivated`}
+
)}
diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx
index 33e6219d9b..92a50ee71c 100644
--- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx
@@ -7,6 +7,7 @@ import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/Enriche
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
+import { SettingsNameCellSecondaryLabel } from '@/settings/components/SettingsNameCellSecondaryLabel';
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
import { TableCell } from '@/ui/layout/table/components/TableCell';
@@ -18,7 +19,11 @@ import { useContext, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
-import { IconChevronRight, useIcons } from 'twenty-ui/icon';
+import {
+ IconChevronRight,
+ IconRelationManyToMany,
+ useIcons,
+} from 'twenty-ui/icon';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
@@ -45,21 +50,6 @@ const StyledNameLabel = styled.div`
white-space: nowrap;
`;
-const StyledInactiveLabel = styled.span`
- color: ${themeCssVariables.font.color.extraLight};
- flex: 0 999 auto;
- font-size: ${themeCssVariables.font.size.sm};
- min-width: 48px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- &::before {
- content: '·';
- margin-right: ${themeCssVariables.spacing[1]};
- }
-`;
-
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
@@ -136,31 +126,47 @@ export const SettingsObjectRelationItemTableRow = ({
fieldName: fieldMetadataItem.name,
});
- const isRelatedObjectLinkable =
- isDefined(relationObjectMetadataItem?.namePlural) &&
- !relationObjectMetadataItem.isSystem;
+ const isRelatedObjectLinkable = isDefined(
+ relationObjectMetadataItem?.namePlural,
+ );
- const morphRelationCount = fieldMetadataItem.morphRelations?.length;
+ const isMorphRelation =
+ fieldMetadataItem.type === FieldMetadataType.MORPH_RELATION;
+
+ const morphRelationCount = fieldMetadataItem.morphRelations?.length ?? 0;
+ const morphRelationTargetLabel =
+ morphRelationCount === 1 ? t`1 Object` : t`${morphRelationCount} Objects`;
+ const morphRelationFieldLabel = fieldMetadataItem.label;
+ const displayRelationType = isMorphRelation
+ ? fieldMetadataItem.morphRelations?.[0]?.type
+ : relationType;
const relationTypeLabel = (() => {
- if (fieldMetadataItem.type === FieldMetadataType.MORPH_RELATION) {
- return t`${morphRelationCount} Objects`;
- }
- if (isDefined(relationType) === true) {
- return RELATION_TYPES[relationType].label;
+ if (isDefined(displayRelationType) === true) {
+ return RELATION_TYPES[displayRelationType].label;
}
return '';
})();
- const RelationIcon = relationType
- ? RELATION_TYPES[relationType].Icon
+ const RelationIcon = displayRelationType
+ ? RELATION_TYPES[displayRelationType].Icon
: undefined;
- const targetObjectLabel =
- isRelatedObjectLinkable && isDefined(relationObjectMetadataItem)
+ const NameIcon = isMorphRelation ? IconRelationManyToMany : Icon;
+
+ const targetObjectLabel = isMorphRelation
+ ? morphRelationTargetLabel
+ : isRelatedObjectLinkable && isDefined(relationObjectMetadataItem)
? relationObjectMetadataItem.labelPlural
: fieldMetadataItem.label;
+ const fieldLabelSubtitle = isMorphRelation
+ ? morphRelationFieldLabel
+ : fieldMetadataItem.label;
+
+ const shouldDisplayFieldLabelAsSubtitle =
+ isMorphRelation || isDefined(relationObjectMetadataItem);
+
return (
- {isDefined(Icon) && (
-
)}
+ {shouldDisplayFieldLabelAsSubtitle && (
+
+ {fieldLabelSubtitle}
+
+ )}
{!fieldMetadataItem.isActive && (
- {t`Deactivated`}
+
+ {t`Deactivated`}
+
)}
diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx
index eb71b23d4b..f6dd780607 100644
--- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationsTable.tsx
@@ -1,5 +1,6 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
+import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -8,6 +9,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
+import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
import { type TableMetadata } from '@/ui/layout/table/types/TableMetadata';
@@ -19,15 +21,11 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
import { useMemo, useState } from 'react';
import { FieldMetadataType } from 'twenty-shared/types';
-import {
- IconArchive,
- IconFilter,
- IconSearch,
- IconSettings,
-} from 'twenty-ui/icon';
+import { IconArchive, IconFilter, IconSearch } from 'twenty-ui/icon';
import { Button } from 'twenty-ui/input';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
+import { isDefined } from 'twenty-shared/utils';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
@@ -79,26 +77,73 @@ type SettingsObjectRelationsTableProps = {
objectMetadataItem: EnrichedObjectMetadataItem;
};
+const getRelationTargetObjectMetadataIds = (field: FieldMetadataItem) => {
+ if (field.type === FieldMetadataType.MORPH_RELATION) {
+ return (
+ field.morphRelations?.map(
+ (relation) => relation.targetObjectMetadata.id,
+ ) ?? []
+ );
+ }
+
+ return isDefined(field.relation?.targetObjectMetadata.id)
+ ? [field.relation.targetObjectMetadata.id]
+ : [];
+};
+
+const getMorphRelationTargetLabel = (field: FieldMetadataItem) => {
+ const morphRelationCount = field.morphRelations?.length ?? 0;
+
+ return morphRelationCount === 1
+ ? '1 Object'
+ : `${morphRelationCount} Objects`;
+};
+
+const getMorphRelationFieldLabel = (field: FieldMetadataItem) => {
+ return field.label;
+};
+
export const SettingsObjectRelationsTable = ({
objectMetadataItem,
}: SettingsObjectRelationsTableProps) => {
const { t } = useLingui();
const [searchTerm, setSearchTerm] = useState('');
const [showInactive, setShowInactive] = useState(true);
- const [showSystemRelations, setShowSystemRelations] = useState(false);
const isAdvancedModeEnabled = useAtomStateValue(isAdvancedModeEnabledState);
const tableMetadata = SETTINGS_OBJECT_RELATION_TABLE_METADATA;
+ const { objectMetadataItems } = useFilteredObjectMetadataItems();
+
const relationFields = useMemo(() => {
- return objectMetadataItem.fields.filter(
- (field) =>
- (showSystemRelations || !isHiddenSystemField(field)) &&
- (field.type === FieldMetadataType.RELATION ||
- field.type === FieldMetadataType.MORPH_RELATION),
- );
- }, [objectMetadataItem.fields, showSystemRelations]);
+ return objectMetadataItem.fields.filter((field) => {
+ const isRelationField =
+ field.type === FieldMetadataType.RELATION ||
+ field.type === FieldMetadataType.MORPH_RELATION;
+
+ if (!isRelationField) {
+ return false;
+ }
+
+ const relationTargetObjectMetadataIds =
+ getRelationTargetObjectMetadataIds(field);
+
+ const isRelationToSystemObject = relationTargetObjectMetadataIds.some(
+ (objectMetadataId) =>
+ objectMetadataItems.some(
+ (objectMetadataItem) =>
+ objectMetadataItem.id === objectMetadataId &&
+ objectMetadataItem.isSystem,
+ ),
+ );
+
+ return (
+ isAdvancedModeEnabled ||
+ (!isHiddenSystemField(field) && !isRelationToSystemObject)
+ );
+ });
+ }, [objectMetadataItem.fields, isAdvancedModeEnabled, objectMetadataItems]);
const sortedRelationFields = useSortedArray(relationFields, tableMetadata);
@@ -107,16 +152,42 @@ export const SettingsObjectRelationsTable = ({
return sortedRelationFields.filter((field) => {
const matchesActiveFilter = showInactive || field.isActive;
- const matchesSearch = normalizeSearchText(field.label).includes(
- searchNormalized,
- );
+ const relationTargetObjectLabels = getRelationTargetObjectMetadataIds(
+ field,
+ ).flatMap((objectMetadataId) => {
+ const relationObjectMetadataItem = objectMetadataItems.find(
+ (objectMetadataItem) => objectMetadataItem.id === objectMetadataId,
+ );
+
+ return isDefined(relationObjectMetadataItem)
+ ? [
+ relationObjectMetadataItem.labelSingular,
+ relationObjectMetadataItem.labelPlural,
+ ]
+ : [];
+ });
+
+ const morphRelationLabels =
+ field.type === FieldMetadataType.MORPH_RELATION
+ ? [
+ getMorphRelationTargetLabel(field),
+ getMorphRelationFieldLabel(field),
+ ]
+ : [];
+
+ const searchableText = [
+ field.label,
+ ...relationTargetObjectLabels,
+ ...morphRelationLabels,
+ ]
+ .map(normalizeSearchText)
+ .join(' ');
+
+ const matchesSearch = searchableText.includes(searchNormalized);
+
return matchesActiveFilter && matchesSearch;
});
- }, [sortedRelationFields, searchTerm, showInactive]);
-
- if (relationFields.length === 0) {
- return null;
- }
+ }, [objectMetadataItems, sortedRelationFields, searchTerm, showInactive]);
return (
<>
@@ -153,17 +224,6 @@ export const SettingsObjectRelationsTable = ({
text={t`Inactive`}
toggleSize="small"
/>
- {isAdvancedModeEnabled && (
-
- setShowSystemRelations(!showSystemRelations)
- }
- toggled={showSystemRelations}
- text={t`System relations`}
- toggleSize="small"
- />
- )}
}
@@ -186,13 +246,19 @@ export const SettingsObjectRelationsTable = ({
- {filteredRelationFields.map((fieldMetadataItem) => (
-
- ))}
+ {filteredRelationFields.length > 0 ? (
+ filteredRelationFields.map((fieldMetadataItem) => (
+
+ ))
+ ) : (
+
+ {t`No relations found`}
+
+ )}
diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx
index fd0400f1a9..b2d60a5e2f 100644
--- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx
@@ -22,6 +22,12 @@ const StyledButtonContainer = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
+const StyledContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${themeCssVariables.spacing[8]};
+`;
+
type ObjectFieldsProps = {
objectMetadataItem: EnrichedObjectMetadataItem;
};
@@ -45,7 +51,7 @@ export const ObjectFields = ({ objectMetadataItem }: ObjectFieldsProps) => {
);
return (
- <>
+
{hasRelations && (
- >
+
);
};
diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx
index 3fdb4c43d5..23644f2c54 100644
--- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx
+++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx
@@ -79,7 +79,8 @@ export const SettingsObjectTable = ({
const [searchTerm, setSearchTerm] = useState('');
const [showDeactivated, setShowDeactivated] = useState(true);
- const [showSystemObjects, setShowSystemObjects] = useState(false);
+ const [showSystemObjects, setShowSystemObjects] = useState(true);
+ const shouldShowSystemObjects = isAdvancedModeEnabled && showSystemObjects;
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem();
@@ -142,13 +143,18 @@ export const SettingsObjectTable = ({
}
const isSystem = item.objectMetadataItem.isSystem;
- if (isSystem && !showSystemObjects) {
+ if (isSystem && !shouldShowSystemObjects) {
return false;
}
return true;
}),
- [sortedObjectSettingsItems, searchTerm, showDeactivated, showSystemObjects],
+ [
+ sortedObjectSettingsItems,
+ searchTerm,
+ showDeactivated,
+ shouldShowSystemObjects,
+ ],
);
return (
diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx
index 4858ed238d..7da09bd050 100644
--- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx
+++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx
@@ -94,7 +94,7 @@ export const SettingsObjects = () => {
diff --git a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjects.stories.tsx b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjects.stories.tsx
index c94959e794..0be0d8b33d 100644
--- a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjects.stories.tsx
+++ b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjects.stories.tsx
@@ -27,6 +27,6 @@ export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
- await canvas.findByText('Existing objects', undefined, { timeout: 5000 });
+ await canvas.findByRole('heading', { name: 'Objects' }, { timeout: 5000 });
},
};
diff --git a/packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectTableMetadata.ts b/packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectTableMetadata.ts
index 6e7c419097..1770a451a5 100644
--- a/packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectTableMetadata.ts
+++ b/packages/twenty-front/src/pages/settings/data-model/constants/SettingsObjectTableMetadata.ts
@@ -25,7 +25,7 @@ export const GET_SETTINGS_OBJECT_TABLE_METADATA: TableMetadata