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> & DeepPartial>; export const useFieldMetadataForm = () => { const [isInitialized, setIsInitialized] = useState(false); const [initialFormValues, setInitialFormValues] = useState( fieldMetadataFormDefaultValues, ); const [formValues, setFormValues] = useState( 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, }; };