Forked from an inaccessible project.
-
Isaac Evavold authoredIsaac Evavold authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
course-item.component.ts 9.79 KiB
import { Component, Input, OnInit } from '@angular/core';
import { MatDialog, MatSnackBar } from '@angular/material';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
import * as utils from '@app/degree-planner/shared/utils';
import {
AddCourse,
AddSaveForLater,
MoveCourseBetweenTerms,
RemoveSaveForLater,
RemoveCourse,
} from '@app/degree-planner/store/actions/course.actions';
import { GlobalState } from '@app/core/state';
import { Course } from '@app/core/models/course';
import * as selectors from '@app/degree-planner/store/selectors';
import { DegreePlannerApiService } from '@app/degree-planner/services/api.service';
import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component';
import { CourseDetailsDialogComponent } from '@app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component';
import { distinctUntilChanged, filter } from 'rxjs/operators';
import { TermCode } from '@app/core/models/termcode';
import { PlannedTerm } from '@app/core/models/planned-term';
import { ConstantsService } from '@app/degree-planner/services/constants.service';
const isntUndefined = <T>(thing: T | undefined): thing is T => {
return thing !== undefined;
};
@Component({
selector: 'cse-course-item',
templateUrl: './course-item.component.html',
styleUrls: ['./course-item.component.scss'],
})
export class CourseItemComponent implements OnInit {
@Input() course: Course;
@Input() isCurrentTerm: boolean;
@Input() isPastTerm: boolean;
@Input() disabled: boolean;
@Input() type: 'saved' | 'course' | 'search';
@Input() era?: unknown;
visibleTerms: any;
activeTerm: any;
public status:
| 'InProgress'
| 'Waitlisted'
| 'Incomplete'
| 'NotOfferedInTerm'
| 'NoLongerOffered'
| 'Normal';
public visibleTermCodes$: Observable<string[]>;
public droppableTermCodes$: Observable<string[]>;
public term$: Observable<PlannedTerm>;
public plannedCourses: ReadonlyArray<Course>;
public toActiveTerm: boolean;
public isStruckthrough = false;
constructor(
private api: DegreePlannerApiService,
public dialog: MatDialog,
private store: Store<GlobalState>,
private constants: ConstantsService,
private snackBar: MatSnackBar,
) {}
ngOnInit() {
const isActive = this.era === 'active';
const isPast = this.era === 'past';
const isNotOffered = this.course.studentEnrollmentStatus === 'NOTOFFERED';
const isNoLongerOffered =
this.course.studentEnrollmentStatus === 'DOESNOTEXIST';
this.isStruckthrough = (isPast || isActive) && isNoLongerOffered;
const isIncomplete =
isPast &&
this.course.grade === null &&
this.course.studentEnrollmentStatus !== 'cart';
const isWaitlisted = this.course.studentEnrollmentStatus === 'Waitlisted';
const isInProgress =
isActive && this.course.studentEnrollmentStatus === 'Enrolled';
if (isIncomplete) {
this.status = 'Incomplete';
} else if (isWaitlisted) {
this.status = 'Waitlisted';
} else if (isInProgress) {
this.status = 'InProgress';
} else if (isNotOffered) {
this.status = 'NotOfferedInTerm';
} else if (isNoLongerOffered) {
this.status = 'NoLongerOffered';
} else {
this.status = 'Normal';
}
}
onMenuOpen() {
this.droppableTermCodes$ = this.store.pipe(
select(selectors.selectAllVisibleYears),
utils.yearsToDroppableTermCodes(),
distinctUntilChanged(utils.compareStringArrays),
);
}
moveToSavedForLater(course) {
this.store.dispatch(
new AddSaveForLater({
courseId: course.courseId,
subjectCode: course.subjectCode,
title: course.title,
catalogNumber: course.catalogNumber,
newIndex: 0,
}),
);
}
/**
*
* 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;
this.toActiveTerm = term.era === 'active';
});
const isCourseInPlannedCourses = this.plannedCourses.some(
course => course.courseId === this.course.courseId,
);
const courseNotOfferedInTerm = this.plannedCourses.some(
course =>
course.studentEnrollmentStatus === 'DOESNOTEXIST' ||
course.studentEnrollmentStatus === 'NOTOFFERED',
);
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;
}
if (this.toActiveTerm && courseNotOfferedInTerm) {
const { short } = this.constants.subjectDescription(
this.course.subjectCode,
);
const catalogNum = this.course.catalogNumber;
const termDesc = toTermCode.description;
this.dialog
.open(ConfirmDialogComponent, {
data: {
title: `Can't add course to term`,
confirmText: 'OK',
dialogClass: 'alertDialog',
text: `${short} ${catalogNum} is not offered in ${termDesc}`,
},
})
.afterClosed();
return;
}
switch (this.type) {
case 'course': {
const id = this.course.id as number;
const { subjectCode, courseId } = this.course;
const from = new TermCode(this.course.termCode);
this.store.dispatch(
new MoveCourseBetweenTerms({
to: toTermCode,
from,
id,
newIndex: 0,
courseId,
subjectCode,
}),
);
break;
}
case 'saved': {
const { subjectCode, courseId } = this.course;
this.addToTerm(toTermCode);
this.store.dispatch(new RemoveSaveForLater({ subjectCode, courseId }));
break;
}
case 'search': {
this.addToTerm(toTermCode);
break;
}
}
}
/**
*
* 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,
newIndex: 0,
}),
);
// If course is in a term, we need to remove it
if (this.type === 'course') {
this.store.dispatch(
new RemoveCourse({
fromTermCode: new TermCode(termCode),
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:
if (this.era === 'future') {
dialogOptions.text = `This will remove "${
this.course.title
}" from your degree plan.`;
} else {
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 && result.confirmed) {
console.log(this.type);
console.log({
type: this.type,
fromTermCode: this.course.termCode,
recordId: this.course.id,
});
switch (this.type) {
case 'course':
this.store.dispatch(
new RemoveCourse({
fromTermCode: new TermCode(this.course.termCode),
recordId: this.course.id as number,
}),
);
break;
case 'saved':
const { subjectCode, courseId } = this.course;
this.store.dispatch(
new RemoveSaveForLater({ subjectCode, courseId }),
);
break;
}
}
});
}
addToTerm(toTermCode: TermCode) {
this.store.dispatch(
new AddCourse({
courseId: this.course.courseId,
termCode: toTermCode,
subjectCode: this.course.subjectCode,
title: this.course.title,
catalogNumber: this.course.catalogNumber,
}),
);
}
openCourseDetailsDialog() {
const { subjectCode, courseId, catalogNumber } = this.course;
if (this.course.studentEnrollmentStatus === 'DOESNOTEXIST') {
const { short } = this.constants.subjectDescription(subjectCode);
this.snackBar.open(`'${short} ${catalogNumber}' no longer offered`);
return;
}
this.api
.getCourseDetails(subjectCode, courseId)
.subscribe(courseDetails => {
const dialogRef = this.dialog.open(CourseDetailsDialogComponent, {
maxWidth: '800px',
width: '80%',
data: { courseDetails: courseDetails, courseType: this.type },
});
});
}
// Check for enter key presses
detectEnter($event) {
if ($event.keyCode === 13) {
this.openCourseDetailsDialog();
}
}
}