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.
new-degree-audit-dialog.component.ts 9.03 KiB
import { Component, OnInit } from '@angular/core';
import { DarsApiService } from '../services/api.service';
import { DegreePrograms } from '../models/degree-program';
import { StudentDegreeProgram } from '../models/student-degree-program';
import { Observable, combineLatest, Subject } from 'rxjs';
import {
  FormBuilder,
  FormGroup,
  Validators,
  FormControl,
  FormArray,
} from '@angular/forms';
import { HonorsOption } from '../models/honors-option';
import { map, share, filter, flatMap, shareReplay } from 'rxjs/operators';
import { CourseBase } from '@app/core/models/course';
import { Store } from '@ngrx/store';
import { GlobalState } from '@app/core/state';
import { degreePlans } from '../store/selectors';
import { DegreePlan } from '@app/core/models/degree-plan';
import { MatDialogRef } from '@angular/material';

const inclusiveRange = (from: number, to: number) => {
  const range: number[] = [];
  for (let i = from; i <= to; i++) {
    range.push(i);
  }
  return range;
};

export interface NewDegreeAuditFields {
  darsInstitutionCode: string;
  darsDegreeProgramCode: string;
  degreePlannerPlanName: string;
  darsHonorsOptionCode: string;
  whichEnrolledCoursesIncluded: string;
  fixedCredits: {
    termCode: string;
    subjectCode: string;
    courseId: string;
    credits: number;
  }[];
}

@Component({
  selector: 'cse-new-degree-audit-dialog',
  templateUrl: './new-degree-audit-dialog.component.html',
  styleUrls: ['./new-degree-audit-dialog.component.scss'],
})
export class NewDegreeAuditDialogComponent implements OnInit {
  // Form-builder objects
  public chosenProgram: FormControl;
  public chosenAuditSettings: FormGroup;
  public chosenCreditSettings: FormArray;

  // API observables
  public degreePrograms$: Observable<StudentDegreeProgram[]>;
  public institutions$: Observable<DegreePrograms>;
  public degreePlans$: Observable<DegreePlan[]>;
  public chosenRoadmapId$ = new Subject<number>();
  public honorsOptions$: Observable<HonorsOption[]>;
  public variableCreditCourses$: Observable<
    (CourseBase & { range: number[] })[]
  >;

  constructor(
    private fb: FormBuilder,
    private api: DarsApiService,
    private store: Store<GlobalState>,
    private dialogRef: MatDialogRef<NewDegreeAuditDialogComponent>,
  ) {
    this.chosenProgram = fb.control('', Validators.required);

    this.chosenAuditSettings = fb.group({
      honorsOptions: fb.control(' ', Validators.required),
      includeCoursesFrom: fb.control('future', Validators.required),
      degreePlan: fb.control('', Validators.required),
    });

    this.chosenCreditSettings = fb.array([]);
  }

  public ngOnInit() {
    this.degreePrograms$ = this.api.getStudentDegreePrograms().pipe(share());
    this.degreePrograms$.subscribe(programs => {
      if (programs.length > 0) {
        this.chosenProgram.setValue(programs[0]);
      }
    });

    this.institutions$ = this.api.getStaticData().pipe(share());

    this.honorsOptions$ = combineLatest([
      this.institutions$,
      this.chosenProgram.valueChanges.pipe(
        filter(value => value.hasOwnProperty('darsInstitutionCode')),
        map(value => value.darsInstitutionCode as string),
      ),
    ]).pipe(
      map(([institutions, darsInstitutionCode]) => {
        return institutions.hasOwnProperty(darsInstitutionCode)
          ? institutions[darsInstitutionCode].honorsOptions
          : [];
      }),
    );

    this.degreePlans$ = this.store.select(degreePlans).pipe(shareReplay());

    this.degreePlans$.subscribe(plans => {
      const primaryPlan = plans.find(p => p.primary);
      this.chosenAuditSettings.controls['degreePlan'].setValue(primaryPlan);
    });

    const degreePlanFormControl = this.chosenAuditSettings.get('degreePlan');
    const includeCoursesFrom = this.chosenAuditSettings.get(
      'includeCoursesFrom',
    );

    if (degreePlanFormControl) {
      degreePlanFormControl.valueChanges.subscribe(currentPlan => {
        this.chosenRoadmapId$.next(currentPlan.roadmapId);
        this.variableCreditCourses$ = this.store.select(degreePlans).pipe(
          flatMap(plan => this.api.getAllCourses(currentPlan.roadmapId)),
          map(courses => {
            return courses.filter(course => {
              return (
                !!course.creditMin &&
                !!course.creditMax &&
                course.creditMax > course.creditMin
              );
            });
          }),
          map(courses =>
            courses.map(course => ({
              ...course,
              range: inclusiveRange(
                course.creditMin as number,
                course.creditMax as number,
              ),
            })),
          ),
          share(),
        );
        this.variableCreditCourses$.subscribe(courses => {
          while (this.chosenCreditSettings.length !== 0) {
            this.chosenCreditSettings.removeAt(0);
          }

          courses.forEach(course => {
            this.chosenCreditSettings.push(
              this.fb.group({
                course,
                credits: this.fb.control(
                  course.credits || '',
                  Validators.required,
                ),
              }),
            );
          });
        });
      });
      if (includeCoursesFrom) {
        includeCoursesFrom.valueChanges.subscribe(selected => {
          if (selected === 'planned') {
            this.variableCreditCourses$ = this.store.select(degreePlans).pipe(
              map(plans => plans.find(plan => plan.primary)),
              filter((plan): plan is DegreePlan => plan !== undefined),
              flatMap(plan => this.api.getAllCourses(plan.roadmapId)),
              map(courses => {
                return courses.filter(course => {
                  return (
                    !!course.creditMin &&
                    !!course.creditMax &&
                    course.creditMax > course.creditMin
                  );
                });
              }),
              map(courses =>
                courses.map(course => ({
                  ...course,
                  range: inclusiveRange(
                    course.creditMin as number,
                    course.creditMax as number,
                  ),
                })),
              ),
              share(),
            );

            this.variableCreditCourses$.subscribe(courses => {
              while (this.chosenCreditSettings.length !== 0) {
                this.chosenCreditSettings.removeAt(0);
              }

              courses.forEach(course => {
                this.chosenCreditSettings.push(
                  this.fb.group({
                    course,
                    credits: this.fb.control(
                      course.credits || '',
                      Validators.required,
                    ),
                  }),
                );
              });
            });
          }
        });
      }
    }
  }

  public darsInstitutionCode<T>(fallback: T): string | T {
    if (typeof this.chosenProgram.value === 'object') {
      return this.chosenProgram.value.darsInstitutionCode;
    } else {
      return fallback;
    }
  }

  public darsDegreeProgramCode<T>(fallback: T): string | T {
    if (typeof this.chosenProgram.value === 'object') {
      return this.chosenProgram.value.darsDegreeProgramCode;
    } else {
      return fallback;
    }
  }

  public degreePlannerPlanName<T>(fallback: T): string | T {
    const control = this.chosenAuditSettings.get('degreePlan');
    const coursesControl = this.chosenAuditSettings.get('includeCoursesFrom');

    if (coursesControl && coursesControl.value === 'planned') {
      if (control && control.value !== null) {
        return control.value.name;
      } else {
        return fallback;
      }
    } else {
      return fallback;
    }
  }

  public darsHonorsOptionCode<T>(fallback: T): string | T {
    const control = this.chosenAuditSettings.get('honorsOptions');
    if (control !== null) {
      return control.value.toString();
    } else {
      return fallback;
    }
  }

  // If a degree plan name is included in the request we do not need this property
  public includeCoursesFrom<T>(fallback: T): string | T {
    const control = this.chosenAuditSettings.get('includeCoursesFrom');
    if (control && control.value !== 'planned') {
      return control.value.toString();
    } else {
      return fallback;
    }
  }

  public fixedCredits(): NewDegreeAuditFields['fixedCredits'] {
    return (this.chosenCreditSettings.value as any[]).map(
      (group: { course: CourseBase; credits: number }) => {
        return {
          termCode: group.course.termCode || '',
          subjectCode: group.course.subjectCode || '',
          courseId: group.course.courseId || '',
          credits: group.credits,
        };
      },
    );
  }

  public submitAudit() {
    this.dialogRef.close({
      darsInstitutionCode: this.darsInstitutionCode(''),
      darsDegreeProgramCode: this.darsDegreeProgramCode(''),
      degreePlannerPlanName: this.degreePlannerPlanName(''),
      darsHonorsOptionCode: this.darsHonorsOptionCode(''),
      whichEnrolledCoursesIncluded: this.includeCoursesFrom(''),
      fixedCredits: this.fixedCredits(),
    });
  }
}