diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts index 15040ef1..1b3d783c 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -45,6 +45,7 @@ export interface Stat { module: Module; solutions: any[]; type: string; + timeSpent?: number; score: { correct: number; total: number; diff --git a/src/pages/exam.tsx b/src/pages/exam.tsx index 3a860a16..9b73d366 100644 --- a/src/pages/exam.tsx +++ b/src/pages/exam.tsx @@ -74,6 +74,7 @@ export default function Page() { const [isEvaluationLoading, setIsEvaluationLoading] = useState(false); const [showAbandonPopup, setShowAbandonPopup] = useState(false); const [avoidRepeated, setAvoidRepeated] = useState(false); + const [timeSpent, setTimeSpent] = useState(0); const [exams, setExams] = useExamStore((state) => [state.exams, state.setExams]); const [userSolutions, setUserSolutions] = useExamStore((state) => [state.userSolutions, state.setUserSolutions]); @@ -85,6 +86,20 @@ export default function Page() { useEffect(() => setSessionId(uuidv4()), []); + useEffect(() => { + selectedModules.length > 0 && timeSpent === 0 && !showSolutions; + if (selectedModules.length > 0 && timeSpent === 0 && !showSolutions) { + const timerInterval = setInterval(() => { + setTimeSpent((prev) => prev + 1); + }, 1000); + + return () => { + clearInterval(timerInterval); + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedModules.length]); + useEffect(() => { (async () => { if (selectedModules.length > 0 && exams.length > 0 && moduleIndex < selectedModules.length) { @@ -116,6 +131,7 @@ export default function Page() { if (selectedModules.length > 0 && exams.length !== 0 && moduleIndex >= selectedModules.length && !hasBeenUploaded && !showSolutions) { const newStats: Stat[] = userSolutions.map((solution) => ({ ...solution, + timeSpent, session: sessionId, exam: solution.exam!, module: solution.module!, diff --git a/src/pages/exercises.tsx b/src/pages/exercises.tsx index e71ea07c..61004268 100644 --- a/src/pages/exercises.tsx +++ b/src/pages/exercises.tsx @@ -77,6 +77,7 @@ export default function Page() { const [isEvaluationLoading, setIsEvaluationLoading] = useState(false); const [showAbandonPopup, setShowAbandonPopup] = useState(false); const [avoidRepeated, setAvoidRepeated] = useState(false); + const [timeSpent, setTimeSpent] = useState(0); const [exams, setExams] = useExamStore((state) => [state.exams, state.setExams]); const [userSolutions, setUserSolutions] = useExamStore((state) => [state.userSolutions, state.setUserSolutions]); @@ -89,6 +90,19 @@ export default function Page() { useEffect(() => console.log({examId: exam?.id, exam}), [exam]); useEffect(() => setSessionId(uuidv4()), []); + useEffect(() => { + if (selectedModules.length > 0 && timeSpent === 0 && !showSolutions) { + const timerInterval = setInterval(() => { + setTimeSpent((prev) => prev + 1); + }, 1000); + + return () => { + clearInterval(timerInterval); + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedModules.length]); + useEffect(() => { (async () => { if (selectedModules.length > 0 && exams.length > 0 && moduleIndex < selectedModules.length) { @@ -120,6 +134,7 @@ export default function Page() { if (selectedModules.length > 0 && exams.length !== 0 && moduleIndex >= selectedModules.length && !hasBeenUploaded && !showSolutions) { const newStats: Stat[] = userSolutions.map((solution) => ({ ...solution, + timeSpent, session: sessionId, exam: solution.exam!, module: solution.module!, diff --git a/src/pages/record.tsx b/src/pages/record.tsx index 4f4c5868..3af61a2c 100644 --- a/src/pages/record.tsx +++ b/src/pages/record.tsx @@ -153,6 +153,8 @@ export default function History({user}: {user: User}) { level: calculateBandScore(x.correct, x.total, x.module, user.focus), })); + const timeSpent = dateStats[0].timeSpent; + const selectExam = () => { const examPromises = uniqBy(dateStats, "exam").map((stat) => getExamById(stat.module, stat.exam)); @@ -184,7 +186,14 @@ export default function History({user}: {user: User}) { onClick={selectExam} role="button">
- {formatTimestamp(timestamp)} +
+ {formatTimestamp(timestamp)} + {timeSpent && ( + <> + • {Math.floor(timeSpent / 60)} minutes + + )} +
= 0.7 && "text-mti-purple", diff --git a/src/pages/stats.tsx b/src/pages/stats.tsx index 8b7fb90d..d8a35c06 100644 --- a/src/pages/stats.tsx +++ b/src/pages/stats.tsx @@ -20,7 +20,6 @@ import useUsers from "@/hooks/useUsers"; import Select from "react-select"; import useGroups from "@/hooks/useGroups"; import DatePicker from "react-datepicker"; -import moment from "moment"; import {shouldRedirectHome} from "@/utils/navigation.disabled"; ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, LineController, Legend, Tooltip); @@ -104,6 +103,18 @@ export default function Stats() { return sessionAverage; }; + const calculateAverageTimePerModule = () => { + const groupedBySession = groupBySession(stats.filter((x) => !!x.timeSpent)); + const sessionAverage = Object.keys(groupedBySession).map((x: string) => { + const session = groupedBySession[x]; + const timeSpent = session[0].timeSpent!; + + return Math.floor(timeSpent / session.length / 60); + }); + + return sessionAverage; + }; + const calculateModularScorePerSession = (module: Module) => { const groupedBySession = groupBySession(stats); const sessionAverage = Object.keys(groupedBySession).map((x: string) => { @@ -185,44 +196,43 @@ export default function Stats() {
- {stats.length > 0 && ( -
-
- <> - {(user.type === "developer" || user.type === "owner") && ( - groups.flatMap((y) => y.participants).includes(x.id)) - .map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))} - defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}} - onChange={(value) => setStatsUserId(value?.value)} - styles={{ - option: (styles, state) => ({ - ...styles, - backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", - color: state.isFocused ? "black" : styles.color, - }), - }} - /> - )} - - {/* +
+ <> + {(user.type === "developer" || user.type === "owner") && ( + groups.flatMap((y) => y.participants).includes(x.id)) + .map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))} + defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}} + onChange={(value) => setStatsUserId(value?.value)} + styles={{ + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + /> + )} + + {/* */} -
+
+ + {stats.length > 0 && (
{/* Exams per module */}
@@ -326,9 +338,32 @@ export default function Stats() { }} />
+ + {/* Average Time per Module */} +
+ Average Time per Module (in Minutes) + !!s.timeSpent))).map((_, index) => index), + datasets: [ + { + type: "line", + label: "Average (in minutes)", + fill: false, + borderColor: "#6A5FB1", + backgroundColor: "#7872BF", + borderWidth: 2, + spanGaps: true, + data: calculateAverageTimePerModule(), + }, + ], + }} + /> +
-
- )} + )} + {stats.length === 0 && (
No stats to display...