fix question

This commit is contained in:
karimaldeen 2024-09-09 13:46:31 +03:00
parent 084762e51e
commit 6e1e79465f
27 changed files with 251 additions and 505 deletions

View File

@ -56,6 +56,7 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/node": "^20.14.0", "@types/node": "^20.14.0",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"@vitejs/plugin-legacy": "^5.4.1", "@vitejs/plugin-legacy": "^5.4.1",

View File

@ -84,6 +84,9 @@ importers:
'@types/react': '@types/react':
specifier: ^18.3.3 specifier: ^18.3.3
version: 18.3.3 version: 18.3.3
'@types/react-beautiful-dnd':
specifier: ^13.1.8
version: 13.1.8
'@types/react-dom': '@types/react-dom':
specifier: ^18.3.0 specifier: ^18.3.0
version: 18.3.0 version: 18.3.0
@ -1342,6 +1345,9 @@ packages:
'@types/prop-types@15.7.12': '@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
'@types/react-beautiful-dnd@13.1.8':
resolution: {integrity: sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==}
'@types/react-dom@18.3.0': '@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
@ -4997,7 +5003,7 @@ snapshots:
'@jridgewell/trace-mapping@0.3.9': '@jridgewell/trace-mapping@0.3.9':
dependencies: dependencies:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.5.0
optional: true optional: true
'@popperjs/core@2.11.8': {} '@popperjs/core@2.11.8': {}
@ -5252,6 +5258,10 @@ snapshots:
'@types/prop-types@15.7.12': {} '@types/prop-types@15.7.12': {}
'@types/react-beautiful-dnd@13.1.8':
dependencies:
'@types/react': 18.3.3
'@types/react-dom@18.3.0': '@types/react-dom@18.3.0':
dependencies: dependencies:
'@types/react': 18.3.3 '@types/react': 18.3.3

View File

@ -14,6 +14,9 @@ const ImageBoxField = ({ name }: any) => {
const value = getNestedValue(formik.values, name); const value = getNestedValue(formik.values, name);
const [imagePreview, setImagePreview] = useState<string | null>(null); const [imagePreview, setImagePreview] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement | null>(null); const fileInputRef = useRef<HTMLInputElement | null>(null);
console.log(formik.values);
console.log(value,name);
useEffect(() => { useEffect(() => {
if (value instanceof File) { if (value instanceof File) {

View File

@ -42,6 +42,7 @@ const AddPage: React.FC = () => {
isBseQuestion, isBseQuestion,
setTagsSearch, setTagsSearch,
setObjectToEdit, setObjectToEdit,
objectToEdit,
setSuccess, setSuccess,
SavedQuestionData, SavedQuestionData,
} = useObjectToEdit(); } = useObjectToEdit();
@ -102,8 +103,6 @@ const AddPage: React.FC = () => {
{ resetForm }: { resetForm: () => void }, { resetForm }: { resetForm: () => void },
) => { ) => {
const DataToSend = structuredClone(values); const DataToSend = structuredClone(values);
console.log(DataToSend);
setTagsSearch(null); setTagsSearch(null);
const canAnswersBeShuffled = DataToSend?.canAnswersBeShuffled ? 1 : 0; const canAnswersBeShuffled = DataToSend?.canAnswersBeShuffled ? 1 : 0;
if (isBseQuestion || DataToSend?.isBase === 1) { if (isBseQuestion || DataToSend?.isBase === 1) {
@ -166,51 +165,54 @@ const AddPage: React.FC = () => {
}; };
const navigate = useNavigate(); const navigate = useNavigate();
// console.log(SavedQuestionData);
useEffect(() => { // useEffect(() => {
if ( // if (
isSuccessAsync && // isSuccessAsync &&
(SavedQuestionData?.Questions?.length > 0 ? isSuccess : true) // (SavedQuestionData?.Questions?.length > 0 ? isSuccess : true)
) { // ) {
setObjectToEdit(null); // setObjectToEdit(null);
setSuccess(true); // setSuccess(true);
localStorage.removeItem(QUESTION_OBJECT_KEY); // localStorage.removeItem(QUESTION_OBJECT_KEY);
} // }
if (isSuccess && !SavedQuestionData?.Questions?.length) { // if (isSuccess && !SavedQuestionData?.Questions?.length) {
toast.success(t("validation.the_possess_done_successful")); // toast.success(t("validation.the_possess_done_successful"));
setObjectToEdit(null); // setObjectToEdit(null);
setSuccess(true); // setSuccess(true);
localStorage.removeItem(QUESTION_OBJECT_KEY); // localStorage.removeItem(QUESTION_OBJECT_KEY);
} // }
}, [isSuccess, isSuccessAsync]); // }, [isSuccess, isSuccessAsync]);
let cleanedAnswers = cleanObject(SavedQuestionData); // let cleanedAnswers = cleanObject(SavedQuestionData);
let noChange = hasItems(cleanedAnswers); // let noChange = hasItems(cleanedAnswers);
// console.log(SavedQuestionData);
useSaveOnDisconnect(noChange, QUESTION_OBJECT_KEY, SavedQuestionData); // useSaveOnDisconnect(noChange, QUESTION_OBJECT_KEY, SavedQuestionData);
const SavedData = getLocalStorageQuestions(QUESTION_OBJECT_KEY); // const SavedData = getLocalStorageQuestions(QUESTION_OBJECT_KEY);
// console.log(SavedData);
const handleCancel = () => { const handleCancel = () => {
if (!noChange) {
navigate(-1); navigate(-1);
localStorage.removeItem(QUESTION_OBJECT_KEY); // if (!noChange) {
} else { // localStorage.removeItem(QUESTION_OBJECT_KEY);
setIsOpen(ModalEnum?.QUESTION_ACCEPT); // } else {
} // setIsOpen(ModalEnum?.QUESTION_ACCEPT);
// }
}; };
if (isBseQuestion || SavedData?.isBase === 1) { if (isBseQuestion ) {
return ( return (
<div className="exercise_add"> <div className="exercise_add">
<FormikForm <FormikForm
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
initialValues={getInitialValuesBase(SavedData)} initialValues={getInitialValuesBase(objectToEdit)}
validationSchema={getValidationSchemaBase} validationSchema={getValidationSchemaBase}
> >
<main className="w-100 exercise_add_main"> <main className="w-100 exercise_add_main">
<Header/> <Header />
<ModelForm/> <BaseForm />
<div className="exercise_add_buttons"> <div className="exercise_add_buttons">
<div onClick={handleCancel}>{t("practical.back")}</div> <div onClick={handleCancel}>{t("practical.back")}</div>
@ -236,7 +238,7 @@ const AddPage: React.FC = () => {
<div className="exercise_add"> <div className="exercise_add">
<FormikForm <FormikForm
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
initialValues={getInitialValues(SavedData)} initialValues={getInitialValues(objectToEdit)}
validationSchema={getValidationSchema} validationSchema={getValidationSchema}
> >
<main className="w-100 exercise_add_main"> <main className="w-100 exercise_add_main">

View File

@ -15,18 +15,18 @@ const CheckboxField = ({
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
const [t] = useTranslation(); const [t] = useTranslation();
const CheckboxhandleChange = (value: any, index: number) => { const CheckboxhandleChange = (value: any, index: number) => {
const allAreZero = formik?.values?.QuestionOptions?.some( const allAreZero = formik?.values?.answers?.some(
(item: any) => item.isCorrect === 1, (item: any) => item.isCorrect === 1,
); );
if (allAreZero) { if (allAreZero) {
formik?.values.QuestionOptions.forEach((item: any, index: number) => { formik?.values.answers.forEach((item: any, index: number) => {
formik.setFieldValue(`QuestionOptions[${index}].isCorrect`, 0); formik.setFieldValue(`answers[${index}].isCorrect`, 0);
}); });
} }
formik.setFieldValue( formik.setFieldValue(
`QuestionOptions[${name}].isCorrect`, `answers[${name}].isCorrect`,
value?.target?.checked ? 1 : 0, value?.target?.checked ? 1 : 0,
); );
}; };
@ -35,9 +35,8 @@ const CheckboxField = ({
<Checkbox <Checkbox
onChange={onChange || CheckboxhandleChange} onChange={onChange || CheckboxhandleChange}
disabled={isDisabled} disabled={isDisabled}
checked={formik.values?.QuestionOptions?.[name]?.isCorrect === 1} checked={formik.values?.answers?.[name]?.isCorrect === 1}
className={className} className={className}
> >
{t(`input.${label ? label : name}`)} {t(`input.${label ? label : name}`)}
</Checkbox> </Checkbox>

View File

@ -6,11 +6,8 @@ import { useTranslation } from "react-i18next";
import { getCharFromNumber } from "../../../../../utils/getCharFromNumber"; import { getCharFromNumber } from "../../../../../utils/getCharFromNumber";
import CheckboxField from "./CheckboxField"; import CheckboxField from "./CheckboxField";
import TextField from "./TextField"; import TextField from "./TextField";
import File from "./File";
import { FaCirclePlus, FaDeleteLeft } from "react-icons/fa6";
import { GoTrash } from "react-icons/go";
import { LuImagePlus } from "react-icons/lu";
import ImageBoxField from "../../../../../Components/CustomFields/ImageBoxField/ImageBoxField"; import ImageBoxField from "../../../../../Components/CustomFields/ImageBoxField/ImageBoxField";
import { GoTrash } from "react-icons/go";
const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => { const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
@ -19,16 +16,17 @@ const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
const handleDeleteChoice = () => { const handleDeleteChoice = () => {
console.log(index); console.log(index);
console.log(formik.values.QuestionOptions[index]); console.log(formik.values.answers[index]);
const updatedQuestionOptions = formik.values.QuestionOptions.filter( const updatedAnswers = formik.values.answers.filter(
(_: any, i: any) => i !== index, (_: any, i: any) => i !== index,
); );
formik.setFieldValue("QuestionOptions", updatedQuestionOptions); formik.setFieldValue("answers", updatedAnswers);
}; };
return ( return (
<>
<div className="ChoiceFields"> <div className="ChoiceFields">
<TextField <TextField
className="textarea_exercise" className="textarea_exercise"
@ -38,31 +36,36 @@ const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
id={`choice_${index + 1}`} id={`choice_${index + 1}`}
type="TextArea" type="TextArea"
/> />
{/* <File
className="file_exercise" <ImageBoxField name={`answers.${index}.answer_image`} />
label={"attachment"}
name={index}
type="File"
placeholder=""
icon={<LuImagePlus/>}
/> */}
<ImageBoxField name={`QuestionOptions.${index}.answer_image`} />
<div className="answer_status"> <div className="answer_status">
<CheckboxField <CheckboxField
className="" className=""
label="The_correct_answer" label="The_correct_answer"
name={index} name={index}
type="Checkbox" type="Checkbox"
/> />
<p className="delete_question_options"> <p className="delete_question_options">
{t("header.delete_choice")} {t("header.delete_choice")}
<GoTrash className="trash_icon" onClick={handleDeleteChoice} size={17} /> <GoTrash className="trash_icon" onClick={handleDeleteChoice} size={17} />
</p> </p>
</div>
</div> </div>
<div className="exercise_form_width">
<ValidationField
className=" "
placeholder="_"
name={`answers.${index}.hint`}
label="hint_question"
type="text"
style={{ width: "100%" }}
/>
</div> </div>
</>
); );
}; };

View File

@ -35,7 +35,7 @@ const Choices = () => {
return ( return (
<DragDropContext onDragEnd={handleDragEnd}> <DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="choices"> <Droppable droppableId="choices">
{(provided:any) => ( {(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}> <div {...provided.droppableProps} ref={provided.innerRef}>
{formik?.values?.answers?.map( {formik?.values?.answers?.map(
(item: Choice, index: number) => { (item: Choice, index: number) => {
@ -50,7 +50,7 @@ const Choices = () => {
draggableId={draggableId} draggableId={draggableId}
index={index} index={index}
> >
{(provided:any) => ( {(provided) => (
<div <div
ref={provided.innerRef} ref={provided.innerRef}
{...provided.draggableProps} {...provided.draggableProps}

View File

@ -1,87 +0,0 @@
import { Button, Upload, UploadFile } from "antd";
import useFormField from "../../../../../Hooks/useFormField";
import { UploadOutlined } from "@ant-design/icons";
import { useMemo } from "react";
const File = ({
name,
label,
onChange,
isDisabled,
placeholder,
className,
props,
icon,
}: any) => {
const newName = `QuestionOptions[${name}].answer_image`;
const { formik, t, isError, errorMsg } = useFormField(newName, props);
let imageUrl = formik?.values?.QuestionOptions[name]?.answer_image ?? null;
// console.log(imageUrl);
console.log(imageUrl);
const fileList: UploadFile[] = useMemo(() => {
if (!imageUrl) return [];
return [
typeof imageUrl === "string"
? {
uid: "-1",
name: "uploaded-image",
status: "done",
url: imageUrl,
thumbUrl: imageUrl,
}
: {
uid: imageUrl.uid || "-1",
name: imageUrl.name || "uploaded-image",
status: "done",
originFileObj: imageUrl,
},
];
}, [imageUrl]);
// console.log(1);
const FilehandleChange = (value: any) => {
// console.log(value,"filevalue");
if (value.fileList.length === 0) {
formik.setFieldValue(newName, null);
} else {
formik.setFieldValue(
`QuestionOptions[${name}].answer_image`,
value?.file?.originFileObj,
);
}
};
const customRequest = async ({ onSuccess, no_label, label_icon }: any) => {
onSuccess();
};
return (
<div className={`ValidationField upload_image_button ${className ?? ""} `}>
<label htmlFor={name} className="text">
{t(`input.${label || name}`)}
</label>
<Upload
disabled={isDisabled}
listType="picture"
maxCount={1}
fileList={[...fileList]}
onChange={onChange || FilehandleChange}
customRequest={customRequest}
className={` w-100`}
>
<Button
className={isError ? "isError w-100 " : " w-100"}
icon={icon ? icon : <UploadOutlined />}
>
{placeholder ?? t("input.Click_to_upload_the_image")}
</Button>
<div className="Error_color"> {isError ? "required" : ""}</div>
{errorMsg}
</Upload>
</div>
);
};
export default File;

View File

@ -1,69 +0,0 @@
import { Form, Input } from "antd";
import React from "react";
import useFormField from "../../../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md";
import { Field } from "formik";
const { TextArea } = Input;
const HintField = ({
name,
label,
label2,
placeholder,
isDisabled,
onChange,
props,
no_label,
label_icon,
id,
className,
}: any) => {
const newName = `answers[${name}].hint`;
const { formik, isError, errorMsg, t } = useFormField(newName, props);
const TextFilehandleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
// console.log('Change:', e.target.value);
formik.setFieldValue(newName, e.target.value);
};
return (
<div className={`ValidationField w-100 ${className ?? ""} `}>
{no_label ? (
<label htmlFor={name} className="text">
<span>empty</span>
</label>
) : label_icon ? (
<div className="LabelWithIcon">
<label htmlFor={name} className="text">
{label2 ? label2 : t(`input.${label ? label : name}`)}
</label>
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
</div>
) : (
<label htmlFor={name} className="text">
{label2 ? label2 : t(`input.${label ? label : name}`)}
</label>
)}
<Form.Item
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Field
as={Input}
placeholder={t(`input.${placeholder ? placeholder : name}`)}
name={newName}
disabled={isDisabled}
size="large"
onChange={onChange || TextFilehandleChange}
style={{ width: 200 }}
id={id}
/>
</Form.Item>
</div>
);
};
export default React.memo(HintField);

View File

@ -1,8 +1,8 @@
import { Form, Input } from "antd"; import { Form, Input } from "antd";
import React from "react"; import React from "react";
import useFormField from "../../../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md"; import { MdOutlineEdit } from "react-icons/md";
import { Field } from "formik"; import { Field } from "formik";
import useFormField from "../../../../../Hooks/useFormField";
const { TextArea } = Input; const { TextArea } = Input;
const TextField = ({ const TextField = ({
@ -18,7 +18,7 @@ const TextField = ({
id, id,
className, className,
}: any) => { }: any) => {
const newName = `QuestionOptions[${name}].answer`; const newName = `answers[${name}].content`;
const { formik, isError, errorMsg, t } = useFormField(newName, props); const { formik, isError, errorMsg, t } = useFormField(newName, props);
const TextFilehandleChange = ( const TextFilehandleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,

View File

@ -1,25 +0,0 @@
import React from 'react'
import ValidationField from '../../../../Components/ValidationField/ValidationField'
import { Col, Row } from "reactstrap";
const FilterForm = () => {
return (
<div>
<Row>
<Col>
<ValidationField placeholder="name" label="name" name="name" />
<ValidationField placeholder="name" label="name" name="name" />
</Col>
<Col>
<ValidationField placeholder="name" label="name" name="name" />
<ValidationField placeholder="name" label="name" name="name" />
</Col>
</Row>
</div>
)
}
export default FilterForm

View File

@ -10,17 +10,14 @@ import { useTranslation } from "react-i18next";
import DynamicTags from "./Tags/DynamicTags"; import DynamicTags from "./Tags/DynamicTags";
import QuestionFIeld from "./QuestionFIeld/QuestionFIeld"; import QuestionFIeld from "./QuestionFIeld/QuestionFIeld";
import { useObjectToEdit } from "../../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../../zustand/ObjectToEditState";
import Choices from "./ChoiceField/Choices";
const Form = () => { const Form = () => {
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
const { isOpen } = useModalState((state) => state);
// const {data} = useGetAllQuestion();
const { setSuccess, Success, setSavedQuestionData } = useObjectToEdit(); const { setSuccess, Success, setSavedQuestionData } = useObjectToEdit();
useEffect(() => { useEffect(() => {
if (Success) { if (Success) {
console.log(1);
formik.setErrors({}); formik.setErrors({});
formik.resetForm({ values: {} }); formik.resetForm({ values: {} });
setSuccess(false); setSuccess(false);
@ -32,14 +29,13 @@ const Form = () => {
}, [formik?.values]); }, [formik?.values]);
// console.log(formik?.errors); // console.log(formik?.errors);
console.log(formik?.values?.Questions, "formik?.values?.Questions");
const handleAddChoice = (parent_index: number) => { const handleAddChoice = (parent_index: number) => {
console.log(parent_index); console.log(parent_index);
formik.setFieldValue(`Questions.[${parent_index}].QuestionOptions`, [ formik.setFieldValue(`Questions.[${parent_index}].answers`, [
...((formik?.values as any)?.Questions?.[parent_index] ...((formik?.values as any)?.Questions?.[parent_index]
.QuestionOptions as Choice[]), .answers as Choice[]),
{ {
answer: null, answer: null,
@ -58,9 +54,9 @@ const Form = () => {
image: "", image: "",
parent: "", parent: "",
isBase: 0, isBase: 0,
max_mark: 1, // max_mark: 1,
min_mark_to_pass: 1, // min_mark_to_pass: 1,
QuestionOptions: [{ answer: null, answer_image: null, isCorrect: 0 }], answers: [{ answer: null, answer_image: null, isCorrect: 0 }],
tags: [], tags: [],
}, },
]); ]);
@ -70,7 +66,6 @@ const Form = () => {
formik.setFieldValue("max_mark", max_mark); formik.setFieldValue("max_mark", max_mark);
}; };
const [t] = useTranslation(); const [t] = useTranslation();
console.log(formik.errors);
return ( return (
<Row className="w-100"> <Row className="w-100">
@ -109,21 +104,9 @@ const Form = () => {
/> />
</div> </div>
{( <Choices parent_index={parent_index} />
(formik?.values as any)?.Questions?.[parent_index]
?.QuestionOptions || []
).map((item: Choice, index: number) => {
return (
<ChoiceFields
key={index}
parent_index={parent_index}
index={index}
data={item}
/>
);
})}
{formik?.values?.Questions?.[parent_index]?.QuestionOptions {formik?.values?.Questions?.[parent_index]?.answers
?.length < 5 && ( ?.length < 5 && (
<p className="add_new_button"> <p className="add_new_button">
<FaCirclePlus <FaCirclePlus

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import useFormField from "../../../../../../Hooks/useFormField";
import { Checkbox, Form } from "antd"; import { Checkbox, Form } from "antd";
import { useFormik, useFormikContext } from "formik"; import { useFormik, useFormikContext } from "formik";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -15,16 +16,15 @@ const CheckboxField = ({
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
const [t] = useTranslation(); const [t] = useTranslation();
const CheckboxhandleChange = (value: any) => { const CheckboxhandleChange = (value: any) => {
console.log(value?.target?.checked);
const allAreZero = formik?.values?.Questions?.[ const allAreZero = formik?.values?.Questions?.[
parent_index parent_index
]?.QuestionOptions?.some((item: any) => item.isCorrect === 1); ]?.answers?.some((item: any) => item.isCorrect === 1);
if (allAreZero) { if (allAreZero) {
formik?.values?.Questions?.[parent_index]?.QuestionOptions.forEach( formik?.values?.Questions?.[parent_index]?.answers.forEach(
(item: any, index: number) => { (item: any, index: number) => {
formik.setFieldValue( formik.setFieldValue(
`Questions[${parent_index}].QuestionOptions[${index}].isCorrect`, `Questions[${parent_index}].answers[${index}].isCorrect`,
0, 0,
); );
}, },
@ -32,7 +32,7 @@ const CheckboxField = ({
} }
formik.setFieldValue( formik.setFieldValue(
`Questions[${parent_index}].QuestionOptions[${name}].isCorrect`, `Questions[${parent_index}].answers[${name}].isCorrect`,
value?.target?.checked ? 1 : 0, value?.target?.checked ? 1 : 0,
); );
}; };
@ -42,7 +42,7 @@ const CheckboxField = ({
onChange={onChange || CheckboxhandleChange} onChange={onChange || CheckboxhandleChange}
disabled={isDisabled} disabled={isDisabled}
checked={ checked={
formik.values?.Questions?.[parent_index]?.QuestionOptions?.[name] formik.values?.Questions?.[parent_index]?.answers?.[name]
?.isCorrect === 1 ?.isCorrect === 1
} }
className={className} className={className}

View File

@ -9,6 +9,7 @@ import TextField from "./TextField";
import File from "./File"; import File from "./File";
import { FaTrash } from "react-icons/fa"; import { FaTrash } from "react-icons/fa";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import HintField from "./HintField";
const ChoiceFields = ({ const ChoiceFields = ({
index, index,
@ -25,7 +26,7 @@ const ChoiceFields = ({
const handleDeleteChoice = () => { const handleDeleteChoice = () => {
const arrayLength = const arrayLength =
formik.values.Questions?.[parent_index].QuestionOptions?.length; formik.values.Questions?.[parent_index].answers?.length;
console.log(arrayLength); console.log(arrayLength);
@ -36,15 +37,16 @@ const ChoiceFields = ({
return; return;
} }
const updatedQuestionOptions = formik.values.Questions?.[ const updatedAnswers = formik.values.Questions?.[
parent_index parent_index
].QuestionOptions.filter((_: any, i: any) => i !== index); ].answers.filter((_: any, i: any) => i !== index);
formik.setFieldValue( formik.setFieldValue(
`Questions[${parent_index}].QuestionOptions`, `Questions[${parent_index}].answers`,
updatedQuestionOptions, updatedAnswers,
); );
}; };
return ( return (
<>
<div className="ChoiceFields"> <div className="ChoiceFields">
<TextField <TextField
className="textarea_exercise" className="textarea_exercise"
@ -73,6 +75,18 @@ const ChoiceFields = ({
<FaTrash onClick={handleDeleteChoice} size={17} /> <FaTrash onClick={handleDeleteChoice} size={17} />
</p> </p>
</div> </div>
<div>
<HintField
className=""
label="hint"
placeholder="hint"
name={index}
parent_index={parent_index}
/>
</div>
</>
); );
}; };

View File

@ -13,11 +13,11 @@ const File = ({
parent_index, parent_index,
props, props,
}: any) => { }: any) => {
const newName = `Questions[${parent_index}].QuestionOptions[${name}].answer_image`; const newName = `Questions[${parent_index}].answers[${name}].answer_image`;
const { formik, t, isError, errorMsg } = useFormField(newName, props); const { formik, t, isError, errorMsg } = useFormField(newName, props);
let imageUrl = let imageUrl =
formik?.values?.Questions?.[parent_index]?.QuestionOptions[name] formik?.values?.Questions?.[parent_index]?.answers[name]
?.answer_image ?? null; ?.answer_image ?? null;
// console.log(imageUrl); // console.log(imageUrl);
@ -49,7 +49,7 @@ const File = ({
formik.setFieldValue(newName, null); formik.setFieldValue(newName, null);
} else { } else {
formik.setFieldValue( formik.setFieldValue(
`Questions[${parent_index}].QuestionOptions[${name}].answer_image`, `Questions[${parent_index}].answers[${name}].answer_image`,
value?.file?.originFileObj, value?.file?.originFileObj,
); );
} }

View File

@ -18,7 +18,7 @@ const TextField = ({
parent_index, parent_index,
className, className,
}: any) => { }: any) => {
const newName = `Questions[${parent_index}].QuestionOptions[${name}].answer`; const newName = `Questions[${parent_index}].answers[${name}].content`;
const { formik, isError, errorMsg, t } = useFormField(newName, props); const { formik, isError, errorMsg, t } = useFormField(newName, props);
const TextFilehandleChange = ( const TextFilehandleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,

View File

@ -3,6 +3,7 @@ import React, { useEffect } from "react";
import ValidationField from "../../../../../Components/ValidationField/ValidationField"; import ValidationField from "../../../../../Components/ValidationField/ValidationField";
import { useFormikContext } from "formik"; import { useFormikContext } from "formik";
import { useModalState } from "../../../../../zustand/Modal"; import { useModalState } from "../../../../../zustand/Modal";
import PdfUploader from "../../../../../Components/CustomFields/PdfUploader";
import ChoiceFields from "./ChoiceField/ChoiceFields"; import ChoiceFields from "./ChoiceField/ChoiceFields";
import { FaCirclePlus } from "react-icons/fa6"; import { FaCirclePlus } from "react-icons/fa6";
import { Choice } from "../../../../../types/Item"; import { Choice } from "../../../../../types/Item";
@ -29,9 +30,9 @@ const Form = () => {
const handleAddChoice = (parent_index: number) => { const handleAddChoice = (parent_index: number) => {
console.log(parent_index); console.log(parent_index);
console.log(formik?.values?.Questions); console.log(formik?.values?.Questions);
formik.setFieldValue(`Questions.[${parent_index}].QuestionOptions`, [ formik.setFieldValue(`Questions.[${parent_index}].answers`, [
...((formik?.values as any)?.Questions?.[parent_index] ...((formik?.values as any)?.Questions?.[parent_index]
.QuestionOptions as Choice[]), .answers as Choice[]),
{ {
answer: null, answer: null,
@ -52,7 +53,7 @@ const Form = () => {
isBase: 0, isBase: 0,
max_mark: 1, max_mark: 1,
min_mark_to_pass: 1, min_mark_to_pass: 1,
QuestionOptions: [{ answer: null, answer_image: null, isCorrect: 0 }], answers: [{ answer: null, answer_image: null, isCorrect: 0 }],
tags: [], tags: [],
}, },
]); ]);
@ -102,7 +103,7 @@ const Form = () => {
{( {(
(formik?.values as any)?.Questions?.[parent_index] (formik?.values as any)?.Questions?.[parent_index]
?.QuestionOptions || [] ?.answers || []
).map((item: Choice, index: number) => { ).map((item: Choice, index: number) => {
return ( return (
<ChoiceFields <ChoiceFields
@ -113,7 +114,7 @@ const Form = () => {
/> />
); );
})} })}
{formik?.values?.Questions?.[parent_index]?.QuestionOptions {formik?.values?.Questions?.[parent_index]?.answers
?.length < 5 && ( ?.length < 5 && (
<p className="add_new_button"> <p className="add_new_button">
<FaCirclePlus <FaCirclePlus

View File

@ -9,10 +9,11 @@ import File from "./File";
import { FaTrash } from "react-icons/fa"; import { FaTrash } from "react-icons/fa";
import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import CheckboxField from "./CheckboxField";
import HintField from "./HintField";
const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => { const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
console.log(index);
const { setDeletedQuestions, DeletedQuestions } = useObjectToEdit(); const { setDeletedQuestions, DeletedQuestions } = useObjectToEdit();
const [t] = useTranslation(); const [t] = useTranslation();
@ -25,13 +26,14 @@ const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
if (DeleteQuestionId?.id) { if (DeleteQuestionId?.id) {
setDeletedQuestions([...DeletedQuestions, DeleteQuestionId]); setDeletedQuestions([...DeletedQuestions, DeleteQuestionId]);
} }
const updatedQuestionOptions = formik.values.Questions.filter( const updatedAnswers = formik.values.Questions.filter(
(_: any, i: any) => i !== index, (_: any, i: any) => i !== index,
); );
formik.setFieldValue(`Questions`, updatedQuestionOptions); formik.setFieldValue(`Questions`, updatedAnswers);
}; };
return ( return (
<div className="d-c">
<div className="ChoiceFields"> <div className="ChoiceFields">
<TextField <TextField
className="textarea_exercise" className="textarea_exercise"
@ -46,10 +48,24 @@ const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
name={index} name={index}
type="File" type="File"
/> />
<CheckboxField
className="canAnswersBeShuffled"
label={"canAnswersBeShuffled"}
name={index}
/>
<p className="delete_question_options"> <p className="delete_question_options">
<FaTrash onClick={handleDeleteQuestion} size={17} /> <FaTrash onClick={handleDeleteQuestion} size={17} />
</p> </p>
</div> </div>
<div>
<HintField placeholder={"hint"} name={index} label="hint" id={`hint`} />
</div>
</div>
); );
}; };

View File

@ -1,33 +0,0 @@
import React, { useState } from "react";
import MathJax from "react-mathjax";
const MathInput: React.FC = () => {
const [input, setInput] = useState<string>(
"a^2+b^2=c^2 (x+a)^n=x=(-b±√(b^2-4ac))/2a ∑_(k=0)^n▒〖(n¦k) x^k a^(n-k) 〗",
);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const formattedInput = event.target.value.replace("_", " _ ");
console.log(event.target.value);
setInput(formattedInput);
};
return (
<MathJax.Provider>
<div>
<input
type="text"
id="mathInput"
value={input}
onChange={handleChange}
/>
<div>
<MathJax.Node formula={input} />
</div>
</div>
</MathJax.Provider>
);
};
export default MathInput;

View File

@ -8,12 +8,12 @@ import { useTranslation } from "react-i18next";
import DynamicTags from "./Tags/DynamicTags"; import DynamicTags from "./Tags/DynamicTags";
import { useObjectToEdit } from "../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../zustand/ObjectToEditState";
import { useEffect } from "react"; import { useEffect } from "react";
import { LuImagePlus } from "react-icons/lu"; import Choices from "./Field/Choices";
import ImageBoxField from "../../../../Components/CustomFields/ImageBoxField/ImageBoxField"; import ImageBoxField from "../../../../Components/CustomFields/ImageBoxField/ImageBoxField";
import SelectTag from "./Tags/SelectTag"; import SelectTag from "../../../../Components/CustomFields/SelectTag";
const Form = () => { const Form = () => {
const [t] = useTranslation() const [t] = useTranslation();
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
const { setSuccess, Success, setSavedQuestionData } = useObjectToEdit(); const { setSuccess, Success, setSavedQuestionData } = useObjectToEdit();
@ -26,41 +26,50 @@ const Form = () => {
}, [Success]); }, [Success]);
useEffect(() => { useEffect(() => {
setSavedQuestionData(formik.values) console.log(formik.values,"formik.values");
}, [formik?.values])
setSavedQuestionData(formik.values);
}, [formik?.values]);
const handleAddChoice = () => { const handleAddChoice = () => {
formik.setFieldValue('QuestionOptions', [...(formik?.values as any)?.QuestionOptions as Choice[], formik.setFieldValue("answers", [
...((formik?.values as any)?.answers as Choice[]),
{ {
answer:null, content: null,
answer_image:null, answer_image: null,
isCorrect:0, isCorrect: 0,
}]) },
} ]);
};
console.log(formik?.values);
return ( return (
<Row className="w-100 exercise_form_container"> <Row className="w-100 exercise_form_container">
<div className="exercise_form"> <div className="exercise_form">
<ValidationField className="textarea_exercise" name="answer_content" label="answer_content" type="TextArea" /> <ValidationField className="textarea_exercise" name="content" label="answer_content" type="TextArea" />
<ImageBoxField name="image" /> <ImageBoxField name="image" />
</div> </div>
{
(((formik?.values as any)?.QuestionOptions as Choice[])||[]) .map((item:Choice,index:number)=>{ <Choices />
return <ChoiceFields key={index} index={index} data={item}/> {formik?.values?.answers?.length < 5 && (
})} <p className="add_new_button">
{formik?.values?.QuestionOptions?.length < 5 && ( <FaCirclePlus onClick={handleAddChoice} size={23} />{" "}
<p className="add_new_button" > {t("header.add_new_choice")}
<FaCirclePlus onClick={handleAddChoice} size={23} /> {t("header.add_new_choice")}
</p> </p>
)} )}
{/* <DynamicTags/> */}
<div className="exercise_form_width"> <div className="exercise_form_width">
<ValidationField
<SelectTag/> className=" "
placeholder="_"
name="hint"
label="hint"
type="text"
style={{ width: "100%" }}
/>
<SelectTag />
</div> </div>
</Row> </Row>
); );

View File

@ -1,74 +0,0 @@
import React, { useState, useMemo } from 'react';
import { Select, Spin } from 'antd';
import { useGetAllTag } from '../../../../../api/tags';
import { useDebounce } from '../../../../../utils/useDebounce';
import { useTranslation } from 'react-i18next';
const SelectTag: React.FC = () => {
const [searchValue, setSearchValue] = useState<string>('');
const [fieldValue, setFieldValue] = useState<string>('');
const handleChange = (value: string[]) => {
setSearchValue('');
setFieldValue('');
};
const handleSearch = useDebounce((value: string) => {
setSearchValue(value);
});
const handleFieldChange = (value: string) => {
setFieldValue(value);
};
const handleBlur = () => {
setSearchValue('');
setFieldValue('');
};
const { data, isLoading } = useGetAllTag({
name: searchValue,
});
const [t] = useTranslation();
const options = data?.data ?? []
console.log(options,"options");
const additionalData = options?.length < 1 && searchValue.length > 1 && !isLoading ? [{id:`new_${searchValue}`,name:searchValue}] :[];
console.log(additionalData);
return (
<div className='SelectTag'>
<label htmlFor="">
{t("models.tag")}
</label>
<Select
mode="multiple"
allowClear
style={{ width: '100%' ,height:"40px"}}
placeholder=""
fieldNames={{ label: 'name', value: 'id' }}
onChange={handleChange}
options={[...options,...additionalData]}
filterOption={false}
loading={isLoading}
notFoundContent={isLoading ? <Spin /> : t("practical.not_found")}
onSearch={(value) => {
handleSearch(value);
handleFieldChange(value);
}}
searchValue={fieldValue}
onDropdownVisibleChange={(open) => {
if (!open) {
handleBlur();
}
}}
/>
</div>
);
};
export default SelectTag;

View File

@ -7,15 +7,18 @@ export const getInitialValues = (objectToEdit: Question): any => {
const tags = objectToEdit?.tags?.map((item: any, index: number) => { const tags = objectToEdit?.tags?.map((item: any, index: number) => {
return { ...item, key: index }; return { ...item, key: index };
}); });
console.log(objectToEdit);
return { return {
id: objectToEdit?.id ?? null, id: objectToEdit?.id ?? null,
content: objectToEdit?.content ?? "", content: objectToEdit?.content ?? "",
image: objectToEdit?.image ?? "", image: objectToEdit?.image ?? "",
subject_id: objectToEdit?.subject_id ?? "", subject_id: objectToEdit?.subject_id ?? "",
canAnswersBeShuffled: objectToEdit?.canAnswersBeShuffled ? 1 : 0,
hint: objectToEdit?.hint ?? "",
isBase: 0, isBase: 0,
parent_id: objectToEdit?.parent_id ?? "", parent_id: objectToEdit?.parent_id ?? "",
QuestionOptions: objectToEdit?.QuestionOptions ?? [], answers: objectToEdit?.answers ?? [],
tags: tags ?? [], tags: tags ?? [],
}; };
}; };
@ -25,10 +28,10 @@ export const getValidationSchema = () => {
return Yup.object().shape({ return Yup.object().shape({
image: Yup.string().nullable(), image: Yup.string().nullable(),
content: Yup.string().required("validation.required"), content: Yup.string().required("validation.required"),
QuestionOptions: Yup.array() answers: Yup.array()
.of( .of(
Yup.object().shape({ Yup.object().shape({
answer: Yup.string().required("validation.required"), content: Yup.string().required("validation.required"),
answer_image: Yup.string().nullable(), answer_image: Yup.string().nullable(),
isCorrect: Yup.boolean(), isCorrect: Yup.boolean(),
}), }),
@ -38,11 +41,6 @@ export const getValidationSchema = () => {
}; };
export const getInitialValuesBase = (objectToEdit: Question): any => { export const getInitialValuesBase = (objectToEdit: Question): any => {
const tags = objectToEdit?.tags?.map((item: any, index: number) => {
return { ...item, key: index };
});
console.log(objectToEdit);
const newQuestions = objectToEdit?.Questions?.map((item: any) => { const newQuestions = objectToEdit?.Questions?.map((item: any) => {
const tags = item?.tags?.map((tag: any) => ({ const tags = item?.tags?.map((tag: any) => ({
id: tag?.id, id: tag?.id,
@ -52,6 +50,8 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
return { return {
...item, ...item,
canAnswersBeShuffled: objectToEdit?.canAnswersBeShuffled ? 1 : 0,
hint: item?.hint ?? "",
tags, tags,
}; };
}); });
@ -65,6 +65,8 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
subject_id: objectToEdit?.subject_id ?? "", subject_id: objectToEdit?.subject_id ?? "",
isBase: 1, isBase: 1,
parent_id: objectToEdit?.parent_id ?? "", parent_id: objectToEdit?.parent_id ?? "",
canAnswersBeShuffled: objectToEdit?.canAnswersBeShuffled ? 1 : 0,
hint: objectToEdit?.hint ?? "",
Questions: questions, Questions: questions,
}; };
}; };
@ -78,10 +80,10 @@ export const getValidationSchemaBase = () => {
Yup.object().shape({ Yup.object().shape({
image: Yup.string().nullable(), image: Yup.string().nullable(),
content: Yup.string().required("validation.required"), content: Yup.string().required("validation.required"),
QuestionOptions: Yup.array() answers: Yup.array()
.of( .of(
Yup.object().shape({ Yup.object().shape({
answer: Yup.string().required("validation.required"), content: Yup.string().required("validation.required"),
answer_image: Yup.string().nullable(), answer_image: Yup.string().nullable(),
isCorrect: Yup.boolean(), isCorrect: Yup.boolean(),
}), }),

View File

@ -12,11 +12,6 @@ import { ModalEnum } from "../../../enums/Model";
import { useGetAllSubject } from "../../../api/subject"; import { useGetAllSubject } from "../../../api/subject";
import { useGetAllGrade } from "../../../api/grade"; import { useGetAllGrade } from "../../../api/grade";
import { useGetAllCurriculum } from "../../../api/curriculum"; import { useGetAllCurriculum } from "../../../api/curriculum";
import PageHeader from "../../../Layout/Dashboard/PageHeader";
import FilterLayout from "../../../Layout/Dashboard/FilterLayout";
import FilterForm from "./Model/FilterForm";
import { canAddQuestion } from "../../../utils/hasAbilityFn";
import { ABILITIES_ENUM } from "../../../enums/abilities";
const Table = lazy(() => import("./Table")); const Table = lazy(() => import("./Table"));
const TableHeader = () => { const TableHeader = () => {
@ -74,15 +69,9 @@ const TableHeader = () => {
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<PageHeader <header>
pageTitle="question" <h6>{t("models.Question")}</h6>
ModelAbility={ModalEnum?.QUESTION_ACCEPT} </header>
canAdd={canAddQuestion}
locationToNavigate={`${ABILITIES_ENUM?.QUESTION}/add`}
openModel={false}/>
<FilterLayout
sub_children={<FilterForm/>}
filterTitle="sidebar.question"/>
<Table /> <Table />
</Suspense> </Suspense>
<DeleteModels <DeleteModels

View File

@ -69,16 +69,15 @@ export const useColumns = () => {
}, },
{ {
// canAddQuestion ? ( title: canAddQuestion ? (
// <button onClick={() => handelAdd()} className="add_button"> <button onClick={() => handelAdd()} className="add_button">
// {t("practical.add")} {t("models.Question")} <FaPlus /> {t("practical.add")} {t("models.Question")} <FaPlus />
// </button> </button>
// ) : ( ) : (
// "" ""
// ), ),
title: t("columns.procedure"),
key: "actions", key: "actions",
align: "center", align: "end",
width: "25vw", width: "25vw",
render: (_text, record, index) => { render: (_text, record, index) => {
return ( return (

View File

@ -182,10 +182,10 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
margin-block: 10px;
} }
.exercise_form_width{ .exercise_form_width{
margin-block: 20px;
max-width: 50vw; max-width: 50vw;
>*{ >*{

View File

@ -373,7 +373,10 @@
"question": "السؤال", "question": "السؤال",
"id": "الرقم التعريفي", "id": "الرقم التعريفي",
"icon": "الايقونة", "icon": "الايقونة",
"answer_content":"نص السؤال" "answer_content":"نص السؤال",
"hint":"شرح السؤال",
"hint_question":"شرح الاختيار",
"_":""
}, },
"select": { "select": {
"Payments": { "Payments": {

View File

@ -8,9 +8,9 @@ export const setLocalStorageQuestions = async (key: string, data: any) => {
data.image = await convertFileToBase64(data.image); data.image = await convertFileToBase64(data.image);
} }
// Check for QuestionOptions array and convert answer_image if it's a File // Check for answers array and convert answer_image if it's a File
if (Array.isArray(data.QuestionOptions)) { if (Array.isArray(data.answers)) {
for (const option of data.QuestionOptions) { for (const option of data.answers) {
if (option.answer_image instanceof File) { if (option.answer_image instanceof File) {
option.answer_image = await convertFileToBase64(option.answer_image); option.answer_image = await convertFileToBase64(option.answer_image);
} }
@ -18,14 +18,14 @@ export const setLocalStorageQuestions = async (key: string, data: any) => {
} }
// Check for Questions array and convert image if it's a File // Check for Questions array and convert image if it's a File
if (Array.isArray(data.Questions)) { if (Array.isArray(data.answers)) {
for (const question of data.Questions) { for (const question of data.answers) {
if (question.image instanceof File) { if (question.image instanceof File) {
question.image = await convertFileToBase64(question.image); question.image = await convertFileToBase64(question.image);
} }
// Check for nested QuestionOptions and convert answer_image if it's a File // Check for nested answers and convert answer_image if it's a File
if (Array.isArray(question.QuestionOptions)) { if (Array.isArray(question.answers)) {
for (const option of question.QuestionOptions) { for (const option of question.answers) {
if (option.answer_image instanceof File) { if (option.answer_image instanceof File) {
option.answer_image = await convertFileToBase64( option.answer_image = await convertFileToBase64(
option.answer_image, option.answer_image,
@ -55,9 +55,9 @@ export const getLocalStorageQuestions = (key: string): any | null => {
data.image = base64StringToFile(data.image); data.image = base64StringToFile(data.image);
} }
// Check for QuestionOptions array and convert answer_image if it's a base64 string // Check for answers array and convert answer_image if it's a base64 string
if (Array.isArray(data.QuestionOptions)) { if (Array.isArray(data.answers)) {
for (const option of data.QuestionOptions) { for (const option of data.answers) {
if ( if (
typeof option.answer_image === "string" && typeof option.answer_image === "string" &&
option.answer_image.length > 0 option.answer_image.length > 0
@ -68,14 +68,14 @@ export const getLocalStorageQuestions = (key: string): any | null => {
} }
// Check for Questions array and convert image if it's a base64 string // Check for Questions array and convert image if it's a base64 string
if (Array.isArray(data.Questions)) { if (Array.isArray(data.answers)) {
for (const question of data.Questions) { for (const question of data.answers) {
if (typeof question.image === "string" && question.image.length > 0) { if (typeof question.image === "string" && question.image.length > 0) {
question.image = base64StringToFile(question.image); question.image = base64StringToFile(question.image);
} }
// Check for nested QuestionOptions and convert answer_image if it's a base64 string // Check for nested answers and convert answer_image if it's a base64 string
if (Array.isArray(question.QuestionOptions)) { if (Array.isArray(question.answers)) {
for (const option of question.QuestionOptions) { for (const option of question.answers) {
if ( if (
typeof option.answer_image === "string" && typeof option.answer_image === "string" &&
option.answer_image.length > 0 option.answer_image.length > 0