Newer
Older
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { MatDialog, ErrorStateMatcher } from '@angular/material';
import { Observable, Subscription, pipe } from 'rxjs';
import { filter, map, distinctUntilChanged } from 'rxjs/operators';

Scott Berg
committed
import { Store, select } from '@ngrx/store';
import { DegreePlannerState } from '@app/degree-planner/store/state';
import {
ToggleCourseSearch,
OpenCourseSearch,
} from '@app/degree-planner/store/actions/ui.actions';
import * as actions from '@app/degree-planner/store/actions/course.actions';
import * as selectors from '@app/degree-planner/store/selectors';
import { PlannedTerm, PlannedTermNote } from '@app/core/models/planned-term';
import { Note } from '@app/core/models/note';
import { Course } from '@app/core/models/course';
} from '@app/degree-planner/dialogs/notes-dialog/notes-dialog.component';
import * as utils from '@app/degree-planner/shared/utils';
import { findSafariExecutable } from 'selenium-webdriver/safari';
import { TermCode } from '@app/core/models/termcode';
// Dialogs
import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component';
import { maybeQueueResolutionOfComponentResources } from '@angular/core/src/metadata/resource_loading';
import { WeekDay } from '@angular/common';
import { MediaMatcher } from '@angular/cdk/layout';
const isntUndefined = <T>(thing: T | undefined): thing is T => {
return thing !== undefined;
};
pnogal
committed
@Component({
selector: 'cse-term-container',
templateUrl: './term-container.component.html',
styleUrls: ['./term-container.component.scss'],
pnogal
committed
})
export class TermContainerComponent implements OnInit, OnDestroy {
public termSubscription: Subscription;
public activeTermHasNotOffered: boolean;
// List of courses pulled for the Observable
public plannedCourses: ReadonlyArray<Course>;
public enrolledCourses: ReadonlyArray<Course>;
public era: 'past' | 'active' | 'future';
public hasItemDraggedOver: boolean;
public plannedCredits: string;
public enrolledCredits: number;
public visibleCredits: 'enrolled' | 'planned';
public courseNotOfferedInTerm: ReadonlyArray<Course>;
public mobileView: MediaQueryList;
constructor(
public dialog: MatDialog,
private store: Store<{ degreePlanner: DegreePlannerState }>,
mediaMatcher: MediaMatcher,
) {
this.mobileView = mediaMatcher.matchMedia('(max-width: 900px)');
}
this.hasItemDraggedOver = false;
this.term$ = this.store.pipe(
select(selectors.selectVisibleTerm, { termCode: this.termCode }),
filter(isntUndefined),
distinctUntilChanged(),
this.termSubscription = this.term$.subscribe(term => {
// const {plannedCourses, enrolledCourses} = term;
this.plannedCourses = term.plannedCourses;
this.plannedCredits = this.sumPlannedCredits(term.plannedCourses);
this.enrolledCourses = term.enrolledCourses;
this.enrolledCredits = this.sumEnrolledCredits(term.enrolledCourses);
this.era = term.era;
const activeTermEnrollmentStatus = this.plannedCourses.forEach(
course => course.studentEnrollmentStatus,
);
this.visibleCredits = 'planned';
if (term.era === 'past' && this.plannedCourses.length === 0) {
this.visibleCredits = 'enrolled';
}
this.note$ = this.term$.pipe(
map(term => term.note),
distinctUntilChanged(),
);
this.dropZoneIds$ = this.store.pipe(
select(selectors.selectAllVisibleYears),
utils.yearsToDropZoneIds(),
distinctUntilChanged(utils.compareStringArrays),
);
ngOnDestroy() {
this.termSubscription.unsubscribe();
openNotesDialog(note?: PlannedTermNote) {
if (note === undefined || note.isLoaded) {
const termCode = this.termCode;
const data: NotesDialogData = note
? {
termCode,
hasExistingNote: true,
initialText: note.text,
noteId: note.id,
}
: { termCode, hasExistingNote: false };
this.dialog.open(NotesDialogComponent, { data });
}
openCourseSearch() {
this.store.dispatch(new OpenCourseSearch(this.termCode));
changeVisibleCredits(event) {
this.visibleCredits = 'enrolled';
break;
default:
this.visibleCredits = 'planned';
}
}
const newContainer = event.container.id;
const previousContainer = event.previousContainer.id;
const { courseId } = event.item.data as Course;
const isCourseInPlannedCourses = this.plannedCourses.some(
course => course.courseId === courseId,
);
if (newContainer !== previousContainer && isCourseInPlannedCourses) {
this.dialog
.open(ConfirmDialogComponent, {
data: {
title: "Can't add course to term",
confirmText: 'OK',
dialogClass: 'alertDialog',

Paulina Nogal
committed
text: `This course already exists in selected term`,
},
})
.afterClosed();
return;
}
const newIndex = event.currentIndex;
const { id: recordId, termCode } = event.item.data as Course;
if (recordId !== null) {
const action = new actions.MoveCourseInsideTerm({
recordId,
newIndex,
});
this.store.dispatch(action);
}
} else if (previousContainer.indexOf('term-') === 0) {
// If moving from term to term
// Get the pervious and new term code, and the record ID
const from = event.previousContainer.data;
const to = event.container.data;
// FIXME: if `event.item.data` is a Course, the `id` property could be null
const newIndex = event.currentIndex;
const { classNumber } = event.item.data as Course;
if (classNumber !== null) {
// If moving course with packages to future term
this.dialog
.open(ConfirmDialogComponent, {
data: {
title: 'Are you sure?',
confirmText: 'Move course',
text: `Moving this course to a future term will remove your selected section`,
},
})
.afterClosed()
.subscribe((result: { confirmed: true } | undefined) => {
if (result !== undefined && result.confirmed === true) {
this.store.dispatch(
new actions.MoveCourseBetweenTerms({
to,
from,
id,
newIndex,
courseId,
subjectCode,
}),
);
}
});
} else {
// Dispatch a new change request
this.store.dispatch(
new actions.MoveCourseBetweenTerms({
to,
from,
id,
newIndex,
courseId,
subjectCode,
}),
} else if (previousContainer === 'saved-courses') {
// If moving from saved courses to term
// Get the term code from the new term dropzone's ID
const newIndex = event.currentIndex;
const { subjectCode, courseId, title, catalogNumber } = event.item.data;
this.store.dispatch(
new actions.AddCourse({
courseId,
termCode,
}),
);
this.store.dispatch(
new actions.RemoveSaveForLater({ subjectCode, courseId }),
);
} else if (
previousContainer === 'queried-courses-list' &&
newContainer.indexOf('term-') === 0
) {
const newIndex = event.currentIndex;
new actions.AddCourse({
courseId: event.item.data.courseId,
termCode,
subjectCode: event.item.data.subjectCode,
title: event.item.data.title,
catalogNumber: event.item.data.catalogNumber,
newIndex,
}),
dragEnter(event, item) {
this.hasItemDraggedOver = true;
}
dragExit(event, item) {
this.hasItemDraggedOver = false;
}
sumEnrolledCredits(courses: ReadonlyArray<Course>): number {
return courses.reduce((sum, course) => sum + course.credits, 0);
}
sumPlannedCredits(courses: ReadonlyArray<Course>): string {
const credits = { min: 0, max: 0 };
courses.forEach(course => {
if (course.creditMin !== undefined && course.creditMax !== undefined) {
credits.min = credits.min + course.creditMin;
credits.max = credits.max + course.creditMax;
}
});
return credits.min === credits.max
? credits.min.toString()
: `${credits.min}-${credits.max}`;
}