Skip to content
Snippets Groups Projects
Forked from an inaccessible project.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
course.effects.ts 6.11 KiB
// Libraries
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import {
  tap,
  map,
  flatMap,
  withLatestFrom,
  filter,
  catchError,
} from 'rxjs/operators';
import { GlobalState } from '@app/core/state';
import { Store } from '@ngrx/store';
import { MatSnackBar } from '@angular/material';

// Services
import { DegreePlannerApiService } from '@app/degree-planner/services/api.service';
import * as selectors from '@app/degree-planner/store/selectors';

// Actions
import {
  AddCourse,
  AddCourseSuccess,
  AddSaveForLater,
  AddSaveForLaterSuccess,
  CourseActionTypes,
  CourseError,
  MoveCourseBetweenTerms,
  MoveCourseBetweenTermsSuccess,
  RemoveCourse,
  RemoveCourseSuccess,
  RemoveSaveForLater,
  RemoveSaveForLaterSuccess,
} from '@app/degree-planner/store/actions/course.actions';

// Models
import { DegreePlan } from '@app/core/models/degree-plan';
import { Course, CourseBase } from '@app/core/models/course';

// Pipes
import { GetTermDescriptionPipe } from '@app/shared/pipes/get-term-description.pipe';

@Injectable()
export class CourseEffects {
  constructor(
    private actions$: Actions,
    private api: DegreePlannerApiService,
    private store$: Store<GlobalState>,
    private snackBar: MatSnackBar,
  ) {}

  @Effect()
  MoveCourseBetweenTerms$ = this.actions$.pipe(
    ofType<MoveCourseBetweenTerms>(CourseActionTypes.MoveCourseBetweenTerms),

    withLatestFrom(this.store$.select(selectors.selectVisibleDegreePlan)),
    filter<[MoveCourseBetweenTerms, DegreePlan]>(([_, degreePlan]) => {
      return degreePlan !== undefined;
    }),

    // Get term data for the degree plan specified by the roadmap ID.
    flatMap(([action, degreePlan]) => {
      const roadmapId = degreePlan.roadmapId;
      const recordId = action.payload.id;
      const termCode = action.payload.to;
      return this.api
        .updateCourseTerm(roadmapId, recordId, termCode)
        .pipe(map(() => action));
    }),

    map(action => new MoveCourseBetweenTermsSuccess(action.payload)),

    tap(state => {
      const touchedTerm = new GetTermDescriptionPipe().transform(
        state.payload.to,
      );
      const message = `Course has been moved to ${touchedTerm}`;
      this.snackBar.open(message, undefined, {});
    }),

    catchError(error => {
      return of(
        new CourseError({
          message: 'Unable to move course',
          error,
        }),
      );
    }),
  );

  @Effect()
  AddCourse$ = this.actions$.pipe(
    ofType<AddCourse>(CourseActionTypes.AddCourse),

    withLatestFrom(this.store$.select(selectors.selectVisibleDegreePlan)),

    filter(([_, visibleDegreePlan]) => visibleDegreePlan !== undefined),

    withLatestFrom(this.store$.select(selectors.selectSubjects)),

    flatMap(([[action, visibleDegreePlan], subjects]) => {
      // TODO error handle the API calls
      const roadmapId = (visibleDegreePlan as DegreePlan).roadmapId;
      const { subjectCode, termCode, courseId, newIndex } = action.payload;

      const addCourse$ = this.api.addCourse(
        roadmapId,
        subjectCode,
        courseId,
        termCode,
      );

      const courseBaseToCourse$ = addCourse$.pipe(
        map<CourseBase, Course>(courseBase => ({
          ...courseBase,
          termCode,
          subject: subjects[courseBase.subjectCode],
        })),
      );
      const toSuccessAction$ = courseBaseToCourse$.pipe(
        map(course => new AddCourseSuccess({ course, newIndex })),
      );

      return toSuccessAction$;
    }),

    tap(state => {
      const touchedCourse: any = state.payload.course;
      const touchedTerm: any = new GetTermDescriptionPipe().transform(
        touchedCourse.termCode,
      );
      const message = `${touchedCourse.subject} ${
        touchedCourse.catalogNumber
      } has been added to ${touchedTerm}`;
      this.snackBar.open(message, undefined, {});
    }),

    catchError(error => {
      return of(
        new CourseError({
          message: 'Unable to add course',
          error,
        }),
      );
    }),
  );

  @Effect()
  RemoveCourse$ = this.actions$.pipe(
    ofType<RemoveCourse>(CourseActionTypes.RemoveCourse),

    withLatestFrom(this.store$.select(selectors.selectVisibleDegreePlan)),

    filter(([_, visibleDegreePlan]) => visibleDegreePlan !== undefined),

    flatMap(([action, visibleDegreePlan]) => {
      const roadmapId = (visibleDegreePlan as DegreePlan).roadmapId;
      const recordId = action.payload.recordId;
      const fromTermCode = action.payload.fromTermCode;

      const removeCourse$ = this.api.removeCourse(roadmapId, recordId);
      const toSuccessAction$ = removeCourse$.pipe(
        map(() => new RemoveCourseSuccess({ fromTermCode, recordId })),
      );

      return toSuccessAction$;
    }),

    catchError(error => {
      return of(
        new CourseError({
          message: 'Unable to remove course',
          error,
        }),
      );
    }),
  );

  @Effect()
  RemoveSavedForLater$ = this.actions$.pipe(
    ofType<RemoveSaveForLater>(CourseActionTypes.RemoveSaveForLater),

    flatMap(action => {
      const { subjectCode, courseId } = action.payload;
      return this.api
        .removeSavedForLater(subjectCode, courseId)
        .pipe(map(() => action));
    }),

    map(action => new RemoveSaveForLaterSuccess(action.payload)),

    catchError(error => {
      return of(
        new CourseError({
          message: 'Unable to remove saved course',
          error,
        }),
      );
    }),
  );

  @Effect()
  SaveForLater$ = this.actions$.pipe(
    ofType<AddSaveForLater>(CourseActionTypes.AddSaveForLater),

    flatMap(action => {
      const { subjectCode, courseId } = action.payload;
      return this.api
        .saveForLater(subjectCode, courseId)
        .pipe(map(() => action));
    }),

    map(action => new AddSaveForLaterSuccess(action.payload)),

    tap(() => {
      const message = 'Course has been saved for later';
      this.snackBar.open(message, undefined, {});
    }),

    catchError(error => {
      return of(
        new CourseError({
          message: 'Unable to save course for later',
          error,
        }),
      );
    }),
  );
}