diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx index 85fbb22e..596dcf6f 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx @@ -1,11 +1,11 @@ "use client"; import Link from "next/link"; import { getUriWithOrg } from "@services/config/config"; -import Canva from "@components/Pages/Activities/DynamicCanva/DynamicCanva"; -import VideoActivity from "@components/Pages/Activities/Video/Video"; +import Canva from "@components/Objects/Activities/DynamicCanva/DynamicCanva"; +import VideoActivity from "@components/Objects/Activities/Video/Video"; import { Check } from "lucide-react"; import { markActivityAsComplete } from "@services/courses/activity"; -import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/DocumentPdf"; +import DocumentPdfActivity from "@components/Objects/Activities/DocumentPdf/DocumentPdf"; import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators"; import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; import { useRouter } from "next/navigation"; diff --git a/front/components/Pages/Activities/DocumentPdf/DocumentPdf.tsx b/front/components/Objects/Activities/DocumentPdf/DocumentPdf.tsx similarity index 100% rename from front/components/Pages/Activities/DocumentPdf/DocumentPdf.tsx rename to front/components/Objects/Activities/DocumentPdf/DocumentPdf.tsx diff --git a/front/components/Pages/Activities/DynamicCanva/DynamicCanva.tsx b/front/components/Objects/Activities/DynamicCanva/DynamicCanva.tsx similarity index 93% rename from front/components/Pages/Activities/DynamicCanva/DynamicCanva.tsx rename to front/components/Objects/Activities/DynamicCanva/DynamicCanva.tsx index 69ebf363..4c945527 100644 --- a/front/components/Pages/Activities/DynamicCanva/DynamicCanva.tsx +++ b/front/components/Objects/Activities/DynamicCanva/DynamicCanva.tsx @@ -10,6 +10,7 @@ import VideoBlock from "@components/Objects/Editor/Extensions/Video/VideoBlock"; import MathEquationBlock from "@components/Objects/Editor/Extensions/MathEquation/MathEquationBlock"; import PDFBlock from "@components/Objects/Editor/Extensions/PDF/PDFBlock"; import { OrderedList } from "@tiptap/extension-ordered-list"; +import QuizBlock from "@components/Objects/Editor/Extensions/Quiz/QuizBlock"; interface Editor { content: string; @@ -46,6 +47,10 @@ function Canva(props: Editor) { editable: true, activity: props.activity, }), + QuizBlock.configure({ + editable: isEditable, + activity: props.activity, + }), Youtube.configure({ controls: true, modestBranding: true, diff --git a/front/components/Pages/Activities/Video/Video.tsx b/front/components/Objects/Activities/Video/Video.tsx similarity index 100% rename from front/components/Pages/Activities/Video/Video.tsx rename to front/components/Objects/Activities/Video/Video.tsx diff --git a/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx b/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx index c2532242..0a654655 100644 --- a/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx +++ b/front/components/Objects/Editor/Extensions/Quiz/QuizBlockComponent.tsx @@ -1,7 +1,9 @@ import { NodeViewWrapper } from "@tiptap/react"; import { v4 as uuidv4 } from "uuid"; +import { twJoin, twMerge } from 'tailwind-merge' import React from "react"; -import { BadgeHelp, Check, Minus, MoreVertical, Plus, X } from "lucide-react"; +import { BadgeHelp, Check, Info, Minus, MoreVertical, Plus, RefreshCcw, X } from "lucide-react"; +import ReactConfetti from "react-confetti"; interface Answer { answer_id: string; @@ -17,11 +19,80 @@ interface Question { function QuizBlockComponent(props: any) { const [questions, setQuestions] = React.useState(props.node.attrs.questions) as [Question[], any]; + const [userAnswers, setUserAnswers] = React.useState([]) as [any[], any]; + const [submitted, setSubmitted] = React.useState(false) as [boolean, any]; + const [submissionMessage, setSubmissionMessage] = React.useState("") as [string, any]; const isEditable = props.extension.options.editable; - const getAlphabetFromIndex = (index: number) => { + const handleAnswerClick = (question_id: string, answer_id: string) => { + // if the quiz is submitted, do nothing + if (submitted) { + return; + } + + const userAnswer = { + question_id: question_id, + answer_id: answer_id + } + const newAnswers = [...userAnswers, userAnswer]; + + // only accept one answer per question + const filteredAnswers = newAnswers.filter((answer: any) => answer.question_id !== question_id); + + setUserAnswers([...filteredAnswers, userAnswer]); + + } + + const refreshUserSubmission = () => { + setUserAnswers([]); + setSubmitted(false); + } + + const handleUserSubmission = () => { + + if (userAnswers.length === 0) { + setSubmissionMessage("Please answer at least one question!"); + return; + } + + setSubmitted(true); + + // check if all submitted answers are correct + const correctAnswers = questions.map((question: Question) => { + const correctAnswer: any = question.answers.find((answer: Answer) => answer.correct); + const userAnswer = userAnswers.find((userAnswer: any) => userAnswer.question_id === question.question_id); + if (correctAnswer.answer_id === userAnswer.answer_id) { + return true; + } else { + return false; + } + }); + + // check if all answers are correct + const allCorrect = correctAnswers.every((answer: boolean) => answer === true); + + if (allCorrect) { + setSubmissionMessage("All answers are correct!"); + console.log("All answers are correct!"); + } + else { + setSubmissionMessage("Some answers are incorrect!"); + console.log("Some answers are incorrect!"); + } + + + + } + + const getAnswerID = (answerIndex: number, questionId : string) => { const alphabet = Array.from({ length: 26 }, (_, i) => String.fromCharCode('A'.charCodeAt(0) + i)); - return alphabet[index]; + let alphabetID = alphabet[answerIndex]; + + // Get question index + const questionIndex = questions.findIndex((question: Question) => question.question_id === questionId); + let questionID = questionIndex + 1; + + return `${alphabetID}`; } const saveQuestions = (questions: any) => { @@ -121,6 +192,7 @@ function QuizBlockComponent(props: any) { } else { answer.correct = false; } + return answer; }); } @@ -132,18 +204,38 @@ function QuizBlockComponent(props: any) { return ( +
-
-
+
+ {(submitted && submissionMessage == "All answers are correct!") && + + } +

Quiz

-
- +
+
+ {isEditable ? +
+ +
+ : +
+
refreshUserSubmission()} className="cursor-pointer px-2"> + +
+ +
+ }
{questions.map((question: Question) => ( @@ -151,44 +243,71 @@ function QuizBlockComponent(props: any) {
- 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"> + {isEditable ? + 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"> + : +

{question.question}

+ }
-
- deleteQuestion(question.question_id)} - className="mx-auto text-red-200" size={12} /> -
+ className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer"> + +
+ }
{question.answers.map((answer: Answer) => ( -
-
-

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

+
(userAnswer.question_id === question.question_id && userAnswer.answer_id === answer.answer_id) && !isEditable) ? 'outline-slate-300' : '', + (submitted && answer.correct) ? 'outline-lime-300 text-lime' : '', + (submitted && !answer.correct) && userAnswers.find((userAnswer: any) => userAnswer.question_id === question.question_id && userAnswer.answer_id === answer.answer_id) ? 'outline-red-400' : '', + ) + } + onClick={() => handleAnswerClick(question.question_id, answer.answer_id)} + > +
userAnswer.question_id === question.question_id && userAnswer.answer_id === answer.answer_id) ? 'bg-red-400 text-red-800 outline-none' : '', + )}> +

{getAnswerID(question.answers.indexOf(answer),question.question_id)}

- 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 "> - + {isEditable ? + 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"> + : +

{answer.answer}

+ } + {isEditable && +
+
markAnswerCorrect(question.question_id, answer.answer_id)} + className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-lime-200 hover:bg-lime-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-slate-200 hover:bg-slate-300 text-sm transition-all ease-linear 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"> - -
+ {isEditable && +
addAnswer(question.question_id)} className="outline outline-3 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"> + +
+ }
diff --git a/front/package.json b/front/package.json index 592c2f7c..604a5242 100644 --- a/front/package.json +++ b/front/package.json @@ -31,6 +31,7 @@ "re-resizable": "^6.9.9", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", + "react-confetti": "^6.1.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", "react-katex": "^3.0.1", @@ -38,6 +39,7 @@ "react-youtube": "^10.1.0", "styled-components": "^6.0.0-beta.9", "swr": "^2.0.1", + "tailwind-merge": "^1.14.0", "uuid": "^9.0.0", "y-indexeddb": "^9.0.9", "y-webrtc": "^10.2.3",