import { useDeleteOneFieldMetadataItem } from '@/object-metadata/hooks/useDeleteOneFieldMetadataItem'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown'; import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown'; import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState'; import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useMemo } from 'react'; import { useRecoilState } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined, isLabelIdentifierFieldMetadataTypes, } from 'twenty-shared/utils'; import { IconMinus, IconPlus, useIcons } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; import { UndecoratedLink } from 'twenty-ui/navigation'; import { RelationType } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem'; import { RELATION_TYPES } from '../../constants/RelationTypes'; import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType'; import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly'; type SettingsObjectFieldItemTableRowProps = { settingsObjectDetailTableItem: SettingsObjectDetailTableItem; status: 'active' | 'disabled'; mode: 'view' | 'new-field'; }; export const StyledObjectFieldTableRow = styled(TableRow)` grid-auto-columns: 180px 148px 148px 36px; `; const StyledNameTableCell = styled(TableCell)` color: ${({ theme }) => theme.font.color.primary}; gap: ${({ theme }) => theme.spacing(2)}; `; const StyledNameLabel = styled.div` white-space: nowrap; text-overflow: ellipsis; overflow: hidden; `; const StyledIconTableCell = styled(TableCell)` justify-content: center; padding-right: ${({ theme }) => theme.spacing(1)}; `; export const SettingsObjectFieldItemTableRow = ({ settingsObjectDetailTableItem, mode, status, }: SettingsObjectFieldItemTableRowProps) => { const { fieldMetadataItem, identifierType, objectMetadataItem } = settingsObjectDetailTableItem; const readonly = isObjectMetadataReadOnly({ objectMetadataItem, }); const isRemoteObjectField = objectMetadataItem.isRemote; const variant = objectMetadataItem.isCustom ? 'identifier' : 'field-type'; const navigate = useNavigateSettings(); const [navigationMemorizedUrl, setNavigationMemorizedUrl] = useRecoilState( navigationMemorizedUrlState, ); const theme = useTheme(); const { getIcon } = useIcons(); const Icon = getIcon(fieldMetadataItem.icon); const getRelationMetadata = useGetRelationMetadata(); const { relationObjectMetadataItem, relationType } = useMemo( () => getRelationMetadata({ fieldMetadataItem }), [fieldMetadataItem, getRelationMetadata], ) ?? {}; const fieldType = fieldMetadataItem.type; const isFieldTypeSupported = isFieldTypeSupportedInSettings(fieldType); const RelationIcon = relationType ? RELATION_TYPES[relationType].Icon : undefined; const isLabelIdentifier = isLabelIdentifierField({ fieldMetadataItem, objectMetadataItem, }); const canToggleField = !isLabelIdentifier; const canBeSetAsLabelIdentifier = objectMetadataItem.isCustom && !isLabelIdentifier && isLabelIdentifierFieldMetadataTypes(fieldMetadataItem.type); const linkToNavigate = getSettingsPath(SettingsPath.ObjectFieldEdit, { objectNamePlural: objectMetadataItem.namePlural, fieldName: fieldMetadataItem.name, }); const { activateMetadataField, deactivateMetadataField } = useFieldMetadataItem(); const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem(); const handleDisableField = async ( activeFieldMetadatItem: FieldMetadataItem, ) => { if (readonly) { return; } await deactivateMetadataField( activeFieldMetadatItem.id, objectMetadataItem.id, ); // TODO: Add optimistic rendering for core views const deletedViewIds: string[] = []; const [baseUrl, queryParams] = navigationMemorizedUrl.includes('?') ? navigationMemorizedUrl.split('?') : [navigationMemorizedUrl, '']; const params = new URLSearchParams(queryParams); const currentViewId = params.get('view'); if (isDefined(currentViewId) && deletedViewIds.includes(currentViewId)) { params.delete('view'); const updatedUrl = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl; setNavigationMemorizedUrl(updatedUrl); } }; const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem(); const handleSetLabelIdentifierField = ( activeFieldMetadatItem: FieldMetadataItem, ) => { if (readonly) { return; } updateOneObjectMetadataItem({ idToUpdate: objectMetadataItem.id, updatePayload: { labelIdentifierFieldMetadataId: activeFieldMetadatItem.id, }, }); }; const [, setActiveSettingsObjectFields] = useRecoilState( settingsObjectFieldsFamilyState({ objectMetadataItemId: objectMetadataItem.id, }), ); const handleToggleField = () => { setActiveSettingsObjectFields((previousFields) => { const newFields = isDefined(previousFields) ? previousFields?.map((field) => field.id === fieldMetadataItem.id ? { ...field, isActive: !field.isActive } : field, ) : null; return newFields; }); }; const typeLabel = variant === 'field-type' ? isRemoteObjectField ? 'Remote' : fieldMetadataItem.isCustom ? 'Custom' : 'Standard' : variant === 'identifier' ? isDefined(identifierType) ? identifierType === 'label' ? 'Record text' : 'Record image' : '' : ''; if (!isFieldTypeSupported) return null; const isRelatedObjectLinkable = isDefined(relationObjectMetadataItem?.namePlural) && !relationObjectMetadataItem.isSystem; return ( navigate(SettingsPath.ObjectFieldEdit, { objectNamePlural: objectMetadataItem.namePlural, fieldName: fieldMetadataItem.name, }) : undefined } > {!!Icon && ( )} {fieldMetadataItem.label} {typeLabel} { if (isRelatedObjectLinkable) { e.stopPropagation(); } }} /> {status === 'active' ? ( mode === 'view' ? ( navigate(SettingsPath.ObjectFieldEdit, { objectNamePlural: objectMetadataItem.namePlural, fieldName: fieldMetadataItem.name, }) } onSetAsLabelIdentifier={ canBeSetAsLabelIdentifier ? () => handleSetLabelIdentifierField(fieldMetadataItem) : undefined } onDeactivate={ isLabelIdentifier ? undefined : () => handleDisableField(fieldMetadataItem) } /> ) : ( canToggleField && ( ) ) ) : mode === 'view' ? ( navigate(SettingsPath.ObjectFieldEdit, { objectNamePlural: objectMetadataItem.namePlural, fieldName: fieldMetadataItem.name, }) } onActivate={() => activateMetadataField(fieldMetadataItem.id, objectMetadataItem.id) } onDelete={() => deleteOneFieldMetadataItem({ idToDelete: fieldMetadataItem.id, objectMetadataId: objectMetadataItem.id, }) } /> ) : ( )} ); };