Updated Writing and Speaking to have a tab system for the evaluation vs the "perfect answer"

This commit is contained in:
Tiago Ribeiro
2023-12-06 14:48:54 +00:00
parent 523149327b
commit 72083439af
5 changed files with 200 additions and 60 deletions

View File

@@ -25,19 +25,19 @@ export default function Writing({
const hasExamEnded = useExamStore((state) => state.hasExamEnded); const hasExamEnded = useExamStore((state) => state.hasExamEnded);
useEffect(() => { // useEffect(() => {
const listener = (e: KeyboardEvent) => { // const listener = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === "v") { // if ((e.ctrlKey || e.metaKey) && e.key === "v") {
e.preventDefault(); // e.preventDefault();
} // }
}; // };
document.addEventListener("keydown", listener); // document.addEventListener("keydown", listener);
return () => { // return () => {
document.removeEventListener("keydown", listener); // document.removeEventListener("keydown", listener);
}; // };
}, []); // }, []);
useEffect(() => { useEffect(() => {
if (hasExamEnded) onNext({exercise: id, solutions: [{id, solution: inputText}], score: {correct: 1, total: 1, missing: 0}, type}); if (hasExamEnded) onNext({exercise: id, solutions: [{id, solution: inputText}], score: {correct: 1, total: 1, missing: 0}, type});
@@ -93,22 +93,8 @@ export default function Writing({
)} )}
<div className="flex flex-col h-full w-full gap-9 mb-20"> <div className="flex flex-col h-full w-full gap-9 mb-20">
<div className="flex flex-col w-full gap-7 bg-mti-gray-smoke rounded-xl py-8 pb-12 px-16"> <div className="flex flex-col w-full gap-7 bg-mti-gray-smoke rounded-xl py-8 pb-12 px-16">
<span> <span className="whitespace-pre-wrap">{prefix}</span>
{prefix.split("\\n").map((line, index) => ( <span className="font-semibold whitespace-pre-wrap">{prompt}</span>
<React.Fragment key={index}>
{line}
<br />
</React.Fragment>
))}
</span>
<span className="font-semibold">
{prompt.split("\\n").map((line, index) => (
<Fragment key={index}>
<p>{line}</p>
<br />
</Fragment>
))}
</span>
{attachment && ( {attachment && (
<img <img
onClick={() => setIsModalOpen(true)} onClick={() => setIsModalOpen(true)}
@@ -120,14 +106,7 @@ export default function Writing({
</div> </div>
<div className="w-full h-full flex flex-col gap-4"> <div className="w-full h-full flex flex-col gap-4">
<span> <span className="whitespace-pre-wrap">{suffix}</span>
{suffix.split("\\n").map((line, index) => (
<React.Fragment key={index}>
{line}
<br />
</React.Fragment>
))}
</span>
<textarea <textarea
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
className="w-full h-full min-h-[300px] cursor-text px-7 py-8 input border-2 border-mti-gray-platinum bg-white rounded-3xl" className="w-full h-full min-h-[300px] cursor-text px-7 py-8 input border-2 border-mti-gray-platinum bg-white rounded-3xl"

View File

@@ -6,6 +6,8 @@ import Button from "../Low/Button";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import axios from "axios"; import axios from "axios";
import {speakingReverseMarking} from "@/utils/score"; import {speakingReverseMarking} from "@/utils/score";
import {Tab} from "@headlessui/react";
import clsx from "clsx";
const Waveform = dynamic(() => import("../Waveform"), {ssr: false}); const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
@@ -47,7 +49,7 @@ export default function InteractiveSpeaking({
</div> </div>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<span className="font-bold">You should talk about the following things:</span> <span className="font-bold">You should talk about the following things:</span>
<div className="grid grid-cols-3 gap-6 text-center"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 text-center">
{prompts.map((x, index) => ( {prompts.map((x, index) => (
<div className="italic flex flex-col gap-2 text-sm" key={index}> <div className="italic flex flex-col gap-2 text-sm" key={index}>
<video key={index} controls className=""> <video key={index} controls className="">
@@ -61,11 +63,11 @@ export default function InteractiveSpeaking({
</div> </div>
<div className="w-full h-full flex flex-col gap-8"> <div className="w-full h-full flex flex-col gap-8">
<div className="flex items-center gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{solutionsURL.map((x, index) => ( {solutionsURL.map((x, index) => (
<div <div
key={index} key={index}
className="w-full min-w-[460px] p-4 px-8 bg-transparent border-2 border-mti-gray-platinum rounded-2xl flex-col gap-8 items-center"> className="w-full p-4 px-8 bg-transparent border-2 border-mti-gray-platinum rounded-2xl flex-col gap-8 items-center">
<div className="flex gap-8 items-center justify-center py-8"> <div className="flex gap-8 items-center justify-center py-8">
<Waveform audio={x} waveColor="#FCDDEC" progressColor="#EF5DA8" /> <Waveform audio={x} waveColor="#FCDDEC" progressColor="#EF5DA8" />
</div> </div>
@@ -73,7 +75,7 @@ export default function InteractiveSpeaking({
))} ))}
</div> </div>
{userSolutions && userSolutions.length > 0 && ( {userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
<div className="flex flex-col gap-4 w-full"> <div className="flex flex-col gap-4 w-full">
<div className="flex gap-4 px-1"> <div className="flex gap-4 px-1">
{Object.keys(userSolutions[0].evaluation!.task_response).map((key) => ( {Object.keys(userSolutions[0].evaluation!.task_response).map((key) => (
@@ -82,9 +84,81 @@ export default function InteractiveSpeaking({
</div> </div>
))} ))}
</div> </div>
<div className="w-full h-full min-h-fit cursor-text px-7 py-8 bg-mti-gray-smoke rounded-3xl"> {userSolutions[0].evaluation &&
{userSolutions[0].evaluation!.comment} Object.keys(userSolutions[0].evaluation).filter((x) => x.startsWith("perfect_answer")).length === 3 ? (
</div> <Tab.Group>
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-speaking",
)
}>
Evaluation
</Tab>
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-speaking",
)
}>
Recommended Answer (Prompt 1)
</Tab>
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-speaking",
)
}>
Recommended Answer (Prompt 2)
</Tab>
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-speaking",
)
}>
Recommended Answer (Prompt 3)
</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel className="w-full bg-ielts-speaking/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text">{userSolutions[0].evaluation!.comment}</span>
</Tab.Panel>
<Tab.Panel className="w-full bg-ielts-speaking/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text whitespace-pre-wrap">
{userSolutions[0].evaluation!.perfect_answer_1!.replaceAll(/\s{2,}/g, "\n\n")}
</span>
</Tab.Panel>
<Tab.Panel className="w-full bg-ielts-speaking/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text whitespace-pre-wrap">
{userSolutions[0].evaluation!.perfect_answer_2!.replaceAll(/\s{2,}/g, "\n\n")}
</span>
</Tab.Panel>
<Tab.Panel className="w-full bg-ielts-speaking/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text whitespace-pre-wrap">
{userSolutions[0].evaluation!.perfect_answer_3!.replaceAll(/\s{2,}/g, "\n\n")}
</span>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
) : (
<div className="w-full h-full min-h-fit cursor-text px-7 py-8 bg-ielts-speaking/10 rounded-3xl">
{userSolutions[0].evaluation!.comment}
</div>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -6,6 +6,8 @@ import Button from "../Low/Button";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import axios from "axios"; import axios from "axios";
import {speakingReverseMarking} from "@/utils/score"; import {speakingReverseMarking} from "@/utils/score";
import {Tab} from "@headlessui/react";
import clsx from "clsx";
const Waveform = dynamic(() => import("../Waveform"), {ssr: false}); const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
@@ -69,7 +71,7 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
{solutionURL && <Waveform audio={solutionURL} waveColor="#FCDDEC" progressColor="#EF5DA8" />} {solutionURL && <Waveform audio={solutionURL} waveColor="#FCDDEC" progressColor="#EF5DA8" />}
</div> </div>
</div> </div>
{userSolutions && userSolutions.length > 0 && ( {userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
<div className="flex flex-col gap-4 w-full"> <div className="flex flex-col gap-4 w-full">
<div className="flex gap-4 px-1"> <div className="flex gap-4 px-1">
{Object.keys(userSolutions[0].evaluation!.task_response).map((key) => ( {Object.keys(userSolutions[0].evaluation!.task_response).map((key) => (
@@ -78,9 +80,48 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
</div> </div>
))} ))}
</div> </div>
<div className="w-full h-full min-h-fit cursor-text px-7 py-8 bg-mti-gray-smoke rounded-3xl"> {userSolutions[0].evaluation && userSolutions[0].evaluation.perfect_answer ? (
{userSolutions[0].evaluation!.comment} <Tab.Group>
</div> <Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-speaking",
)
}>
Evaluation
</Tab>
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-speaking",
)
}>
Recommended Answer
</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel className="w-full bg-ielts-speaking/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text">{userSolutions[0].evaluation!.comment}</span>
</Tab.Panel>
<Tab.Panel className="w-full bg-ielts-speaking/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text whitespace-pre-wrap">
{userSolutions[0].evaluation!.perfect_answer.replaceAll(/\s{2,}/g, "\n\n")}
</span>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
) : (
<div className="w-full h-full min-h-fit cursor-text px-7 py-8 bg-ielts-speaking/10 rounded-3xl">
{userSolutions[0].evaluation!.comment}
</div>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -1,15 +1,11 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
import {WritingExercise} from "@/interfaces/exam"; import {WritingExercise} from "@/interfaces/exam";
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
import Icon from "@mdi/react";
import clsx from "clsx";
import {CommonProps} from "."; import {CommonProps} from ".";
import {Fragment, useEffect, useState} from "react"; import {Fragment, useState} from "react";
import {toast} from "react-toastify";
import Button from "../Low/Button"; import Button from "../Low/Button";
import {Dialog, Transition} from "@headlessui/react"; import {Dialog, Tab, Transition} from "@headlessui/react";
import {writingReverseMarking} from "@/utils/score"; import {writingReverseMarking} from "@/utils/score";
import clsx from "clsx";
export default function Writing({id, type, prompt, attachment, userSolutions, onNext, onBack}: WritingExercise & CommonProps) { export default function Writing({id, type, prompt, attachment, userSolutions, onNext, onBack}: WritingExercise & CommonProps) {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@@ -79,7 +75,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
/> />
</div> </div>
)} )}
{userSolutions && userSolutions.length > 0 && ( {userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
<div className="flex flex-col gap-4 w-full"> <div className="flex flex-col gap-4 w-full">
<div className="flex gap-4 px-1"> <div className="flex gap-4 px-1">
{Object.keys(userSolutions[0].evaluation!.task_response).map((key) => ( {Object.keys(userSolutions[0].evaluation!.task_response).map((key) => (
@@ -88,9 +84,48 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
</div> </div>
))} ))}
</div> </div>
<div className="w-full h-full min-h-fit cursor-text px-7 py-8 bg-mti-gray-smoke rounded-3xl"> {userSolutions[0].evaluation && userSolutions[0].evaluation.perfect_answer ? (
{userSolutions[0].evaluation!.comment} <Tab.Group>
</div> <Tab.List className="flex space-x-1 rounded-xl bg-ielts-writing/20 p-1">
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-writing",
)
}>
Evaluation
</Tab>
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-writing",
)
}>
Recommended Answer
</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel className="w-full bg-ielts-writing/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text">{userSolutions[0].evaluation!.comment}</span>
</Tab.Panel>
<Tab.Panel className="w-full bg-ielts-writing/10 h-fit rounded-xl p-6 flex flex-col gap-4">
<span className="w-full h-full min-h-fit cursor-text whitespace-pre-wrap">
{userSolutions[0].evaluation!.perfect_answer.replaceAll(/\s{2,}/g, "\n\n")}
</span>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
) : (
<div className="w-full h-full min-h-fit cursor-text px-7 py-8 bg-ielts-writing/10 rounded-3xl">
{userSolutions[0].evaluation!.comment}
</div>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -92,6 +92,17 @@ export interface Evaluation {
overall: number; overall: number;
task_response: {[key: string]: number}; task_response: {[key: string]: number};
} }
interface InteractiveSpeakingEvaluation extends Evaluation {
perfect_answer_1?: string;
perfect_answer_2?: string;
perfect_answer_3?: string;
}
interface CommonEvaluation extends Evaluation {
perfect_answer?: string;
}
export interface WritingExercise { export interface WritingExercise {
id: string; id: string;
type: "writing"; type: "writing";
@@ -106,7 +117,7 @@ export interface WritingExercise {
userSolutions: { userSolutions: {
id: string; id: string;
solution: string; solution: string;
evaluation?: Evaluation; evaluation?: CommonEvaluation;
}[]; }[];
} }
@@ -120,7 +131,7 @@ export interface SpeakingExercise {
userSolutions: { userSolutions: {
id: string; id: string;
solution: string; solution: string;
evaluation?: Evaluation; evaluation?: CommonEvaluation;
}[]; }[];
} }
@@ -133,7 +144,7 @@ export interface InteractiveSpeakingExercise {
userSolutions: { userSolutions: {
id: string; id: string;
solution: {question: string; answer: string}[]; solution: {question: string; answer: string}[];
evaluation?: Evaluation; evaluation?: InteractiveSpeakingEvaluation;
}[]; }[];
} }