Revamped the whole Solutions stuff with Zustand
This commit is contained in:
@@ -40,16 +40,16 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||
}, sessionOptions);
|
||||
|
||||
export default function Page() {
|
||||
const [userSolutions, setUserSolutions] = useState<UserSolution[]>([]);
|
||||
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
|
||||
const [hasBeenUploaded, setHasBeenUploaded] = useState(false);
|
||||
const [moduleIndex, setModuleIndex] = useState(0);
|
||||
const [sessionId, setSessionId] = useState("");
|
||||
const [exam, setExam] = useState<Exam>();
|
||||
const [timer, setTimer] = useState(-1);
|
||||
|
||||
const [showSolutions, setShowSolutions] = useExamStore((state) => [state.showSolutions, state.setShowSolutions]);
|
||||
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"});
|
||||
|
||||
@@ -66,15 +66,16 @@ export default function Page() {
|
||||
}, [selectedModules, moduleIndex, exams]);
|
||||
|
||||
useEffect(() => {
|
||||
async () => {
|
||||
(async () => {
|
||||
if (selectedModules.length > 0) {
|
||||
const examPromises = selectedModules.map(getExam);
|
||||
Promise.all(examPromises).then((values) => {
|
||||
if (values.every((x) => !!x)) {
|
||||
setExams(values.map((x) => x!));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
}, [selectedModules, setExams]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -141,7 +142,7 @@ export default function Page() {
|
||||
const onFinish = (solutions: UserSolution[]) => {
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -158,6 +159,7 @@ export default function Page() {
|
||||
onViewResults={() => {
|
||||
setShowSolutions(true);
|
||||
setModuleIndex(0);
|
||||
setExam(exams[0]);
|
||||
}}
|
||||
scores={userSolutions.map((x) => ({...x.score, module: x.module}))}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {sessionOptions} from "@/lib/session";
|
||||
import {Stat, User} from "@/interfaces/user";
|
||||
import {useEffect, useState} from "react";
|
||||
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 useUser from "@/hooks/useUser";
|
||||
import {Timeline} from "primereact/timeline";
|
||||
@@ -16,6 +16,12 @@ import moment from "moment";
|
||||
import {AutoComplete} from "primereact/autocomplete";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
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}) => {
|
||||
const user = req.session.user;
|
||||
@@ -43,12 +49,40 @@ export default function History({user}: {user: User}) {
|
||||
const {users, isLoading: isUsersLoading} = useUsers();
|
||||
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(() => {
|
||||
if (stats && !isStatsLoading) {
|
||||
setGroupedStats(groupByDate(stats));
|
||||
}
|
||||
}, [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 date = moment(parseInt(timestamp));
|
||||
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 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 (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span>{formatTimestamp(timestamp)}</span>
|
||||
@@ -77,6 +127,9 @@ export default function History({user}: {user: User}) {
|
||||
<span>
|
||||
Score: {correct}/{total} | {((correct / total) * 100).toFixed(2)}%
|
||||
</span>
|
||||
<button onClick={selectExam} className="btn">
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import {Module} from "@/interfaces";
|
||||
import {Exam} from "@/interfaces/exam";
|
||||
import {Exam, UserSolution} from "@/interfaces/exam";
|
||||
import {Stat} from "@/interfaces/user";
|
||||
import {getExamsBySession} from "@/utils/stats";
|
||||
import {create} from "zustand";
|
||||
|
||||
export interface ExamState {
|
||||
exams: Exam[];
|
||||
stats: Stat[];
|
||||
userSolutions: UserSolution[];
|
||||
showSolutions: boolean;
|
||||
setStats: (stats: Stat[]) => void;
|
||||
selectedModules: Module[];
|
||||
setUserSolutions: (userSolutions: UserSolution[]) => void;
|
||||
setExams: (exams: Exam[]) => void;
|
||||
setShowSolutions: (showSolutions: boolean) => void;
|
||||
setSelectedModules: (modules: Module[]) => void;
|
||||
}
|
||||
|
||||
const useExamStore = create<ExamState>((set) => ({
|
||||
exams: [],
|
||||
stats: [],
|
||||
userSolutions: [],
|
||||
showSolutions: false,
|
||||
setStats: (stats: Stat[]) => set(() => ({stats})),
|
||||
selectedModules: [],
|
||||
setUserSolutions: (userSolutions: UserSolution[]) => set(() => ({userSolutions})),
|
||||
setExams: (exams: Exam[]) => set(() => ({exams})),
|
||||
setShowSolutions: (showSolutions: boolean) => set(() => ({showSolutions})),
|
||||
setSelectedModules: (modules: Module[]) => set(() => ({selectedModules: modules})),
|
||||
}));
|
||||
|
||||
export default useExamStore;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Stat} from "@/interfaces/user";
|
||||
import {capitalize, groupBy} from "lodash";
|
||||
import {convertCamelCaseToReadable} from "@/utils/string";
|
||||
import {UserSolution} from "@/interfaces/exam";
|
||||
|
||||
export const totalExams = (stats: Stat[]): number => {
|
||||
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 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,
|
||||
}));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user