Revamped the whole Solutions stuff with Zustand
This commit is contained in:
@@ -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}))}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user