Started to update the exam to work with Zustand for the history review

This commit is contained in:
Tiago Ribeiro
2023-05-08 10:44:16 +01:00
parent 4b5c99c654
commit 1813de499d
6 changed files with 88 additions and 8 deletions

View File

@@ -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 {

View 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);
}
}

View File

@@ -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);
}; };

View File

@@ -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;

View File

@@ -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");