feat(exam): add SAT style testing component

This commit is contained in:
shafin-r
2026-01-28 15:22:19 +06:00
parent 61b7c5220e
commit 355ca0c0c4
17 changed files with 1136 additions and 267 deletions

127
src/stores/useSatExam.ts Normal file
View File

@ -0,0 +1,127 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { type ExamPhase } from "../types/sheet";
import type { SessionModuleQuestions } from "../types/session";
// interface SatExamState {
// modules: Module[];
// phase: ExamPhase;
// moduleIndex: number;
// questionIndex: number;
// endTime: number | null;
// startExam: () => void;
// setModuleQuestionss: (modules: Module[]) => void;
// nextQuestion: () => void;
// prevQuestion: () => void;
// nextModule: () => void;
// nextPhase: () => void;
// skipBreak: () => void;
// getRemainingTime: () => number;
// finishExam: () => void;
// resetExam: () => void;
// replaceModules: (modules: Module[]) => void;
// }
interface SatExamState {
currentModuleQuestions: SessionModuleQuestions | null;
phase: ExamPhase;
questionIndex: number;
endTime: number | null;
setModuleQuestions: (module: SessionModuleQuestions) => void;
startExam: () => void;
nextQuestion: () => void;
prevQuestion: () => void;
startBreak: () => void;
skipBreak: () => void;
finishExam: () => void;
resetExam: () => void;
getRemainingTime: () => number;
}
const BREAK_DURATION = 30; // seconds
export const useSatExam = create<SatExamState>()(
persist(
(set, get) => ({
currentModuleQuestions: null,
phase: "IDLE",
questionIndex: 0,
endTime: null,
setModuleQuestions: (module: SessionModuleQuestions) => {
const endTime = Date.now() + module.time_limit_minutes * 1000;
set({
currentModuleQuestions: module,
questionIndex: 0,
endTime,
});
},
startExam: () => {
set({ phase: "MODULE", questionIndex: 0 });
},
nextQuestion: () => {
const { currentModuleQuestions, questionIndex } = get();
const questions = currentModuleQuestions?.questions ?? [];
if (questionIndex < questions.length - 1) {
set({ questionIndex: questionIndex + 1 });
}
},
prevQuestion: () => {
const { questionIndex } = get();
if (questionIndex > 0) {
set({ questionIndex: questionIndex - 1 });
}
},
startBreak: () => {
const endTime = Date.now() + BREAK_DURATION * 1000;
set((state) => ({
phase: "BREAK",
endTime,
questionIndex: 0, // optional: reset question index for next module UX
}));
},
skipBreak: () => {
const module = get().currentModuleQuestions;
if (!module) return;
const endTime = Date.now() + module.time_limit_minutes * 1000;
set({
phase: "MODULE",
endTime,
questionIndex: 0,
});
},
finishExam: () => set({ phase: "FINISHED", endTime: null }),
getRemainingTime: () => {
const { endTime } = get();
if (!endTime) return 0;
return Math.max(0, Math.floor((endTime - Date.now()) / 1000));
},
resetExam: () =>
set({
currentModuleQuestions: null,
phase: "IDLE",
questionIndex: 0,
endTime: null,
}),
}),
{ name: "sat-exam-storage" },
),
);