import React, { forwardRef, RefObject, useEffect, useImperativeHandle, useMemo, type MutableRefObject, type ReactElement, type ReactNode, type Ref, } from 'react' import { useFormContext, useFormState, type FieldValues, type UseFormReturn } from 'react-hook-form' import { v4 as uuid } from 'uuid' import { ActionIcon, Group, List, LoadingOverlay, Paper, Spoiler, Stack, Title, Tooltip, Transition, } from '@mantine/core' import { IconChevronsLeft, IconChevronsRight } from '@tabler/icons-react' import { useUncontrolled } from '@mantine/hooks' import useRemote from '../hooks/useRemote' import { useStore } from '../store/SuperForm.store' import classes from '../styles/Form.module.css' import { Form } from './Form' import { FormLayout } from './FormLayout' import type { GridRef, SuperFormRef } from '../types' const SuperFormLayout = ( { children, gridRef, }: { children: React.ReactNode | ((props: UseFormReturn) => React.ReactNode) gridRef?: MutableRefObject | null> }, ref ) => { // Component store State const { layoutProps, meta, nested, onBeforeSubmit, onCancel, onLayoutMounted, onLayoutUnMounted, onResetForm, onSubmit, primeData, request, tableName, value, } = useStore((state) => ({ extraButtons: state.extraButtons, layoutProps: state.layoutProps, meta: state.meta, nested: state.nested, onBeforeSubmit: state.onBeforeSubmit, onCancel: state.onCancel, onLayoutMounted: state.onLayoutMounted, onLayoutUnMounted: state.onLayoutUnMounted, onResetForm: state.onResetForm, onSubmit: state.onSubmit, primeData: state.primeData, request: state.request, tableName: state.remote?.tableName, value: state.value, })) const [_opened, _setOpened] = useUncontrolled({ value: layoutProps?.bodyRightSection?.opened, defaultValue: false, onChange: layoutProps?.bodyRightSection?.setOpened, }) // Component Hooks const form = useFormContext() const formState = useFormState({ control: form.control }) const { isFetching, mutateAsync, error, queryKey } = useRemote(gridRef) // Component variables const formUID = useMemo(() => { return meta?.id ?? uuid() }, []) const requestString = request?.charAt(0).toUpperCase() + request?.slice(1) const renderRightSection = ( <> _setOpened(!_opened)} radius='6' m={2} > {_opened ? : } {typeof children === 'function' ? children({ ...form }) : children} {(transitionStyles) => ( {layoutProps?.bodyRightSection?.render?.({ form, formValue: form.getValues(), isFetching, opened: _opened, queryKey, setOpened: _setOpened, })} )} ) // Component Callback Functions const onFormSubmit = async (data: T | any, closeForm: boolean = true) => { const res: any = typeof onBeforeSubmit === 'function' ? await mutateAsync?.(await onBeforeSubmit(data, request, form)) : await mutateAsync?.(data) if ((tableName?.length ?? 0) > 0) { if (res?.ok || (res?.status >= 200 && res?.status < 300)) { onSubmit?.(res?.data, request, data, form, closeForm) } else { form.setError('root', { message: res.status === 401 ? 'Username or password is incorrect' : res?.error, }) } } else { onSubmit?.(data, request, data, form, closeForm) } } // Component use Effects useEffect(() => { if (request === 'insert') { if (onResetForm) { onResetForm(primeData, form).then((resetData) => { form.reset(resetData) }) } else { form.reset(primeData) } } else if ((request === 'change' || request === 'delete') && (tableName?.length ?? 0) === 0) { if (onResetForm) { onResetForm(value, form).then((resetData) => { form.reset(resetData) }) } else { form.reset(value) } } onLayoutMounted?.() return onLayoutUnMounted }, [ request, primeData, tableName, value, form.reset, onResetForm, onLayoutMounted, onLayoutUnMounted, ]) useEffect(() => { if ( (Object.keys(formState.errors)?.length > 0 || error) && _opened === false && layoutProps?.showErrorList !== false ) { _setOpened(true) } }, [Object.keys(formState.errors)?.length > 0, error, layoutProps?.showErrorList]) useImperativeHandle, SuperFormRef>(ref, () => ({ form, mutation: { isFetching, mutateAsync, error }, submit: (closeForm: boolean = true, afterSubmit?: (data: T | any) => void) => { return form.handleSubmit(async (data: T | any) => { await onFormSubmit(data, closeForm) afterSubmit?.(data) })() }, queryKey, getFormState: () => formState, })) return (
{ e.stopPropagation() e.preventDefault() form.handleSubmit((data: T | any) => { onFormSubmit(data) })(e) }} style={{ height: '100%' }} className={request === 'view' ? classes.disabled : ''} > {/* */} {layoutProps?.noLayout ? ( typeof layoutProps?.bodyRightSection?.render === 'function' ? ( renderRightSection ) : typeof children === 'function' ? ( <> {children({ ...form })} ) : ( <> {children} ) ) : ( onCancel?.(request)} onSubmit={form.handleSubmit((data: T | any) => { onFormSubmit(data) })} request={request} modal={false} nested={nested} > {!layoutProps?.noHeader && ( )} {(Object.keys(formState.errors)?.length > 0 || error) && ( {(error?.message?.length ?? 0) > 0 ? 'Server Error' : 'Required information is incomplete*'} {(error as any)?.response?.data?.msg || (error as any)?.response?.data?._error || error?.message} {layoutProps?.showErrorList !== false && ( {getErrorMessages(formState.errors)} )} )} {typeof layoutProps?.bodyRightSection?.render === 'function' ? ( {renderRightSection} ) : ( {typeof children === 'function' ? children({ ...form }) : children} )} {!layoutProps?.noFooter && ( >) : layoutProps?.footerSectionProps)} > {typeof layoutProps?.extraButtons === 'function' ? layoutProps?.extraButtons(form) : layoutProps?.extraButtons} )} )} ) } const getErrorMessages = (errors: any): ReactNode | null => { return Object.keys(errors ?? {}).map((key) => { if (typeof errors[key] === 'object' && key !== 'ref') { return getErrorMessages(errors[key]) } if (key !== 'message') { return null } return {errors[key]} }) } const FRSuperFormLayout = forwardRef(SuperFormLayout) as ( props: { children: React.ReactNode | ((props: UseFormReturn) => React.ReactNode) gridRef?: MutableRefObject } & { ref?: Ref> } ) => ReactElement export default FRSuperFormLayout