178 lines
6.8 KiB
TypeScript
178 lines
6.8 KiB
TypeScript
import { Module } from "@/interfaces";
|
|
import { ExamState } from "../types";
|
|
import { SESSION_ACTIONS, SessionActions, sessionReducer } from "./session";
|
|
import { Exam, UserSolution } from "@/interfaces/exam";
|
|
import { updateExamWithUserSolutions } from "../utils";
|
|
import { defaultExamUserSolutions } from "@/utils/exams";
|
|
import { Assignment } from "@/interfaces/results";
|
|
import { Stat } from "@/interfaces/user";
|
|
import { convertToUserSolutions } from "@/utils/stats";
|
|
|
|
export type RootActions =
|
|
{ type: 'INIT_EXAM'; payload: { exams: Exam[], modules: Module[], assignment?: Assignment } } |
|
|
{ type: 'INIT_SOLUTIONS'; payload: { exams: Exam[], modules: Module[], stats: Stat[], timeSpent?: number, inactivity?: number } } |
|
|
{ type: 'UPDATE_TIMERS'; payload: { timeSpent: number; inactivity: number; timeSpentCurrentModule: number; } } |
|
|
{ type: 'FINALIZE_MODULE'; payload: { updateTimers: boolean } } |
|
|
{ type: 'FINALIZE_MODULE_SOLUTIONS' } |
|
|
{ type: 'UPDATE_EXAMS'}
|
|
|
|
|
|
export type Action = RootActions | SessionActions;
|
|
|
|
export const rootReducer = (
|
|
state: ExamState,
|
|
action: Action
|
|
): Partial<ExamState> => {
|
|
|
|
if (SESSION_ACTIONS.includes(action.type as any)) {
|
|
return sessionReducer(action as SessionActions);
|
|
}
|
|
|
|
switch (action.type) {
|
|
case 'INIT_EXAM': {
|
|
const { exams, modules, assignment } = action.payload;
|
|
|
|
let examAndSolutions = {}
|
|
|
|
// A new exam is about to start,
|
|
// fill the first module with defaultUserSolutions
|
|
let defaultSolutions = exams.map(defaultExamUserSolutions).flat();
|
|
examAndSolutions = {
|
|
userSolutions: defaultSolutions,
|
|
exam: updateExamWithUserSolutions(exams[0], defaultSolutions)
|
|
}
|
|
|
|
if (assignment) {
|
|
examAndSolutions = { ...examAndSolutions, assignment }
|
|
}
|
|
|
|
// now all the modules start at 0 since navigation
|
|
// is now handled at the module page's and no re-renders
|
|
// reset the initial render caused by the timers
|
|
// no need to do all that weird chainning with -1
|
|
// on some modules and triggering next() to update final solution
|
|
// with hasExamEnded flag
|
|
return {
|
|
moduleIndex: 0,
|
|
partIndex: 0,
|
|
exerciseIndex: 0,
|
|
questionIndex: 0,
|
|
exams: exams,
|
|
selectedModules: modules,
|
|
showSolutions: false,
|
|
...examAndSolutions
|
|
}
|
|
};
|
|
case 'INIT_SOLUTIONS': {
|
|
const { exams, modules, stats, timeSpent, inactivity } = action.payload;
|
|
|
|
let time = {}
|
|
if (timeSpent) time = { timeSpent }
|
|
if (inactivity) time = { ...time, inactivity }
|
|
|
|
return {
|
|
moduleIndex: -1,
|
|
partIndex: 0,
|
|
exerciseIndex: 0,
|
|
questionIndex: 0,
|
|
exams: exams,
|
|
selectedModules: modules,
|
|
showSolutions: true,
|
|
userSolutions: convertToUserSolutions(stats),
|
|
...time
|
|
}
|
|
}
|
|
case 'UPDATE_TIMERS': {
|
|
// Just assigning the timers at once instead of two different calls
|
|
const { timeSpent, inactivity, timeSpentCurrentModule } = action.payload;
|
|
return {
|
|
timeSpentCurrentModule,
|
|
timeSpent,
|
|
inactivity
|
|
}
|
|
};
|
|
case 'FINALIZE_MODULE': {
|
|
const { updateTimers } = action.payload;
|
|
const solutions = state.userSolutions;
|
|
const evaluated = state.evaluated;
|
|
|
|
const hasUnevaluatedSolutions = solutions.some(solution =>
|
|
(solution.type === 'speaking' ||
|
|
solution.type === 'writing' ||
|
|
solution.type === 'interactiveSpeaking') &&
|
|
!evaluated.some(evaluation => evaluation.exercise === solution.exercise)
|
|
);
|
|
|
|
// To finalize a module first flag the timers to be updated
|
|
if (updateTimers) {
|
|
return {
|
|
flags: { ...state.flags, finalizeModule: true, pendingEvaluation: hasUnevaluatedSolutions }
|
|
}
|
|
} else {
|
|
// then check whether there are more modules in the exam, if there are
|
|
// setup the next module
|
|
if (state.moduleIndex === state.selectedModules.length - 1) {
|
|
return {
|
|
showSolutions: true,
|
|
flags: {
|
|
...state.flags,
|
|
finalizeModule: false,
|
|
finalizeExam: true,
|
|
pendingEvaluation: hasUnevaluatedSolutions,
|
|
}
|
|
}
|
|
} else if (state.moduleIndex < state.selectedModules.length - 1) {
|
|
return {
|
|
moduleIndex: state.moduleIndex + 1,
|
|
partIndex: 0,
|
|
exerciseIndex: 0,
|
|
questionIndex: 0,
|
|
exam: updateExamWithUserSolutions(state.exams[state.moduleIndex + 1], state.userSolutions),
|
|
flags: {
|
|
...state.flags,
|
|
finalizeModule: false,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case 'FINALIZE_MODULE_SOLUTIONS': {
|
|
if (state.flags.reviewAll) {
|
|
const notLastModule = state.moduleIndex < state.selectedModules.length - 1;
|
|
const moduleIndex = notLastModule ? state.moduleIndex + 1 : -1;
|
|
|
|
if (notLastModule) {
|
|
return {
|
|
questionIndex: 0,
|
|
exerciseIndex: 0,
|
|
partIndex: 0,
|
|
exam: state.exams[moduleIndex],
|
|
moduleIndex: moduleIndex
|
|
}
|
|
} else {
|
|
return {
|
|
questionIndex: 0,
|
|
exerciseIndex: 0,
|
|
partIndex: 0,
|
|
moduleIndex: -1
|
|
}
|
|
}
|
|
} else {
|
|
return {
|
|
moduleIndex: -1
|
|
}
|
|
}
|
|
|
|
}
|
|
case 'UPDATE_EXAMS': {
|
|
const exams = state.exams.map((e) => updateExamWithUserSolutions(e, state.userSolutions));
|
|
const exam = updateExamWithUserSolutions(state.exam!, state.userSolutions);
|
|
return {
|
|
exams,
|
|
exam
|
|
}
|
|
}
|
|
default:
|
|
return {};
|
|
}
|
|
}; |