Started to update the exam to work with Zustand for the history review
This commit is contained in:
@@ -69,6 +69,10 @@ export interface WritingExercise {
|
|||||||
info: string; //* The information about the task, like the amount of time they should spend on it
|
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
|
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
|
wordCounter: WordCounter; //* The minimum or maximum amount of words that should be written
|
||||||
|
userSolutions: {
|
||||||
|
id: string;
|
||||||
|
solution: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpeakingExercise {
|
export interface SpeakingExercise {
|
||||||
@@ -77,6 +81,10 @@ export interface SpeakingExercise {
|
|||||||
title: string;
|
title: string;
|
||||||
text: string;
|
text: string;
|
||||||
prompts: string[];
|
prompts: string[];
|
||||||
|
userSolutions: {
|
||||||
|
id: string;
|
||||||
|
solution: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FillBlanksExercise {
|
export interface FillBlanksExercise {
|
||||||
|
|||||||
31
src/pages/api/exam/[module]/[id].ts
Normal file
31
src/pages/api/exam/[module]/[id].ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type {NextApiRequest, NextApiResponse} from "next";
|
||||||
|
import {app} from "@/firebase";
|
||||||
|
import {getFirestore, doc, getDoc} from "firebase/firestore";
|
||||||
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
|
import {sessionOptions} from "@/lib/session";
|
||||||
|
|
||||||
|
const db = getFirestore(app);
|
||||||
|
|
||||||
|
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||||
|
|
||||||
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (!req.session.user) {
|
||||||
|
res.status(401).json({ok: false});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {module, id} = req.query as {module: string; id: string};
|
||||||
|
|
||||||
|
const docRef = doc(db, module, id);
|
||||||
|
const docSnap = await getDoc(docRef);
|
||||||
|
|
||||||
|
if (docSnap.exists()) {
|
||||||
|
res.status(200).json({
|
||||||
|
id: docSnap.id,
|
||||||
|
...docSnap.data(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(404).json(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import {Stat, User} from "@/interfaces/user";
|
|||||||
import Speaking from "@/exams/Speaking";
|
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, {ExamState} from "@/stores/examStore";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -42,30 +43,44 @@ export default function Page() {
|
|||||||
const [userSolutions, setUserSolutions] = useState<UserSolution[]>([]);
|
const [userSolutions, setUserSolutions] = useState<UserSolution[]>([]);
|
||||||
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
|
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
|
||||||
const [hasBeenUploaded, setHasBeenUploaded] = useState(false);
|
const [hasBeenUploaded, setHasBeenUploaded] = useState(false);
|
||||||
const [showSolutions, setShowSolutions] = useState(false);
|
|
||||||
const [moduleIndex, setModuleIndex] = useState(0);
|
const [moduleIndex, setModuleIndex] = useState(0);
|
||||||
const [sessionId, setSessionId] = useState("");
|
const [sessionId, setSessionId] = useState("");
|
||||||
const [exam, setExam] = useState<Exam>();
|
const [exam, setExam] = useState<Exam>();
|
||||||
const [timer, setTimer] = useState(-1);
|
const [timer, setTimer] = useState(-1);
|
||||||
|
|
||||||
|
const [showSolutions, setShowSolutions] = useExamStore((state) => [state.showSolutions, state.setShowSolutions]);
|
||||||
|
const [exams, setExams] = useExamStore((state) => [state.exams, state.setExams]);
|
||||||
|
|
||||||
const {user} = useUser({redirectTo: "/login"});
|
const {user} = useUser({redirectTo: "/login"});
|
||||||
|
|
||||||
useEffect(() => setSessionId(uuidv4()), []);
|
useEffect(() => setSessionId(uuidv4()), []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (selectedModules.length > 0 && moduleIndex < selectedModules.length) {
|
if (selectedModules.length > 0 && exams.length > 0 && moduleIndex < selectedModules.length) {
|
||||||
const nextExam = await getExam(selectedModules[moduleIndex]);
|
const nextExam = exams[moduleIndex];
|
||||||
setExam(nextExam ? updateExamWithUserSolutions(nextExam) : undefined);
|
setExam(nextExam ? updateExamWithUserSolutions(nextExam) : undefined);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedModules, moduleIndex]);
|
}, [selectedModules, moduleIndex, exams]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async () => {
|
||||||
|
if (selectedModules.length > 0) {
|
||||||
|
const examPromises = selectedModules.map(getExam);
|
||||||
|
Promise.all(examPromises).then((values) => {
|
||||||
|
if (values.every((x) => !!x)) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [selectedModules, setExams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (selectedModules.length > 0 && moduleIndex >= selectedModules.length && !hasBeenUploaded) {
|
if (selectedModules.length > 0 && moduleIndex >= selectedModules.length && !hasBeenUploaded) {
|
||||||
const stats: Stat[] = userSolutions.map((solution) => ({
|
const newStats: Stat[] = userSolutions.map((solution) => ({
|
||||||
...solution,
|
...solution,
|
||||||
session: sessionId,
|
session: sessionId,
|
||||||
exam: solution.exam!,
|
exam: solution.exam!,
|
||||||
@@ -75,7 +90,7 @@ export default function Page() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.post<{ok: boolean}>("/api/stats", stats)
|
.post<{ok: boolean}>("/api/stats", newStats)
|
||||||
.then((response) => setHasBeenUploaded(response.data.ok))
|
.then((response) => setHasBeenUploaded(response.data.ok))
|
||||||
.catch(() => setHasBeenUploaded(false));
|
.catch(() => setHasBeenUploaded(false));
|
||||||
}
|
}
|
||||||
@@ -116,7 +131,9 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateExamWithUserSolutions = (exam: Exam): Exam => {
|
const updateExamWithUserSolutions = (exam: Exam): Exam => {
|
||||||
const exercises = exam.exercises.map((x) => Object.assign(x, {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions}));
|
const exercises = exam.exercises.map((x) =>
|
||||||
|
Object.assign(x, !x.userSolutions ? {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions} : x.userSolutions),
|
||||||
|
);
|
||||||
|
|
||||||
return Object.assign(exam, exercises);
|
return Object.assign(exam, exercises);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
|
import {Exam} from "@/interfaces/exam";
|
||||||
|
import {Stat} from "@/interfaces/user";
|
||||||
|
import {getExamsBySession} from "@/utils/stats";
|
||||||
import {create} from "zustand";
|
import {create} from "zustand";
|
||||||
|
|
||||||
const useExamStore = create((set) => ({}));
|
export interface ExamState {
|
||||||
|
exams: Exam[];
|
||||||
|
stats: Stat[];
|
||||||
|
showSolutions: boolean;
|
||||||
|
setStats: (stats: Stat[]) => void;
|
||||||
|
setExams: (exams: Exam[]) => void;
|
||||||
|
setShowSolutions: (showSolutions: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useExamStore = create<ExamState>((set) => ({
|
||||||
|
exams: [],
|
||||||
|
stats: [],
|
||||||
|
showSolutions: false,
|
||||||
|
setStats: (stats: Stat[]) => set(() => ({stats})),
|
||||||
|
setExams: (exams: Exam[]) => set(() => ({exams})),
|
||||||
|
setShowSolutions: (showSolutions: boolean) => set(() => ({showSolutions})),
|
||||||
|
}));
|
||||||
|
|
||||||
export default useExamStore;
|
export default useExamStore;
|
||||||
|
|||||||
@@ -91,5 +91,10 @@ export const formatExerciseAverageScoreStats = (stats: Stat[]): {label: string;
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getExamsBySession = (stats: Stat[], session: string) => {
|
||||||
|
const grouped = groupBySession(stats);
|
||||||
|
return grouped[session].map((exam) => exam.exam);
|
||||||
|
};
|
||||||
|
|
||||||
export const groupBySession = (stats: Stat[]) => groupBy(stats, "session");
|
export const groupBySession = (stats: Stat[]) => groupBy(stats, "session");
|
||||||
export const groupByDate = (stats: Stat[]) => groupBy(stats, "date");
|
export const groupByDate = (stats: Stat[]) => groupBy(stats, "date");
|
||||||
|
|||||||
Reference in New Issue
Block a user