Skip to content
Snippets Groups Projects
course-item.component.ts 7.58 KiB
Newer Older
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
import { Component, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material';
import { Store, select } from '@ngrx/store';
Isaac Evavold's avatar
Isaac Evavold committed
import { Observable } from 'rxjs';
Isaac Evavold's avatar
Isaac Evavold committed
import * as utils from '@app/degree-planner/shared/utils';
import {
  AddCourse,
  AddSaveForLater,
  MoveCourseBetweenTerms,
Scott Berg's avatar
Scott Berg committed
  RemoveSaveForLater,
  RemoveCourse,
Isaac Evavold's avatar
Isaac Evavold committed
} from '@app/degree-planner/store/actions/course.actions';
import { GlobalState } from '@app/core/state';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
import { Course } from '@app/core/models/course';
Isaac Evavold's avatar
Isaac Evavold committed
import * as selectors from '@app/degree-planner/store/selectors';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
import { DegreePlannerApiService } from '@app/degree-planner/services/api.service';
Scott Berg's avatar
Scott Berg committed
import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
import { CourseDetailsDialogComponent } from '@app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component';
import { distinctUntilChanged, filter } from 'rxjs/operators';
Isaac Evavold's avatar
Isaac Evavold committed
import { TermCode } from '@app/core/models/termcode';
Isaac Evavold's avatar
Isaac Evavold committed
import { PlannedTerm, PlannedTermEra } from '@app/core/models/planned-term';

const isntUndefined = <T>(thing: T | undefined): thing is T => {
  return thing !== undefined;
};
@Component({
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  selector: 'cse-course-item',
  templateUrl: './course-item.component.html',
  styleUrls: ['./course-item.component.scss'],
export class CourseItemComponent implements OnInit {
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  @Input() course: Course;
  @Input() isCurrentTerm: boolean;
  @Input() isPastTerm: boolean;
  @Input() disabled: boolean;
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  @Input() type: 'saved' | 'course' | 'search';
Isaac Evavold's avatar
Isaac Evavold committed
  @Input() era?: unknown;
  visibleTerms: any;
Isaac Evavold's avatar
Isaac Evavold committed
  public visibleTermCodes$: Observable<string[]>;
  public droppableTermCodes$: Observable<string[]>;
  public term$: Observable<PlannedTerm>;
  public plannedCourses: Course[];
Isaac Evavold's avatar
Isaac Evavold committed
  public isStruckthrough = false;
  constructor(
    private api: DegreePlannerApiService,
    public dialog: MatDialog,
    private store: Store<GlobalState>,
  ) {}
  ngOnInit() {
    switch (true) {
      case this.course.studentEnrollmentStatus === 'Enrolled' &&
        this.isCurrentTerm:
        this.status = 'Enrolled';
        break;
      case this.course.studentEnrollmentStatus === 'Waitlisted':
        this.status = 'Waitlisted';
        break;
      case !this.course.grade && this.isPastTerm:
        this.status = 'Incomplete';
        break;
      default:
        this.status = '';
    }
Isaac Evavold's avatar
Isaac Evavold committed

    const isPastOrActive = this.era === 'past' || this.era === 'active';
    const isNotOffered = this.course.studentEnrollmentStatus === 'NOTOFFERED';
    this.isStruckthrough = isPastOrActive && isNotOffered;
  onMenuOpen() {
Isaac Evavold's avatar
Isaac Evavold committed
    this.droppableTermCodes$ = this.store.pipe(
      select(selectors.selectAllVisibleYears),
      utils.yearsToDroppableTermCodes(),
      distinctUntilChanged(utils.compareStringArrays),
    );
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  moveToSavedForLater(course) {
    this.store.dispatch(
      new AddSaveForLater({
        courseId: course.courseId,
        subjectCode: course.subjectCode,
        title: course.title,
        catalogNumber: course.catalogNumber,
Scott Berg's avatar
Scott Berg committed
  /**
   *
   *  Handle moving a course to different terms based on course type
   *
   */
  onMove(termCode: string) {
    const toTermCode = new TermCode(termCode);

    this.term$ = this.store.pipe(
      select(selectors.selectVisibleTerm, { termCode: toTermCode }),
      filter(isntUndefined),
      distinctUntilChanged(),
    );

    this.term$.subscribe(term => {
      this.plannedCourses = term.plannedCourses;
    });

    const isCourseInPlannedCourses = this.plannedCourses.some(
      course => course.courseId === this.course.courseId,
    );
    if (isCourseInPlannedCourses) {
      this.dialog
        .open(ConfirmDialogComponent, {
          data: {
            title: "Can't add course",
            confirmText: 'OK',
            dialogClass: 'alertDialog',
            text: `This course already exists in selected term!`,
          },
        })
        .afterClosed();
      return;
    }

Scott Berg's avatar
Scott Berg committed
    switch (this.type) {
Isaac Evavold's avatar
Isaac Evavold committed
      case 'course': {
        const id = this.course.id as number;
        const from = new TermCode(this.course.termCode);
Scott Berg's avatar
Scott Berg committed
        this.store.dispatch(
          new MoveCourseBetweenTerms({
            to: toTermCode,
            from,
            id,
            newIndex: 0,
          }),
Scott Berg's avatar
Scott Berg committed
        );
        break;
Scott Berg's avatar
Scott Berg committed

Isaac Evavold's avatar
Isaac Evavold committed
      case 'saved': {
Scott Berg's avatar
Scott Berg committed
        const { subjectCode, courseId } = this.course;
Isaac Evavold's avatar
Isaac Evavold committed
        this.addToTerm(toTermCode);
Scott Berg's avatar
Scott Berg committed
        this.store.dispatch(new RemoveSaveForLater({ subjectCode, courseId }));
        break;
Scott Berg's avatar
Scott Berg committed

Isaac Evavold's avatar
Isaac Evavold committed
      case 'search': {
        this.addToTerm(toTermCode);
Scott Berg's avatar
Scott Berg committed
        break;
Scott Berg's avatar
Scott Berg committed
    }
  }

  /**
   *
   *  Handle saving a course for later (This is not possible if a course is already saved)
   *
   */
  onSaveForLater() {
    const {
      courseId,
      subjectCode,
      title,
      catalogNumber,
      termCode,
    } = this.course;

    // Dispatch a save for later event
    this.store.dispatch(
      new AddSaveForLater({
        courseId: courseId,
        subjectCode: subjectCode,
        title: title,
        catalogNumber: catalogNumber,
Scott Berg's avatar
Scott Berg committed
      }),
    );

    // If course is in a term, we need to remove it
    if (this.type === 'course') {
      this.store.dispatch(
        new RemoveCourse({
Isaac Evavold's avatar
Isaac Evavold committed
          fromTermCode: new TermCode(termCode),
Scott Berg's avatar
Scott Berg committed
          recordId: this.course.id as number,
        }),
      );
    }
  }

  /**
   *
   *  Handle removing a course (This is not possible for type 'search')
   *
   */
  onRemove() {
    const dialogOptions = {
      title: 'Remove Course?',
      text: '',
      confirmText: 'Remove Course',
      confirmColor: 'accent',
    };

    switch (this.type) {
      case 'saved':
        dialogOptions.text = `This will remove "${
          this.course.title
        }" from your saved courses.`;
        break;

      default:
        dialogOptions.text = `This will remove "${
          this.course.title
        }" from your degree plan and your cart.`;
    }

    this.dialog
      .open(ConfirmDialogComponent, { data: dialogOptions })
      .afterClosed()
      .subscribe((result: { confirmed: boolean }) => {
        // If the user confirmed the removal, remove course
        if (result.confirmed) {
          console.log(this.type);

          console.log({
            type: this.type,
            fromTermCode: this.course.termCode,
            recordId: this.course.id,
          });

Scott Berg's avatar
Scott Berg committed
          switch (this.type) {
            case 'course':
              this.store.dispatch(
                new RemoveCourse({
Isaac Evavold's avatar
Isaac Evavold committed
                  fromTermCode: new TermCode(this.course.termCode),
Scott Berg's avatar
Scott Berg committed
                  recordId: this.course.id as number,
                }),
              );
              break;

            case 'saved':
              const { subjectCode, courseId } = this.course;
              this.store.dispatch(
                new RemoveSaveForLater({ subjectCode, courseId }),
              );
              break;
          }
        }
      });
  }

Isaac Evavold's avatar
Isaac Evavold committed
  addToTerm(toTermCode: TermCode) {
Isaac Evavold's avatar
Isaac Evavold committed
    this.store.dispatch(
      new AddCourse({
        courseId: this.course.courseId,
        termCode: toTermCode,
        subjectCode: this.course.subjectCode,
        title: this.course.title,
        catalogNumber: this.course.catalogNumber,
      }),
    );
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  openCourseDetailsDialog(course) {
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
    this.api
      .getCourseDetails(course.subjectCode, course.courseId)
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      .subscribe(courseDetails => {
        const dialogRef = this.dialog.open(CourseDetailsDialogComponent, {
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
          maxWidth: '800px',
Scott Berg's avatar
Scott Berg committed
          width: '80%',
          data: { courseDetails: courseDetails, courseType: this.type },
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
        });
      });
  }