refactor: use react-hook-form for Field type config forms (#5326)

Closes #4295

Note: for the sake of an easier code review, I did not rename/move some
files and added "todo" comments instead so Github is able to match those
files with their previous version.
This commit is contained in:
Thaïs
2024-05-07 21:07:56 +02:00
committed by GitHub
parent b7a2e72c32
commit bb995d5488
34 changed files with 714 additions and 1068 deletions

View File

@ -1,20 +1,25 @@
import { useFormContext } from 'react-hook-form';
import styled from '@emotion/styled';
import { z } from 'zod';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SettingsDataModelDefaultValueForm } from '@/settings/data-model/components/SettingsDataModelDefaultValue';
import {
SettingsDataModelFieldBooleanForm,
settingsDataModelFieldBooleanFormSchema,
} from '@/settings/data-model/components/SettingsDataModelDefaultValue';
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
import {
SettingsObjectFieldCurrencyForm,
SettingsObjectFieldCurrencyFormValues,
SettingsDataModelFieldCurrencyForm,
settingsDataModelFieldCurrencyFormSchema,
} from '@/settings/data-model/components/SettingsObjectFieldCurrencyForm';
import {
SettingsObjectFieldRelationForm,
SettingsObjectFieldRelationFormValues,
SettingsDataModelFieldRelationForm,
settingsDataModelFieldRelationFormSchema,
} from '@/settings/data-model/components/SettingsObjectFieldRelationForm';
import {
SettingsObjectFieldSelectForm,
SettingsObjectFieldSelectFormValues,
SettingsDataModelFieldSelectForm,
settingsDataModelFieldSelectFormSchema,
} from '@/settings/data-model/components/SettingsObjectFieldSelectForm';
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
import {
@ -23,26 +28,44 @@ import {
} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export type SettingsDataModelFieldSettingsFormValues = {
currency: SettingsObjectFieldCurrencyFormValues;
relation: SettingsObjectFieldRelationFormValues;
select: SettingsObjectFieldSelectFormValues;
multiSelect: SettingsObjectFieldSelectFormValues;
defaultValue: any;
};
const booleanFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.Boolean) })
.merge(settingsDataModelFieldBooleanFormSchema);
const currencyFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.Currency) })
.merge(settingsDataModelFieldCurrencyFormSchema);
const relationFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.Relation) })
.merge(settingsDataModelFieldRelationFormSchema);
const selectFieldFormSchema = z
.object({
type: z.enum([FieldMetadataType.Select, FieldMetadataType.MultiSelect]),
})
.merge(settingsDataModelFieldSelectFormSchema);
export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion(
'type',
[
booleanFieldFormSchema,
currencyFieldFormSchema,
relationFieldFormSchema,
selectFieldFormSchema,
],
);
type SettingsDataModelFieldSettingsFormValues = z.infer<
typeof settingsDataModelFieldSettingsFormSchema
>;
type SettingsDataModelFieldSettingsFormCardProps = {
disableCurrencyForm?: boolean;
onChange: (values: Partial<SettingsDataModelFieldSettingsFormValues>) => void;
relationFieldMetadataItem?: Pick<
FieldMetadataItem,
'id' | 'isCustom' | 'name'
>;
values: SettingsDataModelFieldSettingsFormValues;
} & Pick<
SettingsDataModelFieldPreviewCardProps,
'fieldMetadataItem' | 'objectMetadataItem'
>;
fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> &
Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>;
relationFieldMetadataItem?: FieldMetadataItem;
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
display: grid;
@ -81,18 +104,23 @@ export const SettingsDataModelFieldSettingsFormCard = ({
disableCurrencyForm,
fieldMetadataItem,
objectMetadataItem,
onChange,
relationFieldMetadataItem,
values,
}: SettingsDataModelFieldSettingsFormCardProps) => {
const { watch: watchFormValue } =
useFormContext<SettingsDataModelFieldSettingsFormValues>();
const { findObjectMetadataItemById } = useFilteredObjectMetadataItems();
if (!previewableTypes.includes(fieldMetadataItem.type)) return null;
const relationObjectMetadataItem = findObjectMetadataItemById(
values.relation.objectMetadataId,
);
const relationTypeConfig = RELATION_TYPES[values.relation.type];
const relationObjectMetadataId = watchFormValue('relation.objectMetadataId');
const relationObjectMetadataItem = relationObjectMetadataId
? findObjectMetadataItemById(relationObjectMetadataId)
: undefined;
const relationType = watchFormValue('relation.type');
const relationTypeConfig = relationType
? RELATION_TYPES[relationType]
: undefined;
return (
<SettingsDataModelPreviewFormCard
@ -103,14 +131,11 @@ export const SettingsDataModelFieldSettingsFormCard = ({
shrink={fieldMetadataItem.type === FieldMetadataType.Relation}
objectMetadataItem={objectMetadataItem}
relationObjectMetadataItem={relationObjectMetadataItem}
selectOptions={
fieldMetadataItem.type === FieldMetadataType.MultiSelect
? values.multiSelect
: values.select
}
selectOptions={watchFormValue('options')}
/>
{fieldMetadataItem.type === FieldMetadataType.Relation &&
!!relationObjectMetadataItem && (
!!relationObjectMetadataItem &&
!!relationTypeConfig && (
<>
<StyledRelationImage
src={relationTypeConfig.imageSrc}
@ -119,11 +144,11 @@ export const SettingsDataModelFieldSettingsFormCard = ({
/>
<StyledFieldPreviewCard
fieldMetadataItem={{
icon: values.relation.field.icon,
label: values.relation.field.label || 'Field name',
...relationFieldMetadataItem,
icon: watchFormValue('relation.field.icon'),
label:
watchFormValue('relation.field.label') || 'Field name',
type: FieldMetadataType.Relation,
name: relationFieldMetadataItem?.name,
id: relationFieldMetadataItem?.id,
}}
shrink
objectMetadataItem={relationObjectMetadataItem}
@ -134,49 +159,25 @@ export const SettingsDataModelFieldSettingsFormCard = ({
</StyledPreviewContent>
}
form={
fieldMetadataItem.type === FieldMetadataType.Currency ? (
<SettingsObjectFieldCurrencyForm
fieldMetadataItem.type === FieldMetadataType.Boolean ? (
<SettingsDataModelFieldBooleanForm
fieldMetadataItem={fieldMetadataItem}
/>
) : fieldMetadataItem.type === FieldMetadataType.Currency ? (
<SettingsDataModelFieldCurrencyForm
disabled={disableCurrencyForm}
values={values.currency}
onChange={(nextCurrencyValues) =>
onChange({
currency: { ...values.currency, ...nextCurrencyValues },
})
}
fieldMetadataItem={fieldMetadataItem}
/>
) : fieldMetadataItem.type === FieldMetadataType.Relation ? (
<SettingsObjectFieldRelationForm
disableFieldEdition={
relationFieldMetadataItem && !relationFieldMetadataItem.isCustom
}
disableRelationEdition={!!relationFieldMetadataItem}
values={values.relation}
onChange={(nextRelationValues) =>
onChange({
relation: { ...values.relation, ...nextRelationValues },
})
}
<SettingsDataModelFieldRelationForm
fieldMetadataItem={fieldMetadataItem}
/>
) : fieldMetadataItem.type === FieldMetadataType.Select ? (
<SettingsObjectFieldSelectForm
values={values.select}
onChange={(nextSelectValues) =>
onChange({ select: nextSelectValues })
}
/>
) : fieldMetadataItem.type === FieldMetadataType.MultiSelect ? (
<SettingsObjectFieldSelectForm
values={values.multiSelect}
onChange={(nextMultiSelectValues) =>
onChange({ multiSelect: nextMultiSelectValues })
}
isMultiSelect={true}
/>
) : fieldMetadataItem.type === FieldMetadataType.Boolean ? (
<SettingsDataModelDefaultValueForm
value={values.defaultValue}
onChange={(nextValueDefaultValue) =>
onChange({ defaultValue: nextValueDefaultValue })
) : fieldMetadataItem.type === FieldMetadataType.Select ||
fieldMetadataItem.type === FieldMetadataType.MultiSelect ? (
<SettingsDataModelFieldSelectForm
fieldMetadataItem={fieldMetadataItem}
isMultiSelect={
fieldMetadataItem.type === FieldMetadataType.MultiSelect
}
/>
) : undefined

View File

@ -1,38 +1,47 @@
import { Controller, useFormContext } from 'react-hook-form';
import omit from 'lodash.omit';
import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import {
SETTINGS_FIELD_TYPE_CONFIGS,
SettingsFieldTypeConfig,
} from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { Select, SelectOption } from '@/ui/input/components/Select';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const settingsDataModelFieldTypeFormSchema = z.object({
type: z.enum(
Object.keys(SETTINGS_FIELD_TYPE_CONFIGS) as [
SettingsSupportedFieldType,
...SettingsSupportedFieldType[],
],
),
});
type SettingsDataModelFieldTypeFormValues = z.infer<
typeof settingsDataModelFieldTypeFormSchema
>;
type SettingsDataModelFieldTypeSelectProps = {
className?: string;
disabled?: boolean;
excludedFieldTypes?: SettingsSupportedFieldType[];
onChange?: ({
type,
defaultValue,
}: {
type: SettingsSupportedFieldType;
defaultValue: any;
}) => void;
value?: SettingsSupportedFieldType;
fieldMetadataItem?: FieldMetadataItem;
};
export const SettingsDataModelFieldTypeSelect = ({
className,
disabled,
excludedFieldTypes = [],
onChange,
value,
fieldMetadataItem,
}: SettingsDataModelFieldTypeSelectProps) => {
const fieldTypeConfigs = omit(
SETTINGS_FIELD_TYPE_CONFIGS,
excludedFieldTypes,
);
const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>();
const fieldTypeConfigs: Partial<
Record<SettingsSupportedFieldType, SettingsFieldTypeConfig>
> = omit(SETTINGS_FIELD_TYPE_CONFIGS, excludedFieldTypes);
const fieldTypeOptions = Object.entries<SettingsFieldTypeConfig>(
fieldTypeConfigs,
).map<SelectOption<SettingsSupportedFieldType>>(([key, dataTypeConfig]) => ({
@ -42,19 +51,25 @@ export const SettingsDataModelFieldTypeSelect = ({
}));
return (
<Select
className={className}
fullWidth
disabled={disabled}
dropdownId="object-field-type-select"
value={value}
onChange={(value) =>
onChange?.({
type: value,
defaultValue: value === FieldMetadataType.Boolean ? false : undefined,
})
<Controller
name="type"
control={control}
defaultValue={
fieldMetadataItem && fieldMetadataItem.type in fieldTypeConfigs
? (fieldMetadataItem.type as SettingsSupportedFieldType)
: undefined
}
options={fieldTypeOptions}
render={({ field: { onChange, value } }) => (
<Select
className={className}
fullWidth
disabled={disabled}
dropdownId="object-field-type-select"
value={value}
onChange={onChange}
options={fieldTypeOptions}
/>
)}
/>
);
};

View File

@ -1,12 +1,8 @@
import { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { ComponentDecorator } from 'twenty-ui';
import { fieldMetadataFormDefaultValues } from '@/settings/data-model/fields/forms/hooks/useFieldMetadataForm';
import {
FieldMetadataType,
RelationMetadataType,
} from '~/generated-metadata/graphql';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator';
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@ -22,14 +18,6 @@ const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find(
({ type }) => type === FieldMetadataType.Text,
)!;
const defaultValues = {
currency: fieldMetadataFormDefaultValues.currency,
relation: fieldMetadataFormDefaultValues.relation,
select: fieldMetadataFormDefaultValues.select,
multiSelect: fieldMetadataFormDefaultValues.multiSelect,
defaultValue: fieldMetadataFormDefaultValues.defaultValue,
};
const meta: Meta<typeof SettingsDataModelFieldSettingsFormCard> = {
title:
'Modules/Settings/DataModel/Fields/Forms/SettingsDataModelFieldSettingsFormCard',
@ -39,12 +27,11 @@ const meta: Meta<typeof SettingsDataModelFieldSettingsFormCard> = {
ComponentDecorator,
ObjectMetadataItemsDecorator,
SnackBarDecorator,
FormProviderDecorator,
],
args: {
fieldMetadataItem,
objectMetadataItem: mockedCompanyObjectMetadataItem,
onChange: fn(),
values: defaultValues,
},
parameters: {
container: { width: 512 },
@ -57,24 +44,14 @@ type Story = StoryObj<typeof SettingsDataModelFieldSettingsFormCard>;
export const Default: Story = {};
const relationFieldMetadataItem = mockedPersonObjectMetadataItem.fields.find(
({ name }) => name === 'company',
)!;
export const WithRelationForm: Story = {
args: {
fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find(
({ name }) => name === 'people',
),
relationFieldMetadataItem,
values: {
...defaultValues,
relation: {
field: relationFieldMetadataItem,
objectMetadataId: mockedPersonObjectMetadataItem.id,
type: RelationMetadataType.OneToMany,
},
},
relationFieldMetadataItem: mockedPersonObjectMetadataItem.fields.find(
({ name }) => name === 'company',
)!,
},
};
@ -85,34 +62,5 @@ export const WithSelectForm: Story = {
icon: 'IconBuildingFactory2',
type: FieldMetadataType.Select,
},
values: {
...defaultValues,
select: [
{
color: 'pink',
isDefault: true,
label: '💊 Health',
value: 'HEALTH',
},
{
color: 'purple',
label: '🏭 Industry',
value: 'INDUSTRY',
},
{ color: 'sky', label: '🤖 SaaS', value: 'SAAS' },
{
color: 'turquoise',
label: '🌿 Green tech',
value: 'GREEN_TECH',
},
{
color: 'yellow',
label: '🚲 Mobility',
value: 'MOBILITY',
},
{ color: 'green', label: '🌏 NGO', value: 'NGO' },
],
defaultValue: undefined,
},
},
};

View File

@ -1,5 +1,5 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { expect, userEvent, within } from '@storybook/test';
import { ComponentDecorator } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -12,10 +12,6 @@ const meta: Meta<typeof SettingsDataModelFieldTypeSelect> = {
'Modules/Settings/DataModel/Fields/Forms/SettingsDataModelFieldTypeSelect',
component: SettingsDataModelFieldTypeSelect,
decorators: [ComponentDecorator],
args: {
onChange: fn(),
value: FieldMetadataType.Text,
},
parameters: {
container: { width: 512 },
msw: graphqlMocks,

View File

@ -1,55 +0,0 @@
import { act, renderHook } from '@testing-library/react';
import { FieldMetadataType } from '~/generated/graphql';
import { useFieldMetadataForm } from '../useFieldMetadataForm';
describe('useFieldMetadataForm', () => {
it('should initialize with default values', () => {
const { result } = renderHook(() => useFieldMetadataForm());
expect(result.current.isInitialized).toBe(false);
act(() => {
result.current.initForm({});
});
expect(result.current.isInitialized).toBe(true);
expect(result.current.formValues).toEqual({
type: 'TEXT',
currency: { currencyCode: 'USD' },
relation: {
type: 'ONE_TO_MANY',
objectMetadataId: '',
field: { label: '' },
},
defaultValue: null,
select: [
{ color: 'green', label: 'Option 1', value: expect.any(String) },
],
multiSelect: [
{ color: 'green', label: 'Option 1', value: expect.any(String) },
],
});
});
it('should handle form changes', () => {
const { result } = renderHook(() => useFieldMetadataForm());
act(() => {
result.current.initForm({});
});
expect(result.current.hasFieldFormChanged).toBe(false);
expect(result.current.hasRelationFormChanged).toBe(false);
expect(result.current.hasSelectFormChanged).toBe(false);
act(() => {
result.current.handleFormChange({ type: FieldMetadataType.Number });
});
expect(result.current.hasFieldFormChanged).toBe(true);
expect(result.current.hasRelationFormChanged).toBe(false);
expect(result.current.hasSelectFormChanged).toBe(false);
});
});

View File

@ -1,262 +0,0 @@
import { useState } from 'react';
import { DeepPartial } from 'react-hook-form';
import { v4 } from 'uuid';
import { z } from 'zod';
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
import {
FieldMetadataType,
RelationMetadataType,
} from '~/generated-metadata/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { SettingsDataModelFieldSettingsFormValues } from '../components/SettingsDataModelFieldSettingsFormCard';
type FormValues = {
defaultValue: any;
type: SettingsSupportedFieldType;
} & SettingsDataModelFieldSettingsFormValues;
export const fieldMetadataFormDefaultValues: FormValues = {
type: FieldMetadataType.Text,
currency: { currencyCode: CurrencyCode.USD },
relation: {
type: RelationMetadataType.OneToMany,
objectMetadataId: '',
field: { label: '' },
},
defaultValue: null,
select: [{ color: 'green', label: 'Option 1', value: v4() }],
multiSelect: [{ color: 'green', label: 'Option 1', value: v4() }],
};
const relationTargetFieldSchema = z.object({
description: z.string().optional(),
icon: z.string().startsWith('Icon'),
label: z.string().min(1),
defaultValue: z.any(),
});
const fieldSchema = z.object({
defaultValue: z.any(),
type: z.enum(
Object.values(FieldMetadataType) as [
FieldMetadataType,
...FieldMetadataType[],
],
),
});
const currencySchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.Currency),
currency: z.object({
currencyCode: z.nativeEnum(CurrencyCode),
}),
}),
);
const relationSchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.Relation),
relation: z.object({
field: relationTargetFieldSchema,
objectMetadataId: z.string().uuid(),
type: z.enum([
RelationMetadataType.OneToMany,
RelationMetadataType.OneToOne,
'MANY_TO_ONE',
]),
}),
}),
);
const selectSchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.Select),
select: z
.array(
z.object({
color: themeColorSchema,
id: z.string().optional(),
isDefault: z.boolean().optional(),
label: z.string().min(1),
}),
)
.nonempty(),
}),
);
const multiSelectSchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.MultiSelect),
multiSelect: z
.array(
z.object({
color: themeColorSchema,
id: z.string().optional(),
isDefault: z.boolean().optional(),
label: z.string().min(1),
}),
)
.nonempty(),
}),
);
const {
Currency: _Currency,
Relation: _Relation,
Select: _Select,
MultiSelect: _MultiSelect,
...otherFieldTypes
} = FieldMetadataType;
type OtherFieldType = Exclude<
FieldMetadataType,
| FieldMetadataType.Currency
| FieldMetadataType.Relation
| FieldMetadataType.Select
| FieldMetadataType.MultiSelect
>;
const otherFieldTypesSchema = fieldSchema.merge(
z.object({
type: z.enum(
Object.values(otherFieldTypes) as [OtherFieldType, ...OtherFieldType[]],
),
}),
);
const schema = z.discriminatedUnion('type', [
currencySchema,
relationSchema,
selectSchema,
multiSelectSchema,
otherFieldTypesSchema,
]);
type PartialFormValues = Partial<Omit<FormValues, 'relation'>> &
DeepPartial<Pick<FormValues, 'relation'>>;
export const useFieldMetadataForm = () => {
const [isInitialized, setIsInitialized] = useState(false);
const [initialFormValues, setInitialFormValues] = useState<FormValues>(
fieldMetadataFormDefaultValues,
);
const [formValues, setFormValues] = useState<FormValues>(
fieldMetadataFormDefaultValues,
);
const [hasFieldFormChanged, setHasFieldFormChanged] = useState(false);
const [hasCurrencyFormChanged, setHasCurrencyFormChanged] = useState(false);
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);
const [hasMultiSelectFormChanged, setHasMultiSelectFormChanged] =
useState(false);
const [hasDefaultValueChanged, setHasDefaultValueFormChanged] =
useState(false);
const [validationResult, setValidationResult] = useState(
schema.safeParse(formValues),
);
const mergePartialValues = (
previousValues: FormValues,
nextValues: PartialFormValues,
): FormValues => ({
...previousValues,
...nextValues,
currency: { ...previousValues.currency, ...nextValues.currency },
relation: {
...previousValues.relation,
...nextValues.relation,
field: {
...previousValues.relation?.field,
...nextValues.relation?.field,
},
},
});
const initForm = (lazyInitialFormValues: PartialFormValues) => {
if (isInitialized) return;
const mergedFormValues = mergePartialValues(
initialFormValues,
lazyInitialFormValues,
);
setInitialFormValues(mergedFormValues);
setFormValues(mergedFormValues);
setValidationResult(schema.safeParse(mergedFormValues));
setIsInitialized(true);
};
const handleFormChange = (values: PartialFormValues) => {
const nextFormValues = mergePartialValues(formValues, values);
setFormValues(nextFormValues);
setValidationResult(schema.safeParse(nextFormValues));
const {
currency: initialCurrencyFormValues,
relation: initialRelationFormValues,
select: initialSelectFormValues,
multiSelect: initialMultiSelectFormValues,
defaultValue: initialDefaultValue,
...initialFieldFormValues
} = initialFormValues;
const {
currency: nextCurrencyFormValues,
relation: nextRelationFormValues,
select: nextSelectFormValues,
multiSelect: nextMultiSelectFormValues,
defaultValue: nextDefaultValue,
...nextFieldFormValues
} = nextFormValues;
setHasFieldFormChanged(
!isDeeplyEqual(initialFieldFormValues, nextFieldFormValues),
);
setHasCurrencyFormChanged(
nextFieldFormValues.type === FieldMetadataType.Currency &&
!isDeeplyEqual(initialCurrencyFormValues, nextCurrencyFormValues),
);
setHasRelationFormChanged(
nextFieldFormValues.type === FieldMetadataType.Relation &&
!isDeeplyEqual(initialRelationFormValues, nextRelationFormValues),
);
setHasSelectFormChanged(
nextFieldFormValues.type === FieldMetadataType.Select &&
!isDeeplyEqual(initialSelectFormValues, nextSelectFormValues),
);
setHasMultiSelectFormChanged(
nextFieldFormValues.type === FieldMetadataType.MultiSelect &&
!isDeeplyEqual(initialMultiSelectFormValues, nextMultiSelectFormValues),
);
setHasDefaultValueFormChanged(
nextFieldFormValues.type === FieldMetadataType.Boolean &&
!isDeeplyEqual(initialDefaultValue, nextDefaultValue),
);
};
return {
formValues,
handleFormChange,
hasFieldFormChanged,
hasFormChanged:
hasFieldFormChanged ||
hasCurrencyFormChanged ||
hasRelationFormChanged ||
hasSelectFormChanged ||
hasMultiSelectFormChanged ||
hasDefaultValueChanged,
hasRelationFormChanged,
hasSelectFormChanged,
hasMultiSelectFormChanged,
hasDefaultValueChanged,
initForm,
isInitialized,
isValid: validationResult.success,
validatedFormValues: validationResult.success
? validationResult.data
: undefined,
};
};

View File

@ -1,3 +1,11 @@
import { settingsDataModelFieldAboutFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldAboutForm';
import { z } from 'zod';
export const settingsFieldFormSchema = settingsDataModelFieldAboutFormSchema;
import { settingsDataModelFieldAboutFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldAboutForm';
import { settingsDataModelFieldSettingsFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
import { settingsDataModelFieldTypeFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect';
export const settingsFieldFormSchema = z
.object({})
.merge(settingsDataModelFieldAboutFormSchema)
.merge(settingsDataModelFieldTypeFormSchema)
.and(settingsDataModelFieldSettingsFormSchema);

View File

@ -8,7 +8,7 @@ import { FieldDisplay } from '@/object-record/record-field/components/FieldDispl
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { BooleanFieldInput } from '@/object-record/record-field/meta-types/input/components/BooleanFieldInput';
import { RatingFieldInput } from '@/object-record/record-field/meta-types/input/components/RatingFieldInput';
import { SettingsObjectFieldSelectFormValues } from '@/settings/data-model/components/SettingsObjectFieldSelectForm';
import { SettingsDataModelFieldSelectFormValues } from '@/settings/data-model/components/SettingsObjectFieldSelectForm';
import { SettingsDataModelSetFieldValueEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetFieldValueEffect';
import { SettingsDataModelSetRecordEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetRecordEffect';
import { useFieldPreview } from '@/settings/data-model/fields/preview/hooks/useFieldPreview';
@ -24,7 +24,7 @@ export type SettingsDataModelFieldPreviewProps = {
};
objectMetadataItem: ObjectMetadataItem;
relationObjectMetadataItem?: ObjectMetadataItem;
selectOptions?: SettingsObjectFieldSelectFormValues;
selectOptions?: SettingsDataModelFieldSelectFormValues['options'];
shrink?: boolean;
withFieldLabel?: boolean;
};

View File

@ -4,7 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
import { SettingsObjectFieldSelectFormValues } from '@/settings/data-model/components/SettingsObjectFieldSelectForm';
import { SettingsDataModelFieldSelectFormValues } from '@/settings/data-model/components/SettingsObjectFieldSelectForm';
import { getFieldDefaultPreviewValue } from '@/settings/data-model/utils/getFieldDefaultPreviewValue';
import { getFieldPreviewValueFromRecord } from '@/settings/data-model/utils/getFieldPreviewValueFromRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -16,7 +16,7 @@ type UseFieldPreviewParams = {
};
objectMetadataItem: ObjectMetadataItem;
relationObjectMetadataItem?: ObjectMetadataItem;
selectOptions?: SettingsObjectFieldSelectFormValues;
selectOptions?: SettingsDataModelFieldSelectFormValues['options'];
};
export const useFieldPreview = ({