From 71e511ea35961279c2ffdde4befbb842e2a3421b Mon Sep 17 00:00:00 2001 From: Bananayosostene Date: Thu, 24 Oct 2024 16:27:58 +0200 Subject: [PATCH] adding input alidations #328 --- src/pages/gradeSystem/addNew.tsx | 326 +++++++++++++++++++--------- src/pages/gradeSystem/gradeItem.tsx | 156 +++++++------ 2 files changed, 312 insertions(+), 170 deletions(-) diff --git a/src/pages/gradeSystem/addNew.tsx b/src/pages/gradeSystem/addNew.tsx index 49bd0c803..87c2fb911 100644 --- a/src/pages/gradeSystem/addNew.tsx +++ b/src/pages/gradeSystem/addNew.tsx @@ -1,160 +1,267 @@ -import React, { useState } from 'react'; +import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { MutationFunction } from '@apollo/client'; +import { useForm, useFieldArray, UseFormRegister, FieldArrayWithId } from 'react-hook-form'; +import { toast } from 'react-toastify'; import Button from '../../components/Buttons'; -import GradeItem, { gradeType, initialGrade } from './gradeItem'; +import GradeItem from './gradeItem'; import Loader2 from '../../components/loaders/loader2'; import css from './style.module.css'; -type Props = { - removeModel: any; - setValue: any; +export interface GradeFormData { + id: number; + grade: string; + description: string; + min: number; + max: number; +} + +interface FormData { + name: string; + grades: GradeFormData[]; + isPercentageRequired: boolean; + isDescriptionRequired: boolean; +} + +interface Props { + removeModel: () => void; + setValue: (value: string) => void; loading?: boolean; create: MutationFunction; -}; +} -interface NewGrade extends gradeType { - id: number | any; +export interface GradeItemProps { + gradeItem: FieldArrayWithId; + register: UseFormRegister; + index: number; + errors?: any; + percentageRequired: boolean; + descriptionRequired: boolean; + removable: boolean; + onRemove: () => void; + validationRules: typeof VALIDATION_RULES; } -function AddGradingSystem({ removeModel, setValue, loading, create }: Props) { - const { t } = useTranslation(); +export const initialGrade: Omit = { + grade: '', + description: '', + min: 0, + max: 100, +}; - const [required, setRequired] = useState({ - percentage: true, - description: false, +const DEFAULT_VALUES: FormData = { + name: '', + grades: [{ ...initialGrade, id: 0 }], + isPercentageRequired: true, + isDescriptionRequired: false, +}; + +const VALIDATION_RULES = { + name: { + required: 'Name cannot be empty', + minLength: { + value: 2, + message: 'Name must be at least 2 characters' + } + }, + grade: { + required: 'Grade cannot be empty', + minLength: { + value: 1, + message: 'Grade cannot be empty' + } + }, + percentage: { + min: { + message: 'Minimum percentage cannot be negative' + }, + max: { + message: 'Maximum percentage cannot exceed 100' + } + }, + description: { + minLength: { + value: 3, + message: 'Description must be at least 3 characters' + } + } +}; + +function AddGradingSystem({ removeModel, setValue, loading = false, create }: Props) { + const { t } = useTranslation(); + + const { + register, + control, + handleSubmit, + formState: { errors, isSubmitting }, + reset, + watch, + trigger, + setError, + clearErrors + } = useForm({ + defaultValues: DEFAULT_VALUES, + mode: 'onChange' }); - const [data, setData] = useState({ - name: '', + + const { fields, append, remove } = useFieldArray({ + control, + name: 'grades' }); - const [grades, setGrades] = useState([ - { - id: 0, + + const isPercentageRequired = watch('isPercentageRequired'); + const isDescriptionRequired = watch('isDescriptionRequired'); + const grades = watch('grades'); + + const validateMinMax = useCallback((index: number) => { + const grade = grades[index]; + if (grade && grade.min > grade.max) { + setError(`grades.${index}.min`, { + type: 'manual', + message: 'Minimum grade cannot be greater than maximum grade' + }); + return false; + } + clearErrors(`grades.${index}.min`); + return true; + }, [grades, setError, clearErrors]); + + const addGrade = useCallback(() => { + append({ ...initialGrade, - }, - ]); - - /* istanbul ignore next */ - const addGrade = () => { - setGrades((prev) => [ - ...prev, - { id: (prev[prev.length - 1]?.id || 0) + 1, ...initialGrade }, - ]); - }; + id: fields.length > 0 ? fields[fields.length - 1].id + 1 : 0 + } as GradeFormData); + }, [append, fields]); - /* istanbul ignore next */ - const removeGrade = (id: number | any) => { - setGrades((prev) => { - if (prev.length <= 1) return prev; - return prev.filter((grade) => grade.id !== id); - }); - }; + const removeGrade = useCallback((index: number) => { + if (fields.length > 1) { + remove(index); + } + }, [remove, fields.length]); + + const onSubmit = async (data: FormData) => { + const isValid = data.grades.every((_, index) => validateMinMax(index)); + if (!isValid) { + return; + } + + try { + const formattedData = { + name: data.name, + grade: data.grades.map(({ grade }) => grade), + description: data.grades.map(({ description }) => + description || 'No description' + ), + percentage: data.grades.map(({ min, max }) => + isPercentageRequired ? `${min} - ${max}` : 'No percentage' + ), + }; - /* istanbul ignore next */ - const onSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - const info = { - name: data.name, - grade: grades.map(({ grade: val }) => val), - description: grades.map( - ({ description: val }) => val || 'No description', - ), - percentage: grades.map(({ min, max }) => - required.percentage ? `${min} - ${max}` : 'No percentage', - ), - }; - - create({ - variables: { ...info, orgToken: localStorage.getItem('orgToken') }, - }).then(() => { - setData({ - name: '', + await create({ + variables: { + ...formattedData, + orgToken: localStorage.getItem('orgToken') + } }); - setGrades([ - { - id: 0, - ...initialGrade, - }, - ]); - (e.target as any)?.reset(); + + reset(DEFAULT_VALUES); removeModel(); - }); + setValue(''); + } catch (error) { + toast.error(t('Failed to create grading system') as string); + } }; + const registerMinMax = (index: number) => ({ + min: { + ...register(`grades.${index}.min`, { + valueAsNumber: true, + onChange: () => validateMinMax(index), + }) + }, + max: { + ...register(`grades.${index}.max`, { + valueAsNumber: true, + onChange: () => validateMinMax(index), + }) + } + }); + return ( -
-
-

+
+
+

{t('Add Grading System')}

+
-
-
+ +
- /* istanbul ignore next */ - setData((prev) => ({ ...prev, name: e.target.value })) - } - required + aria-invalid={errors.name ? 'true' : 'false'} />
+ {errors.name && ( +

+ {errors.name.message as string} +

+ )}
+