Updated part of the Selection screen to the new design
This commit is contained in:
@@ -9,6 +9,10 @@ import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
|||||||
import ProfileLevel from "@/components/ProfileLevel";
|
import ProfileLevel from "@/components/ProfileLevel";
|
||||||
import {User} from "@/interfaces/user";
|
import {User} from "@/interfaces/user";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
|
import ProgressBar from "@/components/Low/ProgressBar";
|
||||||
|
import {BsBook, BsFileEarmarkText, BsHeadphones, BsMegaphone, BsPen, BsPencil, BsStar} from "react-icons/bs";
|
||||||
|
import {averageScore, formatModuleTotalStats, totalExams, totalExamsByModule} from "@/utils/stats";
|
||||||
|
import useStats from "@/hooks/useStats";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -17,6 +21,11 @@ interface Props {
|
|||||||
|
|
||||||
export default function Selection({user, onStart}: Props) {
|
export default function Selection({user, onStart}: Props) {
|
||||||
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
|
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
|
||||||
|
const {stats} = useStats(user?.id);
|
||||||
|
|
||||||
|
const calculateAverageLevel = () => {
|
||||||
|
return Object.keys(user!.levels).reduce((accumulator, current) => user!.levels[current as Module] + accumulator, 0) / 4;
|
||||||
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -27,81 +36,71 @@ export default function Selection({user, onStart}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full h-full relative">
|
<div className="w-full h-full relative flex flex-col gap-12">
|
||||||
<section className="h-full w-full flex flex-col items-center justify-center gap-8">
|
<section className="w-full flex gap-8">
|
||||||
<ProfileLevel user={user} className="h-1/2" />
|
<img src={user.profilePicture} alt={user.name} className="aspect-square h-64 rounded-3xl drop-shadow-xl" />
|
||||||
<div className="h-1/2 flex flex-col gap-8">
|
<div className="flex flex-col gap-4 py-4 w-full">
|
||||||
<div className="h-1/2 items-center flex flex-col lg:flex-row gap-8">
|
<div className="flex justify-between w-full gap-8">
|
||||||
<div
|
<div className="flex flex-col gap-2 py-2">
|
||||||
role="button"
|
<h1 className="font-bold text-4xl">{user.name}</h1>
|
||||||
tabIndex={0}
|
<h6 className="font-normal text-base text-mti-gray-taupe capitalize">{user.type}</h6>
|
||||||
onClick={() => toggleModule("reading")}
|
|
||||||
className={clsx(
|
|
||||||
"flex flex-col gap-2 items-center justify-center",
|
|
||||||
"border-ielts-reading hover:bg-ielts-reading text-white",
|
|
||||||
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
|
|
||||||
selectedModules.includes("reading") ? "bg-ielts-reading " : "bg-ielts-reading-transparent ",
|
|
||||||
)}>
|
|
||||||
<Icon path={mdiBookOpen} color="white" size={3} />
|
|
||||||
<span>Reading</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<ProgressBar
|
||||||
role="button"
|
label={`Level ${calculateAverageLevel().toFixed(1)}`}
|
||||||
tabIndex={0}
|
percentage={Math.round((calculateAverageLevel() * 100) / 9)}
|
||||||
onClick={() => toggleModule("listening")}
|
color="blue"
|
||||||
className={clsx(
|
className="max-w-xs w-32 self-end h-10"
|
||||||
"flex flex-col gap-2 items-center justify-center",
|
/>
|
||||||
"border-ielts-listening hover:bg-ielts-listening text-white",
|
|
||||||
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
|
|
||||||
selectedModules.includes("listening") ? "bg-ielts-listening " : "bg-ielts-listening-transparent ",
|
|
||||||
)}>
|
|
||||||
<Icon path={mdiHeadphones} color="white" size={3} />
|
|
||||||
<span>Listening</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<ProgressBar label="" percentage={70} color="blue" className="w-full h-3 drop-shadow-lg" />
|
||||||
role="button"
|
<div className="flex justify-between w-full mt-8">
|
||||||
tabIndex={0}
|
<div className="flex gap-4 items-center">
|
||||||
onClick={() => toggleModule("writing")}
|
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
|
||||||
className={clsx(
|
<BsBook className="text-ielts-reading w-8 h-8" />
|
||||||
"flex flex-col gap-2 items-center justify-center",
|
|
||||||
"border-ielts-writing hover:bg-ielts-writing text-white",
|
|
||||||
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
|
|
||||||
selectedModules.includes("writing") ? "bg-ielts-writing " : "bg-ielts-writing-transparent ",
|
|
||||||
)}>
|
|
||||||
<Icon path={mdiPen} color="white" size={3} />
|
|
||||||
<span>Writing</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="flex flex-col">
|
||||||
role="button"
|
<span className="font-bold text-xl">{totalExamsByModule(stats, "reading")}</span>
|
||||||
tabIndex={0}
|
<span className="font-normal text-base text-mti-gray-dim">Reading</span>
|
||||||
onClick={() => toggleModule("speaking")}
|
|
||||||
className={clsx(
|
|
||||||
"flex flex-col gap-2 items-center justify-center",
|
|
||||||
"border-ielts-speaking hover:bg-ielts-speaking text-white",
|
|
||||||
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
|
|
||||||
selectedModules.includes("speaking") ? "bg-ielts-speaking " : "bg-ielts-speaking-transparent ",
|
|
||||||
)}>
|
|
||||||
<Icon path={mdiAccountVoice} color="white" size={3} />
|
|
||||||
<span>Speaking</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-col gap-4 md:flex-row justify-between">
|
<div className="flex gap-4 items-center">
|
||||||
<button onClick={() => router.push("/")} className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}>
|
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
|
||||||
<div className="absolute left-4">
|
<BsHeadphones className="text-ielts-listening w-8 h-8" />
|
||||||
<Icon path={mdiArrowLeft} color="white" size={1} />
|
|
||||||
</div>
|
</div>
|
||||||
Back
|
<div className="flex flex-col">
|
||||||
</button>
|
<span className="font-bold text-xl">{totalExamsByModule(stats, "listening")}</span>
|
||||||
<button
|
<span className="font-normal text-base text-mti-gray-dim">Listening</span>
|
||||||
className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)}
|
|
||||||
onClick={() => onStart(selectedModules)}>
|
|
||||||
Start
|
|
||||||
<div className="absolute right-4">
|
|
||||||
<Icon path={mdiArrowRight} color="white" size={1} />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
|
||||||
|
<BsPen className="text-ielts-writing w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-bold text-xl">{totalExamsByModule(stats, "writing")}</span>
|
||||||
|
<span className="font-normal text-base text-mti-gray-dim">Writing</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
|
||||||
|
<BsMegaphone className="text-ielts-speaking w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-bold text-xl">{totalExamsByModule(stats, "speaking")}</span>
|
||||||
|
<span className="font-normal text-base text-mti-gray-dim">Speaking</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="flex flex-col gap-3">
|
||||||
|
<span className="font-bold text-lg">About Exams</span>
|
||||||
|
<span className="text-mti-gray-taupe">
|
||||||
|
This comprehensive test will assess your proficiency in reading, listening, writing, and speaking English. Be prepared to dive
|
||||||
|
into a variety of interesting and challenging topics while showcasing your ability to communicate effectively in English.
|
||||||
|
Master the vocabulary, grammar, and interpretation skills required to succeed in this high-level exam. Are you ready to
|
||||||
|
demonstrate your mastery of the English language to the world?
|
||||||
|
</span>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import Speaking from "@/exams/Speaking";
|
|||||||
import {v4 as uuidv4} from "uuid";
|
import {v4 as uuidv4} from "uuid";
|
||||||
import useUser from "@/hooks/useUser";
|
import useUser from "@/hooks/useUser";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -207,7 +208,15 @@ export default function Page() {
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
{user && <main className="w-full h-full min-h-[100vh] flex flex-col items-center bg-neutral-100 pb-4 gap-4">{renderScreen()}</main>}
|
{user && (
|
||||||
|
<main className="w-full h-[100vh] flex flex-col bg-mti-gray-smoke">
|
||||||
|
<Navbar user={user} />
|
||||||
|
<div className="h-full w-full flex py-4 pb-8 gap-2">
|
||||||
|
<Sidebar path="/exam" />
|
||||||
|
<div className="w-5/6 h-full mr-8 bg-white shadow-md rounded-2xl p-12 flex flex-col gap-12">{renderScreen()}</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Stat} from "@/interfaces/user";
|
|||||||
import {capitalize, groupBy} from "lodash";
|
import {capitalize, groupBy} from "lodash";
|
||||||
import {convertCamelCaseToReadable} from "@/utils/string";
|
import {convertCamelCaseToReadable} from "@/utils/string";
|
||||||
import {UserSolution} from "@/interfaces/exam";
|
import {UserSolution} from "@/interfaces/exam";
|
||||||
|
import {Module} from "@/interfaces";
|
||||||
|
|
||||||
export const totalExams = (stats: Stat[]): number => {
|
export const totalExams = (stats: Stat[]): number => {
|
||||||
const moduleStats = formatModuleTotalStats(stats);
|
const moduleStats = formatModuleTotalStats(stats);
|
||||||
@@ -35,6 +36,22 @@ export const formatModuleTotalStats = (stats: Stat[]): {label: string; value: nu
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const totalExamsByModule = (stats: Stat[], module: Module): number => {
|
||||||
|
const moduleSessions: {[key: string]: string[]} = {};
|
||||||
|
|
||||||
|
stats.forEach((stat) => {
|
||||||
|
if (stat.module in moduleSessions) {
|
||||||
|
if (!moduleSessions[stat.module].includes(stat.session)) {
|
||||||
|
moduleSessions[stat.module] = [...moduleSessions[stat.module], stat.session];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
moduleSessions[stat.module] = [stat.session];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return moduleSessions[module]?.length || 0;
|
||||||
|
};
|
||||||
|
|
||||||
export const formatModuleAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => {
|
export const formatModuleAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => {
|
||||||
const moduleScores: {[key: string]: {correct: number; total: number}} = {};
|
const moduleScores: {[key: string]: {correct: number; total: number}} = {};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user