Created a simple Practice Badge to showcase when an exercise is a practice exercise
This commit is contained in:
@@ -7,6 +7,7 @@ import { CommonProps } from "..";
|
|||||||
import Button from "../../Low/Button";
|
import Button from "../../Low/Button";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import MCDropdown from "./MCDropdown";
|
import MCDropdown from "./MCDropdown";
|
||||||
|
import PracticeBadge from "@/components/Low/PracticeBadge";
|
||||||
|
|
||||||
const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
||||||
id,
|
id,
|
||||||
@@ -196,7 +197,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{!disableProgressButtons && progressButtons()}
|
{!disableProgressButtons && progressButtons()}
|
||||||
|
|
||||||
<div className={clsx("flex flex-col gap-4 mt-4 h-full w-full", !disableProgressButtons && "mb-20")}>
|
<div className={clsx("flex flex-col gap-4 mt-4 h-full w-full relative", !disableProgressButtons && "mb-20")}>
|
||||||
{variant !== "mc" && (
|
{variant !== "mc" && (
|
||||||
<span className="text-sm w-full leading-6">
|
<span className="text-sm w-full leading-6">
|
||||||
{prompt.split("\\n").map((line, index) => (
|
{prompt.split("\\n").map((line, index) => (
|
||||||
@@ -207,6 +208,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{isPractice && <PracticeBadge className="w-fit self-end" />}
|
||||||
<span className="bg-mti-gray-smoke rounded-xl px-5 py-6">{memoizedLines}</span>
|
<span className="bg-mti-gray-smoke rounded-xl px-5 py-6">{memoizedLines}</span>
|
||||||
{variant !== "mc" && (
|
{variant !== "mc" && (
|
||||||
<div className="bg-mti-gray-smoke rounded-xl px-5 py-6 flex flex-col gap-4">
|
<div className="bg-mti-gray-smoke rounded-xl px-5 py-6 flex flex-col gap-4">
|
||||||
@@ -234,6 +236,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!disableProgressButtons && progressButtons()}
|
{!disableProgressButtons && progressButtons()}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Button from "../Low/Button";
|
|||||||
import Xarrow from "react-xarrows";
|
import Xarrow from "react-xarrows";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import { DndContext, DragEndEvent, useDraggable, useDroppable } from "@dnd-kit/core";
|
import { DndContext, DragEndEvent, useDraggable, useDroppable } from "@dnd-kit/core";
|
||||||
|
import PracticeBadge from "../Low/PracticeBadge";
|
||||||
|
|
||||||
function DroppableQuestionArea({ question, answer }: { question: MatchSentenceExerciseSentence; answer?: string }) {
|
function DroppableQuestionArea({ question, answer }: { question: MatchSentenceExerciseSentence; answer?: string }) {
|
||||||
const { isOver, setNodeRef } = useDroppable({ id: `droppable_sentence_${question.id}` });
|
const { isOver, setNodeRef } = useDroppable({ id: `droppable_sentence_${question.id}` });
|
||||||
@@ -148,6 +149,8 @@ export default function MatchSentences({
|
|||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
{isPractice && <PracticeBadge className="w-fit self-end" />}
|
||||||
|
|
||||||
<DndContext onDragEnd={handleDragEnd}>
|
<DndContext onDragEnd={handleDragEnd}>
|
||||||
<div className="flex flex-col gap-8 w-full items-center justify-between bg-mti-gray-smoke rounded-xl px-24 py-6">
|
<div className="flex flex-col gap-8 w-full items-center justify-between bg-mti-gray-smoke rounded-xl px-24 py-6">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import reactStringReplace from "react-string-replace";
|
|||||||
import { CommonProps } from ".";
|
import { CommonProps } from ".";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
import PracticeBadge from "../Low/PracticeBadge";
|
||||||
|
|
||||||
function Question({
|
function Question({
|
||||||
id,
|
id,
|
||||||
@@ -15,10 +16,12 @@ function Question({
|
|||||||
options,
|
options,
|
||||||
userSolution,
|
userSolution,
|
||||||
onSelectOption,
|
onSelectOption,
|
||||||
|
isPractice = false
|
||||||
}: MultipleChoiceQuestion & {
|
}: MultipleChoiceQuestion & {
|
||||||
userSolution: string | undefined;
|
userSolution: string | undefined;
|
||||||
onSelectOption?: (option: string) => void;
|
onSelectOption?: (option: string) => void;
|
||||||
showSolution?: boolean;
|
showSolution?: boolean;
|
||||||
|
isPractice?: boolean
|
||||||
}) {
|
}) {
|
||||||
const renderPrompt = (prompt: string) => {
|
const renderPrompt = (prompt: string) => {
|
||||||
return reactStringReplace(prompt, /(<u>.*?<\/u>)/g, (match) => {
|
return reactStringReplace(prompt, /(<u>.*?<\/u>)/g, (match) => {
|
||||||
@@ -28,7 +31,8 @@ function Question({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8 relative">
|
||||||
|
{isPractice && <PracticeBadge className="absolute -top-4 -right-12" />}
|
||||||
{isNaN(Number(id)) ? (
|
{isNaN(Number(id)) ? (
|
||||||
<span className="text-lg">{renderPrompt(prompt).filter((x) => x?.toString() !== "<u>")}</span>
|
<span className="text-lg">{renderPrompt(prompt).filter((x) => x?.toString() !== "<u>")}</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -194,6 +198,7 @@ export default function MultipleChoice({
|
|||||||
key={question.id} className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
key={question.id} className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||||
<Question
|
<Question
|
||||||
{...question}
|
{...question}
|
||||||
|
isPractice={isPractice}
|
||||||
userSolution={answers.find((x) => question.id === x.question)?.option}
|
userSolution={answers.find((x) => question.id === x.question)?.option}
|
||||||
onSelectOption={(option) => onSelectOption(option, question)}
|
onSelectOption={(option) => onSelectOption(option, question)}
|
||||||
/>
|
/>
|
||||||
@@ -206,6 +211,7 @@ export default function MultipleChoice({
|
|||||||
{questionIndex < questions.length && (
|
{questionIndex < questions.length && (
|
||||||
<Question
|
<Question
|
||||||
{...questions[questionIndex]}
|
{...questions[questionIndex]}
|
||||||
|
isPractice={isPractice}
|
||||||
userSolution={answers.find((x) => questions[questionIndex].id === x.question)?.option}
|
userSolution={answers.find((x) => questions[questionIndex].id === x.question)?.option}
|
||||||
onSelectOption={(option) => onSelectOption(option, questions[questionIndex])}
|
onSelectOption={(option) => onSelectOption(option, questions[questionIndex])}
|
||||||
/>
|
/>
|
||||||
@@ -216,6 +222,7 @@ export default function MultipleChoice({
|
|||||||
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||||
<Question
|
<Question
|
||||||
{...questions[questionIndex + 1]}
|
{...questions[questionIndex + 1]}
|
||||||
|
isPractice={isPractice}
|
||||||
userSolution={answers.find((x) => questions[questionIndex + 1].id === x.question)?.option}
|
userSolution={answers.find((x) => questions[questionIndex + 1].id === x.question)?.option}
|
||||||
onSelectOption={(option) => onSelectOption(option, questions[questionIndex + 1])}
|
onSelectOption={(option) => onSelectOption(option, questions[questionIndex + 1])}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import useExamStore from "@/stores/examStore";
|
|||||||
import { downloadBlob } from "@/utils/evaluation";
|
import { downloadBlob } from "@/utils/evaluation";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
|
import PracticeBadge from "../Low/PracticeBadge";
|
||||||
|
|
||||||
const Waveform = dynamic(() => import("../Waveform"), { ssr: false });
|
const Waveform = dynamic(() => import("../Waveform"), { ssr: false });
|
||||||
const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), {
|
const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), {
|
||||||
@@ -172,6 +173,8 @@ export default function Speaking({ id, title, text, video_url, type, prompts, su
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isPractice && <PracticeBadge className="w-fit self-end" />}
|
||||||
|
|
||||||
{prompts && prompts.length > 0 && (
|
{prompts && prompts.length > 0 && (
|
||||||
<div className="w-full h-full flex flex-col gap-4">
|
<div className="w-full h-full flex flex-col gap-4">
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import clsx from "clsx";
|
|||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { CommonProps } from ".";
|
import { CommonProps } from ".";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
|
import PracticeBadge from "../Low/PracticeBadge";
|
||||||
|
|
||||||
export default function TrueFalse({
|
export default function TrueFalse({
|
||||||
id,
|
id,
|
||||||
@@ -108,6 +109,7 @@ export default function TrueFalse({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm w-full leading-6">You can click a selected option again to deselect it.</span>
|
<span className="text-sm w-full leading-6">You can click a selected option again to deselect it.</span>
|
||||||
|
{isPractice && <PracticeBadge className="w-fit self-end" />}
|
||||||
<div className="bg-mti-gray-smoke rounded-xl px-5 py-6 flex flex-col gap-8">
|
<div className="bg-mti-gray-smoke rounded-xl px-5 py-6 flex flex-col gap-8">
|
||||||
{questions.map((question, index) => {
|
{questions.map((question, index) => {
|
||||||
const id = question.id.toString();
|
const id = question.id.toString();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { CommonProps } from ".";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
|
import PracticeBadge from "../Low/PracticeBadge";
|
||||||
|
|
||||||
function Blank({
|
function Blank({
|
||||||
id,
|
id,
|
||||||
@@ -128,7 +129,7 @@ export default function WriteBlanks({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4 relative">
|
||||||
{!disableProgressButtons && progressButtons()}
|
{!disableProgressButtons && progressButtons()}
|
||||||
|
|
||||||
<div className={clsx("flex flex-col gap-4 mt-4 h-full w-full", !disableProgressButtons && "mb-20")}>
|
<div className={clsx("flex flex-col gap-4 mt-4 h-full w-full", !disableProgressButtons && "mb-20")}>
|
||||||
@@ -140,6 +141,7 @@ export default function WriteBlanks({
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
|
{isPractice && <PracticeBadge className="w-fit self-end" />}
|
||||||
<span className="bg-mti-gray-smoke rounded-xl px-5 py-6">
|
<span className="bg-mti-gray-smoke rounded-xl px-5 py-6">
|
||||||
{text.split("\\n").map((line, index) => (
|
{text.split("\\n").map((line, index) => (
|
||||||
<p key={index}>
|
<p key={index}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { toast } from "react-toastify";
|
|||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
|
import PracticeBadge from "../Low/PracticeBadge";
|
||||||
|
|
||||||
export default function Writing({
|
export default function Writing({
|
||||||
id,
|
id,
|
||||||
@@ -159,6 +160,8 @@ export default function Writing({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isPractice && <PracticeBadge className="w-fit self-end" />}
|
||||||
|
|
||||||
<div className="w-full h-full flex flex-col gap-4">
|
<div className="w-full h-full flex flex-col gap-4">
|
||||||
<span className="whitespace-pre-wrap">{suffix}</span>
|
<span className="whitespace-pre-wrap">{suffix}</span>
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
11
src/components/Low/PracticeBadge.tsx
Normal file
11
src/components/Low/PracticeBadge.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { clsx } from "clsx"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PracticeBadge({ className }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={clsx("bg-mti-purple/20 px-2 py-1 rounded-full text-sm ring-1 ring-mti-purple", className)}>Practice Exercise</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import reactStringReplace from "react-string-replace";
|
|||||||
import { CommonProps } from ".";
|
import { CommonProps } from ".";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
import PracticeBadge from "../Low/PracticeBadge";
|
||||||
|
|
||||||
function Question({
|
function Question({
|
||||||
id,
|
id,
|
||||||
@@ -14,7 +15,8 @@ function Question({
|
|||||||
solution,
|
solution,
|
||||||
options,
|
options,
|
||||||
userSolution,
|
userSolution,
|
||||||
}: MultipleChoiceQuestion & { userSolution: string | undefined; onSelectOption?: (option: string) => void; showSolution?: boolean }) {
|
isPractice = false
|
||||||
|
}: MultipleChoiceQuestion & { userSolution: string | undefined; onSelectOption?: (option: string) => void; showSolution?: boolean, isPractice?: boolean }) {
|
||||||
const { userSolutions } = useExamStore((state) => state);
|
const { userSolutions } = useExamStore((state) => state);
|
||||||
|
|
||||||
const questionShuffleMap = userSolutions.reduce((foundMap, userSolution) => {
|
const questionShuffleMap = userSolutions.reduce((foundMap, userSolution) => {
|
||||||
@@ -44,7 +46,8 @@ function Question({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4 relative">
|
||||||
|
{isPractice && <PracticeBadge className="absolute -top-4 -right-12" />}
|
||||||
{isNaN(Number(id)) ? (
|
{isNaN(Number(id)) ? (
|
||||||
<span>{renderPrompt(prompt).filter((x) => x?.toString() !== "<u>")} </span>
|
<span>{renderPrompt(prompt).filter((x) => x?.toString() !== "<u>")} </span>
|
||||||
) : (
|
) : (
|
||||||
@@ -89,7 +92,7 @@ function Question({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MultipleChoice({ id, type, prompt, questions, userSolutions, onNext, onBack, disableProgressButtons = false }: MultipleChoiceExercise & CommonProps) {
|
export default function MultipleChoice({ id, type, prompt, questions, userSolutions, onNext, onBack, isPractice = false, disableProgressButtons = false }: MultipleChoiceExercise & CommonProps) {
|
||||||
const { questionIndex, setQuestionIndex, partIndex, exam } = useExamStore((state) => state);
|
const { questionIndex, setQuestionIndex, partIndex, exam } = useExamStore((state) => state);
|
||||||
|
|
||||||
const stats = useExamStore((state) => state.userSolutions);
|
const stats = useExamStore((state) => state.userSolutions);
|
||||||
@@ -149,6 +152,7 @@ export default function MultipleChoice({ id, type, prompt, questions, userSoluti
|
|||||||
key={question.id} className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
key={question.id} className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||||
<Question
|
<Question
|
||||||
{...question}
|
{...question}
|
||||||
|
isPractice={isPractice}
|
||||||
userSolution={userSolutions.find((x) => question.id === x.question)?.option}
|
userSolution={userSolutions.find((x) => question.id === x.question)?.option}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -160,6 +164,7 @@ export default function MultipleChoice({ id, type, prompt, questions, userSoluti
|
|||||||
{questionIndex < questions.length && (
|
{questionIndex < questions.length && (
|
||||||
<Question
|
<Question
|
||||||
{...questions[questionIndex]}
|
{...questions[questionIndex]}
|
||||||
|
isPractice={isPractice}
|
||||||
userSolution={userSolutions.find((x) => questions[questionIndex].id === x.question)?.option}
|
userSolution={userSolutions.find((x) => questions[questionIndex].id === x.question)?.option}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -169,6 +174,7 @@ export default function MultipleChoice({ id, type, prompt, questions, userSoluti
|
|||||||
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||||
<Question
|
<Question
|
||||||
{...questions[questionIndex + 1]}
|
{...questions[questionIndex + 1]}
|
||||||
|
isPractice={isPractice}
|
||||||
userSolution={userSolutions.find((x) => questions[questionIndex + 1].id === x.question)?.option}
|
userSolution={userSolutions.find((x) => questions[questionIndex + 1].id === x.question)?.option}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user