Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isNonEmptyString } from '@sniptt/guards';
import { CurrencyCode } from 'twenty-shared/constants';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';

export const getFieldMetadataItemInitialValues = (
fieldMetadataItem: FieldMetadataItem | null | undefined,
) => {
if (fieldMetadataItem?.type !== FieldMetadataType.CURRENCY) {
return {
settings: fieldMetadataItem?.settings ?? undefined,
defaultValue: fieldMetadataItem?.defaultValue ?? undefined,
};
}

const settings = fieldMetadataItem.settings ?? {
format: 'short',
decimals: DEFAULT_DECIMAL_VALUE,
};

const defaultValue = fieldMetadataItem.defaultValue
? {
amountMicros: fieldMetadataItem.defaultValue.amountMicros ?? null,
currencyCode: isNonEmptyString(
stripSimpleQuotesFromString(
fieldMetadataItem.defaultValue.currencyCode,
),
)
? fieldMetadataItem.defaultValue.currencyCode
: applySimpleQuotesToString(CurrencyCode.USD),

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}
: {
amountMicros: null,
currencyCode: applySimpleQuotesToString(CurrencyCode.USD),
};

return { settings, defaultValue };
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useFormContext } from 'react-hook-form';

import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { getFieldMetadataItemInitialValues } from '@/object-metadata/utils/getFieldMetadataItemInitialValues';
import { type SettingsDataModelFieldCurrencyFormValues } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencyForm';
import { isNonEmptyString } from '@sniptt/guards';
import { CurrencyCode } from 'twenty-shared/constants';
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';

type UseCurrencySettingsFormInitialValuesArgs = {
existingFieldMetadataId: string;
Expand All @@ -18,25 +17,23 @@ export const useCurrencySettingsFormInitialValues = ({
existingFieldMetadataId,
);

const initialAmountMicrosValue =
(fieldMetadataItem?.defaultValue?.amountMicros as number | null) ?? null;
const initialCurrencyCodeValue = isNonEmptyString(
stripSimpleQuotesFromString(fieldMetadataItem?.defaultValue?.currencyCode),
)
? fieldMetadataItem?.defaultValue?.currencyCode
: applySimpleQuotesToString(CurrencyCode.USD);
const { settings, defaultValue } =
getFieldMetadataItemInitialValues(fieldMetadataItem);

const initialFormValues: SettingsDataModelFieldCurrencyFormValues = {
settings: {
format: fieldMetadataItem?.settings?.format ?? 'short',
decimals: fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE,
settings: settings ?? {
format: 'short',
decimals: DEFAULT_DECIMAL_VALUE,
},
defaultValue: {
amountMicros: initialAmountMicrosValue,
currencyCode: initialCurrencyCodeValue,
defaultValue: defaultValue ?? {
amountMicros: null,
currencyCode: applySimpleQuotesToString(CurrencyCode.USD),
},
};

const initialAmountMicrosValue = initialFormValues.defaultValue.amountMicros;
const initialCurrencyCodeValue = initialFormValues.defaultValue.currencyCode;

const { resetField } =
useFormContext<SettingsDataModelFieldCurrencyFormValues>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMe
import { useUpdateOneFieldMetadataItem } from '@/object-metadata/hooks/useUpdateOneFieldMetadataItem';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { formatFieldMetadataItemInput } from '@/object-metadata/utils/formatFieldMetadataItemInput';
import { getFieldMetadataItemInitialValues } from '@/object-metadata/utils/getFieldMetadataItemInitialValues';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
Expand Down Expand Up @@ -107,6 +108,9 @@ export const SettingsObjectFieldEdit = () => {
const getRelationMetadata = useGetRelationMetadata();
const { updateOneFieldMetadataItem } = useUpdateOneFieldMetadataItem();

const { settings, defaultValue } =
getFieldMetadataItemInitialValues(fieldMetadataItem);

const formConfig = useForm<SettingsDataModelFieldEditFormValues>({
mode: 'onTouched',
resolver: zodResolver(settingsFieldFormSchema()),
Expand All @@ -116,7 +120,8 @@ export const SettingsObjectFieldEdit = () => {
label: fieldMetadataItem?.label ?? '',
description: fieldMetadataItem?.description,
isLabelSyncedWithName: fieldMetadataItem?.isLabelSyncedWithName ?? true,
settings: fieldMetadataItem?.settings,
settings,
defaultValue,
},
});
Comment on lines 116 to 126

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name field is intentionally excluded and it has been the case before the changes. It's managed by a child component (SettingsDataModelFieldIconLabelForm) which has its own Controller with defaultValue={fieldMetadataItem?.name}.

Comment on lines 117 to 126

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the values prop to avoid useEffect, consistent with the codebase patterns. React Hook Form performs deep comparison, so isDirty is not reset when values are unchanged.

Comment on lines 120 to 126

This comment was marked as outdated.


Comment on lines 120 to 127

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values prop is appropriate here because:

  • It syncs the form with external state (fieldMetadataItem)
  • React Hook Form performs deep comparison, so isDirty should work correctly
  • It avoids useEffect, following the codebase pattern

Expand Down