Merged in feature/training-content (pull request #67)
Tooltips for assessment criteria on Writing and Speaking Approved-by: Tiago Ribeiro
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
import {InteractiveSpeakingExercise} from "@/interfaces/exam";
|
import { InteractiveSpeakingExercise } from "@/interfaces/exam";
|
||||||
import {CommonProps} from ".";
|
import { CommonProps } from ".";
|
||||||
import {useEffect, useState} from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Button from "../Low/Button";
|
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 { Tab } from "@headlessui/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import ReactDiffViewer, {DiffMethod} from "react-diff-viewer";
|
import ReactDiffViewer, { DiffMethod } from "react-diff-viewer";
|
||||||
|
|
||||||
const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
|
const Waveform = dynamic(() => import("../Waveform"), { ssr: false });
|
||||||
|
|
||||||
export default function InteractiveSpeaking({
|
export default function InteractiveSpeaking({
|
||||||
id,
|
id,
|
||||||
@@ -26,13 +26,20 @@ export default function InteractiveSpeaking({
|
|||||||
const [solutionsURL, setSolutionsURL] = useState<string[]>([]);
|
const [solutionsURL, setSolutionsURL] = useState<string[]>([]);
|
||||||
const [diffNumber, setDiffNumber] = useState(0);
|
const [diffNumber, setDiffNumber] = useState(0);
|
||||||
|
|
||||||
|
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.",
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userSolutions && userSolutions.length > 0 && userSolutions[0].solution) {
|
if (userSolutions && userSolutions.length > 0 && userSolutions[0].solution) {
|
||||||
Promise.all(userSolutions[0].solution.map((x) => axios.post(`/api/speaking`, {path: x.answer}, {responseType: "arraybuffer"}))).then(
|
Promise.all(userSolutions[0].solution.map((x) => axios.post(`/api/speaking`, { path: x.answer }, { responseType: "arraybuffer" }))).then(
|
||||||
(values) => {
|
(values) => {
|
||||||
setSolutionsURL(
|
setSolutionsURL(
|
||||||
values.map(({data}) => {
|
values.map(({ data }) => {
|
||||||
const blob = new Blob([data], {type: "audio/wav"});
|
const blob = new Blob([data], { type: "audio/wav" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
@@ -64,13 +71,13 @@ export default function InteractiveSpeaking({
|
|||||||
fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif',
|
fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif',
|
||||||
padding: "32px 28px",
|
padding: "32px 28px",
|
||||||
},
|
},
|
||||||
marker: {display: "none"},
|
marker: { display: "none" },
|
||||||
diffRemoved: {padding: "32px 28px"},
|
diffRemoved: { padding: "32px 28px" },
|
||||||
diffAdded: {padding: "32px 28px"},
|
diffAdded: { padding: "32px 28px" },
|
||||||
|
|
||||||
wordRemoved: {padding: "0px", display: "initial"},
|
wordRemoved: { padding: "0px", display: "initial" },
|
||||||
wordAdded: {padding: "0px", display: "initial"},
|
wordAdded: { padding: "0px", display: "initial" },
|
||||||
wordDiff: {padding: "0px", display: "initial"},
|
wordDiff: { padding: "0px", display: "initial" },
|
||||||
}}
|
}}
|
||||||
oldValue={userSolutions[0].evaluation[`transcript_${diffNumber}`]?.replaceAll("\\n", "\n")}
|
oldValue={userSolutions[0].evaluation[`transcript_${diffNumber}`]?.replaceAll("\\n", "\n")}
|
||||||
newValue={userSolutions[0].evaluation[`fixed_text_${diffNumber}`]?.replaceAll("\\n", "\n")}
|
newValue={userSolutions[0].evaluation[`fixed_text_${diffNumber}`]?.replaceAll("\\n", "\n")}
|
||||||
@@ -132,12 +139,14 @@ export default function InteractiveSpeaking({
|
|||||||
{userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
|
{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, index) => {
|
||||||
const taskResponse = userSolutions[0].evaluation!.task_response[key];
|
const taskResponse = userSolutions[0].evaluation!.task_response[key];
|
||||||
const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade;
|
const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx("bg-ielts-speaking text-ielts-speaking-light rounded-xl px-4 py-2")} key={key}>
|
<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}
|
{key}: Level {grade}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -148,7 +157,7 @@ export default function InteractiveSpeaking({
|
|||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
|
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({ selected }) =>
|
||||||
clsx(
|
clsx(
|
||||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
|
"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",
|
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
|
||||||
@@ -159,7 +168,7 @@ export default function InteractiveSpeaking({
|
|||||||
General Feedback
|
General Feedback
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({ selected }) =>
|
||||||
clsx(
|
clsx(
|
||||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
|
"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",
|
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
|
||||||
@@ -232,7 +241,7 @@ export default function InteractiveSpeaking({
|
|||||||
onBack({
|
onBack({
|
||||||
exercise: id,
|
exercise: id,
|
||||||
solutions: userSolutions,
|
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,
|
type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
import {SpeakingExercise} from "@/interfaces/exam";
|
import { SpeakingExercise } from "@/interfaces/exam";
|
||||||
import {CommonProps} from ".";
|
import { CommonProps } from ".";
|
||||||
import {Fragment, useEffect, useState} from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import Button from "../Low/Button";
|
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 { Tab } from "@headlessui/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import {BsQuestionCircleFill} from "react-icons/bs";
|
import { BsQuestionCircleFill } from "react-icons/bs";
|
||||||
import ReactDiffViewer, {DiffMethod} from "react-diff-viewer";
|
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 [solutionURL, setSolutionURL] = useState<string>();
|
||||||
const [showDiff, setShowDiff] = useState(false);
|
const [showDiff, setShowDiff] = useState(false);
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
|
|||||||
const solution = userSolutions[0].solution;
|
const solution = userSolutions[0].solution;
|
||||||
|
|
||||||
if (solution.startsWith("https://")) return setSolutionURL(solution);
|
if (solution.startsWith("https://")) return setSolutionURL(solution);
|
||||||
axios.post(`/api/speaking`, {path: userSolutions[0].solution}, {responseType: "arraybuffer"}).then(({data}) => {
|
axios.post(`/api/speaking`, { path: userSolutions[0].solution }, { responseType: "arraybuffer" }).then(({ data }) => {
|
||||||
const blob = new Blob([data], {type: "audio/wav"});
|
const blob = new Blob([data], { type: "audio/wav" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
setSolutionURL(url);
|
setSolutionURL(url);
|
||||||
@@ -32,6 +32,13 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
|
|||||||
}
|
}
|
||||||
}, [userSolutions]);
|
}, [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.",
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal title="Correction" isOpen={showDiff} onClose={() => setShowDiff(false)}>
|
<Modal title="Correction" isOpen={showDiff} onClose={() => setShowDiff(false)}>
|
||||||
@@ -51,13 +58,13 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
|
|||||||
fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif',
|
fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif',
|
||||||
padding: "32px 28px",
|
padding: "32px 28px",
|
||||||
},
|
},
|
||||||
marker: {display: "none"},
|
marker: { display: "none" },
|
||||||
diffRemoved: {padding: "32px 28px"},
|
diffRemoved: { padding: "32px 28px" },
|
||||||
diffAdded: {padding: "32px 28px"},
|
diffAdded: { padding: "32px 28px" },
|
||||||
|
|
||||||
wordRemoved: {padding: "0px", display: "initial"},
|
wordRemoved: { padding: "0px", display: "initial" },
|
||||||
wordAdded: {padding: "0px", display: "initial"},
|
wordAdded: { padding: "0px", display: "initial" },
|
||||||
wordDiff: {padding: "0px", display: "initial"},
|
wordDiff: { padding: "0px", display: "initial" },
|
||||||
}}
|
}}
|
||||||
oldValue={userSolutions[0].evaluation.transcript_1.replaceAll("\\n", "\n")}
|
oldValue={userSolutions[0].evaluation.transcript_1.replaceAll("\\n", "\n")}
|
||||||
newValue={userSolutions[0].evaluation.fixed_text_1.replaceAll("\\n", "\n")}
|
newValue={userSolutions[0].evaluation.fixed_text_1.replaceAll("\\n", "\n")}
|
||||||
@@ -126,12 +133,14 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
|
|||||||
{userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
|
{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, index) => {
|
||||||
const taskResponse = userSolutions[0].evaluation!.task_response[key];
|
const taskResponse = userSolutions[0].evaluation!.task_response[key];
|
||||||
const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade;
|
const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx("bg-ielts-speaking text-ielts-speaking-light rounded-xl px-4 py-2")} key={key}>
|
<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}
|
{key}: Level {grade}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -142,7 +151,7 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
|
|||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
|
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({ selected }) =>
|
||||||
clsx(
|
clsx(
|
||||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80",
|
"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",
|
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2",
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
import {WritingExercise} from "@/interfaces/exam";
|
import { WritingExercise } from "@/interfaces/exam";
|
||||||
import {CommonProps} from ".";
|
import { CommonProps } from ".";
|
||||||
import {Fragment, useEffect, useState} from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
import {Dialog, Tab, 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";
|
import clsx from "clsx";
|
||||||
import ReactDiffViewer, {DiffMethod} from "react-diff-viewer";
|
import ReactDiffViewer, { DiffMethod } from "react-diff-viewer";
|
||||||
import useUser from "@/hooks/useUser";
|
import useUser from "@/hooks/useUser";
|
||||||
import AIDetection from "../AIDetection";
|
import AIDetection from "../AIDetection";
|
||||||
|
|
||||||
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);
|
||||||
const [showDiff, setShowDiff] = useState(false);
|
const [showDiff, setShowDiff] = useState(false);
|
||||||
|
|
||||||
const {user} = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
const aiEval = userSolutions && userSolutions.length > 0 ? userSolutions[0].evaluation?.ai_detection : undefined;
|
const aiEval = userSolutions && userSolutions.length > 0 ? userSolutions[0].evaluation?.ai_detection : undefined;
|
||||||
|
|
||||||
|
const tooltips: { [key: string]: string } = {
|
||||||
|
"Lexical Resource": "Assesses the diversity and accuracy of vocabulary used. A higher score indicates varied and precise word choice; a lower score points to limited vocabulary and inaccuracies.",
|
||||||
|
"Task Achievement": "Evaluates how well the task requirements are fulfilled. A higher score means all parts of the task are addressed thoroughly; a lower score shows incomplete or inadequate task response.",
|
||||||
|
"Coherence and Cohesion": "Measures logical organization and flow of writing. A higher score reflects well-structured and connected ideas; a lower score indicates disorganized writing and poor linkage between ideas.",
|
||||||
|
"Grammatical Range and Accuracy": "Looks at the range and precision of grammatical structures. A higher score shows varied and accurate grammar use; a lower score suggests frequent errors and limited range.",
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{attachment && (
|
{attachment && (
|
||||||
@@ -92,13 +99,13 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
|
|||||||
fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif',
|
fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif',
|
||||||
padding: "32px 28px",
|
padding: "32px 28px",
|
||||||
},
|
},
|
||||||
marker: {display: "none"},
|
marker: { display: "none" },
|
||||||
diffRemoved: {padding: "32px 28px"},
|
diffRemoved: { padding: "32px 28px" },
|
||||||
diffAdded: {padding: "32px 28px"},
|
diffAdded: { padding: "32px 28px" },
|
||||||
|
|
||||||
wordRemoved: {padding: "0px", display: "initial"},
|
wordRemoved: { padding: "0px", display: "initial" },
|
||||||
wordAdded: {padding: "0px", display: "initial"},
|
wordAdded: { padding: "0px", display: "initial" },
|
||||||
wordDiff: {padding: "0px", display: "initial"},
|
wordDiff: { padding: "0px", display: "initial" },
|
||||||
}}
|
}}
|
||||||
oldValue={userSolutions[0].solution.replaceAll("\\n", "\n")}
|
oldValue={userSolutions[0].solution.replaceAll("\\n", "\n")}
|
||||||
newValue={userSolutions[0].evaluation!.fixed_text!.replaceAll("\\n", "\n")}
|
newValue={userSolutions[0].evaluation!.fixed_text!.replaceAll("\\n", "\n")}
|
||||||
@@ -123,12 +130,15 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
|
|||||||
{userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
|
{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, index) => {
|
||||||
const taskResponse = userSolutions[0].evaluation!.task_response[key];
|
const taskResponse = userSolutions[0].evaluation!.task_response[key];
|
||||||
const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade;
|
const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx("bg-ielts-writing text-ielts-writing-light rounded-xl px-4 py-2")} key={key}>
|
<div className={clsx(
|
||||||
|
"bg-ielts-writing text-ielts-writing-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}
|
{key}: Level {grade}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -138,7 +148,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
|
|||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-writing/20 p-1">
|
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-writing/20 p-1">
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({ selected }) =>
|
||||||
clsx(
|
clsx(
|
||||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80",
|
"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",
|
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2",
|
||||||
@@ -149,7 +159,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
|
|||||||
General Feedback
|
General Feedback
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({ selected }) =>
|
||||||
clsx(
|
clsx(
|
||||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80",
|
"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",
|
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2",
|
||||||
@@ -160,7 +170,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
|
|||||||
Evaluation
|
Evaluation
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({ selected }) =>
|
||||||
clsx(
|
clsx(
|
||||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80",
|
"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",
|
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2",
|
||||||
@@ -172,7 +182,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
|
|||||||
</Tab>
|
</Tab>
|
||||||
{aiEval && user?.type !== "student" && (
|
{aiEval && user?.type !== "student" && (
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({ selected }) =>
|
||||||
clsx(
|
clsx(
|
||||||
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80",
|
"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",
|
"ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2",
|
||||||
@@ -238,7 +248,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on
|
|||||||
onBack({
|
onBack({
|
||||||
exercise: id,
|
exercise: id,
|
||||||
solutions: userSolutions,
|
solutions: userSolutions,
|
||||||
score: {total: 100, missing: 0, correct: writingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0},
|
score: { total: 100, missing: 0, correct: writingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0 },
|
||||||
type,
|
type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user