diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useClearField.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useClearField.ts new file mode 100644 index 000000000..f65fbe900 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useClearField.ts @@ -0,0 +1,62 @@ +import { useContext } from 'react'; +import { useRecoilCallback } from 'recoil'; + +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue'; + +import { FieldContext } from '../contexts/FieldContext'; + +export const useClearField = () => { + const { + entityId, + fieldDefinition, + useUpdateRecord = () => [], + } = useContext(FieldContext); + + const [updateRecord] = useUpdateRecord(); + + const clearField = useRecoilCallback( + ({ snapshot, set }) => + () => { + const objectMetadataItems = snapshot + .getLoadable(objectMetadataItemsState()) + .getValue(); + + const foundObjectMetadataItem = objectMetadataItems.find( + (item) => + item.nameSingular === + fieldDefinition.metadata.objectMetadataNameSingular, + ); + + const foundFieldMetadataItem = foundObjectMetadataItem?.fields.find( + (field) => field.name === fieldDefinition.metadata.fieldName, + ); + + if (!foundObjectMetadataItem || !foundFieldMetadataItem) { + throw new Error('Field metadata item cannot be found'); + } + + const fieldName = fieldDefinition.metadata.fieldName; + + const emptyFieldValue = generateEmptyFieldValue(foundFieldMetadataItem); + + set( + recordStoreFamilySelector({ recordId: entityId, fieldName }), + emptyFieldValue, + ); + + updateRecord?.({ + variables: { + where: { id: entityId }, + updateOneRecordInput: { + [fieldName]: emptyFieldValue, + }, + }, + }); + }, + [entityId, fieldDefinition, updateRecord], + ); + + return clearField; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldClearable.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldClearable.ts new file mode 100644 index 000000000..0afd8aa91 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldClearable.ts @@ -0,0 +1,21 @@ +import { useContext } from 'react'; + +import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; + +import { FieldContext } from '../contexts/FieldContext'; + +// TODO: have a better clearable settings in metadata ? +// We might want to define what's clearable in the metadata +// Instead of passing it in the context +// See: https://github.com/twentyhq/twenty/issues/4403 +export const useIsFieldClearable = (): boolean => { + const { clearable, isLabelIdentifier, fieldDefinition } = + useContext(FieldContext); + + const isDateField = isFieldDateTime(fieldDefinition); + + const fieldCanBeCleared = + !isLabelIdentifier && !isDateField && clearable !== false; + + return fieldCanBeCleared; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldType.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldType.ts index 1c734a526..e09af18f8 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldType.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldType.ts @@ -15,4 +15,6 @@ export type FieldType = | 'SELECT' | 'TEXT' | 'URL' - | 'UUID'; + | 'UUID' + | 'MULTI_SELECT' + | 'NUMERIC'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx index f48939220..b75d36e29 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx @@ -2,6 +2,8 @@ import { PropsWithChildren, useEffect, useRef } from 'react'; import { useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; +import { useClearField } from '@/object-record/record-field/hooks/useClearField'; +import { useIsFieldClearable } from '@/object-record/record-field/hooks/useIsFieldClearable'; import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput'; import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell'; @@ -21,10 +23,14 @@ export const RecordTableCellSoftFocusMode = ({ const { openTableCell } = useOpenRecordTableCell(); const isFieldInputOnly = useIsFieldInputOnly(); + + const isFieldClearable = useIsFieldClearable(); + const toggleEditOnlyInput = useToggleEditOnlyInput(); const scrollRef = useRef(null); const isSoftFocusUsingMouse = useRecoilValue(isSoftFocusUsingMouseState()); + const clearField = useClearField(); useEffect(() => { if (!isSoftFocusUsingMouse) { @@ -35,12 +41,12 @@ export const RecordTableCellSoftFocusMode = ({ useScopedHotkeys( [Key.Backspace, Key.Delete], () => { - if (!isFieldInputOnly) { - openTableCell(); + if (!isFieldInputOnly && isFieldClearable) { + clearField(); } }, TableHotkeyScope.TableSoftFocus, - [openTableCell], + [clearField, isFieldClearable, isFieldInputOnly], { enabled: !isFieldInputOnly, }, diff --git a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts index e62507484..65b043438 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts @@ -17,14 +17,12 @@ export const generateEmptyFieldValue = ( return { label: '', url: '', - __typename: 'Link', }; } case FieldMetadataType.FullName: { return { firstName: '', lastName: '', - __typename: 'FullName', }; } case FieldMetadataType.DateTime: { @@ -43,6 +41,8 @@ export const generateEmptyFieldValue = ( return true; } case FieldMetadataType.Relation: { + // TODO: refactor with relationDefiniton once the PR is merged : https://github.com/twentyhq/twenty/pull/4378 + // so we can directly check the relation type from this field point of view. if ( !isNonEmptyString( fieldMetadataItem.fromRelationMetadata?.toObjectMetadata @@ -63,7 +63,6 @@ export const generateEmptyFieldValue = ( return { amountMicros: null, currencyCode: null, - __typename: 'Currency', }; } case FieldMetadataType.Select: {