Created a simple component for the writing exam

This commit is contained in:
Tiago Ribeiro
2023-04-06 12:02:07 +01:00
parent 36e6c017b4
commit addef7c674
7 changed files with 118 additions and 7 deletions

11
src/demo/writing.json Normal file
View File

@@ -0,0 +1,11 @@
{
"module": "writing",
"text": {
"info": "You should spend about 20 minutes on this task.",
"prompt": "The charts below show the results of a survey of adult education. The first chart shows the reasons why adults decide to study. The pie chart shows how people think the costs of adult education should be shared.\nWrite a report for a university lecturer, describing the information shown below.",
"wordCounter": {
"type": "min",
"limit": 5
}
}
}

67
src/exams/Writing.tsx Normal file
View File

@@ -0,0 +1,67 @@
import {infoButtonStyle} from "@/constants/buttonStyles";
import {WritingExam} from "@/interfaces/exam";
import clsx from "clsx";
import {Fragment, useEffect, useState} from "react";
import {toast} from "react-toastify";
interface Props {
exam: WritingExam;
}
export default function Writing({exam}: Props) {
const [inputText, setInputText] = useState("");
const [isSubmitEnabled, setIsSubmitEnabled] = useState(false);
useEffect(() => {
const words = inputText.split(" ").filter((x) => x !== "");
const {wordCounter} = exam.text;
if (wordCounter.type === "min") {
setIsSubmitEnabled(wordCounter.limit <= words.length);
} else {
setIsSubmitEnabled(true);
if (wordCounter.limit < words.length) {
toast.warning(`You have reached your word limit of ${wordCounter.limit} words!`);
setInputText(words.slice(0, words.length - 1).join(" "));
}
}
}, [inputText, exam]);
return (
<div className="h-full w-full flex flex-col items-center justify-center gap-8">
<div className="flex flex-col max-w-2xl gap-2">
<span>{exam.text.info}</span>
<span className="font-bold ml-8">
{exam.text.prompt.split("\n").map((line, index) => (
<Fragment key={index}>
<span>{line}</span>
<br />
</Fragment>
))}
</span>
<span>
You should write {exam.text.wordCounter.type === "min" ? "at least" : "at most"} {exam.text.wordCounter.limit} words.
</span>
</div>
<textarea
className="w-1/2 h-1/3 cursor-text p-2 input input-bordered"
onChange={(e) => setInputText(e.target.value)}
value={inputText}
placeholder="Write your text here..."
/>
<div className="w-1/2 flex justify-end">
{!isSubmitEnabled && (
<div className="tooltip" data-tip={`You have not yet reached your minimum word count of ${exam.text.wordCounter.limit} words!`}>
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} disabled={!isSubmitEnabled}>
Next
</button>
</div>
)}
{isSubmitEnabled && (
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} disabled={!isSubmitEnabled}>
Next
</button>
)}
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
export type Exam = ReadingExam | ListeningExam;
export type Exam = ReadingExam | ListeningExam | WritingExam;
export interface ReadingExam {
text: {
@@ -20,6 +20,20 @@ export interface ListeningExam {
module: "listening";
}
export interface WritingExam {
module: "writing";
text: {
info: string; //* The information about the task, like the amount of time they should spend on it
prompt: string; //* The context given to the user containing what they should write about
wordCounter: WordCounter; //* The minimum or maximum amount of words that should be written
};
}
interface WordCounter {
type: "min" | "max";
limit: number;
}
export type Exercise = FillBlanksExercise | MatchSentencesExercise | MultipleChoiceExercise | WriteBlanksExercise;
export interface FillBlanksExercise {

View File

@@ -1,6 +1,7 @@
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import "@/styles/globals.css";
import "react-toastify/dist/ReactToastify.css";
import type {AppProps} from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
export default function App({Component, pageProps}: AppProps) {
return <Component {...pageProps} />;
}

View File

@@ -8,11 +8,14 @@ import {Module} from "@/interfaces";
import JSON_USER from "@/demo/user.json";
import JSON_READING from "@/demo/reading.json";
import JSON_LISTENING from "@/demo/listening.json";
import JSON_WRITING from "@/demo/writing.json";
import Selection from "@/exams/Selection";
import Reading from "@/exams/Reading";
import {Exam, ListeningExam, ReadingExam} from "@/interfaces/exam";
import {Exam, ListeningExam, ReadingExam, WritingExam} from "@/interfaces/exam";
import Listening from "@/exams/Listening";
import Writing from "@/exams/Writing";
import {ToastContainer} from "react-toastify";
export default function Home() {
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
@@ -31,6 +34,8 @@ export default function Home() {
return JSON_READING as ReadingExam;
case "listening":
return JSON_LISTENING as ListeningExam;
case "writing":
return JSON_WRITING as WritingExam;
}
return undefined;
@@ -53,6 +58,10 @@ export default function Home() {
return <Listening exam={exam} onFinish={() => setModuleIndex((prev) => prev + 1)} />;
}
if (exam && exam.module === "writing") {
return <Writing exam={exam} />;
}
return <>Loading...</>;
};
@@ -65,6 +74,7 @@ export default function Home() {
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="w-full h-screen flex flex-col items-center bg-neutral-100 text-black">
<ToastContainer />
<Navbar profilePicture={JSON_USER.profilePicture} />
{renderScreen()}
</main>