Compare commits

..

No commits in common. "42bbdebf6f341f9de4e9d5c3c4df6d2fc0df4e68" and "3a877b5053077ef5250edd35ef2407309f75a0ab" have entirely different histories.

19 changed files with 99 additions and 271 deletions

View File

@ -38,10 +38,12 @@ const ImageBoxField = ({ name }: any) => {
}
// Process the file
console.log('File selected:', file);
}
if (file) {
generateImagePreview(file, setImagePreview);
console.log(file,"file");
formik.setFieldValue(name, file);
}

View File

@ -37,14 +37,14 @@ export const MenuItem = ({ item, location, index, isOpen }: any) => {
className="DropDownIcon"
onClick={() => handleDropdown(index)}
>
<MdExpandLess className="sidebar_menu_icon" />
<MdExpandLess className="sidebar_menu_icaon" />
</div>
) : (
<div
className="DropDownIcon"
onClick={() => handleDropdown(index)}
>
<MdExpandMore className="sidebar_menu_icon"/>
<MdExpandMore className="sidebar_menu_icaon"/>
</div>
)}
</>

View File

@ -7,7 +7,6 @@ import { useTranslation } from "react-i18next";
import { QueryStatusEnum } from "../../enums/QueryStatus";
import SpinContainer from "../../Components/Layout/SpinContainer";
import { MdCancel } from "react-icons/md";
import { Form, Formik } from "formik";
interface LayoutModalProps {
isAddModal?: boolean;
@ -66,29 +65,12 @@ const LayoutModel = ({
open={isOpen === ModelEnum}
onCancel={handleCancel}
>
<Formik
enableReinitialize={true}
<FormikForm
handleSubmit={handleSubmit}
initialValues={getInitialValues}
validationSchema={getValidationSchema}
onSubmit={handleSubmit}
>
{(formik) => {
useEffect(() => {
if (isOpen === "" || isOpen === "isSuccess") {
formik.setErrors({});
formik.resetForm();
}
}, [isOpen]);
console.log(formik.initialValues);
console.log(formik?.values);
return <Form className="w-100">
<header>
<header>
<span>
{t(`practical.${isAddModal ? "add" : "edit"}`)}{" "}
{t(`models.${modelTitle}`)}{" "}
@ -106,7 +88,7 @@ const LayoutModel = ({
</div>
<button
className="add_button"
disabled={status === QueryStatusEnum.LOADING || !formik.dirty}
disabled={status === QueryStatusEnum.LOADING}
type="submit"
>
{t(`practical.${isAddModal ? "add" : "edit"}`)}
@ -118,10 +100,7 @@ const LayoutModel = ({
</button>
</div>
</main>
</Form>;
}}
</Formik>
</FormikForm>
</Modal>
</>
);

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { Suspense, lazy, useEffect } from "react";
import { Spin } from "antd";
import {
getInitialValues,
@ -20,14 +20,13 @@ import ModelForm from "./Model/ModelForm";
import { toast } from "react-toastify";
import { Form, Formik } from "formik";
import { MdOutlineArrowForwardIos } from "react-icons/md";
import { getCharFromNumber } from "../../../utils/getCharFromNumber";
const AddPage: React.FC = () => {
const location = useLocation();
const { mutateAsync,isLoading:LoadingAsync } = useAddQuestionAsync();
const { mutate, isLoading, isSuccess } = useAddQuestion();
const { isBseQuestion, setTagsSearch, setSuccess , ShowHint,setShowHint } =
const { isBseQuestion, setTagsSearch, setSuccess } =
useObjectToEdit();
const [t] = useTranslation();
@ -100,19 +99,12 @@ const AddPage: React.FC = () => {
mutate(NewQuestion);
}
};
const handleValidateSingleQuestion = (values: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")) ;
@ -123,42 +115,20 @@ const AddPage: React.FC = () => {
return false ;
}
if(haveImageOrContent){
toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer_in_answer"))
toast.error(t("validation.one_of_image_and_content_should_be_enter"))
return false
}
}
const handleValidateBaseQuestion = (values: any) => {
const content = values.content ;
const content_image = values.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;
}
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 haveAnswers = values?.Questions?.every((Question: any, QuestionsIndex: number) => {
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) )
console.log(haveImageOrContent,"haveImageOrContent");
if (!haveAnswers) {
toast.error(t("validation.it_should_have_more_than_one_answers"));
@ -176,14 +146,14 @@ const AddPage: React.FC = () => {
}
if(haveImageOrContent){
toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer"))
toast.error(t("validation.one_of_image_and_content_should_be_enter"))
return false
}
return true;
});
console.log(haveAnswers, "haveAnswers");
};
const navigate = useNavigate();

View File

@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { Checkbox, Modal, Popover, Spin } from "antd";
import { Modal, Spin } from "antd";
import {
getInitialValues,
getValidationSchema,
@ -26,12 +26,10 @@ 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";
const EditPage: React.FC = () => {
const { subject_id, lesson_id, question_id } = useParams<ParamsEnum>();
const { isBseQuestion, setIsBseQuestion, setTagsSearch, DeletedQuestions , ShowHint,setShowHint } =
const { isBseQuestion, setIsBseQuestion, setTagsSearch, DeletedQuestions } =
useObjectToEdit();
const { mutate, isSuccess, isLoading } = useUpdateQuestion();
@ -62,7 +60,6 @@ const EditPage: React.FC = () => {
setTagsSearch(null);
if (isBseQuestion) {
const UpdateBseQuestion = {
id: DataToSend?.id,
content: DataToSend?.content,
@ -77,7 +74,7 @@ const EditPage: React.FC = () => {
console.log(DeletedQuestions, "DeletedQuestions");
console.log(UpdateBseQuestion);
// mutate(UpdateBseQuestion);
mutate(UpdateBseQuestion);
DeletedQuestions?.map((item: any) => {
DeleteQuestion({ id: item?.id });
@ -100,16 +97,8 @@ const EditPage: React.FC = () => {
const oldAnswers = [] as any;
const newAnswers = [] as any;
if(updatedObject?.content_image === null){
updatedObject["content_image"] = ""
}
updatedObject?.answers?.forEach((item: any) => {
if (item?.id) {
if(item?.content_image === null){
item["content_image"] = ""
}
oldAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0 });
} else {
newAnswers.push({ ...item, isCorrect: item?.isCorrect ? 1 : 0 });
@ -119,12 +108,11 @@ const EditPage: React.FC = () => {
old: oldAnswers,
new: newAnswers,
};
const emptyTag = tags?.new?.length === 0 && tags?.old?.length === 0
const tagToSend = emptyTag ? "" : tags
console.log(answers);
mutate({
...updatedObject,
answers,
tags:tagToSend,
tags,
});
} else {
console.log(values?.id);
@ -194,15 +182,7 @@ const EditPage: React.FC = () => {
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 ;
@ -212,7 +192,7 @@ const EditPage: React.FC = () => {
return false ;
}
if(haveImageOrContent){
toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer"))
toast.error(t("validation.one_of_image_and_content_should_be_enter"))
return false
}
@ -220,18 +200,6 @@ const EditPage: React.FC = () => {
const handleValidateBaseQuestion = (values: any) => {
const haveAnswers = 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;
@ -255,7 +223,7 @@ const handleValidateBaseQuestion = (values: any) => {
}
if(haveImageOrContent){
toast.error(t("validation.one_of_image_and_content_should_be_enter_in_answer"))
toast.error(t("validation.one_of_image_and_content_should_be_enter"))
return false
}
@ -279,22 +247,6 @@ const handleNavigateToPage = () => {
}
}, [isSuccess]);
const onChange: CheckboxProps['onChange'] = (e) => {
setShowHint(e.target.checked);
};
const contentSetting = (
<div>
<Checkbox checked={ShowHint} onChange={onChange}>
{ t("header.show_hint")}
</Checkbox>
</div>
);
const Loading = LoadingAsync || isLoading
if (dataLoading || QuestionsDataLoading) {
@ -325,13 +277,7 @@ const handleNavigateToPage = () => {
<div>
{t("practical.edit")} {t("models.exercise")}{" "}
</div>
<div className="SettingEdit">
<Popover trigger="click" content={contentSetting}>
<SettingFilled/>
</Popover>
<div>{t("header.exercise")}</div>
</div>
</header>
<BaseForm />
<div className="exercise_add_buttons">
@ -375,7 +321,7 @@ const handleNavigateToPage = () => {
handleSubmit(values);
}}
>
{({ values,handleSubmit , dirty }) => (
{({ values,handleSubmit }) => (
<Form className="w-100">
<main className="w-100 exercise_add_main">
{/* <Header/> */}
@ -389,13 +335,13 @@ const handleNavigateToPage = () => {
<div className="exercise_add_buttons">
<div onClick={handleCancel}>{t("practical.back")}</div>
<button
disabled={Loading || !dirty}
disabled={Loading}
className="relative"
onClick={()=>{handleValidateSingleQuestion(values) ;handleSubmit(values)}}
onSubmit={()=>{handleValidateSingleQuestion(values) ;handleSubmit(values) }}
type="submit"
> {t("practical.edit")}
{Loading && (
<span className="Spinier_Div">
<Spin />

View File

@ -11,23 +11,21 @@ import { GoTrash } from "react-icons/go";
import { Popconfirm } from "antd";
import { useObjectToEdit } from "../../../../../zustand/ObjectToEditState";
const ChoiceFields = ({ index }: { index: number; data: Choice }) => {
const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
const formik = useFormikContext<any>();
const [t] = useTranslation();
const { ShowHint } = useObjectToEdit();
const handleDeleteChoice = () => {
document.getElementById(`ChoiceField_${index}`)?.classList.add("exit")
console.log(index);
console.log(formik.values.answers[index]);
const updatedAnswers = formik.values.answers.filter(
(_: any, i: any) => i !== index,
);
setTimeout(() => {
formik.setFieldValue("answers", updatedAnswers);
document.getElementById(`ChoiceField_${index}`)?.classList.remove("exit")
}, 500);
};
const values = formik?.values?.answers?.[index] ;
@ -42,7 +40,7 @@ const ChoiceFields = ({ index }: { index: number; data: Choice }) => {
return (
<>
<div id={`ChoiceField_${index}`} className="ChoiceFields">
<div className="ChoiceFields">
<TextField
className="textarea_exercise"
placeholder={"choice"}
@ -89,6 +87,7 @@ const ChoiceFields = ({ index }: { index: number; data: Choice }) => {
{t("header.delete_choice")}
<GoTrash
className="trash_icon"
size={17}
/>
</p>

View File

@ -26,19 +26,25 @@ const ChoiceFields = ({
const [t] = useTranslation();
const { ShowHint } = useObjectToEdit();
const handleDeleteChoice = () => {
document.getElementById(`ChoiceField_${parent_index}_${index}`)?.classList.add("exit")
const arrayLength = formik.values.Questions?.[parent_index].answers?.length;
console.log(arrayLength);
if (arrayLength === 1) {
toast.error(
t("validation.Sorry, the question must have at least one option"),
);
return;
}
const updatedAnswers = formik.values.Questions?.[
parent_index
].answers.filter((_: any, i: any) => i !== index);
setTimeout(() => {
formik.setFieldValue(`Questions[${parent_index}].answers`, updatedAnswers);
document.getElementById(`ChoiceField_${parent_index}_${index}`)?.classList.remove("exit")
}, 500);
};
const values = formik.values.Questions?.[parent_index]?.answers?.[index] ;
console.log(values,"values");
const handelCanDeleteAnswers = ()=>{
const content = values?.content ;
@ -52,7 +58,7 @@ const ChoiceFields = ({
return (
<>
<div id={`ChoiceField_${parent_index}_${index}`} className="ChoiceFields">
<div className="ChoiceFields">
<TextField
className="textarea_exercise"
placeholder={"choice"}

View File

@ -17,7 +17,6 @@ import { toast } from "react-toastify";
const Form = () => {
const formik = useFormikContext<any>();
const { setSuccess, Success, setSavedQuestionData ,ShowHint} = useObjectToEdit();
console.log(formik.errors);
useEffect(() => {
setSavedQuestionData(formik.values);
@ -27,6 +26,7 @@ const Form = () => {
parent_index: number,
fromKeyCombination: boolean = false,
) => {
console.log(parent_index);
formik.setFieldValue(`Questions.[${parent_index}].answers`, [
...((formik?.values as any)?.Questions?.[parent_index]
@ -34,8 +34,7 @@ const Form = () => {
{
answer: null,
content_image: null,
content:null,
answer_image: null,
isCorrect: 0,
},
]);

View File

@ -51,7 +51,6 @@ const TableHeader = () => {
{name:LessonName, path:`lesson/${lesson_id }`}
]);
return (
<div className="TableWithHeader">
<Suspense fallback={<Spin />}>

View File

@ -24,7 +24,7 @@ export const getValidationSchema = () => {
// validate input
return Yup.object().shape({
content_image: Yup.string().nullable(),
content: Yup.string().nullable(),
content: Yup.string().required("validation.required").nullable(),
answers: Yup.array().of(
Yup.object().shape({
content: Yup.string().nullable(),
@ -43,16 +43,7 @@ export const getValidationSchema = () => {
return hasCorrectAnswer && !haveImageOrContent ;
},
),
}).test(
"content-or-image-required",
"At least one of content or content_image must be provided",
(obj:any) => {
const isValid = !!obj.content || !!obj.content_image;
console.log(isValid,"isValid");
return isValid;
}
);
});
};
export const getInitialValuesBase = (objectToEdit: Question): any => {
@ -98,16 +89,16 @@ export const getValidationSchemaBase = () => {
// validate input
return Yup.object().shape({
content_image: Yup.string().nullable(),
content: Yup.string(),
content: Yup.string().required("validation.required"),
Questions: Yup.array().of(
Yup.object().shape({
content_image: Yup.string().nullable(),
content: Yup.string().nullable(),
image: Yup.string().nullable(),
content: Yup.string().required("validation.required"),
answers: Yup.array()
.of(
Yup.object().shape({
content: Yup.string().nullable(),
content_image: Yup.string().nullable(),
content: Yup.string().required("validation.required"),
answer_image: Yup.string().nullable(),
isCorrect: Yup.boolean(),
}),
).min(2)
@ -123,24 +114,9 @@ export const getValidationSchemaBase = () => {
return hasCorrectAnswer && !haveImageOrContent ;
},
),
}).test(
"content-or-image-required",
"At least one of content or content_image must be provided in question",
(obj:any) => {
const isValid = !!obj.content || !!obj.content_image;
return isValid;
}
),
}),
).min(1),
}).test(
"content-or-image-required",
"At least one of content or content_image must be provided in base",
(obj:any) => {
const isValid = !!obj.content || !!obj.content_image;
return isValid;
}
);
});
};
export function processTags(DataToSend: any) {

View File

@ -10,6 +10,7 @@ type FormFieldType = {
const FormField = ({ isLoading }: FormFieldType) => {
const [t] = useTranslation();
const {isValid} = useFormikContext();
console.log(isValid,"isValid");
return (
<Form className="AuthForm">
@ -19,6 +20,20 @@ const FormField = ({ isLoading }: FormFieldType) => {
<ValidationField name="username" label="username" />
<ValidationField name="password" label="password" />
{/* <div className="AuthInput">
<label className="form-label" htmlFor="password">
{t("input.Password")}
</label>
<Field
placeholder={t("input.Password")}
as={Input.Password}
type="password"
id="password"
name="password"
className="passwordInput"
size="large"
/>
</div> */}
<button disabled={ !isValid || isLoading} type="submit" className="auth_submit_button">
{t("practical.login")}

View File

@ -1,10 +1,12 @@
import React, { useEffect } from "react";
import { Formik } from "formik";
import useAuthState from "../../zustand/AuthState";
import useNavigateOnSuccess from "../../Hooks/useNavigateOnSuccess";
import { useLoginAdmin } from "../../api/auth";
import FormField from "./FormField";
import { initialValues, validationSchema } from "./formUtils";
import { initialValues, validationSchema } from "./formutils";
import { FormValues } from "../../types/Auth";
import { toast } from "react-toastify";
import { useTranslation } from "react-i18next";
import { getLocalStorage } from "../../utils/LocalStorage";
import { USER_KEY } from "../../config/AppKey";
@ -37,7 +39,7 @@ const LoginForm = () => {
return (
<div className="LoginForm">
<Formik initialValues={initialValues} onSubmit={handelSubmit} validateOnMount={true} validationSchema={validationSchema} >
<Formik initialValues={initialValues} onSubmit={handelSubmit} isInitialValid={false} validationSchema={validationSchema} >
{(formikProps) => <FormField isLoading={isLoading} />}
</Formik>
</div>

View File

@ -5,7 +5,7 @@ import { Select } from "antd";
const Dummy = () => {
const [t] = useTranslation();
// const { FilterButton, FilterBody } = useFilter();
const { FilterButton, FilterBody } = useFilter();
return (
<div className="DummyHomePage">
@ -16,8 +16,8 @@ const Dummy = () => {
<Select
style={{width:"200px"}}
showSearch
/>
*/}
/> */}
</div>
);
};

View File

@ -87,26 +87,9 @@ svg {
font-size: 1vw;
}
.DropDownIcon{
.sidebar_menu_icon{
.DropDownIcon {
.sidebar_menu_icaon {
background: transparent !important;
color: var(--primary) !important;
}
}
.back_button,button{
cursor: pointer;
transition: .5s ease-in-out;
&:hover{
scale: 1.02;
}
}
svg{
cursor: pointer;
transition: .5s ease-in-out;
&:hover{
scale: 1.1;
}
}

View File

@ -58,14 +58,8 @@
justify-content: center;
gap: 7px;
font-size: 14px;
svg {
font-size: 16px;
}
transition: .4s ease-in-out;
&:hover{
scale: 1.1;
}
}

View File

@ -1,7 +1,7 @@
.DataTable {
width: 100%;
border-radius: 0 0 10px 10px;
box-shadow: 0px 0px 32px 2px #080F3414;
box-shadow: 2px 2px 8px 3px rgba(0, 0, 0, 0.1);
}
.ant-table-cell {

View File

@ -65,11 +65,6 @@
gap: 5px;
font-size: 1vw;
width: 10vw;
cursor: pointer;
transition: .5s ease-in-out;
&:hover{
scale: 1.1;
}
}
}
}
@ -149,10 +144,6 @@
div{
margin-left: 25px;
display: flex;
align-items: center;
}
.SettingEdit{
display: flex; gap: 20px;
}
img {
cursor: pointer;
@ -277,9 +268,9 @@
.Choices{
padding-inline: 40px;
padding-inline: 20px;
.textarea_exercise,.hint{
width: calc(50vw - 30px) !important;
width: calc(50vw - 10px) !important;
}
.ValidationField{
@ -290,9 +281,9 @@
}
.QuestionFIeld{
padding-inline: 20px;
padding-inline: 10px;
.textarea_exercise{
width: calc(50vw - 20px) !important;
width: calc(50vw - 10px) !important;
}
@ -305,7 +296,7 @@
.Choices.ChoicesMalty{
.textarea_exercise,.hint{
width: calc(50vw - 40px) !important;
width: calc(50vw - 20px) !important;
}
.ValidationField{
@ -319,35 +310,3 @@
.question_header_setting{
margin-inline: 40px;
}
.Choices {
transition: 0.5s ease-in-out ;
animation: fadeIn .5s ease-in-out forwards ;
}
.exit {
transition: 0.5s ease-in-out ;
animation: fadeOut .7s ease-in-out forwards ;
}
@keyframes fadeIn {
0% {
transform: translateY(-40px);
opacity: 0;
}
100% {
transform: translateY(0px);
opacity: 1;
}
}
@keyframes fadeOut {
0% {
transform: translateY(0px);
opacity: 1;
}
100% {
transform: translateY(-40px);
opacity: 0;
}
}

View File

@ -50,8 +50,7 @@
"it_should_have_more_than_one_answers": "يجب أن يحتوي على أكثر من إجابة",
"it_should_have_more_than_one_correct_answers": "يجب أن يحتوي على إجابة صحيحة",
"File_size_exceeds_2_MB_limit.":"حجم الملف يتجاوز الحد الأقصى البالغ 2 ميجابايت",
"one_of_image_and_content_should_be_enter_in_answer":"يجب إدخال صورة أو محتوى واحد على الأقل في الاجابة",
"one_of_image_and_content_should_be_enter_in_question":"يجب إدخال صورة أو محتوى واحد على الأقل في السؤال"
"one_of_image_and_content_should_be_enter":"يجب إدخال صورة أو محتوى واحد على الأقل"
},
"header": {
"register_students": "تسجيل الطلاب",

View File

@ -52,6 +52,6 @@ export const useObjectToEdit = create<ModelState>((set) => ({
setDeletedQuestions: (data) => set(() => ({ DeletedQuestions: data })),
SavedQuestionData: [],
setSavedQuestionData: (data) => set(() => ({ SavedQuestionData: data })),
ShowHint: false,
ShowHint: true,
setShowHint: (data) => set(() => ({ ShowHint: data })),
}));