ENCOA-137: Top right side Next Button on the exams

This commit is contained in:
Tiago Ribeiro
2024-09-04 15:10:17 +01:00
parent 49aac93618
commit 2d95cbd3dc
16 changed files with 1115 additions and 768 deletions

View File

@@ -1,20 +1,20 @@
/* eslint-disable @next/next/no-img-element */
import { SpeakingExercise } from "@/interfaces/exam";
import { CommonProps } from ".";
import { Fragment, useEffect, useState } from "react";
import {SpeakingExercise} from "@/interfaces/exam";
import {CommonProps} from ".";
import {Fragment, useEffect, useState} from "react";
import Button from "../Low/Button";
import dynamic from "next/dynamic";
import axios from "axios";
import { speakingReverseMarking } from "@/utils/score";
import { Tab } from "@headlessui/react";
import {speakingReverseMarking} from "@/utils/score";
import {Tab} from "@headlessui/react";
import clsx from "clsx";
import Modal from "../Modal";
import { BsQuestionCircleFill } from "react-icons/bs";
import ReactDiffViewer, { DiffMethod } from "react-diff-viewer";
import {BsQuestionCircleFill} from "react-icons/bs";
import ReactDiffViewer, {DiffMethod} from "react-diff-viewer";
const Waveform = dynamic(() => import("../Waveform"), { ssr: false });
const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
export default function Speaking({ id, type, title, video_url, text, prompts, userSolutions, onNext, onBack }: SpeakingExercise & CommonProps) {
export default function Speaking({id, type, title, video_url, text, prompts, userSolutions, onNext, onBack}: SpeakingExercise & CommonProps) {
const [solutionURL, setSolutionURL] = useState<string>();
const [showDiff, setShowDiff] = useState(false);
@@ -23,8 +23,8 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
const solution = userSolutions[0].solution;
if (solution.startsWith("https://")) return setSolutionURL(solution);
axios.post(`/api/speaking`, { path: userSolutions[0].solution }, { responseType: "arraybuffer" }).then(({ data }) => {
const blob = new Blob([data], { type: "audio/wav" });
axios.post(`/api/speaking`, {path: userSolutions[0].solution}, {responseType: "arraybuffer"}).then(({data}) => {
const blob = new Blob([data], {type: "audio/wav"});
const url = URL.createObjectURL(blob);
setSolutionURL(url);
@@ -32,15 +32,53 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
}
}, [userSolutions]);
const tooltips: { [key: string]: string } = {
"Grammatical Range and Accuracy": "Assesses the variety and correctness of grammatical structures used. A higher score indicates a wide range of complex and accurate grammar; a lower score suggests the need for more basic grammar practice.",
"Fluency and Coherence": "Evaluates smoothness and logical flow of speech. A higher score means natural, effortless speech and clear idea progression; a lower score indicates frequent pauses and difficulty in maintaining coherence.",
"Pronunciation": "Measures clarity and accuracy of spoken words. A higher score reflects clear, well-articulated speech with correct intonation; a lower score shows challenges in being understood.",
"Lexical Resource": "Looks at the range and appropriateness of vocabulary. A higher score demonstrates a rich and precise vocabulary; a lower score suggests limited vocabulary usage and appropriateness.",
const tooltips: {[key: string]: string} = {
"Grammatical Range and Accuracy":
"Assesses the variety and correctness of grammatical structures used. A higher score indicates a wide range of complex and accurate grammar; a lower score suggests the need for more basic grammar practice.",
"Fluency and Coherence":
"Evaluates smoothness and logical flow of speech. A higher score means natural, effortless speech and clear idea progression; a lower score indicates frequent pauses and difficulty in maintaining coherence.",
Pronunciation:
"Measures clarity and accuracy of spoken words. A higher score reflects clear, well-articulated speech with correct intonation; a lower score shows challenges in being understood.",
"Lexical Resource":
"Looks at the range and appropriateness of vocabulary. A higher score demonstrates a rich and precise vocabulary; a lower score suggests limited vocabulary usage and appropriateness.",
};
return (
<>
<div className="flex flex-col gap-4 mt-4 w-full">
<div className="flex justify-between w-full gap-8">
<Button
color="purple"
variant="outline"
onClick={() =>
onBack({
exercise: id,
solutions: userSolutions,
score: {total: 100, missing: 0, correct: speakingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0},
type,
})
}
className="max-w-[200px] self-end w-full">
Back
</Button>
<Button
color="purple"
onClick={() =>
onNext({
exercise: id,
solutions: userSolutions,
score: {
total: 100,
missing: 0,
correct: userSolutions[0]?.evaluation ? speakingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0 : 0,
},
type,
})
}
className="max-w-[200px] self-end w-full">
Next
</Button>
</div>
<Modal title="Correction" isOpen={showDiff} onClose={() => setShowDiff(false)}>
<>
{userSolutions &&
@@ -58,13 +96,13 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif',
padding: "32px 28px",
},
marker: { display: "none" },
diffRemoved: { padding: "32px 28px" },
diffAdded: { padding: "32px 28px" },
marker: {display: "none"},
diffRemoved: {padding: "32px 28px"},
diffAdded: {padding: "32px 28px"},
wordRemoved: { padding: "0px", display: "initial" },
wordAdded: { padding: "0px", display: "initial" },
wordDiff: { padding: "0px", display: "initial" },
wordRemoved: {padding: "0px", display: "initial"},
wordAdded: {padding: "0px", display: "initial"},
wordDiff: {padding: "0px", display: "initial"},
}}
oldValue={userSolutions[0].evaluation.transcript_1.replaceAll("\\n", "\n")}
newValue={userSolutions[0].evaluation.fixed_text_1.replaceAll("\\n", "\n")}
@@ -138,20 +176,24 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade;
return (
<div className={clsx("bg-ielts-speaking text-ielts-speaking-light rounded-xl px-4 py-2 tooltip tooltip-bottom",
index === 0 && "tooltip-right"
)} key={key} data-tip={tooltips[key] || "No additional information available"}>
<div
className={clsx(
"bg-ielts-speaking text-ielts-speaking-light rounded-xl px-4 py-2 tooltip tooltip-bottom",
index === 0 && "tooltip-right",
)}
key={key}
data-tip={tooltips[key] || "No additional information available"}>
{key}: Level {grade}
</div>
);
})}
</div>
{userSolutions[0].evaluation &&
(userSolutions[0].evaluation.perfect_answer || userSolutions[0].evaluation.perfect_answer_1) ? (
(userSolutions[0].evaluation.perfect_answer || userSolutions[0].evaluation.perfect_answer_1) ? (
<Tab.Group>
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
<Tab
className={({ selected }) =>
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",
@@ -162,7 +204,7 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
General Feedback
</Tab>
<Tab
className={({ selected }) =>
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",
@@ -173,7 +215,7 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
Evaluation
</Tab>
<Tab
className={({ selected }) =>
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",
@@ -194,10 +236,16 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
return (
<div key={key} className="flex flex-col gap-2">
<div className={clsx("bg-ielts-speaking text-ielts-speaking-light rounded-xl px-4 py-2 w-fit")} key={key}>
<div
className={clsx(
"bg-ielts-speaking text-ielts-speaking-light rounded-xl px-4 py-2 w-fit",
)}
key={key}>
{key}: Level {grade}
</div>
{typeof taskResponse !== "number" && <span className="px-2 py-2">{taskResponse.comment}</span>}
{typeof taskResponse !== "number" && (
<span className="px-2 py-2">{taskResponse.comment}</span>
)}
</div>
);
})}
@@ -236,7 +284,7 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
onBack({
exercise: id,
solutions: userSolutions,
score: { total: 100, missing: 0, correct: speakingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0 },
score: {total: 100, missing: 0, correct: speakingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0},
type,
})
}
@@ -261,6 +309,6 @@ export default function Speaking({ id, type, title, video_url, text, prompts, us
Next
</Button>
</div>
</>
</div>
);
}