#68 reorder questions

This commit is contained in:
Majd_dk 2025-09-28 10:51:22 +03:00
parent 0cf4026a08
commit 5638be0664
9 changed files with 242 additions and 119 deletions

View File

@ -68,7 +68,7 @@ const ImageBoxFieldMemo = memo(
console.log(name); console.log(name);
return ( return (
<div className="ImageBoxField"> <div className="ImageBoxField" key={name}>
<div className="ImageHeader"> <div className="ImageHeader">
{imagePreview ? ( {imagePreview ? (
<> <>
@ -99,10 +99,11 @@ const ImageBoxFieldMemo = memo(
/> />
</div> </div>
); );
}, }
(prevProps, nextProps) => { // ,
return areFieldPropsEqual(prevProps, nextProps); // (prevProps, nextProps) => {
}, // return areFieldPropsEqual(prevProps, nextProps);
// },
); );
export default ImageBoxFieldMemo; export default ImageBoxFieldMemo;

View File

@ -0,0 +1,43 @@
import { useFormikContext } from 'formik';
import React from 'react'
import { moveToBottom, moveToTop } from '../utils/reOrder';
type FormValues = {
Questions: Array<{ id: string; sub_order?: number } & Record<string, any>>;
};
export function useReOrderQuestions() {
const {setFieldValue,values} = useFormikContext<FormValues>()
const withSubOrder = (arr: any[]) => arr.map((it, i) => ({ ...it, sub_order: i }));
const scrollToIdWithOffset = (id: string) => {
const el = document.getElementById(id);
if (!el) return;
const top = el.getBoundingClientRect().top ;
console.log(el.getBoundingClientRect())
window.scrollBy({ top, behavior: "smooth" });
};
const afterLayout = (fn: () => void) => {
requestAnimationFrame(() => requestAnimationFrame(fn));
};
const moveItemUp = (item:any,index: number) => {
if (index <= 0) return;
const next = moveToTop(index, values.Questions);
setFieldValue("Questions", withSubOrder(next));
afterLayout(() => scrollToIdWithOffset(`q-${item.id}`));
};
const moveItemDown = (item:any,index: number) => {
if (index >= values.Questions.length - 1) return;
const next = moveToBottom(index, values.Questions);
setFieldValue("Questions", withSubOrder(next));
afterLayout(() => scrollToIdWithOffset(`q-${item.id}`));
};
return { moveItemDown , moveItemUp }
}

View File

@ -35,7 +35,7 @@ const CheckboxField = ({
); );
}; };
return ( return (
<div className={Group ? "d-inline mt-5 Checkbox" : ``}> <div className={Group ? "d-inline mt-5 Checkbox" : ``} key={name}>
<Checkbox <Checkbox
onChange={onChange || CheckboxhandleChange} onChange={onChange || CheckboxhandleChange}
disabled={isDisabled} disabled={isDisabled}

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect } from "react";
import ValidationField from "../../../../../../Components/ValidationField/ValidationField"; import ValidationField from "../../../../../../Components/ValidationField/ValidationField";
import { Field } from "formik"; import { Field } from "formik";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -9,17 +9,21 @@ import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import LaTeXInputMemo from "../../../../../../Components/LatextInput/LaTeXInputMemo"; import LaTeXInputMemo from "../../../../../../Components/LatextInput/LaTeXInputMemo";
import ImageBoxFieldMemo from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo"; import ImageBoxFieldMemo from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo";
const ChoiceFields = React.memo( const ChoiceFields = React.memo(
({ ({
index, index,
parent_index, parent_index,
setFieldValue, setFieldValue,
values, values,
choiceId,
}: { }: {
index: number; index: number;
parent_index: number; parent_index: number;
setFieldValue: any; setFieldValue: any;
values: any; values: any;
choiceId:number | string
}) => { }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const { ShowHint } = useObjectToEdit(); const { ShowHint } = useObjectToEdit();
@ -49,6 +53,11 @@ const ChoiceFields = React.memo(
} }
return false; return false;
}; };
const nameTxt = `Questions[${parent_index}].answers[${index}].content`;
const nameImg = `Questions[${parent_index}].answers[${index}].content_image`;
// useEffect(()=>{
// })a
return ( return (
<> <>
@ -58,13 +67,15 @@ const ChoiceFields = React.memo(
> >
<Field <Field
component={LaTeXInputMemo} component={LaTeXInputMemo}
name={`Questions[${parent_index}].answers[${index}].content`} name={nameTxt}
key={nameTxt}
label={t(`input.choice`) + ` ` + `( ${index + 1} )`} label={t(`input.choice`) + ` ` + `( ${index + 1} )`}
/> />
<Field <Field
component={ImageBoxFieldMemo} component={ImageBoxFieldMemo}
name={`Questions.${parent_index}.answers.${index}.content_image`} name={nameImg}
key={nameImg}
/> />
<div className="answer_status"> <div className="answer_status">
@ -106,26 +117,27 @@ const ChoiceFields = React.memo(
</div> </div>
</> </>
); );
}, }
(prevProps, nextProps) => { // areEqual
console.log( // (prevProps, nextProps) => {
prevProps.values?.Questions?.[prevProps?.parent_index]?.answers?.[ // console.log(
prevProps?.index // prevProps.values?.Questions?.[prevProps?.parent_index]?.answers?.[
] === // prevProps?.index
nextProps.values?.Questions?.[nextProps?.parent_index]?.answers?.[ // ] ===
prevProps?.index // nextProps.values?.Questions?.[nextProps?.parent_index]?.answers?.[
], // prevProps?.index
); // ],
// );
return ( // return (
prevProps.values?.Questions?.[prevProps?.parent_index]?.answers?.[ // prevProps.values?.Questions?.[prevProps?.parent_index]?.answers?.[
prevProps?.index // prevProps?.index
] === // ] ===
nextProps.values?.Questions?.[nextProps?.parent_index]?.answers?.[ // nextProps.values?.Questions?.[nextProps?.parent_index]?.answers?.[
prevProps?.index // prevProps?.index
] // ]
); // );
}, // },
); );
export default ChoiceFields; export default ChoiceFields;

View File

@ -45,9 +45,10 @@ const Choices = React.memo(
{(values?.Questions?.[parent_index]?.answers || []).map( {(values?.Questions?.[parent_index]?.answers || []).map(
(item: Choice, index: number) => { (item: Choice, index: number) => {
return ( return (
<div className="Choices ChoicesMalty" key={index}> <div className="Choices ChoicesMalty" key={item?.id} onClick={() => console.log(item)}>
<ChoiceFields <ChoiceFields
key={index} key={item?.id}
choiceId={item.id} // جديد
parent_index={parent_index} parent_index={parent_index}
index={index} index={index}
setFieldValue={setFieldValue} setFieldValue={setFieldValue}
@ -104,13 +105,14 @@ const Choices = React.memo(
</DragDropContext> */} </DragDropContext> */}
</> </>
); );
}, }
(prevProps, nextProps) => { // ,
return ( // (prevProps, nextProps) => {
prevProps.values?.Questions?.[prevProps?.parent_index]?.answers === // return (
nextProps.values?.Questions?.[nextProps?.parent_index]?.answers // prevProps.values?.Questions?.[prevProps?.parent_index]?.answers ===
); // nextProps.values?.Questions?.[nextProps?.parent_index]?.answers
}, // );
// },
); );
export default Choices; export default Choices;

View File

@ -19,6 +19,7 @@ const QuestionFIeld = ({
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
const { setDeletedQuestions, DeletedQuestions } = useObjectToEdit(); const { setDeletedQuestions, DeletedQuestions } = useObjectToEdit();
// const path = git
const [t] = useTranslation(); const [t] = useTranslation();
useEffect(() => { useEffect(() => {
setDeletedQuestions([]); setDeletedQuestions([]);
@ -46,9 +47,14 @@ const QuestionFIeld = ({
return false; return false;
}; };
const nameImg = `Questions[${index}].content_image`;
return ( return (
<> <>
<div className="exercise_forms"> <div className="exercise_forms" onClick={() => {
console.log(values.Questions[index])
console.log(index)
}}>
<div className="ChoiceFields"> <div className="ChoiceFields">
<Field <Field
component={LaTeXInputMemo} component={LaTeXInputMemo}
@ -57,8 +63,10 @@ const QuestionFIeld = ({
label={t(`input.question`) + ` ` + `( ${index + 1} )`} label={t(`input.question`) + ` ` + `( ${index + 1} )`}
/> />
<Field <Field
onClick={ () => console.log(index)}
component={ImageBoxFieldMemo} component={ImageBoxFieldMemo}
name={`Questions.${index}.content_image`} name={nameImg}
/> />
{handelCanDeleteAnswers() ? ( {handelCanDeleteAnswers() ? (
<div className="answer_status"> <div className="answer_status">

View File

@ -9,6 +9,7 @@ import { toast } from "react-toastify";
import SelectTagV2 from "../../../../../../Components/CustomFields/SelectTagV2"; import SelectTagV2 from "../../../../../../Components/CustomFields/SelectTagV2";
import LaTeXInputMemo from "../../../../../../Components/LatextInput/LaTeXInputMemo"; import LaTeXInputMemo from "../../../../../../Components/LatextInput/LaTeXInputMemo";
import { Field } from "formik"; import { Field } from "formik";
import { uid } from "../../../../../../utils/reOrder";
export const Question: React.FC<any> = React.memo(({ index, data }) => { export const Question: React.FC<any> = React.memo(({ index, data }) => {
const { values, setFieldValue, ShowHint, t } = data; const { values, setFieldValue, ShowHint, t } = data;
@ -18,6 +19,7 @@ export const Question: React.FC<any> = React.memo(({ index, data }) => {
setFieldValue(`Questions.[${parent_index}].answers`, [ setFieldValue(`Questions.[${parent_index}].answers`, [
...(values?.Questions?.[parent_index]?.answers as Choice[]), ...(values?.Questions?.[parent_index]?.answers as Choice[]),
{ {
id: uid(),
answer: null, answer: null,
content_image: null, content_image: null,
content: null, content: null,
@ -38,7 +40,6 @@ export const Question: React.FC<any> = React.memo(({ index, data }) => {
<QuestionFIeld <QuestionFIeld
setFieldValue={setFieldValue} setFieldValue={setFieldValue}
values={values} values={values}
key={index}
index={index} index={index}
/> />
</div> </div>
@ -65,9 +66,9 @@ export const Question: React.FC<any> = React.memo(({ index, data }) => {
name={`Questions[${index}].hint`} name={`Questions[${index}].hint`}
label={t("input.hint_question")} label={t("input.hint_question")}
type="TextArea" type="TextArea"
style={{ width: "100%", height: 60, resize: "none" }} // style={{ width: "100%", height: 60, resize: "none" }}
showCount={false} showCount={false}
autoSize={{ minRows: 2, maxRows: 10 }} // autoSize={{ minRows: 2, maxRows: 10 }}
/> />
)} )}
<MaltySelectTag parent_index={index} /> <MaltySelectTag parent_index={index} />

View File

@ -1,84 +1,104 @@
import React, { useCallback, useMemo, useRef, useEffect } from "react"; import React, { useCallback, useMemo, useRef, useEffect } from "react";
import { VariableSizeList as List } from "react-window"; import { VariableSizeList as List } from "react-window";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import { Question } from "./Question"; import { Question } from "./Question";
import { moveToBottom, moveToTop } from "../../../../../../utils/reOrder";
import { FaArrowRightLong } from "react-icons/fa6";
import { useReOrderQuestions } from "../../../../../../Hooks/useReOrderQuestions";
interface QuestionsProps { interface QuestionsProps {
setFieldValue: (field: string, value: any) => void; setFieldValue: (field: string, value: any) => void;
values: { values: {
Questions: any[]; Questions: any[];
}; };
} }
const Questions: React.FC<QuestionsProps> = React.memo(
({ setFieldValue, values }) => {
const questions = values?.Questions || [];
const { ShowHint } = useObjectToEdit();
const [t] = useTranslation();
// const listRef = useRef<List>(null);
const rowRefs = useRef<Record<string, HTMLDivElement | null>>({});
const Questions: React.FC<QuestionsProps> = React.memo(
({ setFieldValue, values }) => {
const questions = values?.Questions || [];
const { ShowHint } = useObjectToEdit();
const [t] = useTranslation();
const listRef = useRef<List>(null);
const getItemSize = useCallback( const getItemSize = useCallback(
(index: number) => { (index: number) => {
const question = questions[index]; const question = questions[index];
let height = 300; // Base height for QuestionField let height = 300; // Base height for QuestionField
height += (question.answers?.length || 0) * 212; // Height for each answer height += (question.answers?.length || 0) * 212; // Height for each answer
if (question.answers?.length < 5) height += 40; // "Add new choice" button if (question.answers?.length < 5) height += 40; // "Add new choice" button
if (ShowHint) height += 80; // Hint field if (ShowHint) height += 80; // Hint field
height += 50; // MaltySelectTag height += 50; // MaltySelectTag
console.log(height); console.log(height);
return height; return height;
}, },
[questions, ShowHint], [questions, ShowHint],
); );
// == we don use it ==
const itemData = useMemo( // const itemData = useMemo(
() => ({ // () => ({
values, // values,
setFieldValue, // setFieldValue,
ShowHint, // ShowHint,
t, // t,
}), // }),
[values, setFieldValue, ShowHint, t], // [values, setFieldValue, ShowHint, t],
); // );
useEffect(() => {
if (listRef.current) {
listRef.current.resetAfterIndex(0);
}
}, [questions, ShowHint]);
return (
<>
{/* <List
ref={listRef}
height={window.innerHeight - 450} // Adjust based on your layout
itemCount={questions?.length}
itemSize={getItemSize}
width="100%"
itemData={itemData}
direction='rtl'
className='ListQuestions'
>
{Question} // useEffect(() => {
</List> */} // if (listRef.current) {
{questions?.map((item: any, index: number) => { // listRef.current.resetAfterIndex(0);
return ( // }
<Question // }, [questions, ShowHint]);
key={index}
index={index}
data={{ values, setFieldValue, ShowHint, t }}
/>
);
})}
</>
);
},
(prevProps, nextProps) =>
prevProps?.values?.Questions === nextProps?.values?.Questions,
);
export default Questions; const { moveItemDown , moveItemUp} = useReOrderQuestions()
return (
<>
{/* <List
ref={listRef}
height={window.innerHeight - 450} // Adjust based on your layout
itemCount={questions?.length}
itemSize={getItemSize}
width="100%"
itemData={itemData}
direction='rtl'
className='ListQuestions'
>
{Question}
</List> */}
{questions?.map((item: any, index: number) => {
console.log(item)
console.log(questions)
return (
<div
key={item.id}
id={`q-${item.id}`}
onClick={() => console.log(questions)}
ref={(el) => (rowRefs.current[item.id] = el)}
> <Question
// key={questions[index]?.content}
index={index}
data={{ values, setFieldValue, ShowHint, t }}
/>
<div className="positionButtons">
<FaArrowRightLong className="toTop" onClick={() => moveItemUp(item,index)}/>
<FaArrowRightLong className="toBotton" onClick={() => moveItemDown(item,index)}/>
</div>
</div>
);
})}
</>
);
},
(prevProps, nextProps) =>
prevProps?.values?.Questions === nextProps?.values?.Questions,
);
export default Questions;

36
src/utils/reOrder.ts Normal file
View File

@ -0,0 +1,36 @@
// export function moveToTop(index:number,array:any[]){
// const indexItemBefore = index - 1
// if(indexItemBefore == -1)return array;
// const currentItem = array[index]
// array[index] = array[indexItemBefore]
// array[indexItemBefore] = currentItem
// return array
// }
// export function moveToBottom(index:number,array:any[]){
// const indexItemAfter = index + 1
// if(indexItemAfter == array.length)return array;
// const currentItem = array[index]
// array[index] = array[indexItemAfter]
// array[indexItemAfter] = currentItem
// return array
// }
function swapImmutable<T>(arr: T[], i: number, j: number): T[] {
if (i === j) return arr.slice();
const copy = arr.slice();
[copy[i], copy[j]] = [copy[j], copy[i]];
return copy;
}
export function moveToTop(index: number, array: any[]) {
if (index <= 0) return array.slice();
return swapImmutable(array, index, index - 1);
}
export function moveToBottom(index: number, array: any[]) {
if (index >= array.length - 1) return array.slice();
return swapImmutable(array, index, index + 1);
}
export const uid = () => Date.now().toString() + Math.random().toString(36).slice(2,9);