diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index e59f7f8c00..3bedb55d3b 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -8,6 +8,7 @@ import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapTo import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; +import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { useGenerateCreateManyRecordMutation } from '@/object-record/hooks/useGenerateCreateManyRecordMutation'; import { useGenerateCreateOneRecordMutation } from '@/object-record/hooks/useGenerateCreateOneRecordMutation'; @@ -120,9 +121,8 @@ export const useObjectMetadataItem = ( objectMetadataItem, }); - const labelIdentifierFieldMetadata = objectMetadataItem.fields.find( - ({ name }) => name === 'name', - ); + const labelIdentifierFieldMetadata = + getLabelIdentifierFieldMetadataItem(objectMetadataItem); const basePathToShowPage = getBasePathToShowPage({ objectMetadataItem, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemForSettings.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemForSettings.ts index 0431b78ee5..773a467133 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemForSettings.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemForSettings.ts @@ -51,7 +51,12 @@ export const useObjectMetadataItemForSettings = () => { const editObjectMetadataItem = ( input: Pick< ObjectMetadataItem, - 'id' | 'labelPlural' | 'labelSingular' | 'icon' | 'description' + | 'description' + | 'icon' + | 'id' + | 'labelIdentifierFieldMetadataId' + | 'labelPlural' + | 'labelSingular' >, ) => updateOneObjectMetadataItem({ diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatObjectMetadataItemInput.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatObjectMetadataItemInput.ts index 5a1ffd5e91..0afceb2949 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatObjectMetadataItemInput.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatObjectMetadataItemInput.ts @@ -5,11 +5,17 @@ import { ObjectMetadataItem } from '../types/ObjectMetadataItem'; export const formatObjectMetadataItemInput = ( input: Pick< ObjectMetadataItem, - 'labelPlural' | 'labelSingular' | 'icon' | 'description' + | 'description' + | 'icon' + | 'labelIdentifierFieldMetadataId' + | 'labelPlural' + | 'labelSingular' >, ) => ({ description: input.description?.trim() ?? null, icon: input.icon, + labelIdentifierFieldMetadataId: + input.labelIdentifierFieldMetadataId?.trim() ?? null, labelPlural: input.labelPlural.trim(), labelSingular: input.labelSingular.trim(), namePlural: toCamelCase(input.labelPlural.trim()), diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldMetadataItem.ts index 98fc51134e..d66daa2dce 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldMetadataItem.ts @@ -1,12 +1,13 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; export const getLabelIdentifierFieldMetadataItem = ( objectMetadataItem: ObjectMetadataItem, -): FieldMetadataItem | undefined => { - return objectMetadataItem.fields.find( - (field) => - field.id === objectMetadataItem.labelIdentifierFieldMetadataId || - field.name === 'name', +): FieldMetadataItem | undefined => + objectMetadataItem.fields.find((fieldMetadataItem) => + isLabelIdentifierField({ + fieldMetadataItem, + objectMetadataItem, + }), ); -}; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts b/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts index 8fe573bb29..0fc1ae8e86 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/getObjectOrderByField.ts @@ -1,17 +1,15 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderByField } from '@/object-metadata/types/OrderByField'; +import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { FieldMetadataType } from '~/generated-metadata/graphql'; export const getObjectOrderByField = ( objectMetadataItem: ObjectMetadataItem, orderBy?: OrderBy | null, ): OrderByField => { - const labelIdentifierFieldMetadata = objectMetadataItem.fields.find( - (field) => - field.id === objectMetadataItem.labelIdentifierFieldMetadataId || - field.name === 'name', - ); + const labelIdentifierFieldMetadata = + getLabelIdentifierFieldMetadataItem(objectMetadataItem); if (labelIdentifierFieldMetadata) { switch (labelIdentifierFieldMetadata.type) { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/isLabelIdentifierField.ts b/packages/twenty-front/src/modules/object-metadata/utils/isLabelIdentifierField.ts index 1a40a87672..06bc0c5761 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/isLabelIdentifierField.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/isLabelIdentifierField.ts @@ -13,8 +13,7 @@ export const isLabelIdentifierField = ({ ObjectMetadataItem, 'labelIdentifierFieldMetadataId' >; -}) => { - return isDefined(objectMetadataItem.labelIdentifierFieldMetadataId) +}) => + isDefined(objectMetadataItem.labelIdentifierFieldMetadataId) ? fieldMetadataItem.id === objectMetadataItem.labelIdentifierFieldMetadataId : fieldMetadataItem.name === DEFAULT_LABEL_IDENTIFIER_FIELD_NAME; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx index 153b7e64ad..132a2c9ff4 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellContainer.tsx @@ -1,6 +1,7 @@ import { useContext } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; +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 { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; @@ -65,9 +66,16 @@ export const RecordTableCellContainer = ({ useUpdateRecord: () => [updateRecord, {}], hotkeyScope: customHotkeyScope, basePathToShowPage: objectMetadataConfig?.basePathToShowPage, - isLabelIdentifier: - columnDefinition.fieldMetadataId === - objectMetadataConfig?.labelIdentifierFieldMetadataId, + isLabelIdentifier: isLabelIdentifierField({ + fieldMetadataItem: { + id: columnDefinition.fieldMetadataId, + name: columnDefinition.metadata.fieldName, + }, + objectMetadataItem: { + labelIdentifierFieldMetadataId: + objectMetadataConfig?.labelIdentifierFieldMetadataId, + }, + }), }} > diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx index cdbfb0b6a6..fa8c7d9554 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx @@ -3,6 +3,7 @@ import { IconDotsVertical, IconEye, IconPencil, + IconTextSize, } from '@/ui/display/icon'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; @@ -15,6 +16,7 @@ type SettingsObjectFieldActiveActionDropdownProps = { isCustomField?: boolean; onDisable?: () => void; onEdit: () => void; + onSetAsLabelIdentifier?: () => void; scopeKey: string; }; @@ -22,6 +24,7 @@ export const SettingsObjectFieldActiveActionDropdown = ({ isCustomField, onDisable, onEdit, + onSetAsLabelIdentifier, scopeKey, }: SettingsObjectFieldActiveActionDropdownProps) => { const dropdownId = `${scopeKey}-settings-field-active-action-dropdown`; @@ -38,6 +41,11 @@ export const SettingsObjectFieldActiveActionDropdown = ({ closeDropdown(); }; + const handleSetAsLabelIdentifier = () => { + onSetAsLabelIdentifier?.(); + closeDropdown(); + }; + return ( + {!!onSetAsLabelIdentifier && ( + + )} {!!onDisable && ( { const navigate = useNavigate(); const { objectSlug = '' } = useParams(); - const { disableObjectMetadataItem, findActiveObjectMetadataItemBySlug } = - useObjectMetadataItemForSettings(); + const { + disableObjectMetadataItem, + editObjectMetadataItem, + findActiveObjectMetadataItemBySlug, + } = useObjectMetadataItemForSettings(); const activeObjectMetadataItem = findActiveObjectMetadataItemBySlug(objectSlug); @@ -64,10 +68,17 @@ export const SettingsObjectDetail = () => { navigate('/settings/objects'); }; - const handleDisableField = async ( + const handleDisableField = (activeFieldMetadatItem: FieldMetadataItem) => { + disableMetadataField(activeFieldMetadatItem); + }; + + const handleSetLabelIdentifierField = ( activeFieldMetadatItem: FieldMetadataItem, ) => { - disableMetadataField(activeFieldMetadatItem); + editObjectMetadataItem({ + ...activeObjectMetadataItem, + labelIdentifierFieldMetadataId: activeFieldMetadatItem.id, + }); }; return ( @@ -104,38 +115,56 @@ export const SettingsObjectDetail = () => { {!!activeMetadataFields.length && ( - {activeMetadataFields.map((activeMetadataField) => ( - - navigate(`./${getFieldSlug(activeMetadataField)}`) - } - onDisable={ - isLabelIdentifierField({ - fieldMetadataItem: activeMetadataField, - objectMetadataItem: activeObjectMetadataItem, - }) - ? undefined - : () => handleDisableField(activeMetadataField) - } - /> - } - /> - ))} + {activeMetadataFields.map((activeMetadataField) => { + const isLabelIdentifier = isLabelIdentifierField({ + fieldMetadataItem: activeMetadataField, + objectMetadataItem: activeObjectMetadataItem, + }); + const canBeSetAsLabelIdentifier = + activeObjectMetadataItem.isCustom && + !isLabelIdentifier && + [FieldMetadataType.Text, FieldMetadataType.Number].includes( + activeMetadataField.type, + ); + + return ( + + navigate(`./${getFieldSlug(activeMetadataField)}`) + } + onSetAsLabelIdentifier={ + canBeSetAsLabelIdentifier + ? () => + handleSetLabelIdentifierField( + activeMetadataField, + ) + : undefined + } + onDisable={ + isLabelIdentifier + ? undefined + : () => handleDisableField(activeMetadataField) + } + /> + } + /> + ); + })} )} {!!disabledMetadataFields.length && ( diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index 2fa6b42f6d..853b534532 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -5,6 +5,7 @@ import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataIt import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { useRelationMetadata } from '@/object-metadata/hooks/useRelationMetadata'; import { getFieldSlug } from '@/object-metadata/utils/getFieldSlug'; +import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; @@ -114,6 +115,11 @@ export const SettingsObjectFieldEdit = () => { const canSave = isValid && hasFormChanged; + const isLabelIdentifier = isLabelIdentifierField({ + fieldMetadataItem: activeMetadataField, + objectMetadataItem: activeObjectMetadataItem, + }); + const handleSave = async () => { if (!validatedFormValues) return; @@ -203,15 +209,17 @@ export const SettingsObjectFieldEdit = () => { select: formValues.select, }} /> -
- -
+ {!isLabelIdentifier && ( +
+ +
+ )} ); diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx index 9af04f3a22..8852096755 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx @@ -4,6 +4,7 @@ import styled from '@emotion/styled'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; +import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; @@ -133,16 +134,23 @@ export const SettingsObjectNewFieldStep1 = () => { {!!activeMetadataFields.length && ( - {activeMetadataFields.map((field) => ( + {activeMetadataFields.map((activeMetadataField) => ( handleToggleField(field.id)} - /> + isLabelIdentifierField({ + fieldMetadataItem: activeMetadataField, + objectMetadataItem: activeObjectMetadataItem, + }) ? undefined : ( + + handleToggleField(activeMetadataField.id) + } + /> + ) } /> ))} @@ -150,15 +158,17 @@ export const SettingsObjectNewFieldStep1 = () => { )} {!!disabledMetadataFields.length && ( - {disabledMetadataFields.map((field) => ( + {disabledMetadataFields.map((disabledMetadataField) => ( handleToggleField(field.id)} + onClick={() => + handleToggleField(disabledMetadataField.id) + } /> } /> diff --git a/packages/twenty-server/src/metadata/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/metadata/field-metadata/field-metadata.service.ts index 37a41c6132..63aec135ac 100644 --- a/packages/twenty-server/src/metadata/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/metadata/field-metadata/field-metadata.service.ts @@ -186,6 +186,14 @@ export class FieldMetadataService extends TypeOrmQueryService