From 02419678b7a1452bf3960bae5faceeebd1381ea4 Mon Sep 17 00:00:00 2001
From: shafin-r
Date: Sat, 7 Feb 2026 15:28:43 +0600
Subject: [PATCH] feat(test): add functionality for drill, hard test module
testing
---
src/components/ChoiceCard.tsx | 34 +++
src/pages/student/Home.tsx | 11 +-
src/pages/student/drills/page.tsx | 207 ++++++++++++++++++-
src/pages/student/hard-test-modules/page.tsx | 114 +++++++++-
src/pages/student/practice/Pretest.tsx | 2 +-
src/pages/student/practice/Test.tsx | 1 +
src/pages/student/targeted-practice/page.tsx | 51 +----
src/stores/useExamConfigStore.ts | 8 +
src/types/test.ts | 2 +-
9 files changed, 378 insertions(+), 52 deletions(-)
diff --git a/src/components/ChoiceCard.tsx b/src/components/ChoiceCard.tsx
index e69de29..0055a72 100644
--- a/src/components/ChoiceCard.tsx
+++ b/src/components/ChoiceCard.tsx
@@ -0,0 +1,34 @@
+import { Badge } from "./ui/badge";
+
+export const ChoiceCard = ({
+ label,
+ selected,
+ subLabel,
+ section,
+ onClick,
+}: {
+ label: string;
+ selected?: boolean;
+ subLabel?: string;
+ section?: string;
+ onClick: () => void;
+}) => (
+
+);
diff --git a/src/pages/student/Home.tsx b/src/pages/student/Home.tsx
index ecf7cad..20cf011 100644
--- a/src/pages/student/Home.tsx
+++ b/src/pages/student/Home.tsx
@@ -103,19 +103,16 @@ export const Home = () => {
your scores now!
*/}
-
+
-
+
Your score is low!
-
-
+
+
Score
diff --git a/src/pages/student/drills/page.tsx b/src/pages/student/drills/page.tsx
index 9c0234e..803709c 100644
--- a/src/pages/student/drills/page.tsx
+++ b/src/pages/student/drills/page.tsx
@@ -1,7 +1,212 @@
+import { useEffect, useState } from "react";
+import { useAuthStore } from "../../../stores/authStore";
+import type { Topic } from "../../../types/topic";
+import { api } from "../../../utils/api";
+import { ChoiceCard } from "../../../components/ChoiceCard";
+import { AnimatePresence, motion } from "framer-motion";
+import { slideVariants } from "../../../lib/utils";
+import { Loader2 } from "lucide-react";
+import { useExamConfigStore } from "../../../stores/useExamConfigStore";
+import { useNavigate } from "react-router-dom";
+
+type Step = "topic" | "review";
+
export const Drills = () => {
+ const user = useAuthStore((state) => state.user);
+ const navigate = useNavigate();
+
+ const [direction, setDirection] = useState<1 | -1>(1);
+
+ const [topics, setTopics] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [selectedTopics, setSelectedTopics] = useState([]);
+
+ const [search, setSearch] = useState("");
+ const [step, setStep] = useState("topic");
+
+ const { storeTopics, setMode, setQuestionCount } = useExamConfigStore();
+
+ const toggleTopic = (topic: Topic) => {
+ setSelectedTopics((prev) => {
+ const exists = prev.some((t) => t.id === topic.id);
+
+ if (exists) {
+ return prev.filter((t) => t.id !== topic.id);
+ }
+
+ return [...prev, topic];
+ });
+ };
+
+ function handleStartDrill() {
+ if (!user || !topics) return;
+
+ navigate(`/student/practice/${topics[0].id}/test`, { replace: true });
+ }
+
+ useEffect(() => {
+ const fetchAllTopics = async () => {
+ if (!user) return;
+
+ try {
+ setLoading(true);
+ const authStorage = localStorage.getItem("auth-storage");
+ if (!authStorage) return;
+
+ const parsed = JSON.parse(authStorage) as {
+ state?: { token?: string };
+ };
+
+ const token = parsed.state?.token;
+ if (!token) return;
+
+ const response = await api.fetchAllTopics(token);
+ setTopics(response);
+ setLoading(false);
+ } catch (error) {
+ console.error("Failed to load topics. Reason: " + error);
+ }
+ };
+
+ fetchAllTopics();
+ }, [user]);
+
return (
- Drills
+
+
+
+
+ {step === "topic" && (
+
+ Choose a topic
+
+ setSearch(e.target.value)}
+ className="w-full rounded-xl border px-4 py-2"
+ />
+
+
+ {loading ? (
+ <>
+
+
+
+ >
+ ) : (
+ topics
+ .filter((t) =>
+ t.name.toLowerCase().includes(search.toLowerCase()),
+ )
+ .map((t) => (
+
st.id === t.id)}
+ onClick={() => toggleTopic(t)}
+ />
+ ))
+ )}
+
+
+
+ )}
+
+ {step === "review" && (
+
+
+ Review your choices
+
+
+
+
+ Topics:{" "}
+ {selectedTopics.map((t) => t.name).join(", ")}
+
+
+
+ )}
+
+
+
+
+
+
);
};
diff --git a/src/pages/student/hard-test-modules/page.tsx b/src/pages/student/hard-test-modules/page.tsx
index 82f2651..3199623 100644
--- a/src/pages/student/hard-test-modules/page.tsx
+++ b/src/pages/student/hard-test-modules/page.tsx
@@ -1,7 +1,119 @@
+import {
+ DecimalsArrowRight,
+ Languages,
+ Percent,
+ Pilcrow,
+ Superscript,
+ WholeWord,
+} from "lucide-react";
+import { Card, CardContent } from "../../../components/ui/card";
+import { useState } from "react";
+import { useAuthStore } from "../../../stores/authStore";
+import { useNavigate } from "react-router-dom";
+import { useExamConfigStore } from "../../../stores/useExamConfigStore";
+
+type Module = "EBRW" | "MATH" | null;
+
export const HardTestModules = () => {
+ const user = useAuthStore((state) => state.user);
+ const navigate = useNavigate();
+ const [selected, setSelected] = useState(null);
+
+ const { setMode, storeDuration, setSection } = useExamConfigStore();
+
+ function handleStartModule() {
+ if (!user) return;
+
+ (setMode("MODULE"), storeDuration(7), setSection(selected));
+
+ navigate(`/student/practice/${selected}/test`, { replace: true });
+ }
return (
- HardTestModules
+
+
+
+ setSelected((prev) => (prev === "EBRW" ? null : "EBRW"))
+ }
+ className={`relative cursor-pointer overflow-hidden transition
+ ${
+ selected === "EBRW"
+ ? "ring-2 ring-blue-500 scale-[1.02]"
+ : "hover:scale-[1.01]"
+ }
+ bg-linear-to-br from-blue-400 to-blue-600
+ `}
+ >
+
+
+ Reading & Writing
+
+
+
+
+
+
+
+ setSelected((prev) => (prev === "MATH" ? null : "MATH"))
+ }
+ className={`relative cursor-pointer overflow-hidden transition
+ ${
+ selected === "MATH"
+ ? "ring-2 ring-rose-500 scale-[1.02]"
+ : "hover:scale-[1.01]"
+ }
+ bg-linear-to-br from-rose-400 to-rose-600
+ `}
+ >
+
+
+ Mathematics
+
+
+
+
+
+
+
+ {selected && (
+
+
+
+ )}
);
};
diff --git a/src/pages/student/practice/Pretest.tsx b/src/pages/student/practice/Pretest.tsx
index ab0c64e..d28a07d 100644
--- a/src/pages/student/practice/Pretest.tsx
+++ b/src/pages/student/practice/Pretest.tsx
@@ -36,7 +36,7 @@ export const Pretest = () => {
}
setSheetId(sheetId);
- setMode("MODULE");
+ setMode("SIMULATION");
storeDuration(practiceSheet?.time_limit ?? 0);
setQuestionCount(2);
diff --git a/src/pages/student/practice/Test.tsx b/src/pages/student/practice/Test.tsx
index 60b998f..9a37229 100644
--- a/src/pages/student/practice/Test.tsx
+++ b/src/pages/student/practice/Test.tsx
@@ -187,6 +187,7 @@ export const Test = () => {
};
const handleQuitExam = () => {
+ useExamConfigStore.getState().clearPayload();
finishExam();
navigate("/student/home");
};
diff --git a/src/pages/student/targeted-practice/page.tsx b/src/pages/student/targeted-practice/page.tsx
index 0916ee5..da56d5a 100644
--- a/src/pages/student/targeted-practice/page.tsx
+++ b/src/pages/student/targeted-practice/page.tsx
@@ -5,51 +5,14 @@ import { useAuthStore } from "../../../stores/authStore";
import { Loader2 } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import { slideVariants } from "../../../lib/utils";
-import { Badge } from "../../../components/ui/badge";
+import { ChoiceCard } from "../../../components/ChoiceCard";
import { useAuthToken } from "../../../hooks/useAuthToken";
-import type {
- TargetedSessionRequest,
- TargetedSessionResponse,
-} from "../../../types/session";
import { useExamConfigStore } from "../../../stores/useExamConfigStore";
-import { replace, useNavigate } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
type Step = "topic" | "difficulty" | "duration" | "review";
-const ChoiceCard = ({
- label,
- selected,
- subLabel,
- section,
- onClick,
-}: {
- label: string;
- selected?: boolean;
- subLabel?: string;
- section?: string;
- onClick: () => void;
-}) => (
-
-);
-
export const TargetedPractice = () => {
const navigate = useNavigate();
const {
@@ -129,7 +92,13 @@ export const TargetedPractice = () => {
return (
- Targeted Practice
+
@@ -183,7 +152,7 @@ export const TargetedPractice = () => {