diff --git a/src/components/CustomRadioWidget.tsx b/src/components/CustomRadioWidget.tsx new file mode 100644 index 00000000..b8d59c08 --- /dev/null +++ b/src/components/CustomRadioWidget.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormLabel from '@mui/material/FormLabel'; +import { WidgetProps } from '@rjsf/utils'; + +interface CustomRadioWidgetProps { + value: string; + id: string; + required: boolean; + disabled: boolean; + readonly: boolean; + label: string; + options: any; + onChange: any; +} +const CustomRadioWidget: React.FC = ({ + id, + value, + required, + disabled, + readonly, + label, + options, + onChange, +}) => { + const handleChange = (event: any) => { + onChange(event.target.value); + }; + + return ( +
+ {label} + + {options?.enumOptions?.map((option: any) => ( + } + label={option.label} + disabled={disabled || readonly} + /> + ))} + +
+ ); +}; + +export default CustomRadioWidget; diff --git a/src/components/DynamicForm.tsx b/src/components/DynamicForm.tsx index a0c12a5b..c6cf2382 100644 --- a/src/components/DynamicForm.tsx +++ b/src/components/DynamicForm.tsx @@ -1,10 +1,14 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { IChangeEvent } from '@rjsf/core'; import ISubmitEvent from '@rjsf/core'; import validator from '@rjsf/validator-ajv8'; import { Theme as MaterialUITheme } from '@rjsf/mui'; import { withTheme } from '@rjsf/core'; -import { RJSFSchema } from '@rjsf/utils'; +import MultiSelectCheckboxes from './MultiSelectCheckboxes'; +import CustomCheckboxWidget from './CustomCheckboxWidget'; +import CustomRadioWidget from './CustomRadioWidget'; +import CustomErrorList from './CustomErrorList'; +import { RJSFSchema, WidgetProps } from '@rjsf/utils'; const FormWithMaterialUI = withTheme(MaterialUITheme); @@ -19,6 +23,9 @@ interface DynamicFormProps { onChange: (event: IChangeEvent) => void; onError: (errors: any) => void; showErrorList: boolean; + widgets: { + [key: string]: React.FC>; + }; } const DynamicForm: React.FC = ({ schema, @@ -28,6 +35,34 @@ const DynamicForm: React.FC = ({ onChange, onError, }) => { + const widgets = { + MultiSelectCheckboxes: MultiSelectCheckboxes, + CustomCheckboxWidget: CustomCheckboxWidget, + CustomRadioWidget: CustomRadioWidget, + }; + console.log('CustomErrorList', CustomErrorList); + + const handleError = (errors: any) => { + if (errors.length > 0) { + // Adjust the selector based on the actual structure of the form element names + const property = errors[0].property.replace(/^root\./, ''); + const errorField = document.querySelector( + `[name$="${property}"]` + ) as HTMLElement; + + if (errorField) { + errorField.focus(); + } else { + // If the name-based selector fails, try to select by ID as a fallback + const fallbackField = document.getElementById(property) as HTMLElement; + if (fallbackField) { + fallbackField.focus(); + } + } + } + onError(errors); + }; + return ( = ({ formData={formData} onChange={onChange} onSubmit={onSubmit} - onError={onError} validator={validator} liveValidate showErrorList={false} + widgets={widgets} + noHtml5Validate + onError={handleError} + // ErrorList={CustomErrorList} /> ); }; diff --git a/src/components/MultiSelectCheckboxes.tsx b/src/components/MultiSelectCheckboxes.tsx new file mode 100644 index 00000000..0dae6c30 --- /dev/null +++ b/src/components/MultiSelectCheckboxes.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { + FormControl, + FormControlLabel, + FormGroup, + FormLabel, + Checkbox, + Grid, +} from '@mui/material'; +import { WidgetProps } from '@rjsf/utils'; + +interface CustomMultiselectCheckboxesProps { + label: string; + value: any; + id: string; + required: boolean; + disabled: boolean; + readonly: boolean; + options: any; + onChange: any; + schema: any; +} +const MultiSelectCheckboxes: React.FC = ({ + schema, + options, + value, + required, + disabled, + readonly, + onChange, + label, +}) => { + const handleChange = (event: any) => { + const { value: optionValue } = event.target; + if (event.target.checked) { + onChange([...value, optionValue]); + } else { + onChange(value.filter((v: any) => v !== optionValue)); + } + }; + + return ( + + + {label || schema?.title} + + + {options?.enumOptions?.map((option: any) => ( + + + {option.label} + + + + + + ))} + + + ); +}; + +export default MultiSelectCheckboxes; diff --git a/src/pages/addLearner.tsx b/src/pages/addLearner.tsx new file mode 100644 index 00000000..054b48cf --- /dev/null +++ b/src/pages/addLearner.tsx @@ -0,0 +1,55 @@ +import DynamicForm from '@/components/DynamicForm'; +import React from 'react'; +import { schema, uiSchema } from '@/utils/schema'; +import { IChangeEvent } from '@rjsf/core'; +import ISubmitEvent from '@rjsf/core'; +import { Box } from '@mui/material'; +import { RJSFSchema } from '@rjsf/utils'; + +const addLearner = () => { + const handleSubmit = ( + data: IChangeEvent, + event: React.FormEvent + ) => { + const target = event.target as HTMLFormElement; + const elementsArray = Array.from(target.elements); + + for (const element of elementsArray) { + if ( + (element instanceof HTMLInputElement || + element instanceof HTMLSelectElement || + element instanceof HTMLTextAreaElement) && + (element.value === '' || + (Array.isArray(element.value) && element.value.length === 0)) + ) { + element.focus(); + return; + } + } + console.log('Form data submitted:', data.formData); + }; + + const handleChange = (event: IChangeEvent) => { + console.log('Form data changed:', event.formData); + }; + + const handleError = (errors: any) => { + console.log('Form errors:', errors); + }; + + return ( + + + + ); +}; + +export default addLearner; diff --git a/src/utils/schema.js b/src/utils/schema.js new file mode 100644 index 00000000..ef9b7706 --- /dev/null +++ b/src/utils/schema.js @@ -0,0 +1,216 @@ +export const schema = { + title: 'A registration form', + description: 'A simple form example', + type: 'object', + required: ['firstName', 'lastName', 'age', 'skills'], + properties: { + firstName: { + type: 'string', + title: 'First Name', + default: '', + errorMessage: + 'First name is required and should be less than 50 characters', + }, + lastName: { type: 'string', title: 'Last Name', default: '' }, + age: { type: 'number', title: 'Age', default: 0 }, + gender: { + type: 'string', + title: 'Gender', + enum: ['Male', 'Female'], + default: 'Male', + }, + country: { + type: 'string', + title: 'Country', + enum: ['USA', 'Canada', 'UK'], + default: 'USA', + }, + acceptTerms: { type: 'boolean', title: 'Accept Terms', default: false }, + skills: { + type: 'array', + title: 'Skills', + items: { + type: 'string', + enum: ['JavaScript', 'React', 'Node.js', 'Python', 'Java'], + }, + uniqueItems: true, + }, + experience: { + type: 'string', + title: 'Experience', + enum: ['Less than 1 year', '1-2 years', 'More than 2 years'], + }, + details: { + type: 'string', + title: 'Details', + }, + }, + dependencies: { + experience: { + oneOf: [ + { + properties: { + experience: { enum: ['Less than 1 year'] }, + details: { + type: 'string', + title: 'Details about your learning path', + }, + }, + }, + { + properties: { + experience: { enum: ['1-2 years'] }, + details: { type: 'string', title: 'Details about your projects' }, + }, + }, + { + properties: { + experience: { enum: ['More than 2 years'] }, + details: { + type: 'string', + title: 'Details about your professional experience', + }, + }, + }, + ], + }, + }, +}; + +export const uiSchema = { + acceptTerms: { + 'ui:widget': 'CustomCheckboxWidget', + 'ui:options': { + label: 'I accept the terms and conditions', + }, + }, + gender: { + 'ui:widget': 'CustomRadioWidget', + }, + country: { + 'ui:widget': 'select', + }, + skills: { + 'ui:widget': 'MultiSelectCheckboxes', + }, +}; + +const formReadResponse = { + formName: 'facilitator-create', + formId: 'UUID', //optional + endpoint: '/user/v1/add-facilitator', + schema: [ + { + label: 'FIRST_NAME', + name: 'first_name', + type: 'text', + isRequired: true, + isEditable: true, + isPIIField: false, + placeholder: 'ENTER_FIRST_NAME', + validation: ['string'], + }, + { + label: 'EMAIL', + name: 'email', + type: 'text', + isRequired: true, + isEditable: true, + isPIIField: false, + isMultiSelect: false, //optional + maxSelections: 3, //optional + placeholder: 'ENTER_EMAIL', + hint: 'IT_SHOULD_BE_GMAIL', //optional + validation: ['email'], + pattern: ['regex'], + }, + { + label: 'PASSWORD', + name: 'password', + type: 'text', + isRequired: true, + isEditable: true, + isPIIField: false, + placeholder: 'ENTER_STRONG_PASSWORD', + hint: 'IT_SHOULD_BE_7_CHARACTER_LONG', //optional + validation: ['password'], + }, + { + label: 'GENDER', + name: 'gender', + type: 'radio', + isRequired: true, + isEditable: true, + isPIIField: true, + }, + { + label: 'ADDRESS', + name: 'Address', + type: 'textArea', + isRequired: true, + isEditable: true, + isPIIField: false, + placeholder: 'ENTER_ADDRESS', + maxLength: 25, + minLength: 5, + validation: ['string'], + }, + { + label: 'CONTACT_NUMBER', + name: 'Contact Number', + type: 'text', + isRequired: true, + isEditable: true, + isPIIField: false, + placeholder: 'ENTER_CONTACT_NUMBER', + maxLength: 10, + validation: ['numeric'], + }, + { + label: 'SUBJECT', + name: 'subject', + type: 'select', + isRequired: true, + isEditable: true, + isPIIField: false, + isMultiSelect: true, //optional + // maxSelections: 3, //optional + placeholder: 'SELECT_SUBJECTS', + hint: 'SELECT_TEACHING_SUBJECTS', //optional + fieldId: '2323', // optional + options: [ + { + label: 'ENGLISH', + value: 'english', + }, + { + label: 'MATHS', + value: 'mathematics', + }, + ], + }, + { + label: 'CENTER', + name: 'center', + type: 'checkbox', + isRequired: true, + isEditable: true, + isPIIField: false, + isMultiSelect: true, //optional + maxSelections: 2, //optional + placeholder: 'SELECT_CENTERS', + hint: 'SELECT_TEACHING_CENTERS', //optional + fieldId: '2323', // optional + options: [ + { + label: 'Kamptee', + value: 'kamptee', + }, + { + label: 'Khapri Dharmu', + value: 'Khapri Dharmu', + }, + ], + }, + ], +};