diff --git a/src/Components/LatextInput/LaTeXInputMemo.tsx b/src/Components/LatextInput/LaTeXInputMemo.tsx index 9a3838f..eb86391 100644 --- a/src/Components/LatextInput/LaTeXInputMemo.tsx +++ b/src/Components/LatextInput/LaTeXInputMemo.tsx @@ -1,5 +1,4 @@ import TextArea from 'antd/es/input/TextArea'; -import { useFormikContext } from 'formik'; import React, { Suspense, useEffect, useState } from 'react'; import { parseTextAndLatex } from '../../utils/parseTextAndLatex'; import LatexPreview from '../CustomFields/MathComponent'; @@ -49,15 +48,24 @@ const LaTeXInputMemo: React.FC = React.memo(({ field ,form, label, ...prop setCurrentValue(e.target.value) }; const onBlur = ()=>{ - - setFieldValue(name, curCentValue); + if (curCentValue !== value) { + setFieldValue(name, curCentValue); + } } + useEffect(() => { if(Success){ setCurrentValue(null) } }, [Success]) + useEffect(() => { + if(value){ + setCurrentValue(value) + } + }, [value]) + + const isError = !!touched?.[name] && !!errors?.[name]; const errorMessage = isError ? errors?.[name] as string ?? "" : "" ; return ( @@ -93,7 +101,7 @@ const LaTeXInputMemo: React.FC = React.memo(({ field ,form, label, ...prop {Preview?.map((item: any, index: number) => { if (item?.isLatex) { return ( -
handleEditModal(item)} className='LatexPreview'> +
handleEditModal(item)} className='LatexPreview'>
); @@ -104,9 +112,10 @@ const LaTeXInputMemo: React.FC = React.memo(({ field ,form, label, ...prop )}
- - - }> + }> + + + ); diff --git a/src/Hooks/useUnsavedChangesWarning.tsx b/src/Hooks/useUnsavedChangesWarning.tsx new file mode 100644 index 0000000..c13ba9d --- /dev/null +++ b/src/Hooks/useUnsavedChangesWarning.tsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +const useUnsavedChangesWarning = (unsavedChanges: boolean) => { + const [t] = useTranslation(); + + useEffect(() => { + const handleBeforeUnload = (event: BeforeUnloadEvent) => { + if (unsavedChanges) { + // Prevent default action and stop the event + event.preventDefault(); + // Optionally set returnValue to an empty string + event.returnValue = ''; + } + }; + + const handleNavigation = (event: MouseEvent) => { + if (unsavedChanges) { + console.log(t("Access denied: You have unsaved changes!")); + event.preventDefault(); + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + + // Intercept link clicks (example for elements) + document.querySelectorAll('a').forEach(link => { + link.addEventListener('click', handleNavigation); + }); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + + // Clean up event listeners + document.querySelectorAll('a').forEach(link => { + link.removeEventListener('click', handleNavigation); + }); + }; + }, [unsavedChanges, t]); +}; + +export default useUnsavedChangesWarning; \ No newline at end of file diff --git a/src/Pages/Admin/Tags/synonyms/DynamicTags.tsx b/src/Pages/Admin/Tags/synonyms/DynamicTags.tsx index aa60834..cf6b4b6 100644 --- a/src/Pages/Admin/Tags/synonyms/DynamicTags.tsx +++ b/src/Pages/Admin/Tags/synonyms/DynamicTags.tsx @@ -36,7 +36,6 @@ const DynamicTags = () => { if (currentElement) { currentElement.focus(); // Set focus on the element } - console.log(currentElement,"currentElement"); }, [lastElementIndex]) // console.log(formik?.values); diff --git a/src/Pages/Admin/Unit/DrapableTable.tsx b/src/Pages/Admin/Unit/DrapableTable.tsx index 3c8e0dc..610ce65 100644 --- a/src/Pages/Admin/Unit/DrapableTable.tsx +++ b/src/Pages/Admin/Unit/DrapableTable.tsx @@ -167,7 +167,6 @@ const DrapableTable: React.FC = () => { const [t] = useTranslation(); const columns = useColumns(); const sortedDataSource = dataSource.sort((a, b) => a.order - b.order); - console.log(sortedDataSource, "sortedDataSource"); return ( diff --git a/src/Pages/Admin/question/AddPage.tsx b/src/Pages/Admin/question/AddPage.tsx index 6345d98..96c6a7a 100644 --- a/src/Pages/Admin/question/AddPage.tsx +++ b/src/Pages/Admin/question/AddPage.tsx @@ -1,38 +1,26 @@ import React, { useEffect } from "react"; -import { Spin } from "antd"; import { - getInitialValues, - getValidationSchema, - getInitialValuesBase, - getValidationSchemaBase, processTags, } from "./formUtil"; import { useAddQuestion, useAddQuestionAsync } from "../../../api/Question"; -import { useTranslation } from "react-i18next"; -import { useLocation, useNavigate, useParams } from "react-router-dom"; +import { useParams } from "react-router-dom"; import { ParamsEnum } from "../../../enums/params"; import { useObjectToEdit } from "../../../zustand/ObjectToEditState"; - -import Header from "../../../Components/exercise/Header"; import { Question } from "../../../types/Item"; -import BaseForm from "./Model/Malty/Form"; -import ModelForm from "./Model/ModelForm"; -import { toast } from "react-toastify"; -import { Form, Formik } from "formik"; -import { MdOutlineArrowForwardIos } from "react-icons/md"; +import BaseFormContainer from "./Model/AddForm/BaseForm"; +import FormContainer from "./Model/AddForm/Form"; +import { handleValidateBaseQuestion, handleValidateSingleQuestion } from "./Model/AddForm/ValidationFn"; +import useUnsavedChangesWarning from "../../../Hooks/useUnsavedChangesWarning"; +import { useFormikContext } from "formik"; const AddPage: React.FC = () => { - const location = useLocation(); - const { mutateAsync,isLoading:LoadingAsync } = useAddQuestionAsync(); + const { mutateAsync, isLoading: LoadingAsync } = useAddQuestionAsync(); const { mutate, isLoading, isSuccess } = useAddQuestion(); - const { isBseQuestion, setTagsSearch, setSuccess } = - useObjectToEdit(); - - const [t] = useTranslation(); + const { isBseQuestion, setTagsSearch, setSuccess } = useObjectToEdit(); const { subject_id, lesson_id } = useParams(); - const handleFormSubmit = ( values: any) => { + const handleFormSubmit = (values: any) => { const DataToSend = structuredClone(values); setTagsSearch(null); @@ -63,7 +51,7 @@ const AddPage: React.FC = () => { ...item, }; }); - + mutate({ ...item, parent_id: newBseQuestionId, @@ -99,216 +87,27 @@ const AddPage: React.FC = () => { mutate(NewQuestion); } }; - - const handleValidateSingleQuestion = (values:any,isValid:boolean,handleSubmit:any)=>{ - const haveMoreThanOneAnswer = values?.answers?.length > 1; - const haveOneAnswerRight = haveMoreThanOneAnswer && values?.answers?.some((item:any)=> item?.isCorrect === 1 || item.isCorrect === true ) - const haveImageOrContent = haveOneAnswerRight && values?.answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) - const content = values.content ; - const content_image = values.content_image ; - const haveContentOrContentImage = !!content || !!content_image ; - console.log(haveImageOrContent,"haveImageOrContent"); - if(!haveContentOrContentImage){ - toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); - return false; - } - if(!haveMoreThanOneAnswer){ - toast.error(t("validation.it_should_have_more_than_one_answers")) ; - return false ; - } - if(!haveOneAnswerRight){ - toast.error(t("validation.it_should_have_more_than_one_correct_answers")) ; - return false ; - } - if(haveImageOrContent){ - toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) - return false - } - - console.log(1); - - if(isValid){ - handleSubmit(values) - } - - } - const handleValidateBaseQuestion = (values: any,isValid:boolean,handleSubmit:any) => { - const content = values.content ; - const content_image = values.content_image ; - const haveContentOrContentImage = !!content || !!content_image ; - console.log(2); - - if(!haveContentOrContentImage){ - toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); - return false; - } - console.log(1); - - const isValidate = values?.Questions?.every((Question: any, QuestionsIndex: number) => { - - - const content = Question.content ; - const content_image = Question.content_image ; - const haveContentOrContentImage = !!content || !!content_image ; - if(!haveContentOrContentImage){ - toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); - return false; - } - - - //// answers - const answers = Question?.answers; - const haveAnswers = answers?.length > 0; - const haveMoreThanOneAnswer = haveAnswers && answers?.length > 1; - const haveOneAnswerRight = haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true); - const haveImageOrContent = haveOneAnswerRight && answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) - - - - - if (!haveAnswers) { - toast.error(t("validation.it_should_have_more_than_one_answers")); - return false; - } - - if (!haveMoreThanOneAnswer) { - toast.error(t("validation.it_should_have_more_than_one_answers")); - return false; - } - - if (!haveOneAnswerRight) { - toast.error(t("validation.it_should_have_more_than_one_correct_answers")); - return false; - } - - if(haveImageOrContent){ - toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) - return false - } - - return true - }); - - console.log(1); - - if(isValid && isValidate){ - console.log(2); - handleSubmit(values) - } - - }; - - const navigate = useNavigate(); - - const handleNavigateToPage = () => { - const cleanedUrl = location.pathname?.replace("/Question/add", ""); - navigate(cleanedUrl); - }; - const handleCancel = () => { - navigate(-1); - }; const Loading = LoadingAsync || isLoading; - + useEffect(() => { if (isSuccess) { setSuccess(true); + } }, [isSuccess]); if (isBseQuestion) { return ( - -
-
- {t("header.add_new_question")} -
-
- - {({ values,isValid,handleSubmit }) => ( - -
-
-
- - -
-
{t("practical.back")}
- -
-
-
- )} -
- -
-
+ ); } return ( -
-
- {t("header.add_new_question")} -
-
- { - handleFormSubmit(values); - }} -> - {({ values,isValid ,handleSubmit}) => ( -
-
-
- - -
-
{t("practical.back")}
-
{ Loading ? ()=>{} : handleValidateSingleQuestion(values,isValid,handleSubmit) }} - > - {t("practical.add")} - - {Loading && ( - - - - )} -
-
-
-
- )} -
- -
-
+ ); }; diff --git a/src/Pages/Admin/question/EditPage.tsx b/src/Pages/Admin/question/EditPage.tsx index cb6b529..984da4a 100644 --- a/src/Pages/Admin/question/EditPage.tsx +++ b/src/Pages/Admin/question/EditPage.tsx @@ -1,10 +1,5 @@ import React, { useEffect } from "react"; -import { Checkbox, Modal, Popover, Spin } from "antd"; import { - getInitialValues, - getValidationSchema, - getInitialValuesBase, - getValidationSchemaBase, processTags, } from "./formUtil"; import { @@ -19,25 +14,20 @@ import { ParamsEnum } from "../../../enums/params"; import { useObjectToEdit } from "../../../zustand/ObjectToEditState"; import { removeStringKeys } from "../../../utils/removeStringKeys"; import SpinContainer from "../../../Components/Layout/SpinContainer"; -import ModelForm from "./Model/ModelForm"; -import BaseForm from "./Model/Malty/Form"; import { Question } from "../../../types/Item"; import { toast } from "react-toastify"; import { deletePathSegments } from "../../../utils/deletePathSegments"; -import { Form, Formik } from "formik"; -import { MdOutlineArrowForwardIos } from "react-icons/md"; -import { SettingFilled } from "@ant-design/icons"; -import { CheckboxProps } from "antd/lib"; -import { LocalStorageEnum } from "../../../enums/LocalStorageEnum"; +import BaseFormContainer from "./Model/EditForm/BaseFormContainer"; +import FormContainer from "./Model/EditForm/FormContainer"; const EditPage: React.FC = () => { const { subject_id, lesson_id, question_id } = useParams(); - const { isBseQuestion, setIsBseQuestion, setTagsSearch, DeletedQuestions , ShowHint,setShowHint , ShowLatexOption,setShowLatexOption } = + const { isBseQuestion, setIsBseQuestion, setTagsSearch, DeletedQuestions, ShowHint, setShowHint, ShowLatexOption, setShowLatexOption } = useObjectToEdit(); const { mutate, isSuccess, isLoading } = useUpdateQuestion(); const { mutate: DeleteQuestion } = useDeleteQuestion(); - const { mutate: mutateAdd , isLoading:LoadingAsync } = useAddQuestion(); + const { mutate: mutateAdd, isLoading: LoadingAsync } = useAddQuestion(); const { data, isLoading: dataLoading } = useGetAllQuestion({ show: question_id, @@ -46,7 +36,7 @@ const EditPage: React.FC = () => { const { data: Questions, isLoading: QuestionsDataLoading } = useGetAllQuestion({ parent_id: question_id, - isPaginated:false + isPaginated: false }); const objectToEdit = { ...data?.data, Questions: Questions?.data }; @@ -62,7 +52,7 @@ const EditPage: React.FC = () => { const handleSubmit = (values: any) => { const DataToSend = structuredClone(values); setTagsSearch(null); - + if (isBseQuestion) { const UpdateBseQuestion = { @@ -79,7 +69,7 @@ const EditPage: React.FC = () => { console.log(DeletedQuestions, "DeletedQuestions"); console.log(UpdateBseQuestion); - // mutate(UpdateBseQuestion); + mutate(UpdateBseQuestion); DeletedQuestions?.map((item: any) => { DeleteQuestion({ id: item?.id }); @@ -93,8 +83,8 @@ const EditPage: React.FC = () => { if (item?.id) { const itemToSend = structuredClone(item); const keysToRemove = ["content_image"]; - console.log(itemToSend,"itemToSend"); - + console.log(itemToSend, "itemToSend"); + const updatedObject = removeStringKeys(itemToSend, keysToRemove); console.log(updatedObject, "updatedObject"); @@ -102,14 +92,14 @@ const EditPage: React.FC = () => { const oldAnswers = [] as any; const newAnswers = [] as any; - if(updatedObject?.content_image === null){ + if (updatedObject?.content_image === null) { updatedObject["content_image"] = "" } - + updatedObject?.answers?.forEach((item: any) => { if (item?.id) { - if(item?.content_image === null){ - item["content_image"] = "" + if (item?.content_image === null) { + item["content_image"] = "" } oldAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0 }); @@ -121,20 +111,20 @@ const EditPage: React.FC = () => { old: oldAnswers, new: newAnswers, }; - const emptyTag = tags?.new?.length === 0 && tags?.old?.length === 0 - const tagToSend = emptyTag ? "" : tags + const emptyTag = tags?.new?.length === 0 && tags?.old?.length === 0 + const tagToSend = emptyTag ? "" : tags mutate({ ...updatedObject, answers, - tags:tagToSend, + tags: tagToSend, }); } else { console.log(values?.id); const tags = processTags(item); - console.log(item,"DataToSend"); - - console.log(tags,"tags"); + console.log(item, "DataToSend"); + + console.log(tags, "tags"); mutateAdd({ ...item, subject_id: subject_id, @@ -158,15 +148,15 @@ const EditPage: React.FC = () => { const newAnswers = [] as any; updatedObject?.answers?.forEach((item: any) => { if (item?.id) { - console.log(item,"item"); - const deletedImage = item?.content_image === null - if(deletedImage){ - oldAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0, content_image:"" }); + console.log(item, "item"); + const deletedImage = item?.content_image === null + if (deletedImage) { + oldAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0, content_image: "" }); - }else{ - oldAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0 }); + } else { + oldAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0 }); - } + } } else { newAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0 }); } @@ -187,113 +177,105 @@ const EditPage: React.FC = () => { const location = useLocation(); const navigate = useNavigate(); - const handleCancel = () => { - navigate(-1); - }; - const handleValidateSingleQuestion = (values:any,isValid:boolean,handleSubmit:any)=>{ - const haveMoreThanOneAnswer = values?.answers?.length > 1; - const haveOneAnswerRight = haveMoreThanOneAnswer && values?.answers?.some((item:any)=> item?.isCorrect === 1 || item.isCorrect === true ) - const haveImageOrContent = haveOneAnswerRight && values?.answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) - const content = values.content ; - const content_image = values.content_image ; - const haveContentOrContentImage = !!content || !!content_image ; - console.log(haveImageOrContent,"haveImageOrContent"); - if(!haveContentOrContentImage){ + const handleValidateSingleQuestion = (values: any, isValid: boolean, handleSubmit: any) => { + const haveMoreThanOneAnswer = values?.answers?.length > 1; + const haveOneAnswerRight = haveMoreThanOneAnswer && values?.answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true) + const haveImageOrContent = haveOneAnswerRight && values?.answers?.some((item: any) => !(item?.content) && !(item.content_image)) + const content = values.content; + const content_image = values.content_image; + const haveContentOrContentImage = !!content || !!content_image; + console.log(haveImageOrContent, "haveImageOrContent"); + if (!haveContentOrContentImage) { toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); return false; } - if(!haveMoreThanOneAnswer){ - toast.error(t("validation.it_should_have_more_than_one_answers")) ; - return false ; - } - if(!haveOneAnswerRight){ - toast.error(t("validation.it_should_have_more_than_one_correct_answers")) ; - return false ; - } - if(haveImageOrContent){ - toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) - return false - } - - console.log(1); - - if(isValid){ - handleSubmit(values) - } - -} -const handleValidateBaseQuestion = (values: any,isValid:boolean,handleSubmit:any) => { - const content = values.content ; - const content_image = values.content_image ; - const haveContentOrContentImage = !!content || !!content_image ; - console.log(2); - - if(!haveContentOrContentImage){ - toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); - return false; - } - console.log(1); - - const isValidate = values?.Questions?.every((Question: any, QuestionsIndex: number) => { - - - const content = Question.content ; - const content_image = Question.content_image ; - const haveContentOrContentImage = !!content || !!content_image ; - if(!haveContentOrContentImage){ - toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); - return false; - } - - - //// answers - const answers = Question?.answers; - const haveAnswers = answers?.length > 0; - const haveMoreThanOneAnswer = haveAnswers && answers?.length > 1; - const haveOneAnswerRight = haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true); - const haveImageOrContent = haveOneAnswerRight && answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) - - - - - if (!haveAnswers) { - toast.error(t("validation.it_should_have_more_than_one_answers")); - return false; - } - if (!haveMoreThanOneAnswer) { toast.error(t("validation.it_should_have_more_than_one_answers")); - return false; + return false; } - if (!haveOneAnswerRight) { toast.error(t("validation.it_should_have_more_than_one_correct_answers")); return false; } - - if(haveImageOrContent){ + if (haveImageOrContent) { toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) return false } - return true - }); + console.log(1); - console.log(1); - -if(isValid && isValidate){ - console.log(2); - handleSubmit(values) -} - -}; + if (isValid) { + handleSubmit(values) + } -const handleNavigateToPage = () => { - const cleanedUrl = location.pathname.replace(/\/Question\/\d+$/, ""); - navigate(cleanedUrl); -}; + } + const handleValidateBaseQuestion = (values: any, isValid: boolean, handleSubmit: any) => { + const content = values.content; + const content_image = values.content_image; + const haveContentOrContentImage = !!content || !!content_image; + console.log(2); + + if (!haveContentOrContentImage) { + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + console.log(1); + + const isValidate = values?.Questions?.every((Question: any, QuestionsIndex: number) => { + + + const content = Question.content; + const content_image = Question.content_image; + const haveContentOrContentImage = !!content || !!content_image; + if (!haveContentOrContentImage) { + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + + + //// answers + const answers = Question?.answers; + const haveAnswers = answers?.length > 0; + const haveMoreThanOneAnswer = haveAnswers && answers?.length > 1; + const haveOneAnswerRight = haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true); + const haveImageOrContent = haveOneAnswerRight && answers?.some((item: any) => !(item?.content) && !(item.content_image)) + + + + + if (!haveAnswers) { + toast.error(t("validation.it_should_have_more_than_one_answers")); + return false; + } + + if (!haveMoreThanOneAnswer) { + toast.error(t("validation.it_should_have_more_than_one_answers")); + return false; + } + + if (!haveOneAnswerRight) { + toast.error(t("validation.it_should_have_more_than_one_correct_answers")); + return false; + } + + if (haveImageOrContent) { + toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) + return false + } + + return true + }); + + console.log(1); + + if (isValid && isValidate) { + console.log(2); + handleSubmit(values) + } + + }; useEffect(() => { if (isSuccess) { @@ -302,31 +284,6 @@ const handleNavigateToPage = () => { } }, [isSuccess]); - const onChangeHint: CheckboxProps['onChange'] = (e) => { - setShowHint(e.target.checked); - localStorage?.setItem(LocalStorageEnum.HINT_INPUT,e.target.checked ? "true" : "false" ) - }; - - const onChangeLatexOption: CheckboxProps['onChange'] = (e) => { - setShowLatexOption(e.target.checked); - localStorage?.setItem(LocalStorageEnum.LATEX_OPTION_INPUT,e.target.checked ? "true" : "false" ) - }; - - - const contentSetting = ( -
- - - { t("header.show_hint")} - - - - { t("header.show_MMl")} - - - -
- ); @@ -337,119 +294,13 @@ const handleNavigateToPage = () => { } if (objectToEdit?.isBase) { return ( - - -
-
- {t("header.edit_question")} -
- -
- - {({ values,isValid,handleSubmit }) => ( - -
-
- {/*
*/} -
-
- {t("practical.edit")} {t("models.exercise")}{" "} -
-
- - - - -
{t("header.exercise")}
-
-
- -
-
{t("practical.back")}
- -
-
-
- )} -
- -
-
+ ); } return ( - -
-
- {t("header.edit_question")} -
-
- - { - handleSubmit(values); - }} -> - {({ values , dirty,isValid,handleSubmit }) => ( -
-
- {/*
*/} -
-
- {t("practical.edit")} {t("models.exercise")}{" "} -
-
- - - - - -
{t("header.exercise")}
-
-
- -
-
{t("practical.back")}
-
{ Loading ? ()=>{} : handleValidateSingleQuestion(values,isValid,handleSubmit) }} - > {t("practical.edit")} - {Loading && ( - - - - )} -
-
-
- -
- )} -
- -
-
- + ); }; diff --git a/src/Pages/Admin/question/Model/AddForm/BaseForm.tsx b/src/Pages/Admin/question/Model/AddForm/BaseForm.tsx new file mode 100644 index 0000000..9f29cc8 --- /dev/null +++ b/src/Pages/Admin/question/Model/AddForm/BaseForm.tsx @@ -0,0 +1,72 @@ +import { Form, Formik, useFormikContext } from 'formik' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { MdOutlineArrowForwardIos } from 'react-icons/md' +import { getInitialValuesBase, getValidationSchemaBase } from '../../formUtil' +import Header from '../../../../../Components/exercise/Header' +import { useNavigate } from 'react-router-dom' +import { Spin } from 'antd' +import BaseForm from "../../Model/Malty/Form"; + +const BaseFormContainer = ({handleFormSubmit,Loading,handleValidateBaseQuestion}:{handleFormSubmit:any,Loading:any,handleValidateBaseQuestion:any}) => { + const [t] = useTranslation() + const navigate = useNavigate(); + + const handleNavigateToPage = () => { + const cleanedUrl = location.pathname?.replace("/Question/add", ""); + navigate(cleanedUrl); + }; + const handleCancel = () => { + navigate(-1); + }; + + + + + return ( +
+
+ {t("header.add_new_question")} +
+ +
+ + {({ values,isValid,handleSubmit ,dirty}) => ( + +
+
+
+ + +
+
{t("practical.back")}
+ +
+
+
+ )} +
+ +
+
+ + ) +} + +export default BaseFormContainer \ No newline at end of file diff --git a/src/Pages/Admin/question/Model/AddForm/Form.tsx b/src/Pages/Admin/question/Model/AddForm/Form.tsx new file mode 100644 index 0000000..a8734f4 --- /dev/null +++ b/src/Pages/Admin/question/Model/AddForm/Form.tsx @@ -0,0 +1,72 @@ +import { Form, Formik, useFormikContext } from 'formik' +import { useTranslation } from 'react-i18next' +import { MdOutlineArrowForwardIos } from 'react-icons/md' +import { getInitialValues, getValidationSchema } from '../../formUtil' +import Header from '../../../../../Components/exercise/Header' +import { useNavigate } from 'react-router-dom' +import { Spin } from 'antd' +import ModelForm from "../../Model/ModelForm"; + +const FormContainer = ({handleFormSubmit,Loading,handleValidateSingleQuestion}:{handleFormSubmit:any,Loading:any,handleValidateSingleQuestion:any}) => { + const [t] = useTranslation() + const navigate = useNavigate(); + + const handleNavigateToPage = () => { + const cleanedUrl = location.pathname?.replace("/Question/add", ""); + navigate(cleanedUrl); + }; + const handleCancel = () => { + navigate(-1); + }; + + + return ( +
+
+ {t("header.add_new_question")} +
+ +
+ { + handleFormSubmit(values); + }} + + > + {({ values,isValid ,handleSubmit,dirty}) => ( +
+
+
+ + +
+
{t("practical.back")}
+
{ Loading ? ()=>{} : handleValidateSingleQuestion(values,isValid,handleSubmit,t) }} + > + {t("practical.add")} + + {Loading && ( + + + + )} +
+
+
+
+ )} +
+ +
+
+ + ) +} + +export default FormContainer \ No newline at end of file diff --git a/src/Pages/Admin/question/Model/AddForm/ValidationFn.ts b/src/Pages/Admin/question/Model/AddForm/ValidationFn.ts new file mode 100644 index 0000000..d521647 --- /dev/null +++ b/src/Pages/Admin/question/Model/AddForm/ValidationFn.ts @@ -0,0 +1,100 @@ +import { toast } from "react-toastify"; + +export const handleValidateSingleQuestion = (values:any,isValid:boolean,handleSubmit:any,t:any)=>{ + const haveMoreThanOneAnswer = values?.answers?.length > 1; + const haveOneAnswerRight = haveMoreThanOneAnswer && values?.answers?.some((item:any)=> item?.isCorrect === 1 || item.isCorrect === true ) + const haveImageOrContent = haveOneAnswerRight && values?.answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) + const content = values.content ; + const content_image = values.content_image ; + const haveContentOrContentImage = !!content || !!content_image ; + console.log(haveImageOrContent,"haveImageOrContent"); + if(!haveContentOrContentImage){ + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + + if(!haveMoreThanOneAnswer){ + toast.error(t("validation.it_should_have_more_than_one_answers")) ; + return false ; + } + if(!haveOneAnswerRight){ + toast.error(t("validation.it_should_have_more_than_one_correct_answers")) ; + return false ; + } + if(haveImageOrContent){ + toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) + return false + } + + console.log(1); + + if(isValid){ + handleSubmit(values) + } + +} +export const handleValidateBaseQuestion = (values: any,isValid:boolean,handleSubmit:any,t:any) => { + const content = values.content ; + const content_image = values.content_image ; + const haveContentOrContentImage = !!content || !!content_image ; + console.log(2); + + if(!haveContentOrContentImage){ + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + console.log(1); + + const isValidate = values?.Questions?.every((Question: any, QuestionsIndex: number) => { + + + const content = Question.content ; + const content_image = Question.content_image ; + const haveContentOrContentImage = !!content || !!content_image ; + if(!haveContentOrContentImage){ + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + + + //// answers + const answers = Question?.answers; + const haveAnswers = answers?.length > 0; + const haveMoreThanOneAnswer = haveAnswers && answers?.length > 1; + const haveOneAnswerRight = haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true); + const haveImageOrContent = haveOneAnswerRight && answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) + + + + + if (!haveAnswers) { + toast.error(t("validation.it_should_have_more_than_one_answers")); + return false; + } + + if (!haveMoreThanOneAnswer) { + toast.error(t("validation.it_should_have_more_than_one_answers")); + return false; + } + + if (!haveOneAnswerRight) { + toast.error(t("validation.it_should_have_more_than_one_correct_answers")); + return false; + } + + if(haveImageOrContent){ + toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) + return false + } + + return true + }); + + console.log(1); + +if(isValid && isValidate){ + console.log(2); + handleSubmit(values) +} + +}; \ No newline at end of file diff --git a/src/Pages/Admin/question/Model/EditForm/BaseFormContainer.tsx b/src/Pages/Admin/question/Model/EditForm/BaseFormContainer.tsx new file mode 100644 index 0000000..c0b70b6 --- /dev/null +++ b/src/Pages/Admin/question/Model/EditForm/BaseFormContainer.tsx @@ -0,0 +1,108 @@ +import { Form, Formik } from 'formik'; +import React from 'react' +import { MdOutlineArrowForwardIos } from 'react-icons/md'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { getInitialValuesBase, getValidationSchemaBase } from '../../formUtil'; +import { Checkbox, Popover, Spin } from 'antd'; +import { SettingFilled } from '@ant-design/icons'; +import { CheckboxProps } from 'antd/lib'; +import { LocalStorageEnum } from '../../../../../enums/LocalStorageEnum'; +import { useObjectToEdit } from '../../../../../zustand/ObjectToEditState'; +import ModelForm from "../../Model/Malty/Form"; + +const BaseFormContainer = ({objectToEdit,handleSubmit,Loading,handleValidateBaseQuestion,t}:{objectToEdit:any,handleSubmit:any,Loading:any,handleValidateBaseQuestion:any,t:any}) => { + const location = useLocation(); + const navigate = useNavigate(); + const { ShowHint,setShowHint , ShowLatexOption,setShowLatexOption } = + useObjectToEdit(); + const handleCancel = () => { + navigate(-1); + }; + const handleNavigateToPage = () => { + const cleanedUrl = location.pathname.replace(/\/Question\/\d+$/, ""); + navigate(cleanedUrl); + }; + + const onChangeHint: CheckboxProps['onChange'] = (e) => { + setShowHint(e.target.checked); + localStorage?.setItem(LocalStorageEnum.HINT_INPUT,e.target.checked ? "true" : "false" ) + }; + + const onChangeLatexOption: CheckboxProps['onChange'] = (e) => { + setShowLatexOption(e.target.checked); + localStorage?.setItem(LocalStorageEnum.LATEX_OPTION_INPUT,e.target.checked ? "true" : "false" ) + }; + + + const contentSetting = ( +
+ + + { t("header.show_hint")} + + + + { t("header.show_MMl")} + + + +
+ ); + + + return ( +
+
+ {t("header.edit_question")} +
+ +
+ + {({ values,isValid,handleSubmit,dirty }) => ( + +
+
+ {/*
*/} +
+
+ {t("practical.edit")} {t("models.exercise")}{" "} +
+
+ + + + +
{t("header.exercise")}
+
+
+ +
+
{t("practical.back")}
+ +
+
+
+ )} +
+ +
+
+ ) +} + +export default BaseFormContainer \ No newline at end of file diff --git a/src/Pages/Admin/question/Model/EditForm/FormContainer.tsx b/src/Pages/Admin/question/Model/EditForm/FormContainer.tsx new file mode 100644 index 0000000..338e91e --- /dev/null +++ b/src/Pages/Admin/question/Model/EditForm/FormContainer.tsx @@ -0,0 +1,110 @@ +import { Form, Formik } from 'formik'; +import React from 'react' +import { MdOutlineArrowForwardIos } from 'react-icons/md'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { getInitialValues, getValidationSchema } from '../../formUtil'; +import { Checkbox, Popover, Spin } from 'antd'; +import { SettingFilled } from '@ant-design/icons'; +import { CheckboxProps } from 'antd/lib'; +import { LocalStorageEnum } from '../../../../../enums/LocalStorageEnum'; +import { useObjectToEdit } from '../../../../../zustand/ObjectToEditState'; +import ModelForm from "../../Model/ModelForm"; + +const FormContainer = ({objectToEdit,handleSubmit,Loading,handleValidateSingleQuestion,t}:{objectToEdit:any,handleSubmit:any,Loading:any,handleValidateSingleQuestion:any,t:any}) => { + const location = useLocation(); + const navigate = useNavigate(); + const { ShowHint,setShowHint , ShowLatexOption,setShowLatexOption } = + useObjectToEdit(); + const handleCancel = () => { + navigate(-1); + }; + const handleNavigateToPage = () => { + const cleanedUrl = location.pathname.replace(/\/Question\/\d+$/, ""); + navigate(cleanedUrl); + }; + + const onChangeHint: CheckboxProps['onChange'] = (e) => { + setShowHint(e.target.checked); + localStorage?.setItem(LocalStorageEnum.HINT_INPUT,e.target.checked ? "true" : "false" ) + }; + + const onChangeLatexOption: CheckboxProps['onChange'] = (e) => { + setShowLatexOption(e.target.checked); + localStorage?.setItem(LocalStorageEnum.LATEX_OPTION_INPUT,e.target.checked ? "true" : "false" ) + }; + + + const contentSetting = ( +
+ + + { t("header.show_hint")} + + + + { t("header.show_MMl")} + + + +
+ ); + + + return ( +
+
+ {t("header.edit_question")} +
+
+ + { + handleSubmit(values); + }} +> + {({ values , dirty,isValid,handleSubmit }) => ( +
+
+ {/*
*/} +
+
+ {t("practical.edit")} {t("models.exercise")}{" "} +
+
+ + + + + +
{t("header.exercise")}
+
+
+ +
+
{t("practical.back")}
+
{ Loading ? ()=>{} : handleValidateSingleQuestion(values,isValid,handleSubmit,t) }} + > {t("practical.edit")} + {Loading && ( + + + + )} +
+
+
+ +
+ )} +
+ +
+
+ ) +} + +export default FormContainer \ No newline at end of file diff --git a/src/Pages/Admin/question/Model/EditForm/ValidationFn.ts b/src/Pages/Admin/question/Model/EditForm/ValidationFn.ts new file mode 100644 index 0000000..d521647 --- /dev/null +++ b/src/Pages/Admin/question/Model/EditForm/ValidationFn.ts @@ -0,0 +1,100 @@ +import { toast } from "react-toastify"; + +export const handleValidateSingleQuestion = (values:any,isValid:boolean,handleSubmit:any,t:any)=>{ + const haveMoreThanOneAnswer = values?.answers?.length > 1; + const haveOneAnswerRight = haveMoreThanOneAnswer && values?.answers?.some((item:any)=> item?.isCorrect === 1 || item.isCorrect === true ) + const haveImageOrContent = haveOneAnswerRight && values?.answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) + const content = values.content ; + const content_image = values.content_image ; + const haveContentOrContentImage = !!content || !!content_image ; + console.log(haveImageOrContent,"haveImageOrContent"); + if(!haveContentOrContentImage){ + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + + if(!haveMoreThanOneAnswer){ + toast.error(t("validation.it_should_have_more_than_one_answers")) ; + return false ; + } + if(!haveOneAnswerRight){ + toast.error(t("validation.it_should_have_more_than_one_correct_answers")) ; + return false ; + } + if(haveImageOrContent){ + toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) + return false + } + + console.log(1); + + if(isValid){ + handleSubmit(values) + } + +} +export const handleValidateBaseQuestion = (values: any,isValid:boolean,handleSubmit:any,t:any) => { + const content = values.content ; + const content_image = values.content_image ; + const haveContentOrContentImage = !!content || !!content_image ; + console.log(2); + + if(!haveContentOrContentImage){ + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + console.log(1); + + const isValidate = values?.Questions?.every((Question: any, QuestionsIndex: number) => { + + + const content = Question.content ; + const content_image = Question.content_image ; + const haveContentOrContentImage = !!content || !!content_image ; + if(!haveContentOrContentImage){ + toast.error(`${t("validation.one_of_image_and_content_should_be_enter_in_question")}`); + return false; + } + + + //// answers + const answers = Question?.answers; + const haveAnswers = answers?.length > 0; + const haveMoreThanOneAnswer = haveAnswers && answers?.length > 1; + const haveOneAnswerRight = haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true); + const haveImageOrContent = haveOneAnswerRight && answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) + + + + + if (!haveAnswers) { + toast.error(t("validation.it_should_have_more_than_one_answers")); + return false; + } + + if (!haveMoreThanOneAnswer) { + toast.error(t("validation.it_should_have_more_than_one_answers")); + return false; + } + + if (!haveOneAnswerRight) { + toast.error(t("validation.it_should_have_more_than_one_correct_answers")); + return false; + } + + if(haveImageOrContent){ + toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer")) + return false + } + + return true + }); + + console.log(1); + +if(isValid && isValidate){ + console.log(2); + handleSubmit(values) +} + +}; \ No newline at end of file diff --git a/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx b/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx index a7e3fb8..5f31229 100644 --- a/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx +++ b/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx @@ -21,7 +21,6 @@ const ChoiceFields = React.memo( setFieldValue:any; values:any }) => { - console.log(567) const [t] = useTranslation(); const { ShowHint } = useObjectToEdit(); const handleDeleteChoice = () => { @@ -120,7 +119,6 @@ const ChoiceFields = React.memo( } , (prevProps, nextProps) => { console.log(prevProps.values?.Questions?.[prevProps?.parent_index]?.answers?.[prevProps?.index] === nextProps.values?.Questions?.[nextProps?.parent_index]?.answers?.[prevProps?.index]); - console.log(2255); return prevProps.values?.Questions?.[prevProps?.parent_index]?.answers?.[prevProps?.index] === nextProps.values?.Questions?.[nextProps?.parent_index]?.answers?.[prevProps?.index]; }); diff --git a/src/Pages/Admin/question/Model/Malty/ChoiceField/Choices.tsx b/src/Pages/Admin/question/Model/Malty/ChoiceField/Choices.tsx index 974c558..883b9a9 100644 --- a/src/Pages/Admin/question/Model/Malty/ChoiceField/Choices.tsx +++ b/src/Pages/Admin/question/Model/Malty/ChoiceField/Choices.tsx @@ -113,8 +113,6 @@ const Choices = React.memo( ({setFieldValue ,values,parent_index }:any) => { ); }, (prevProps, nextProps) => { - console.log(prevProps.values?.Questions?.[prevProps?.parent_index]?.answers === nextProps.values?.Questions?.[nextProps?.parent_index]?.answers); - console.log(22); return prevProps.values?.Questions?.[prevProps?.parent_index]?.answers === nextProps.values?.Questions?.[nextProps?.parent_index]?.answers; }); diff --git a/src/Pages/Admin/question/Model/Malty/Form.tsx b/src/Pages/Admin/question/Model/Malty/Form.tsx index eadad46..6f92ef3 100644 --- a/src/Pages/Admin/question/Model/Malty/Form.tsx +++ b/src/Pages/Admin/question/Model/Malty/Form.tsx @@ -10,6 +10,7 @@ import { CombinationKeyEnum } from "../../../../../enums/CombinationKeyEnum"; import { toast } from "react-toastify"; import MainInputs from "./components/MainInputs"; import Questions from "./components/Questions"; +import useUnsavedChangesWarning from "../../../../../Hooks/useUnsavedChangesWarning"; const Form = () => { const formik = useFormikContext(); @@ -64,6 +65,8 @@ const Form = () => { const lastQuestions = formik?.values?.Questions?.length - 1; + +////////////// hooks useKeyCombination( { ctrlKey: true, shiftKey: true, code: CombinationKeyEnum.CHOICE }, () => { @@ -77,8 +80,10 @@ const lastQuestions = formik?.values?.Questions?.length - 1; handleAddQuestion(true); }, ); + useUnsavedChangesWarning(formik.dirty); + ////////////// useEffect(() => { if (Success) { formik.resetForm() diff --git a/src/Pages/Admin/question/Model/ModelForm.tsx b/src/Pages/Admin/question/Model/ModelForm.tsx index 9b95d40..3c1f6a4 100644 --- a/src/Pages/Admin/question/Model/ModelForm.tsx +++ b/src/Pages/Admin/question/Model/ModelForm.tsx @@ -12,6 +12,7 @@ import { CombinationKeyEnum } from "../../../../enums/CombinationKeyEnum"; import { toast } from "react-toastify"; import LaTeXInputMemo from "../../../../Components/LatextInput/LaTeXInputMemo"; import ImageBoxFieldMemo from "../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo"; +import useUnsavedChangesWarning from "../../../../Hooks/useUnsavedChangesWarning"; const Form = () => { const [t] = useTranslation(); @@ -33,6 +34,10 @@ const Form = () => { } }; + + ////////////// hooks + + useKeyCombination( { ctrlKey: true, shiftKey: true, code: CombinationKeyEnum.CHOICE }, () => { @@ -40,14 +45,16 @@ const Form = () => { }, ); + useUnsavedChangesWarning(formik.dirty); + + + useEffect(() => { if (Success) { formik.resetForm() setSuccess(false); } }, [Success]); - - console.log(formik.errors); return ( diff --git a/src/Pages/Home/Dummy.tsx b/src/Pages/Home/Dummy.tsx index 17ab6aa..7dcc757 100644 --- a/src/Pages/Home/Dummy.tsx +++ b/src/Pages/Home/Dummy.tsx @@ -6,9 +6,7 @@ const Dummy = () => { const [t] = useTranslation(); return (
- - - +
); }; diff --git a/src/Pages/Home/LaTeXInputMemo.tsx b/src/Pages/Home/LaTeXInputMemo.tsx index d60d6fd..eae6b42 100644 --- a/src/Pages/Home/LaTeXInputMemo.tsx +++ b/src/Pages/Home/LaTeXInputMemo.tsx @@ -8,7 +8,7 @@ const LaTeXInputMemo: React.FC = React.memo(({ field ,form, label, ...prop const { setFieldValue } = form; const [curCentValue, setCurrentValue] = useState(value) const handleChangeInput = (e: React.ChangeEvent) => { - setFieldValue(name, e.target.value); + // setFieldValue(name, e.target.value); setCurrentValue(e.target.value) }; console.log(name,"name"); @@ -24,6 +24,7 @@ const LaTeXInputMemo: React.FC = React.memo(({ field ,form, label, ...prop
+