import { DegreePlannerState, INITIAL_DEGREE_PLANNER_STATE, } from '@app/degree-planner/store/state'; import { PlanActionTypes, InitialPlanLoadResponse, ChangeVisiblePlanResponse, } from '@app/degree-planner/store/actions/plan.actions'; import { CourseActionTypes, RemoveCourseResponse, ChangeCourseTermResponse, AddCourseResponse, RemoveSavedForLaterResponse, AddSavedForLaterResponse, } from '@app/degree-planner/store/actions/course.actions'; import { NoteActionTypes, WriteNoteResponse, DeleteNoteResponse, } from '@app/degree-planner/store/actions/note.actions'; import { SavedForLaterCourse } from '@app/core/models/saved-for-later-course'; type SupportedActions = | InitialPlanLoadResponse | ChangeVisiblePlanResponse | WriteNoteResponse | DeleteNoteResponse | ChangeCourseTermResponse | RemoveCourseResponse | AddCourseResponse | RemoveSavedForLaterResponse | AddSavedForLaterResponse; export function degreePlannerReducer( state = INITIAL_DEGREE_PLANNER_STATE, action: SupportedActions, ): DegreePlannerState { switch (action.type) { /** * The `InitialPlanLoadResponse` action is triggered on initial Degree * Planner app load. It downloads a list of the user's degree plans and * picks the primary plan from that list to load as the first visible plan. */ case PlanActionTypes.InitialPlanLoadResponse: { return { ...action.payload }; } /** * The `ChangeVisiblePlanResponse` action is triggered whenever the UI needs * to switch which degree plan is being shown and load the data associated * with that degree plan. The reducer extracts that downloaded data from the * action payload and builds a new state using that data. */ case PlanActionTypes.ChangeVisiblePlanResponse: { return { ...state, ...action.payload }; } /** * The `WriteNoteResponse` action is dispatched by the `Note.write$` effect * upon a successful response from the `updateNote` or `createNote` API * endpoints. The reducer in this case either: * * - Replaces a note on a term that already had a note. * - *OR* adds a note to a term that didn't previously have a note. * - *OR* adds a new term with the given note if no term exists with the note's termCode. */ case NoteActionTypes.WriteNoteResponse: { const updatedNote = action.payload.updatedNote; const updatedTermCode = updatedNote.termCode; const originalTerms = state.visibleTerms; if (termCodeExists(updatedTermCode, originalTerms)) { /** * If a term with the given `termCode` *DOES exist* in the state, * replace just that term with the new data inside the action. */ const newVisibleTerms = originalTerms.map(term => { if (term.termCode === updatedTermCode) { return { ...term, note: updatedNote }; } else { return term; } }); return { ...state, visibleTerms: newVisibleTerms }; } else { /** * If a term with the given `termCode` *DOES NOT exist* * in the state, add it to the end of the term list. */ const newVisibleTerms = originalTerms.concat({ termCode: updatedTermCode, note: updatedNote, courses: [], }); return { ...state, visibleTerms: newVisibleTerms }; } } /** * The `DeleteNoteResponse` action is dispatched after the `deleteNote` API * has been called and it is okay to remote the note with the given * termCode from the degree planner state. */ case NoteActionTypes.DeleteNoteResponse: { const deletedTermCode = action.payload.termCode; const originalTerms = state.visibleTerms; if (termCodeExists(deletedTermCode, originalTerms)) { /** * If a term with the given `termCode` *DOES EXIST* in the state, * remove that term's note. */ const newVisibleTerms = originalTerms.map(term => { if (term.termCode === deletedTermCode) { return { ...term, note: undefined }; } else { return term; } }); return { ...state, visibleTerms: newVisibleTerms }; } else { return state; } } case CourseActionTypes.ChangeCourseTermResponse: { const { to, from, id } = action.payload; const t = state.visibleTerms.find(term => term.termCode === from); if (t) { const course = t.courses.find(c => c.id === id); if (course) { course.termCode = to; // Create new visibleTerms array const newVisibleTerms = state.visibleTerms.map(term => { if (term.termCode === from) { // Remove the course from the previous term term.courses = term.courses.filter(c => c.id !== id); } else if (term.termCode === to) { // Add the new course to this term term.courses = [...term.courses, course]; } return term; }); return { ...state, visibleTerms: newVisibleTerms }; } return state; } return state; } case CourseActionTypes.AddCourseResponse: { const { course } = action.payload; const newVisibleTerms = state.visibleTerms.map(term => { if (term.termCode === course.termCode) { term.courses.push(course); } return term; }); return { ...state, visibleTerms: newVisibleTerms }; // return {...state, visibleTerms: newVisibleTerms}; // return state; } case CourseActionTypes.RemoveCourseResponse: { const { recordId: id } = action.payload; // Create new visibleTerms array const newVisibleTerms = state.visibleTerms.map(term => { term.courses = term.courses.filter(course => { return course.id !== id; }); return term; }); return { ...state, visibleTerms: newVisibleTerms }; } case CourseActionTypes.RemoveSavedForLaterResponse: { const { courseId, subjectCode } = action.payload; // // Create new saved for later array const newSavedForLater = state.savedForLaterCourses.filter( course => course.subjectCode !== subjectCode && course.courseId !== courseId, ); return { ...state, savedForLaterCourses: newSavedForLater }; } case CourseActionTypes.AddSavedForLaterResponse: { const newSavedForLater: SavedForLaterCourse[] = [ ...state.savedForLaterCourses, { id: null, courseId: action.payload.courseId, termCode: '0000', topicId: 0, subjectCode: action.payload.subjectCode, title: action.payload.title, catalogNumber: action.payload.catalogNumber, courseOrder: 0, subject: state.subjects[action.payload.subjectCode], }, ]; return { ...state, savedForLaterCourses: newSavedForLater }; } /** * It's okay if the action didn't match any of the cases above. If that's * the case, just return the existing state object. */ default: return state; } } const termCodeExists = (termCode: string, things: { termCode: string }[]) => { return things.some(thing => thing.termCode === termCode); };