/** * Form Page Factory */ import { mdiChartTimelineVariant } from '@mdi/js'; import Head from 'next/head'; import React, { ReactElement, useEffect, useState } from 'react'; import { Field, Form, Formik } from 'formik'; import { useRouter } from 'next/router'; import CardBox from '../components/CardBox'; import LayoutAuthenticated from '../layouts/Authenticated'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import FormField from '../components/FormField'; import BaseDivider from '../components/BaseDivider'; import BaseButtons from '../components/BaseButtons'; import BaseButton from '../components/BaseButton'; import { getPageTitle } from '../config'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { SelectField } from '../components/SelectField'; import { SelectFieldMany } from '../components/SelectFieldMany'; import { SwitchField } from '../components/SwitchField'; import FormImagePicker from '../components/FormImagePicker'; import type { RootState } from '../stores/store'; import type { AsyncThunk } from '@reduxjs/toolkit'; // Field types supported by the factory export type FormFieldType = | 'text' | 'email' | 'number' | 'textarea' | 'select' | 'selectMany' | 'enumSelect' | 'switch' | 'image' | 'date' | 'datetime' | 'password' | 'custom'; // Field configuration for dynamic form rendering export interface FormFieldConfig { name: string; label: string; type: FormFieldType; placeholder?: string; itemRef?: string; // For select fields - entity to fetch from showField?: string; // For select fields - field to display options?: Array<{ value: string; label: string }>; // For enum selects path?: string; // For image fields schema?: { size?: number; formats?: string[] }; // For image fields component?: React.ComponentType; // For custom field types props?: Record; // Additional props for custom components } interface FormPageConfig { entityName: string; entityTitle: string; singularTitle: string; mode: 'create' | 'edit'; sliceSelector: (state: RootState) => { [key: string]: T | T[] }; fetchAction?: AsyncThunk; createAction?: AsyncThunk; updateAction?: AsyncThunk; permission: string; initialValues: T; fields: FormFieldConfig[]; validate?: (values: T) => Record; } export function createFormPage>( config: FormPageConfig, ) { const { entityName, entityTitle, singularTitle, mode, sliceSelector, fetchAction, createAction, updateAction, permission, initialValues, fields, validate, } = config; const FormPage = () => { const router = useRouter(); const dispatch = useAppDispatch(); const entityState = useAppSelector(sliceSelector); const entityData = entityState[entityName]; const { id } = router.query as { id?: string }; const [formValues, setFormValues] = useState(initialValues); // Fetch data for edit mode useEffect(() => { if (mode === 'edit' && id && fetchAction) { dispatch(fetchAction({ id })); } }, [id, dispatch]); // Sync form values with fetched data useEffect(() => { if ( mode === 'edit' && entityData && typeof entityData === 'object' && !Array.isArray(entityData) ) { const newValues = { ...initialValues }; Object.keys(initialValues).forEach((key) => { if (key in (entityData as Record)) { (newValues as Record)[key] = ( entityData as Record )[key]; } }); setFormValues(newValues); } }, [entityData]); const handleSubmit = async (data: T) => { if (mode === 'edit' && updateAction && id) { await dispatch(updateAction({ id, data })); } else if (mode === 'create' && createAction) { await dispatch(createAction(data)); } await router.push(`/${entityName}/${entityName}-list`); }; const pageTitle = mode === 'edit' ? `Edit ${singularTitle}` : `New ${singularTitle}`; return ( <> {getPageTitle(pageTitle)} {''} handleSubmit(values)} >
{fields.map((field) => ( {renderField(field, formValues)} ))} router.push(`/${entityName}/${entityName}-list`) } />
); }; FormPage.getLayout = function getLayout(page: ReactElement) { return ( {page} ); }; return FormPage; } // Helper function to render form fields based on type function renderField( field: FormFieldConfig, formValues: T, ): React.ReactNode { const { name, type, placeholder, itemRef, showField, options, path, schema, component: CustomComponent, props, } = field; switch (type) { case 'text': case 'email': case 'password': return ( ); case 'number': return ( ); case 'textarea': return ( ); case 'select': return ( )[name]} itemRef={itemRef} showField={showField} /> ); case 'enumSelect': return ( {options?.map((opt) => ( ))} ); case 'selectMany': return ( )[name] || []} itemRef={itemRef} showField={showField} /> ); case 'switch': return ; case 'image': return ( ); case 'date': return ( ); case 'datetime': return ( ); case 'custom': if (CustomComponent) { return ( ); } return ; default: return ; } } export default createFormPage;