Revamped the whole Solutions stuff with Zustand

This commit is contained in:
Tiago Ribeiro
2023-05-17 17:33:53 +01:00
parent 44ad687bcf
commit f337540629
4 changed files with 83 additions and 13 deletions

View File

@@ -40,16 +40,16 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}, sessionOptions); }, sessionOptions);
export default function Page() { export default function Page() {
const [userSolutions, setUserSolutions] = useState<UserSolution[]>([]);
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
const [hasBeenUploaded, setHasBeenUploaded] = useState(false); const [hasBeenUploaded, setHasBeenUploaded] = 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 [exams, setExams] = useExamStore((state) => [state.exams, state.setExams]);
const [userSolutions, setUserSolutions] = useExamStore((state) => [state.userSolutions, state.setUserSolutions]);
const [showSolutions, setShowSolutions] = useExamStore((state) => [state.showSolutions, state.setShowSolutions]);
const [selectedModules, setSelectedModules] = useExamStore((state) => [state.selectedModules, state.setSelectedModules]);
const {user} = useUser({redirectTo: "/login"}); const {user} = useUser({redirectTo: "/login"});
@@ -66,15 +66,16 @@ export default function Page() {
}, [selectedModules, moduleIndex, exams]); }, [selectedModules, moduleIndex, exams]);
useEffect(() => { useEffect(() => {
async () => { (async () => {
if (selectedModules.length > 0) { if (selectedModules.length > 0) {
const examPromises = selectedModules.map(getExam); const examPromises = selectedModules.map(getExam);
Promise.all(examPromises).then((values) => { Promise.all(examPromises).then((values) => {
if (values.every((x) => !!x)) { if (values.every((x) => !!x)) {
setExams(values.map((x) => x!));
} }
}); });
} }
}; })();
}, [selectedModules, setExams]); }, [selectedModules, setExams]);
useEffect(() => { useEffect(() => {
@@ -141,7 +142,7 @@ export default function Page() {
const onFinish = (solutions: UserSolution[]) => { const onFinish = (solutions: UserSolution[]) => {
const solutionIds = solutions.map((x) => x.exercise); const solutionIds = solutions.map((x) => x.exercise);
setUserSolutions((prev) => [...prev.filter((x) => !solutionIds.includes(x.exercise)), ...solutions]); setUserSolutions([...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), ...solutions]);
setModuleIndex((prev) => prev + 1); setModuleIndex((prev) => prev + 1);
}; };
@@ -158,6 +159,7 @@ export default function Page() {
onViewResults={() => { onViewResults={() => {
setShowSolutions(true); setShowSolutions(true);
setModuleIndex(0); setModuleIndex(0);
setExam(exams[0]);
}} }}
scores={userSolutions.map((x) => ({...x.score, module: x.module}))} scores={userSolutions.map((x) => ({...x.score, module: x.module}))}
/> />

View File

@@ -8,7 +8,7 @@ import {sessionOptions} from "@/lib/session";
import {Stat, User} from "@/interfaces/user"; import {Stat, User} from "@/interfaces/user";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import useStats from "@/hooks/useStats"; import useStats from "@/hooks/useStats";
import {averageScore, formatModuleTotalStats, groupByDate, groupBySession, totalExams} from "@/utils/stats"; import {averageScore, convertToUserSolutions, formatModuleTotalStats, groupByDate, groupBySession, totalExams} from "@/utils/stats";
import {Divider} from "primereact/divider"; import {Divider} from "primereact/divider";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import {Timeline} from "primereact/timeline"; import {Timeline} from "primereact/timeline";
@@ -16,6 +16,12 @@ import moment from "moment";
import {AutoComplete} from "primereact/autocomplete"; import {AutoComplete} from "primereact/autocomplete";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {Dropdown} from "primereact/dropdown"; import {Dropdown} from "primereact/dropdown";
import useExamStore from "@/stores/examStore";
import {Exam, ListeningExam, ReadingExam, SpeakingExam, WritingExam} from "@/interfaces/exam";
import {Module} from "@/interfaces";
import axios from "axios";
import {toast} from "react-toastify";
import {useRouter} from "next/router";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -43,12 +49,40 @@ export default function History({user}: {user: User}) {
const {users, isLoading: isUsersLoading} = useUsers(); const {users, isLoading: isUsersLoading} = useUsers();
const {stats, isLoading: isStatsLoading} = useStats(selectedUser?.id); const {stats, isLoading: isStatsLoading} = useStats(selectedUser?.id);
const setExams = useExamStore((state) => state.setExams);
const setShowSolutions = useExamStore((state) => state.setShowSolutions);
const setUserSolutions = useExamStore((state) => state.setUserSolutions);
const setSelectedModules = useExamStore((state) => state.setSelectedModules);
const router = useRouter();
useEffect(() => { useEffect(() => {
if (stats && !isStatsLoading) { if (stats && !isStatsLoading) {
setGroupedStats(groupByDate(stats)); setGroupedStats(groupByDate(stats));
} }
}, [stats, isStatsLoading]); }, [stats, isStatsLoading]);
const getExam = async (module: Module): Promise<Exam | undefined> => {
const examRequest = await axios<Exam[]>(`/api/exam/${module}`);
if (examRequest.status !== 200) {
toast.error("Something went wrong!");
return undefined;
}
const newExam = examRequest.data;
switch (module) {
case "reading":
return newExam.shift() as ReadingExam;
case "listening":
return newExam.shift() as ListeningExam;
case "writing":
return newExam.shift() as WritingExam;
case "speaking":
return newExam.shift() as SpeakingExam;
}
};
const formatTimestamp = (timestamp: string) => { const formatTimestamp = (timestamp: string) => {
const date = moment(parseInt(timestamp)); const date = moment(parseInt(timestamp));
const formatter = "YYYY/MM/DD - HH:mm"; const formatter = "YYYY/MM/DD - HH:mm";
@@ -63,6 +97,22 @@ export default function History({user}: {user: User}) {
const correct = dateStats.reduce((accumulator, current) => accumulator + current.score.correct, 0); const correct = dateStats.reduce((accumulator, current) => accumulator + current.score.correct, 0);
const total = dateStats.reduce((accumulator, current) => accumulator + current.score.total, 0); const total = dateStats.reduce((accumulator, current) => accumulator + current.score.total, 0);
const selectExam = () => {
const examPromises = formatModuleTotalStats(dateStats)
.filter((x) => x.value > 0)
.map((module) => getExam(module.label.toLowerCase() as Module));
Promise.all(examPromises).then((exams) => {
if (exams.every((x) => !!x)) {
setUserSolutions(convertToUserSolutions(dateStats));
setShowSolutions(true);
setExams(exams.map((x) => x!));
setSelectedModules(exams.map((x) => x!.module));
router.push("/exam");
}
});
};
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span>{formatTimestamp(timestamp)}</span> <span>{formatTimestamp(timestamp)}</span>
@@ -77,6 +127,9 @@ export default function History({user}: {user: User}) {
<span> <span>
Score: {correct}/{total} | {((correct / total) * 100).toFixed(2)}% Score: {correct}/{total} | {((correct / total) * 100).toFixed(2)}%
</span> </span>
<button onClick={selectExam} className="btn">
View
</button>
</div> </div>
</div> </div>
); );

View File

@@ -1,25 +1,28 @@
import {Module} from "@/interfaces"; import {Module} from "@/interfaces";
import {Exam} from "@/interfaces/exam"; import {Exam, UserSolution} from "@/interfaces/exam";
import {Stat} from "@/interfaces/user"; import {Stat} from "@/interfaces/user";
import {getExamsBySession} from "@/utils/stats";
import {create} from "zustand"; import {create} from "zustand";
export interface ExamState { export interface ExamState {
exams: Exam[]; exams: Exam[];
stats: Stat[]; userSolutions: UserSolution[];
showSolutions: boolean; showSolutions: boolean;
setStats: (stats: Stat[]) => void; selectedModules: Module[];
setUserSolutions: (userSolutions: UserSolution[]) => void;
setExams: (exams: Exam[]) => void; setExams: (exams: Exam[]) => void;
setShowSolutions: (showSolutions: boolean) => void; setShowSolutions: (showSolutions: boolean) => void;
setSelectedModules: (modules: Module[]) => void;
} }
const useExamStore = create<ExamState>((set) => ({ const useExamStore = create<ExamState>((set) => ({
exams: [], exams: [],
stats: [], userSolutions: [],
showSolutions: false, showSolutions: false,
setStats: (stats: Stat[]) => set(() => ({stats})), selectedModules: [],
setUserSolutions: (userSolutions: UserSolution[]) => set(() => ({userSolutions})),
setExams: (exams: Exam[]) => set(() => ({exams})), setExams: (exams: Exam[]) => set(() => ({exams})),
setShowSolutions: (showSolutions: boolean) => set(() => ({showSolutions})), setShowSolutions: (showSolutions: boolean) => set(() => ({showSolutions})),
setSelectedModules: (modules: Module[]) => set(() => ({selectedModules: modules})),
})); }));
export default useExamStore; export default useExamStore;

View File

@@ -1,6 +1,7 @@
import {Stat} from "@/interfaces/user"; 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";
export const totalExams = (stats: Stat[]): number => { export const totalExams = (stats: Stat[]): number => {
const moduleStats = formatModuleTotalStats(stats); const moduleStats = formatModuleTotalStats(stats);
@@ -98,3 +99,14 @@ export const getExamsBySession = (stats: Stat[], session: string) => {
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");
export const convertToUserSolutions = (stats: Stat[]): UserSolution[] => {
return stats.map((stat) => ({
exercise: stat.exercise,
exam: stat.exam,
score: stat.score,
solutions: stat.solutions,
type: stat.type,
module: stat.module,
}));
};