Skip to content
Snippets Groups Projects
reducer.ts 18.7 KiB
Newer Older
Isaac Evavold's avatar
Isaac Evavold committed
import { pickTermEra } from '@app/degree-planner/shared/utils';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
import { UIActionTypes } from './actions/ui.actions';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  DegreePlannerState,
  INITIAL_DEGREE_PLANNER_STATE,
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
} from '@app/degree-planner/store/state';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  PlanActionTypes,
Isaac Evavold's avatar
Isaac Evavold committed
  InitialLoadSuccess,
Isaac Evavold's avatar
Isaac Evavold committed
  SwitchPlanSuccess,
Scott Berg's avatar
Scott Berg committed
  MakePlanPrimary,
  MakePlanPrimarySuccess,
  MakePlanPrimaryFailure,
Scott Berg's avatar
Scott Berg committed
  ChangePlanNameSuccess,
  ChangePlanNameFailure,
  CreatePlan,
Isaac Evavold's avatar
Isaac Evavold committed
  CreatePlanSuccess,
Isaac Evavold's avatar
Isaac Evavold committed
  DeletePlanSuccess,
} from '@app/degree-planner/store/actions/plan.actions';
import {
  MoveCourseInsideTerm,
  CourseActionTypes,
Isaac Evavold's avatar
Isaac Evavold committed
  RemoveCourse,
Isaac Evavold's avatar
Isaac Evavold committed
  MoveCourseBetweenTerms,
Isaac Evavold's avatar
Isaac Evavold committed
  AddCourse,
Isaac Evavold's avatar
Isaac Evavold committed
  AddCourseSuccess,
Isaac Evavold's avatar
Isaac Evavold committed
  RemoveSaveForLater,
  AddSaveForLater,
} from '@app/degree-planner/store/actions/course.actions';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  NoteActionTypes,
Isaac Evavold's avatar
Isaac Evavold committed
  WriteNote,
Isaac Evavold's avatar
Isaac Evavold committed
  WriteNoteSuccess,
Isaac Evavold's avatar
Isaac Evavold committed
  DeleteNote,
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
} from '@app/degree-planner/store/actions/note.actions';
pnogal's avatar
pnogal committed
import {
  AddAcademicYearActionTypes,
  AddAcademicYearRequest,
Scott Berg's avatar
Scott Berg committed
} from '@app/degree-planner/store/actions/addAcademicYear.actions';
Scott Berg's avatar
Scott Berg committed
import {
Isaac Evavold's avatar
Isaac Evavold committed
  ExpandAcademicYear,
  CollapseAcademicYear,
Scott Berg's avatar
Scott Berg committed
  OpenCourseSearch,
  CloseCourseSearch,
  ToggleCourseSearch,
Scott Berg's avatar
Scott Berg committed
} from '@app/degree-planner/store/actions/ui.actions';
import { SavedForLaterCourse } from '@app/core/models/saved-for-later-course';
Scott Berg's avatar
Scott Berg committed
import { DegreePlan } from '@app/core/models/degree-plan';
Isaac Evavold's avatar
Isaac Evavold committed
import { Year, YearMapping } from '@app/core/models/year';
import { Course } from '@app/core/models/course';
import { Note } from '@app/core/models/note';
Isaac Evavold's avatar
Isaac Evavold committed
import {
  PlannedTerm,
  PlannedTermEra,
  PlannedTermNote,
} from '@app/core/models/planned-term';
Isaac Evavold's avatar
Isaac Evavold committed
import * as utils from '@app/degree-planner/shared/utils';
Isaac Evavold's avatar
Isaac Evavold committed
import { TermCode, YearCode } from '@app/core/models/termcode';
Isaac Evavold's avatar
Isaac Evavold committed
  | InitialLoadSuccess
Isaac Evavold's avatar
Isaac Evavold committed
  | SwitchPlanSuccess
Isaac Evavold's avatar
Isaac Evavold committed
  | WriteNote
Isaac Evavold's avatar
Isaac Evavold committed
  | WriteNoteSuccess
Isaac Evavold's avatar
Isaac Evavold committed
  | DeleteNote
  | MoveCourseInsideTerm
Isaac Evavold's avatar
Isaac Evavold committed
  | MoveCourseBetweenTerms
Isaac Evavold's avatar
Isaac Evavold committed
  | RemoveCourse
Isaac Evavold's avatar
Isaac Evavold committed
  | AddCourse
Isaac Evavold's avatar
Isaac Evavold committed
  | AddCourseSuccess
Isaac Evavold's avatar
Isaac Evavold committed
  | RemoveSaveForLater
  | AddSaveForLater
pnogal's avatar
pnogal committed
  | AddAcademicYearRequest
  | CreatePlan
Isaac Evavold's avatar
Isaac Evavold committed
  | CreatePlanSuccess
Scott Berg's avatar
Scott Berg committed
  | MakePlanPrimary
  | MakePlanPrimarySuccess
Scott Berg's avatar
Scott Berg committed
  | MakePlanPrimaryFailure
  | ChangePlanNameSuccess
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  | ChangePlanNameFailure
Isaac Evavold's avatar
Isaac Evavold committed
  | DeletePlanSuccess
Isaac Evavold's avatar
Isaac Evavold committed
  | ExpandAcademicYear
  | CollapseAcademicYear
Scott Berg's avatar
Scott Berg committed
  | OpenCourseSearch
  | CloseCourseSearch
  | ToggleCourseSearch
  | UpdateSearchTermCode;

export function degreePlannerReducer(
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  state = INITIAL_DEGREE_PLANNER_STATE,
  action: SupportedActions,
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  switch (action.type) {
    case PlanActionTypes.PlanError: {
      return { ...state, isLoadingPlan: false };
    }

    case PlanActionTypes.CreatePlan: {
      return { ...state, isLoadingPlan: true };
    }
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
    /**
     * 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.
     */
Isaac Evavold's avatar
Isaac Evavold committed
    case PlanActionTypes.InitialLoadSuccess: {
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      return { ...action.payload };
    }

    case PlanActionTypes.SwitchPlan: {
      return { ...state, isLoadingPlan: true };
    }

jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
    /**
Isaac Evavold's avatar
Isaac Evavold committed
     * The `SwitchPlanSuccess` action is triggered whenever the UI needs
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
     * 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.
     */
Isaac Evavold's avatar
Isaac Evavold committed
    case PlanActionTypes.SwitchPlanSuccess: {
      return { ...state, ...action.payload, isLoadingPlan: false };
pnogal's avatar
pnogal committed
    /**
     * The `AddAcademicYearRequest` action is triggered after `addAcademicYear()`
     * function runs. A new academic year container with three terms will be created.
Scott Berg's avatar
Scott Berg committed
     */
pnogal's avatar
pnogal committed
    case AddAcademicYearActionTypes.AddAcademicYearRequest: {
Isaac Evavold's avatar
Isaac Evavold committed
      const currentYearCodes = Object.keys(state.visibleYears);
      const largestYearCode = Math.max(
        ...currentYearCodes.map(yearCode => {
          return parseInt(yearCode, 10);
        }),
      );
Isaac Evavold's avatar
Isaac Evavold committed
      const nextYearCode = new YearCode(`${largestYearCode + 1}`);
Isaac Evavold's avatar
Isaac Evavold committed
      const nextYear = emptyYear(nextYearCode, state.activeTermCodes);
      const visibleYears: YearMapping = {
        ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
        [nextYearCode.toString()]: nextYear,
Isaac Evavold's avatar
Isaac Evavold committed
      };
      return { ...state, visibleYears };
    }
Isaac Evavold's avatar
Isaac Evavold committed
    case UIActionTypes.ExpandAcademicYear: {
      const yearCode = action.payload.yearCode;
      return {
        ...state,
        visibleYears: {
          ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
          [yearCode.toString()]: {
            ...state.visibleYears[yearCode.toString()],
            isExpanded: true,
          },
Isaac Evavold's avatar
Isaac Evavold committed
        },
      };
Isaac Evavold's avatar
Isaac Evavold committed
    case UIActionTypes.CollapseAcademicYear: {
      const yearCode = action.payload.yearCode;
      return {
        ...state,
        visibleYears: {
          ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
          [yearCode.toString()]: {
            ...state.visibleYears[yearCode.toString()],
            isExpanded: false,
          },
Isaac Evavold's avatar
Isaac Evavold committed
        },
      };
Scott Berg's avatar
Scott Berg committed
    /**
     * The `ToggleCourseSearch` action toggles the open and close state of the course search side nav
     */
    case UIActionTypes.ToggleCourseSearch: {
      const newSearchState = {
        ...state.search,
        visible: !state.search.visible,
      };

      // If a term was passed into the action
      if (action.termCode) {
        newSearchState.selectedTerm = action.termCode;
      }

      return {
        ...state,
        search: newSearchState,
      };
Scott Berg's avatar
Scott Berg committed
    }

    /**
     * The `ToggleCourseSearch` action opens the course search side nav
     */
    case UIActionTypes.OpenCourseSearch: {
      const newSearchState = {
        ...state.search,
        visible: true,
      };

      // If a term was passed into the action
      if (action.termCode) {
        newSearchState.selectedTerm = action.termCode;
      }

      return { ...state, search: newSearchState };
Scott Berg's avatar
Scott Berg committed
    }

    /**
     * The `ToggleCourseSearch` action closes the course search side nav
     */
    case UIActionTypes.CloseCourseSearch: {
Isaac Evavold's avatar
Isaac Evavold committed
        search: { ...state.search, visible: false },
      };
    }

    /**
     * The `UpdateSearchTermCode` action changes the active seach term code.
     */
    case UIActionTypes.UpdateSearchTermCode: {
      return {
        ...state,
        search: { ...state.search, selectedTerm: action.termCode },
      };
Scott Berg's avatar
Scott Berg committed
    }

Isaac Evavold's avatar
Isaac Evavold committed
    case NoteActionTypes.WriteNote: {
      const { termCode, noteText } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      const { yearCode, termName } = termCode;
      const year = state.visibleYears[yearCode.toString()];
Isaac Evavold's avatar
Isaac Evavold committed
      const existingNote = year ? year[termName].note : undefined;

      const newNote: PlannedTermNote =
        existingNote && existingNote.isLoaded
          ? { isLoaded: true, text: noteText, id: existingNote.id }
          : { isLoaded: false, text: noteText };

      const visibleYears: YearMapping = {
        ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
        [yearCode.toString()]: createYearWithNote(
Isaac Evavold's avatar
Isaac Evavold committed
          termCode,
          newNote,
          state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
          state.visibleYears[yearCode.toString()],
Isaac Evavold's avatar
Isaac Evavold committed
        ),
      };

      return { ...state, visibleYears };
    }

jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
    /**
     * 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.
     */
Isaac Evavold's avatar
Isaac Evavold committed
    case NoteActionTypes.WriteNoteSuccess: {
Isaac Evavold's avatar
Isaac Evavold committed
      const { updatedNote } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      const termCode = new TermCode(updatedNote.termCode);
      const { yearCode } = termCode;
Isaac Evavold's avatar
Isaac Evavold committed

      const visibleYears: YearMapping = {
        ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
        [yearCode.toString()]: createYearWithNote(
Isaac Evavold's avatar
Isaac Evavold committed
          termCode,
Isaac Evavold's avatar
Isaac Evavold committed
          { isLoaded: true, text: updatedNote.note, id: updatedNote.id },
Isaac Evavold's avatar
Isaac Evavold committed
          state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
          state.visibleYears[yearCode.toString()],
Isaac Evavold's avatar
Isaac Evavold committed
        ),
      };

      return { ...state, visibleYears };
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
    }

    /**
     * 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.
     */
Isaac Evavold's avatar
Isaac Evavold committed
    case NoteActionTypes.DeleteNote: {
Isaac Evavold's avatar
Isaac Evavold committed
      const { termCode } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      const { yearCode } = termCode;
Isaac Evavold's avatar
Isaac Evavold committed

      const visibleYears: YearMapping = {
        ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
        [yearCode.toString()]: createYearWithoutNote(
Isaac Evavold's avatar
Isaac Evavold committed
          termCode,
          state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
          state.visibleYears[yearCode.toString()],
Isaac Evavold's avatar
Isaac Evavold committed
        ),
      };
Isaac Evavold's avatar
Isaac Evavold committed
      return { ...state, visibleYears };
    case CourseActionTypes.MoveCourseInsideTerm: {
      const { termCode, recordId, newIndex } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      const { yearCode, termName } = termCode;
      const year = state.visibleYears[yearCode.toString()];
        const courses = year[termName].plannedCourses;
        const course = courses.find(course => course.id === recordId);
        const oldIndex = courses.findIndex(course => course.id === recordId);
        if (course) {
          const newCourses = courses.slice();
          newCourses.splice(oldIndex, 1);
          newCourses.splice(newIndex, 0, course);

          const visibleYears = {
            ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
            [yearCode.toString()]: {
              ...state.visibleYears[yearCode.toString()],
Isaac Evavold's avatar
Isaac Evavold committed
                ...state.visibleYears[yearCode.toString()][termName],
              },
            },
          };

          return { ...state, visibleYears };
        }
      }

      return state;
    }

Isaac Evavold's avatar
Isaac Evavold committed
    case CourseActionTypes.MoveCourseBetweenTerms: {
      const {
        to: toTermCode,
        from: fromTermCode,
        id,
        newIndex,
      } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      const { yearCode: fromYearCode } = fromTermCode;
      const { yearCode: toYearCode } = toTermCode;
Isaac Evavold's avatar
Isaac Evavold committed
      const course = findCourse(state.visibleYears, fromTermCode, id);

      if (course && course.id !== null) {
        const fromYear = createYearWithoutCourse(
          fromTermCode,
          course.id,
          state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
          state.visibleYears[fromYearCode.toString()],
Isaac Evavold's avatar
Isaac Evavold committed
        );

        const toYear = createYearWithCourse(
          toTermCode,
Isaac Evavold's avatar
Isaac Evavold committed
          { ...course, termCode: toTermCode.toString() },
Isaac Evavold's avatar
Isaac Evavold committed
          state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
          fromYearCode.equals(toYearCode)
Isaac Evavold's avatar
Isaac Evavold committed
            ? fromYear
Isaac Evavold's avatar
Isaac Evavold committed
            : state.visibleYears[toYearCode.toString()],
Scott Berg's avatar
Scott Berg committed
          newIndex,
Isaac Evavold's avatar
Isaac Evavold committed
        );

        const visibleYears = {
          ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
          [fromYearCode.toString()]: fromYear,
          [toYearCode.toString()]: toYear,
Isaac Evavold's avatar
Isaac Evavold committed
        };

        return { ...state, visibleYears };
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      }
Isaac Evavold's avatar
Isaac Evavold committed

jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      return state;
    }

Isaac Evavold's avatar
Isaac Evavold committed
    case CourseActionTypes.AddCourse: {
      const { termCode, subjectCode, newIndex } = action.payload;

      const course = {
        ...action.payload,
Isaac Evavold's avatar
Isaac Evavold committed
        id: null,
        termCode: action.payload.termCode.toString(),
Isaac Evavold's avatar
Isaac Evavold committed
        subject: state.subjects[subjectCode],
      } as Course;

      const year: Year = createYearWithCourse(
        termCode,
        course,
        state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
        state.visibleYears[termCode.yearCode.toString()],
Isaac Evavold's avatar
Isaac Evavold committed
        newIndex,
      );

      const visibleYears: YearMapping = {
        ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
        [termCode.yearCode.toString()]: year,
Isaac Evavold's avatar
Isaac Evavold committed
      };

      return { ...state, visibleYears };
    }

Isaac Evavold's avatar
Isaac Evavold committed
    case CourseActionTypes.AddCourseSuccess: {
      const { course, newIndex } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      const termCode = new TermCode(course.termCode);
      const { yearCode } = termCode;
Isaac Evavold's avatar
Isaac Evavold committed

      const year: Year = createYearWithCourse(
        termCode,
        course,
        state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
        state.visibleYears[yearCode.toString()],
Scott Berg's avatar
Scott Berg committed
        newIndex,
Isaac Evavold's avatar
Isaac Evavold committed
      );
Isaac Evavold's avatar
Isaac Evavold committed
      const visibleYears = {
        ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
        [yearCode.toString()]: year,
Isaac Evavold's avatar
Isaac Evavold committed
      };
Isaac Evavold's avatar
Isaac Evavold committed
      return { ...state, visibleYears };
Isaac Evavold's avatar
Isaac Evavold committed
    case CourseActionTypes.RemoveCourse: {
Isaac Evavold's avatar
Isaac Evavold committed
      const { recordId, fromTermCode } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      const { yearCode } = fromTermCode;
Isaac Evavold's avatar
Isaac Evavold committed

      const year: Year = createYearWithoutCourse(
        fromTermCode,
        recordId,
        state.activeTermCodes,
Isaac Evavold's avatar
Isaac Evavold committed
        state.visibleYears[yearCode.toString()],
Isaac Evavold's avatar
Isaac Evavold committed
      );

      const visibleYears = {
        ...state.visibleYears,
Isaac Evavold's avatar
Isaac Evavold committed
        [yearCode.toString()]: year,
Isaac Evavold's avatar
Isaac Evavold committed
      };
Isaac Evavold's avatar
Isaac Evavold committed
      return { ...state, visibleYears };
Isaac Evavold's avatar
Isaac Evavold committed
    case CourseActionTypes.RemoveSaveForLater: {
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      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 };
    }

Isaac Evavold's avatar
Isaac Evavold committed
    case CourseActionTypes.AddSaveForLater: {
      const { newIndex } = action.payload;
      const newSavedCourse: SavedForLaterCourse = {
        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],
      };
      const savedForLaterCoursesArr = state.savedForLaterCourses.slice();

      savedForLaterCoursesArr.splice(newIndex, 0, newSavedCourse);
      return { ...state, savedForLaterCourses: savedForLaterCoursesArr };
Isaac Evavold's avatar
Isaac Evavold committed
    case PlanActionTypes.CreatePlanSuccess: {
      const { newPlan, newYears } = action.payload;
Isaac Evavold's avatar
Isaac Evavold committed
      return {
        ...state,
        visibleDegreePlan: newPlan,
        visibleYears: newYears,
Isaac Evavold's avatar
Isaac Evavold committed
        allDegreePlans: state.allDegreePlans.concat(newPlan),
        isLoadingPlan: false,
Scott Berg's avatar
Scott Berg committed
    case PlanActionTypes.MakePlanPrimary: {
      // TODO add global loading state
      return state;
    }

    case PlanActionTypes.MakePlanPrimarySuccess: {
      // Get current roadmap ID
      const roadmapId = (state.visibleDegreePlan as DegreePlan).roadmapId;

      // Update the visible term object
      const newVisibleDegreePlan = {
        ...(state.visibleDegreePlan as DegreePlan),
        primary: true,
      };

      // Update all plans to only have the current one be primary
      const newPlans = state.allDegreePlans.map(plan => {
        if (plan.roadmapId !== roadmapId) {
          return { ...plan, primary: false };
        }
        return { ...plan, primary: true };
      });

      // Return the new state
      return {
        ...state,
        visibleDegreePlan: newVisibleDegreePlan,
        allDegreePlans: newPlans,
      };
    }

    case PlanActionTypes.MakePlanPrimaryFailure: {
      // TODO add error message
      return state;
    }

Isaac Evavold's avatar
Isaac Evavold committed
    case PlanActionTypes.ChangePlanNameSuccess: {
      const visibleDegreePlan = {
        ...(state.visibleDegreePlan as DegreePlan),
        name: action.payload.newName,
      };
Scott Berg's avatar
Scott Berg committed

Isaac Evavold's avatar
Isaac Evavold committed
      return {
        ...state,
        allDegreePlans: state.allDegreePlans.map(plan => {
          if (plan.roadmapId === action.payload.roadmapId) {
            return { ...plan, name: action.payload.newName };
          } else {
            return plan;
          }
        }),
        visibleDegreePlan,
      };
    }

    case PlanActionTypes.ChangePlanNameFailure: {
      const visibleDegreePlan = {
Scott Berg's avatar
Scott Berg committed
        ...(state.visibleDegreePlan as DegreePlan),
Isaac Evavold's avatar
Isaac Evavold committed
        name: action.payload.oldName,
Scott Berg's avatar
Scott Berg committed
      };

Isaac Evavold's avatar
Isaac Evavold committed
      return {
        ...state,
        allDegreePlans: state.allDegreePlans.map(plan => {
          if (plan.roadmapId === action.payload.roadmapId) {
            return { ...plan, name: action.payload.oldName };
          } else {
            return plan;
          }
        }),
        visibleDegreePlan,
      };
Scott Berg's avatar
Scott Berg committed
    }

Isaac Evavold's avatar
Isaac Evavold committed
    case PlanActionTypes.DeletePlanSuccess: {
      const allDegreePlans = state.allDegreePlans.filter(plan => {
        return plan.roadmapId !== action.payload.roadmapId;
      });
      return { ...state, allDegreePlans };
    }

jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
    /**
     * 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;
  }
Isaac Evavold's avatar
Isaac Evavold committed
const emptyTerm = (termCode: TermCode, era: PlannedTermEra): PlannedTerm => {
  return { termCode, era, plannedCourses: [], enrolledCourses: [] };
Isaac Evavold's avatar
Isaac Evavold committed
};

Isaac Evavold's avatar
Isaac Evavold committed
const emptyYear = (yearCode: YearCode, activeTermCodes: TermCode[]): Year => {
Isaac Evavold's avatar
Isaac Evavold committed
  return {
    yearCode,
    isExpanded: utils.pickYearEra(yearCode, activeTermCodes) !== 'past',
Isaac Evavold's avatar
Isaac Evavold committed
    fall: emptyTerm(yearCode.fall, pickTermEra(yearCode.fall, activeTermCodes)),
    spring: emptyTerm(
      yearCode.spring,
      pickTermEra(yearCode.spring, activeTermCodes),
    ),
    summer: emptyTerm(
      yearCode.summer,
      pickTermEra(yearCode.summer, activeTermCodes),
    ),
Isaac Evavold's avatar
Isaac Evavold committed
  };
};

const generateYearForTermCode = (
Isaac Evavold's avatar
Isaac Evavold committed
  termCode: TermCode,
  activeTermCodes: TermCode[],
Isaac Evavold's avatar
Isaac Evavold committed
): Year => {
Isaac Evavold's avatar
Isaac Evavold committed
  return emptyYear(termCode.yearCode, activeTermCodes);
Isaac Evavold's avatar
Isaac Evavold committed
};

const createYearWithNote = (
Isaac Evavold's avatar
Isaac Evavold committed
  termCode: TermCode,
Isaac Evavold's avatar
Isaac Evavold committed
  note: PlannedTermNote | undefined,
Isaac Evavold's avatar
Isaac Evavold committed
  activeTermCodes: TermCode[],
Isaac Evavold's avatar
Isaac Evavold committed
  year = generateYearForTermCode(termCode, activeTermCodes),
): Year => {
Isaac Evavold's avatar
Isaac Evavold committed
  const term = year[termCode.termName];
  return { ...year, [termCode.termName]: { ...term, note } };
Isaac Evavold's avatar
Isaac Evavold committed
};

const createYearWithoutNote = (
Isaac Evavold's avatar
Isaac Evavold committed
  termCode: TermCode,
  activeTermCodes: TermCode[],
Isaac Evavold's avatar
Isaac Evavold committed
  year?: Year,
) => {
  return createYearWithNote(termCode, undefined, activeTermCodes, year);
};

Isaac Evavold's avatar
Isaac Evavold committed
const findCourse = (
  years: YearMapping,
  termCode: TermCode,
  recordId: number,
) => {
  const year = years[termCode.yearCode.toString()];
Isaac Evavold's avatar
Isaac Evavold committed
  if (year) {
Isaac Evavold's avatar
Isaac Evavold committed
    const term = year[termCode.termName];
    return term.plannedCourses.find(course => course.id === recordId);
Isaac Evavold's avatar
Isaac Evavold committed
  }
};

const createYearWithCourse = (
Isaac Evavold's avatar
Isaac Evavold committed
  termCode: TermCode,
Isaac Evavold's avatar
Isaac Evavold committed
  course: Course,
Isaac Evavold's avatar
Isaac Evavold committed
  activeTermCodes: TermCode[],
Isaac Evavold's avatar
Isaac Evavold committed
  year = generateYearForTermCode(termCode, activeTermCodes),
Scott Berg's avatar
Scott Berg committed
  newIndex?: number,
Isaac Evavold's avatar
Isaac Evavold committed
): Year => {
Isaac Evavold's avatar
Isaac Evavold committed
  const term = year[termCode.termName];
  const courses = term.plannedCourses.filter(
    c => c.courseId !== course.courseId,
  );
Isaac Evavold's avatar
Isaac Evavold committed
  newIndex = newIndex !== undefined ? newIndex : courses.length;
  courses.splice(newIndex, 0, course);
Isaac Evavold's avatar
Isaac Evavold committed
  return { ...year, [termCode.termName]: { ...term, plannedCourses: courses } };
Isaac Evavold's avatar
Isaac Evavold committed
};

const createYearWithoutCourse = (
Isaac Evavold's avatar
Isaac Evavold committed
  termCode: TermCode,
Isaac Evavold's avatar
Isaac Evavold committed
  recordId: number,
Isaac Evavold's avatar
Isaac Evavold committed
  activeTermCodes: TermCode[],
Isaac Evavold's avatar
Isaac Evavold committed
  year = generateYearForTermCode(termCode, activeTermCodes),
): Year => {
Isaac Evavold's avatar
Isaac Evavold committed
  const term = year[termCode.termName];
  const courses = term.plannedCourses.filter(course => course.id !== recordId);
  return {
    ...year,
Isaac Evavold's avatar
Isaac Evavold committed
    [termCode.termName]: { ...term, plannedCourses: courses },
Isaac Evavold's avatar
Isaac Evavold committed
};