diff --git a/front/components/Objects/Editor/Extensions/Quiz/QuizBlock.ts b/front/components/Objects/Editor/Extensions/Quiz/QuizBlock.ts index 36d7706a..b6af7d51 100644 --- a/front/components/Objects/Editor/Extensions/Quiz/QuizBlock.ts +++ b/front/components/Objects/Editor/Extensions/Quiz/QuizBlock.ts @@ -6,7 +6,6 @@ import QuizBlockComponent from "./QuizBlockComponent"; export default Node.create({ name: "blockQuiz", group: "block", - atom: true, addAttributes() { @@ -14,6 +13,9 @@ export default Node.create({ quizId: { value: null, }, + questions: { + default: [], + }, }; }, diff --git a/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx b/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx index 9b1f3ec2..c2532242 100644 --- a/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx +++ b/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx @@ -1,164 +1,203 @@ import { NodeViewWrapper } from "@tiptap/react"; import { v4 as uuidv4 } from "uuid"; import React from "react"; -import styled from "styled-components"; -import { submitQuizBlock } from "@services/blocks/Quiz/quiz"; +import { BadgeHelp, Check, Minus, MoreVertical, Plus, X } from "lucide-react"; -function ImageBlockComponent(props: any) { - const [questions, setQuestions] = React.useState([]) as any; - const [answers, setAnswers] = React.useState([]) as any; +interface Answer { + answer_id: string; + answer: string; + correct: boolean; +} +interface Question { + question_id: string; + question: string; + type: "multiple_choice" | 'custom_answer' + answers: Answer[]; +} - function addSampleQuestion() { - setQuestions([ - ...questions, - { - question_id: "question_" + uuidv4(), - question_value: "", - options: [ - { - option_id: "option_" + uuidv4(), - option_data: "", - option_type: "text", - }, - ], - }, - ]); +function QuizBlockComponent(props: any) { + const [questions, setQuestions] = React.useState(props.node.attrs.questions) as [Question[], any]; + const isEditable = props.extension.options.editable; + + const getAlphabetFromIndex = (index: number) => { + const alphabet = Array.from({ length: 26 }, (_, i) => String.fromCharCode('A'.charCodeAt(0) + i)); + return alphabet[index]; } - const deleteQuestion = (index: number) => { - let modifiedQuestions = [...questions]; - modifiedQuestions.splice(index, 1); - setQuestions(modifiedQuestions); - - - // remove the answers from the answers array - let modifiedAnswers = [...answers]; - modifiedAnswers = modifiedAnswers.filter((answer: any) => answer.question_id !== questions[index].question_id); - setAnswers(modifiedAnswers); - }; - - const onQuestionChange = (e: any, index: number) => { - let modifiedQuestions = [...questions]; - modifiedQuestions[index].question_value = e.target.value; - setQuestions(modifiedQuestions); - }; - - const addOption = (question_id: string) => { - // find the question index from the question_id and add the option to that question index - let modifiedQuestions = [...questions]; - let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id); - modifiedQuestions[questionIndex].options.push({ - option_id: "option_" + uuidv4(), - option_data: "", - option_type: "text", + const saveQuestions = (questions: any) => { + props.updateAttributes({ + questions: questions, }); - setQuestions(modifiedQuestions); + setQuestions(questions); + }; - - const deleteOption = (question_id: string, option_id: string) => { - // find the option index from the option_id and delete the option from that option index - let modifiedQuestions = [...questions]; - let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id); - let optionIndex = modifiedQuestions[questionIndex].options.findIndex((option: any) => option.option_id === option_id); - modifiedQuestions[questionIndex].options.splice(optionIndex, 1); - setQuestions(modifiedQuestions); - - // remove the answer from the answers array - let answerIndex = answers.findIndex((answer: any) => answer.option_id === option_id); - if (answerIndex !== -1) { - let modifiedAnswers = [...answers]; - modifiedAnswers.splice(answerIndex, 1); - setAnswers(modifiedAnswers); - } - }; - - const markOptionAsCorrect = (question_id: string, option_id: string) => { - // find the option index from the option_id and mark the option as correct - let answer = { - question_id: question_id, - option_id: option_id, - }; - setAnswers([...answers, answer]); - - }; - - const saveQuiz = async () => { - // save the questions and answers to the backend - - - - try { - let res = await submitQuizBlock(props.extension.options.activity.activity_id, {questions : questions , answers : answers}) - - props.updateAttributes({ - quizId: { - value : res.block_id + const addSampleQuestion = () => { + const newQuestion = { + question_id: uuidv4(), + question: "", + type: "multiple_choice", + answers: [ + { + answer_id: uuidv4(), + answer: "", + correct: false }, - }); - + ] } - catch (error) { - + setQuestions([...questions, newQuestion]); + } + + const addAnswer = (question_id: string) => { + const newAnswer = { + answer_id: uuidv4(), + answer: "", + correct: false } - - }; + // check if there is already more thqn 5 answers + const question: any = questions.find((question: Question) => question.question_id === question_id); + if (question.answers.length >= 5) { + return; + } - const onOptionChange = (e: any, questionIndex: number, optionIndex: number) => { - let modifiedQuestions = [...questions]; - modifiedQuestions[questionIndex].options[optionIndex].option_data = e.target.value; - setQuestions(modifiedQuestions); - }; - React.useEffect(() => { - // fetch the questions and options from the backend - - - - }, [questions, answers]); + + const newQuestions = questions.map((question: Question) => { + if (question.question_id === question_id) { + question.answers.push(newAnswer); + } + return question; + }); + + saveQuestions(newQuestions); + } + + const changeAnswerValue = (question_id: string, answer_id: string, value: string) => { + const newQuestions = questions.map((question: Question) => { + if (question.question_id === question_id) { + question.answers.map((answer: Answer) => { + if (answer.answer_id === answer_id) { + answer.answer = value; + } + return answer; + }); + } + return question; + }); + saveQuestions(newQuestions); + } + + const changeQuestionValue = (question_id: string, value: string) => { + const newQuestions = questions.map((question: Question) => { + if (question.question_id === question_id) { + question.question = value; + } + return question; + }); + saveQuestions(newQuestions); + } + + const deleteQuestion = (question_id: string) => { + const newQuestions = questions.filter((question: Question) => question.question_id !== question_id); + saveQuestions(newQuestions); + } + + const deleteAnswer = (question_id: string, answer_id: string) => { + const newQuestions = questions.map((question: Question) => { + if (question.question_id === question_id) { + question.answers = question.answers.filter((answer: Answer) => answer.answer_id !== answer_id); + } + return question; + }); + saveQuestions(newQuestions); + } + + const markAnswerCorrect = (question_id: string, answer_id: string) => { + const newQuestions = questions.map((question: Question) => { + if (question.question_id === question_id) { + question.answers.map((answer: Answer) => { + if (answer.answer_id === answer_id) { + answer.correct = true; + } else { + answer.correct = false; + } + return answer; + }); + } + return question; + }); + saveQuestions(newQuestions); + } + return ( - - Questions -
- {questions.map((question: any, qIndex: number) => ( - <> -
- Question : onQuestionChange(e, qIndex)} /> - -
- Answers :
- {question.options.map((option: any, oIndex: number) => ( - <> -
- onOptionChange(e, qIndex, oIndex)} /> +
+
+
+ +

Quiz

+
+
+ +
+
- - - // check if checkbox is checked or not - // if checked then add the answer to the answers array - // if unchecked then remove the answer from the answers array - e.target.checked ? markOptionAsCorrect(question.question_id, option.option_id) : null - } - /> + {questions.map((question: Question) => ( +
+
+
+
+ changeQuestionValue(question.question_id, e.target.value)} className="text-slate-800 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-md font-bold w-full">
- - ))} - - +
+ deleteQuestion(question.question_id)} + className="mx-auto text-red-200" size={12} /> +
+
+
+ {question.answers.map((answer: Answer) => ( +
+
+

{getAlphabetFromIndex(question.answers.indexOf(answer))}

+
+ changeAnswerValue(question.question_id, answer.answer_id, e.target.value)} placeholder="Answer" className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"> + +
+
markAnswerCorrect(question.question_id, answer.answer_id)} + className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-green-200 hover:bg-green-300 transition-all ease-linear text-sm cursor-pointer "> + +
+ +
deleteAnswer(question.question_id, answer.answer_id)} + className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-red-600 hover:bg-red-700 text-sm transition-all ease-linear cursor-pointer"> + +
+
+ +
+ ))} +
addAnswer(question.question_id)} className="outline outline-3 shadow w-[30px] flex-none flex items-center h-[30px] outline-white hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white text-sm hover:scale-105 active:scale-110 duration-150 cursor-pointer ease-linear"> + +
+
+
+
))} - + +
); } -const QuizBlockWrapper = styled.div` - background-color: #0000001d; - border-radius: 5px; - padding: 20px; - height: 100%; -`; -export default ImageBlockComponent; + +export default QuizBlockComponent; diff --git a/front/components/Objects/Editor/Toolbar/ToolbarButtons.tsx b/front/components/Objects/Editor/Toolbar/ToolbarButtons.tsx index d5ebc0b9..22d5804f 100644 --- a/front/components/Objects/Editor/Toolbar/ToolbarButtons.tsx +++ b/front/components/Objects/Editor/Toolbar/ToolbarButtons.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon, DividerVerticalIcon, ListBulletIcon } from "@radix-ui/react-icons"; -import { AlertCircle, AlertTriangle, FileText, GraduationCap, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react"; +import { AlertCircle, AlertTriangle, FileText, GraduationCap, HelpCircle, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react"; import ToolTip from "@components/StyledElements/Tooltip/Tooltip"; export const ToolbarButtons = ({ editor, props }: any) => { @@ -59,7 +59,7 @@ export const ToolbarButtons = ({ editor, props }: any) => { {/* TODO: fix this : toggling only works one-way */} - + editor.chain().focus().toggleNode("calloutInfo").run()}> @@ -113,7 +113,7 @@ export const ToolbarButtons = ({ editor, props }: any) => { .chain() .focus() .insertContent({ - type: "blockMathEquation", + type: "blockMathEquation", }) .run() } @@ -136,7 +136,7 @@ export const ToolbarButtons = ({ editor, props }: any) => { - {/* + editor @@ -148,9 +148,9 @@ export const ToolbarButtons = ({ editor, props }: any) => { .run() } > - + - */} + ); };