import { createZodResolver } from "@components/Form/createZodResolver.ts"; import { DynamicFormField, type FieldProps, } from "@components/Form/DynamicFormField.tsx"; import { FieldWrapper } from "@components/Form/FormWrapper.tsx"; import { Button } from "@components/UI/Button.tsx"; import { Heading } from "@components/UI/Typography/Heading.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { useAppStore } from "@core/stores"; import { useEffect } from "react"; import { type Control, type DefaultValues, type FieldValues, FormProvider, get, type Path, type SubmitHandler, type UseFormReturn, useForm, } from "react-hook-form"; import { useTranslation } from "react-i18next"; import type { ZodType } from "zod/v4"; interface DisabledBy { fieldName: Path; selector?: number; invert?: boolean; } export interface BaseFormBuilderProps { name: Path; disabled?: boolean; disabledBy?: DisabledBy[]; label: string; description?: string; notes?: string; validationText?: string; properties?: Record; } export interface GenericFormElementProps { control: Control; disabled?: boolean; field: Y; isDirty?: boolean; invalid?: boolean; } export interface DynamicFormProps { propMethods?: UseFormReturn; onSubmit: SubmitHandler; onFormInit?: DynamicFormFormInit; submitType?: "onChange" | "onSubmit"; hasSubmitButton?: boolean; defaultValues?: DefaultValues; values?: T; fieldGroups: { label: string; description: string; notes?: string; valid?: boolean; validationText?: string; fields: FieldProps[]; }[]; validationSchema?: ZodType; } export type DynamicFormFormInit = ( methods: UseFormReturn, ) => void; export function DynamicForm({ propMethods, onSubmit, onFormInit, submitType = "onChange", hasSubmitButton, defaultValues, values, fieldGroups, validationSchema, }: DynamicFormProps) { const { t } = useTranslation(); const { setDirtyForm, setValidForm } = useAppStore(); const internalMethods = useForm({ mode: "onChange", defaultValues: defaultValues, resolver: validationSchema ? createZodResolver(validationSchema) : undefined, shouldFocusError: false, resetOptions: { keepDefaultValues: true }, values, }); const methods = propMethods ?? internalMethods; const { handleSubmit, control, getValues, formState, getFieldState } = methods; useEffect(() => { if (!propMethods) { onFormInit?.(internalMethods); } }, [onFormInit, propMethods, internalMethods]); useEffect(() => { setValidForm(formState.isValid); setDirtyForm(formState.isDirty); }, [formState.isDirty, formState.isValid, setValidForm, setDirtyForm]); useEffect( () => () => { setValidForm(true); setDirtyForm(false); }, [setValidForm, setDirtyForm], ); const isDisabled = ( disabledBy?: DisabledBy[], disabled?: boolean, ): boolean => { if (disabled) { return true; } if (!disabledBy) { return false; } return disabledBy.some((field) => { const value = getValues(field.fieldName); if (value === "always") { return true; } if (typeof value === "boolean") { return field.invert ? value : !value; } if (typeof value === "number") { return field.invert ? field.selector !== value : field.selector === value; } return false; }); }; return (
{fieldGroups.map((fieldGroup) => (
{fieldGroup.label} {fieldGroup.description} {fieldGroup?.notes}
{fieldGroup.fields.map((field) => { const error = get(formState.errors, field.name as string); return ( ); })}
))} {hasSubmitButton && ( )}
); }