192 lines
5.1 KiB
TypeScript
192 lines
5.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { DeepPartial } from 'react-hook-form';
|
|
import { v4 } from 'uuid';
|
|
import { z } from 'zod';
|
|
|
|
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
|
|
import {
|
|
FieldMetadataType,
|
|
RelationMetadataType,
|
|
} from '~/generated-metadata/graphql';
|
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
|
|
|
import { SettingsObjectFieldTypeSelectSectionFormValues } from '../components/SettingsObjectFieldTypeSelectSection';
|
|
|
|
type FormValues = {
|
|
description?: string;
|
|
icon: string;
|
|
label: string;
|
|
type: FieldMetadataType;
|
|
relation: SettingsObjectFieldTypeSelectSectionFormValues['relation'];
|
|
select: SettingsObjectFieldTypeSelectSectionFormValues['select'];
|
|
};
|
|
|
|
export const fieldMetadataFormDefaultValues: FormValues = {
|
|
icon: 'IconUsers',
|
|
label: '',
|
|
type: FieldMetadataType.Text,
|
|
relation: {
|
|
type: RelationMetadataType.OneToMany,
|
|
objectMetadataId: '',
|
|
field: { label: '' },
|
|
},
|
|
select: [{ color: 'green', label: 'Option 1', value: v4() }],
|
|
};
|
|
|
|
const fieldSchema = z.object({
|
|
description: z.string().optional(),
|
|
icon: z.string().startsWith('Icon'),
|
|
label: z.string().min(1),
|
|
});
|
|
|
|
const relationSchema = fieldSchema.merge(
|
|
z.object({
|
|
type: z.literal(FieldMetadataType.Relation),
|
|
relation: z.object({
|
|
field: fieldSchema,
|
|
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 {
|
|
Select: _Select,
|
|
Relation: _Relation,
|
|
...otherFieldTypes
|
|
} = FieldMetadataType;
|
|
|
|
type OtherFieldType = Exclude<
|
|
FieldMetadataType,
|
|
FieldMetadataType.Relation | FieldMetadataType.Select
|
|
>;
|
|
|
|
const otherFieldTypesSchema = fieldSchema.merge(
|
|
z.object({
|
|
type: z.enum(
|
|
Object.values(otherFieldTypes) as [OtherFieldType, ...OtherFieldType[]],
|
|
),
|
|
}),
|
|
);
|
|
|
|
const schema = z.discriminatedUnion('type', [
|
|
relationSchema,
|
|
selectSchema,
|
|
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 [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
|
|
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);
|
|
const [validationResult, setValidationResult] = useState(
|
|
schema.safeParse(formValues),
|
|
);
|
|
|
|
const mergePartialValues = (
|
|
previousValues: FormValues,
|
|
nextValues: PartialFormValues,
|
|
): FormValues => ({
|
|
...previousValues,
|
|
...nextValues,
|
|
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 {
|
|
relation: initialRelationFormValues,
|
|
select: initialSelectFormValues,
|
|
...initialFieldFormValues
|
|
} = initialFormValues;
|
|
const {
|
|
relation: nextRelationFormValues,
|
|
select: nextSelectFormValues,
|
|
...nextFieldFormValues
|
|
} = nextFormValues;
|
|
|
|
setHasFieldFormChanged(
|
|
!isDeeplyEqual(initialFieldFormValues, nextFieldFormValues),
|
|
);
|
|
setHasRelationFormChanged(
|
|
nextFieldFormValues.type === FieldMetadataType.Relation &&
|
|
!isDeeplyEqual(initialRelationFormValues, nextRelationFormValues),
|
|
);
|
|
setHasSelectFormChanged(
|
|
nextFieldFormValues.type === FieldMetadataType.Select &&
|
|
!isDeeplyEqual(initialSelectFormValues, nextSelectFormValues),
|
|
);
|
|
};
|
|
|
|
return {
|
|
formValues,
|
|
handleFormChange,
|
|
hasFieldFormChanged,
|
|
hasFormChanged:
|
|
hasFieldFormChanged || hasRelationFormChanged || hasSelectFormChanged,
|
|
hasRelationFormChanged,
|
|
hasSelectFormChanged,
|
|
initForm,
|
|
isInitialized,
|
|
isValid: validationResult.success,
|
|
validatedFormValues: validationResult.success
|
|
? validationResult.data
|
|
: undefined,
|
|
};
|
|
};
|