diff --git a/src/app/core/models/planned-term.ts b/src/app/core/models/planned-term.ts index bc5bad39a3890113d9da8ea0234a7bc0038b73cd..f5b732ce90466412cd5122f1ce0250567d5a02ab 100644 --- a/src/app/core/models/planned-term.ts +++ b/src/app/core/models/planned-term.ts @@ -1,7 +1,5 @@ import { Course } from '@app/core/models/course'; -import { TermCode } from '@app/core/models/termcode'; - -export type PlannedTermEra = 'past' | 'active' | 'future'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; export type PlannedTermNote = | { isLoaded: true; text: string; id: number } @@ -10,7 +8,6 @@ export type PlannedTermNote = export interface PlannedTerm { roadmapId: number; termCode: TermCode; - era: PlannedTermEra; note?: PlannedTermNote; plannedCourses: ReadonlyArray<Course>; enrolledCourses: ReadonlyArray<Course>; diff --git a/src/app/core/models/year.ts b/src/app/core/models/year.ts index c8dcef8dcc2d366381735ee73721a7516ed72bb5..8b7271292a4b55ad94fcc5aea793ed2dff090687 100644 --- a/src/app/core/models/year.ts +++ b/src/app/core/models/year.ts @@ -1,5 +1,5 @@ import { PlannedTerm } from '@app/core/models/planned-term'; -import { YearCode } from '@app/core/models/termcode'; +import { YearCode } from '@app/degree-planner/shared/term-codes/yearcode'; export interface Year { yearCode: YearCode; diff --git a/src/app/degree-planner/course-search/course-search.component.html b/src/app/degree-planner/course-search/course-search.component.html index 54c88a335cc131ee3ba4c3f310a5f4063b7d4cb0..293bc81d42f3e61fd18430858630468fe2dc14ff 100644 --- a/src/app/degree-planner/course-search/course-search.component.html +++ b/src/app/degree-planner/course-search/course-search.component.html @@ -10,7 +10,7 @@ <mat-select placeholder="Term" aria-label="Term" [disableOptionCentering]="true" formControlName="term"> <mat-option value="0000">All courses</mat-option> <mat-option - *ngFor="let termCode of constants.activeTermCodes()" + *ngFor="let termCode of termCodeFactory.active" [value]="termCode.toString()" >{{ termCode | getTermDescription }}</mat-option> </mat-select> diff --git a/src/app/degree-planner/course-search/course-search.component.ts b/src/app/degree-planner/course-search/course-search.component.ts index f5bff94e85b7b57f6f6e1537748aca7beede5cf1..7151bca0a7aedf4f60b3aba9c9128c758ae8655b 100644 --- a/src/app/degree-planner/course-search/course-search.component.ts +++ b/src/app/degree-planner/course-search/course-search.component.ts @@ -21,9 +21,10 @@ import { SubjectCodesTo, SubjectDescription, } from '@app/core/models/course'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; import { MediaMatcher } from '@angular/cdk/layout'; import { ConstantsService } from '../services/constants.service'; +import { TermCodeFactory } from '../services/termcode.factory'; @Component({ selector: 'cse-course-search', @@ -41,7 +42,7 @@ export class CourseSearchComponent implements OnInit, OnDestroy { // Observable used for drag and drop and for populating term select public dropZoneIds$: Observable<string[]>; - public activeTerms$: Observable<ReadonlyArray<TermCode>>; + public activeTerms: ReadonlyArray<TermCode>; public activeSelectedSearchTerm$: Observable<TermCode | undefined>; public isCourseSearchOpen$: Observable<boolean>; @@ -61,6 +62,7 @@ export class CourseSearchComponent implements OnInit, OnDestroy { private fb: FormBuilder, private api: DegreePlannerApiService, private snackBar: MatSnackBar, + public termCodeFactory: TermCodeFactory, mediaMatcher: MediaMatcher, public constants: ConstantsService, ) { @@ -74,6 +76,8 @@ export class CourseSearchComponent implements OnInit, OnDestroy { this.hasResults = false; this.isLoading = false; + this.activeTerms = this.termCodeFactory.active; + // Get active term drop zones this.dropZoneIds$ = this.store.pipe( select(selectors.selectAllVisibleYears), diff --git a/src/app/degree-planner/degree-planner.component.ts b/src/app/degree-planner/degree-planner.component.ts index 2bfea522b49cf96da4ce7216bb29840676848ccb..cdf7fdd6dd267d990cb71c05bd2a3f4882b6fb23 100644 --- a/src/app/degree-planner/degree-planner.component.ts +++ b/src/app/degree-planner/degree-planner.component.ts @@ -35,10 +35,11 @@ import { ExpandAcademicYear, CollapseAcademicYear, } from './store/actions/ui.actions'; -import { YearCode } from '@app/core/models/termcode'; +import { YearCode } from '@app/degree-planner/shared/term-codes/yearcode'; import { ConstantsService } from './services/constants.service'; import { AddAcademicYearRequest } from './store/actions/addAcademicYear.actions'; import { UserPreferences } from '@app/core/models/user-preferences'; +import { TermCodeFactory } from './services/termcode.factory'; @Component({ selector: 'cse-degree-planner', @@ -62,9 +63,11 @@ export class DegreePlannerComponent implements OnInit { constructor( private store: Store<GlobalState>, private constants: ConstantsService, + private termCodeFactory: TermCodeFactory, public mediaMatcher: MediaMatcher, public dialog: MatDialog, private snackBar: MatSnackBar, + private termCodeService: TermCodeFactory, ) { this.mobileView = mediaMatcher.matchMedia('(max-width: 959px)'); this.version = constants.getVersion(); @@ -105,7 +108,7 @@ export class DegreePlannerComponent implements OnInit { select(selectors.selectAllVisibleYears), map(years => Object.keys(years)), distinctUntilChanged(utils.compareStringArrays), - map(yearCodes => yearCodes.map(YearCode.fromString)), + map(ycs => ycs.map(yc => this.termCodeFactory.fromRawYearCode(yc))), ); } @@ -180,7 +183,7 @@ export class DegreePlannerComponent implements OnInit { const text = `This will change your primary plan and replace the current ` + `courses in your cart with the courses in this plan's ` + - `${this.constants.firstActiveTermCode().description} term.`; + `${this.termCodeService.first().description} term.`; this.dialog .open(ConfirmDialogComponent, { diff --git a/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.ts b/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.ts index f39441acc5dfd6f8f7810f897304bc36b61ad0a4..f8070e1b68e7279c6359c3b36f39eb3f2f34d7d3 100644 --- a/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.ts +++ b/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Inject } from '@angular/core'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; import { MAT_DIALOG_DATA } from '@angular/material'; @Component({ diff --git a/src/app/degree-planner/dialogs/notes-dialog/notes-dialog.component.ts b/src/app/degree-planner/dialogs/notes-dialog/notes-dialog.component.ts index c045f30225c82e478bf98f12b6837a175ff28c15..f11adefca7e7df9be821a091b5f24fc981eda6de 100644 --- a/src/app/degree-planner/dialogs/notes-dialog/notes-dialog.component.ts +++ b/src/app/degree-planner/dialogs/notes-dialog/notes-dialog.component.ts @@ -10,7 +10,7 @@ import { WriteNote, DeleteNote, } from '@app/degree-planner/store/actions/note.actions'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; export type NotesDialogData = | { diff --git a/src/app/degree-planner/dialogs/remove-course-confirm-dialog/remove-course-confirm-dialog.component.ts b/src/app/degree-planner/dialogs/remove-course-confirm-dialog/remove-course-confirm-dialog.component.ts index e2e2d3bda2f24c9ce6c41ed15e16e02c3e584b19..a26cd0e462fd45e8a93e0a4043f5245b0c4bb8f0 100644 --- a/src/app/degree-planner/dialogs/remove-course-confirm-dialog/remove-course-confirm-dialog.component.ts +++ b/src/app/degree-planner/dialogs/remove-course-confirm-dialog/remove-course-confirm-dialog.component.ts @@ -8,7 +8,7 @@ import { DegreePlannerState } from '@app/degree-planner/store/state'; import { Store } from '@ngrx/store'; import { RemoveCourse } from '@app/degree-planner/store/actions/course.actions'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCodeFactory } from '@app/degree-planner/services/termcode.factory'; @Component({ selector: 'cse-remove-course-confirm-dialog', @@ -23,6 +23,7 @@ export class RemoveCourseConfirmDialogComponent implements OnInit { constructor( private dialogRef: MatDialogRef<RemoveCourseConfirmDialogComponent>, + private termCodeService: TermCodeFactory, private store: Store<{ degreePlanner: DegreePlannerState }>, @Inject(MAT_DIALOG_DATA) data: any, ) { @@ -43,7 +44,9 @@ export class RemoveCourseConfirmDialogComponent implements OnInit { console.log(this.course); const id = this.course.id; if (typeof id === 'number') { - const fromTermCode = new TermCode(this.course.termCode); + const fromTermCode = this.termCodeService.fromString( + this.course.termCode, + ); this.store.dispatch(new RemoveCourse({ fromTermCode, recordId: id })); } else { throw new Error('cannot remove a course that does not have an ID'); diff --git a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts index 1963d3736c36f8956d1e0b070ac6f9acd1367cdf..13dc6110e7d7f9329df2c27eda58c04f652d374a 100644 --- a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts +++ b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts @@ -15,8 +15,9 @@ import { } from '@app/degree-planner/store/actions/course.actions'; import * as selectors from '@app/degree-planner/store/selectors'; import { distinctUntilChanged } from 'rxjs/operators'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; import { MediaMatcher } from '@angular/cdk/layout'; +import { TermCodeFactory } from '../services/termcode.factory'; @Component({ selector: 'cse-saved-for-later-container', @@ -31,6 +32,7 @@ export class SavedForLaterContainerComponent implements OnInit { constructor( private store: Store<{ degreePlanner: DegreePlannerState }>, + private termCodeService: TermCodeFactory, mediaMatcher: MediaMatcher, ) { this.mobileView = mediaMatcher.matchMedia('(max-width: 900px)'); @@ -68,7 +70,7 @@ export class SavedForLaterContainerComponent implements OnInit { this.store.dispatch( new RemoveCourse({ - fromTermCode: new TermCode(course.termCode), + fromTermCode: this.termCodeService.fromString(course.termCode), recordId: course.id as number, }), ); diff --git a/src/app/degree-planner/services/api.service.ts b/src/app/degree-planner/services/api.service.ts index e9b6823fc18e67247b1e6e2c2122a6ac2e1ba278..20f9b7dc253fdcc7055f1a6d2639ff95a8400d90 100644 --- a/src/app/degree-planner/services/api.service.ts +++ b/src/app/degree-planner/services/api.service.ts @@ -13,8 +13,9 @@ import { DegreePlan } from '@app/core/models/degree-plan'; import { Profile } from '@app/core/models/profile'; import { SavedForLaterCourseBase } from '@app/core/models/saved-for-later-course'; import { SearchResults } from '@app/core/models/search-results'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '../shared/term-codes/termcode'; import { StudentInfo } from '@app/core/models/student-info'; +import { RawTermCode } from '../shared/term-codes/without-era'; const HTTP_OPTIONS = { headers: new HttpHeaders({ @@ -111,7 +112,7 @@ export class DegreePlannerApiService { const url = `/api/search/v1/terms`; return this.http .get<Term[]>(url, HTTP_OPTIONS) - .pipe(map(terms => terms.map(TermCode.fromTerm))); + .pipe(map(terms => terms.map(term => new RawTermCode(term.termCode)))); } public getAllNotes(roadmapId: number): Observable<Note[]> { diff --git a/src/app/degree-planner/services/constants.service.ts b/src/app/degree-planner/services/constants.service.ts index 7adef7968e7631dbe6ee58f014633a033fadc9e3..be64957e69687602cd848a7d3d7ac430f937770f 100644 --- a/src/app/degree-planner/services/constants.service.ts +++ b/src/app/degree-planner/services/constants.service.ts @@ -4,12 +4,10 @@ import { environment } from './../../../environments/environment'; import { forkJoinWithKeys } from '@app/degree-planner/shared/utils'; import { DegreePlannerApiService } from '@app/degree-planner/services/api.service'; import { map, tap } from 'rxjs/operators'; -import { TermCode } from '@app/core/models/termcode'; import { SubjectDescription, SubjectCodesTo } from '@app/core/models/course'; import { StudentInfo } from '@app/core/models/student-info'; export interface ConstantData { - activeTermCodes: TermCode[]; studentInfo: Partial<StudentInfo>; subjectDescriptions: SubjectCodesTo<SubjectDescription>; } @@ -18,25 +16,12 @@ export interface ConstantData { export class ConstantsService implements Resolve<ConstantData> { private version = environment.version; private constants: ConstantData = { - activeTermCodes: [], studentInfo: {}, subjectDescriptions: {}, }; constructor(private api: DegreePlannerApiService) {} - public firstActiveTermCode(): TermCode { - if (this.constants.activeTermCodes.length === 0) { - throw new Error(`tried to use the active term before it was loaded`); - } - - return this.constants.activeTermCodes[0]; - } - - public activeTermCodes(): ReadonlyArray<TermCode> { - return this.constants.activeTermCodes; - } - public getStudentInfo() { return this.constants.studentInfo; } @@ -69,8 +54,6 @@ export class ConstantsService implements Resolve<ConstantData> { } public resolve() { - const activeTermCodes = this.api.getActiveTermCodes(); - const studentInfo = this.api.getStudentInfo(); const subjectDescriptions = forkJoinWithKeys({ @@ -101,7 +84,6 @@ export class ConstantsService implements Resolve<ConstantData> { ); return forkJoinWithKeys({ - activeTermCodes, studentInfo, subjectDescriptions, }).pipe(tap(constants => (this.constants = constants))); diff --git a/src/app/degree-planner/services/termcode.factory.ts b/src/app/degree-planner/services/termcode.factory.ts new file mode 100644 index 0000000000000000000000000000000000000000..bcdef14eab0148161b5659e60e783e000587ff36 --- /dev/null +++ b/src/app/degree-planner/services/termcode.factory.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@angular/core'; +import { RawYearCode, RawTermCode } from '../shared/term-codes/without-era'; +import { YearCode } from '../shared/term-codes/yearcode'; +import { TermCode, Era } from '../shared/term-codes/termcode'; + +@Injectable({ providedIn: 'root' }) +export class TermCodeFactory { + private state: 'uninitialized' | 'initialized' = 'uninitialized'; + public active: ReadonlyArray<TermCode> = []; + + private static pickEra(code: RawTermCode, active: ReadonlyArray<TermCode>) { + const isActive = active.some(t => t.equals(code)); + if (isActive) { + return Era.Active; + } + + const beforeActive = active.every(t => t.comesAfter(code)); + if (beforeActive) { + return Era.Past; + } else { + return Era.Future; + } + } + + private requireInitialization() { + if (this.state !== 'initialized') { + throw new Error('cannot use TermCodeFactory without active terms'); + } + } + + public setActiveTermCodes(active: RawTermCode[]) { + if (this.state !== 'uninitialized') { + throw new Error('the TermCodeFactory was not uninitialized'); + } else if (active.length === 0) { + throw new Error('app cannot have 0 active terms, must have at least 1'); + } + + this.state = 'initialized'; + this.active = active + .sort(RawTermCode.sort) + .map(t => new TermCode(t, Era.Active, this.fromRawYearCode(t.yearCode))); + } + + public first() { + return this.active[0]; + } + + public fromString(str: string) { + this.requireInitialization(); + + const raw = new RawTermCode(str); + const era = TermCodeFactory.pickEra(raw, this.active); + return new TermCode(raw, era, this.fromRawYearCode(raw.yearCode)); + } + + public fromRawYearCode(raw: RawYearCode | string): YearCode { + if (typeof raw === 'string') { + raw = new RawYearCode(raw); + } + + const fall = TermCodeFactory.pickEra(raw.fall(), this.active); + const spring = TermCodeFactory.pickEra(raw.spring(), this.active); + const summer = TermCodeFactory.pickEra(raw.summer(), this.active); + return new YearCode(raw.toString(), fall, spring, summer); + } + + public fromYear(year: YearCode) { + return { + fall: this.fromString(`${year.toString()}2`), + spring: this.fromString(`${year.toString()}4`), + summer: this.fromString(`${year.toString()}6`), + }; + } +} diff --git a/src/app/degree-planner/shared/course-item/course-item.component.ts b/src/app/degree-planner/shared/course-item/course-item.component.ts index a464f25c9a0d69b85a0a9310fde1ad6205cc104c..b3e2b251c69885b5855de300c7c9fd8790a6af0e 100644 --- a/src/app/degree-planner/shared/course-item/course-item.component.ts +++ b/src/app/degree-planner/shared/course-item/course-item.component.ts @@ -19,9 +19,10 @@ import { DegreePlannerApiService } from '@app/degree-planner/services/api.servic 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 { TermCode, Era } from '@app/degree-planner/shared/term-codes/termcode'; import { PlannedTerm } from '@app/core/models/planned-term'; import { ConstantsService } from '@app/degree-planner/services/constants.service'; +import { TermCodeFactory } from '@app/degree-planner/services/termcode.factory'; const isntUndefined = <T>(thing: T | undefined): thing is T => { return thing !== undefined; @@ -38,7 +39,7 @@ export class CourseItemComponent implements OnInit { @Input() isPastTerm: boolean; @Input() disabled: boolean; @Input() type: 'saved' | 'course' | 'search'; - @Input() era?: unknown; + @Input() era?: Era; visibleTerms: any; activeTerm: any; public status: @@ -48,8 +49,7 @@ export class CourseItemComponent implements OnInit { | 'NotOfferedInTerm' | 'DoesNotExist' | 'Normal'; - public visibleTermCodes$: Observable<string[]>; - public droppableTermCodes$: Observable<string[]>; + public droppableTermCodes$: Observable<TermCode[]>; public term$: Observable<PlannedTerm>; public plannedCourses: ReadonlyArray<Course>; public toActiveTerm: boolean; @@ -63,13 +63,14 @@ export class CourseItemComponent implements OnInit { private constants: ConstantsService, private snackBar: MatSnackBar, public mediaMatcher: MediaMatcher, + private termCodeFactory: TermCodeFactory, ) { this.mobileView = mediaMatcher.matchMedia('(max-width: 959px)'); } ngOnInit() { - const isActive = this.era === 'active'; - const isPast = this.era === 'past'; + const isActive = this.era === Era.Active; + const isPast = this.era === Era.Past; const isNotOffered = this.course.studentEnrollmentStatus === 'NOTOFFERED'; const doesNotExist = this.course.studentEnrollmentStatus === 'DOESNOTEXIST'; @@ -102,7 +103,7 @@ export class CourseItemComponent implements OnInit { this.droppableTermCodes$ = this.store.pipe( select(selectors.selectAllVisibleYears), utils.yearsToDroppableTermCodes(), - distinctUntilChanged(utils.compareStringArrays), + distinctUntilChanged(utils.compareArrays((a, b) => a.equals(b))), ); } @@ -123,8 +124,7 @@ export class CourseItemComponent implements OnInit { * Handle moving a course to different terms based on course type * */ - onMove(termCode: string) { - const toTermCode = new TermCode(termCode); + onMove(toTermCode: TermCode) { this.term$ = this.store.pipe( select(selectors.selectVisibleTerm, { termCode: toTermCode }), filter(isntUndefined), @@ -133,7 +133,7 @@ export class CourseItemComponent implements OnInit { this.term$.subscribe(term => { this.plannedCourses = term.plannedCourses; - this.toActiveTerm = term.era === 'active'; + this.toActiveTerm = term.termCode.era === Era.Active; }); const isCourseInPlannedCourses = this.plannedCourses.some( @@ -158,7 +158,7 @@ export class CourseItemComponent implements OnInit { case 'course': { const id = this.course.id as number; const { subjectCode, courseId } = this.course; - const from = new TermCode(this.course.termCode); + const from = this.termCodeFactory.fromString(this.course.termCode); this.store.dispatch( new MoveCourseBetweenTerms({ to: toTermCode, @@ -220,7 +220,7 @@ export class CourseItemComponent implements OnInit { case 'course': this.store.dispatch( new RemoveCourse({ - fromTermCode: new TermCode(termCode), + fromTermCode: this.termCodeFactory.fromString(termCode), recordId: this.course.id as number, }), ); @@ -255,7 +255,7 @@ export class CourseItemComponent implements OnInit { break; default: - if (this.era === 'future') { + if (this.era === Era.Future) { dialogOptions.text = `This will remove "${ this.course.title }" from your degree plan.`; @@ -284,7 +284,9 @@ export class CourseItemComponent implements OnInit { case 'course': this.store.dispatch( new RemoveCourse({ - fromTermCode: new TermCode(this.course.termCode), + fromTermCode: this.termCodeFactory.fromString( + this.course.termCode, + ), recordId: this.course.id as number, }), ); @@ -320,7 +322,6 @@ export class CourseItemComponent implements OnInit { this.snackBar.open(`'${short} ${catalogNumber}' no longer offered`); return; } - console.log('mobile view', this.mobileView); this.api .getCourseDetails(subjectCode, courseId) diff --git a/src/app/degree-planner/shared/term-codes/termcode.ts b/src/app/degree-planner/shared/term-codes/termcode.ts new file mode 100644 index 0000000000000000000000000000000000000000..0cfe906f5641b0fb19fcaad81758c99e91ea787d --- /dev/null +++ b/src/app/degree-planner/shared/term-codes/termcode.ts @@ -0,0 +1,31 @@ +import { RawTermCode } from './without-era'; +import { YearCode } from './yearcode'; + +export enum Era { + Past, + Active, + Future, +} + +export class TermCode extends RawTermCode { + public era: Era; + public yearCode: YearCode; + + constructor(from: RawTermCode | string, era: Era, yearCode: YearCode) { + super(from.toString()); + this.yearCode = yearCode; + this.era = era; + } + + public isPast() { + return this.era === Era.Past; + } + + public isActive() { + return this.era === Era.Active; + } + + public isFuture() { + return this.era === Era.Future; + } +} diff --git a/src/app/core/models/termcode.ts b/src/app/degree-planner/shared/term-codes/without-era.ts similarity index 60% rename from src/app/core/models/termcode.ts rename to src/app/degree-planner/shared/term-codes/without-era.ts index 6f51c5a48c676d6e4ff34b3f755987ea33fd665e..05be64cdd4542c8d5f07915f21bb46aaf10b22a2 100644 --- a/src/app/core/models/termcode.ts +++ b/src/app/degree-planner/shared/term-codes/without-era.ts @@ -1,52 +1,74 @@ -import { Term } from './term'; - const YEARCODE_PATTERN = /^[01]\d{2}$/i; const TERMCODE_PATTERN = /^[01]\d{2}[2346]$/i; -export class YearCode { +export class RawYearCode { public readonly centuryOffset: '0' | '1'; public readonly yearOffset: string; + public static isValid(str: string): boolean { + return YEARCODE_PATTERN.test(str); + } + + public static sort(a: RawYearCode, b: RawYearCode): -1 | 0 | 1 { + if (a.comesBefore(b)) { + return -1; + } + + if (a.comesAfter(b)) { + return 1; + } + + return 0; + } + + public static fromString(str: string): RawYearCode { + return new RawYearCode(str); + } + public get fromYear(): number { const century = 1900 + parseInt(this.centuryOffset, 10) * 100; const year = century + parseInt(this.yearOffset, 10) - 1; return year; } - public get fall(): TermCode { - return new TermCode(`${this.toString()}2`); + public get toYear(): number { + return this.fromYear + 1; } - public get spring(): TermCode { - return new TermCode(`${this.toString()}4`); + public fall() { + return new RawTermCode(`${this.toString()}2`); } - public get summer(): TermCode { - return new TermCode(`${this.toString()}6`); + public spring() { + return new RawTermCode(`${this.toString()}4`); } - public get toYear(): number { - return this.fromYear + 1; + public summer() { + return new RawTermCode(`${this.toString()}6`); } constructor(str: string) { - if (YearCode.isValid(str) === false) { + if (typeof str !== 'string') { + throw new Error(`constructor expected string, got ${typeof str}`); + } + + if (RawYearCode.isValid(str) === false) { throw new Error(`'${str}' is not a valid year code`); } - this.centuryOffset = str.substr(0, 1) as YearCode['centuryOffset']; - this.yearOffset = str.substr(1, 2) as YearCode['yearOffset']; + this.centuryOffset = str.substr(0, 1) as RawYearCode['centuryOffset']; + this.yearOffset = str.substr(1, 2) as RawYearCode['yearOffset']; } - public equals(other: YearCode): boolean { + public equals(other: RawYearCode): boolean { return this.toString() === other.toString(); } - public comesBefore(other: YearCode): boolean { + public comesBefore(other: RawYearCode): boolean { return this.toString() < other.toString(); } - public comesAfter(other: YearCode): boolean { + public comesAfter(other: RawYearCode): boolean { return this.toString() > other.toString(); } @@ -60,12 +82,21 @@ export class YearCode { public toString() { return `${this.centuryOffset}${this.yearOffset}`; } +} + +export class RawTermCode { + public readonly yearCode: RawYearCode; + public readonly termId: '2' | '3' | '4' | '6'; + + public static describe(str: string): string { + return new RawTermCode(str).description; + } public static isValid(str: string): boolean { - return YEARCODE_PATTERN.test(str); + return TERMCODE_PATTERN.test(str); } - public static sort(a: YearCode, b: YearCode): -1 | 0 | 1 { + public static sort(a: RawTermCode, b: RawTermCode): -1 | 0 | 1 { if (a.comesBefore(b)) { return -1; } @@ -77,15 +108,6 @@ export class YearCode { return 0; } - public static fromString(str: string): YearCode { - return new YearCode(str); - } -} - -export class TermCode { - public readonly yearCode: YearCode; - public readonly termId: '2' | '3' | '4' | '6'; - public get termName(): 'fall' | 'spring' | 'summer' { switch (this.termId) { case '2': @@ -111,23 +133,31 @@ export class TermCode { } constructor(str: string) { - if (TermCode.isValid(str) === false) { + if (typeof str !== 'string') { + throw new Error(`constructor expected string, got ${typeof str}`); + } + + if (RawTermCode.isValid(str) === false) { throw new Error(`'${str}' is not a valid term code`); } - this.yearCode = new YearCode(str.substr(0, 3)); - this.termId = str.substr(3, 1) as TermCode['termId']; + if (typeof str !== 'string') { + console.log({ str }); + } + + this.yearCode = new RawYearCode(str.substr(0, 3)); + this.termId = str.substr(3, 1) as RawTermCode['termId']; } - public equals(other: TermCode): boolean { + public equals(other: RawTermCode): boolean { return this.toString() === other.toString(); } - public comesBefore(other: TermCode): boolean { + public comesBefore(other: RawTermCode): boolean { return this.toString() < other.toString(); } - public comesAfter(other: TermCode): boolean { + public comesAfter(other: RawTermCode): boolean { return this.toString() > other.toString(); } @@ -141,28 +171,4 @@ export class TermCode { public toString(): string { return `${this.yearCode}${this.termId}`; } - - public static isValid(str: string): boolean { - return TERMCODE_PATTERN.test(str); - } - - public static sort(a: TermCode, b: TermCode): -1 | 0 | 1 { - if (a.comesBefore(b)) { - return -1; - } - - if (a.comesAfter(b)) { - return 1; - } - - return 0; - } - - public static fromString(str: string): TermCode { - return new TermCode(str); - } - - public static fromTerm(term: Term): TermCode { - return new TermCode(term.termCode); - } } diff --git a/src/app/degree-planner/shared/term-codes/yearcode.ts b/src/app/degree-planner/shared/term-codes/yearcode.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5c865ca599dd7a1455c383f9e5e879552c2d135 --- /dev/null +++ b/src/app/degree-planner/shared/term-codes/yearcode.ts @@ -0,0 +1,23 @@ +import { RawYearCode } from './without-era'; +import { Era, TermCode } from './termcode'; + +export class YearCode extends RawYearCode { + private eras: { fall: Era; spring: Era; summer: Era }; + + constructor(str: string, fall: Era, spring: Era, summer: Era) { + super(str); + this.eras = { fall, spring, summer }; + } + + public fall() { + return new TermCode(super.fall(), this.eras.fall, this); + } + + public spring() { + return new TermCode(super.spring(), this.eras.spring, this); + } + + public summer() { + return new TermCode(super.summer(), this.eras.summer, this); + } +} diff --git a/src/app/degree-planner/shared/utils.ts b/src/app/degree-planner/shared/utils.ts index f2be7be3a9ff38243a23095bd41bfdcb46630360..190c2eafcc4465ab88573e2db72851c2e6da184a 100644 --- a/src/app/degree-planner/shared/utils.ts +++ b/src/app/degree-planner/shared/utils.ts @@ -1,78 +1,22 @@ import { Observable, forkJoin } from 'rxjs'; import { map } from 'rxjs/operators'; -import { PlannedTermEra, PlannedTerm } from '@app/core/models/planned-term'; import { YearMapping } from '@app/core/models/year'; -import { YearCode, TermCode } from '@app/core/models/termcode'; - -export const isValidTermCode = (anything: any): anything is string => { - return /^\d{4}$/.test(anything); -}; - -export const pickTermName = (termOffset: string) => { - switch (termOffset) { - case '2': - case '3': - return 'fall'; - case '4': - return 'spring'; - case '6': - return 'summer'; - default: - throw new Error(`'${termOffset}' is not a valid term offset`); - } -}; - -export const pickTermEra = ( - termCode: TermCode, - activeTermCodes: ReadonlyArray<TermCode>, -): PlannedTermEra => { - const noActiveTermCodes = activeTermCodes.length === 0; - const isActiveTermCode = activeTermCodes.some(tc => tc.equals(termCode)); - const beforeAllActiveTermCodes = activeTermCodes.every(tc => - tc.comesAfter(termCode), - ); - if (noActiveTermCodes || isActiveTermCode) { - return 'active'; - } else if (beforeAllActiveTermCodes) { - return 'past'; - } else { - return 'future'; - } -}; - -export const pickYearEra = ( - yearCode: YearCode, - activeTermCodes: ReadonlyArray<TermCode>, -): PlannedTermEra => { - const activeYearCodes = activeTermCodes.map(tc => tc.yearCode); - const noActiveYearCodes = activeYearCodes.length === 0; - const isActiveYearCode = activeYearCodes.some(yc => yc.equals(yearCode)); - const beforeAllActiveYearCodes = activeYearCodes.every(yc => - yc.comesAfter(yearCode), - ); - if (noActiveYearCodes || isActiveYearCode) { - return 'active'; - } else if (beforeAllActiveYearCodes) { - return 'past'; - } else { - return 'future'; - } -}; +import { TermCode, Era } from './term-codes/termcode'; export const yearsToDropZoneIds = () => (years$: Observable<YearMapping>) => { return years$.pipe( map(years => { const yearCodes = Object.keys(years); const termCodes = yearCodes.reduce<string[]>((acc, yearCode) => { - if (years[yearCode].fall.era !== 'past') { + if (years[yearCode].fall.termCode.era !== Era.Past) { acc = acc.concat(years[yearCode].fall.termCode.toString()); } - if (years[yearCode].spring.era !== 'past') { + if (years[yearCode].spring.termCode.era !== Era.Past) { acc = acc.concat(years[yearCode].spring.termCode.toString()); } - if (years[yearCode].summer.era !== 'past') { + if (years[yearCode].summer.termCode.era !== Era.Past) { acc = acc.concat(years[yearCode].summer.termCode.toString()); } @@ -100,33 +44,23 @@ export const compareArrays = <T>(cmp: (a: T, b: T) => boolean) => { export const compareStringArrays = compareArrays<string>((a, b) => a === b); -export const yearsToYearCodes = () => (years$: Observable<YearMapping>) => { - return years$.pipe( - map(years => { - const yearCodes = Object.keys(years); - const sortedYearCodes = yearCodes.sort(); - return sortedYearCodes; - }), - ); -}; - export const yearsToDroppableTermCodes = () => ( years$: Observable<YearMapping>, ) => { return years$.pipe( map(years => { const yearCodes = Object.keys(years); - return yearCodes.reduce<string[]>((acc, yearCode) => { - if (years[yearCode].fall.era !== 'past') { - acc = acc.concat(years[yearCode].fall.termCode.toString()); + return yearCodes.reduce<TermCode[]>((acc, yearCode) => { + if (years[yearCode].fall.termCode.era !== Era.Past) { + acc = acc.concat(years[yearCode].fall.termCode); } - if (years[yearCode].spring.era !== 'past') { - acc = acc.concat(years[yearCode].spring.termCode.toString()); + if (years[yearCode].spring.termCode.era !== Era.Past) { + acc = acc.concat(years[yearCode].spring.termCode); } - if (years[yearCode].summer.era !== 'past') { - acc = acc.concat(years[yearCode].summer.termCode.toString()); + if (years[yearCode].summer.termCode.era !== Era.Past) { + acc = acc.concat(years[yearCode].summer.termCode); } return acc; diff --git a/src/app/degree-planner/store/actions/course.actions.ts b/src/app/degree-planner/store/actions/course.actions.ts index 16bf00a03b60aeb5476697b7d0ff37fc3807eeab..df0a07f269bd3c8ede05300546c8e7768fbc31a9 100644 --- a/src/app/degree-planner/store/actions/course.actions.ts +++ b/src/app/degree-planner/store/actions/course.actions.ts @@ -1,6 +1,6 @@ import { Action } from '@ngrx/store'; import { Course } from '@app/core/models/course'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; export enum CourseActionTypes { AddCourse = '[Course] Add', @@ -80,7 +80,9 @@ export class AddCourse implements Action { export class AddCourseSuccess implements Action { public readonly type = CourseActionTypes.AddCourseSuccess; - constructor(public payload: { course: Course; newIndex?: number }) {} + constructor( + public payload: { termCode: TermCode; course: Course; newIndex?: number }, + ) {} } export class RemoveCourse implements Action { diff --git a/src/app/degree-planner/store/actions/note.actions.ts b/src/app/degree-planner/store/actions/note.actions.ts index f1fed10160b85118aea2eb76d6aa452695697097..be7299f2b70740ec764d91429dca7dafe286421d 100644 --- a/src/app/degree-planner/store/actions/note.actions.ts +++ b/src/app/degree-planner/store/actions/note.actions.ts @@ -1,6 +1,6 @@ import { Action } from '@ngrx/store'; import { Note } from '@app/core/models/note'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; export enum NoteActionTypes { WriteNote = '[Note] Write', @@ -19,7 +19,7 @@ export class WriteNote implements Action { export class WriteNoteSuccess implements Action { public readonly type = NoteActionTypes.WriteNoteSuccess; - constructor(public payload: { updatedNote: Note }) {} + constructor(public payload: { termCode: TermCode; updatedNote: Note }) {} } export class DeleteNote implements Action { diff --git a/src/app/degree-planner/store/actions/ui.actions.ts b/src/app/degree-planner/store/actions/ui.actions.ts index 76b57e7ed454ad7a2938903174db0a96d1f237c5..90fbe3013e6593dbd8c68be4f63a1a4b0c414229 100644 --- a/src/app/degree-planner/store/actions/ui.actions.ts +++ b/src/app/degree-planner/store/actions/ui.actions.ts @@ -1,6 +1,7 @@ import { Action } from '@ngrx/store'; -import { YearCode, TermCode } from '@app/core/models/termcode'; import { UserPreferences } from '@app/core/models/user-preferences'; +import { YearCode } from '@app/degree-planner/shared/term-codes/yearcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; export enum UIActionTypes { ToggleAcademicYear = '[UI] Toggle Academic Year', diff --git a/src/app/degree-planner/store/effects/course.effects.ts b/src/app/degree-planner/store/effects/course.effects.ts index ab36ab3e22134ed40efda860c708727b202dd477..bb54b17451c7bbb68bb927f767a4ffbf99106933 100644 --- a/src/app/degree-planner/store/effects/course.effects.ts +++ b/src/app/degree-planner/store/effects/course.effects.ts @@ -31,7 +31,7 @@ import { } from '@app/degree-planner/store/actions/course.actions'; import { DegreePlan } from '@app/core/models/degree-plan'; import { Course, CourseBase } from '@app/core/models/course'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; import { ConstantsService } from '@app/degree-planner/services/constants.service'; @Injectable() @@ -56,7 +56,6 @@ export class CourseEffects { // Get term data for the degree plan specified by the roadmap ID. flatMap(([action, degreePlan]) => { const roadmapId = degreePlan.roadmapId; - const activeTerms = this.constants.activeTermCodes(); const { id: recordId, to: toTermCode, @@ -64,15 +63,13 @@ export class CourseEffects { courseId, } = action.payload; - const toActiveTerm = activeTerms.some(t => t.equals(toTermCode)); - const moveCourse = this.api.updateCourseTerm( roadmapId, recordId, toTermCode, ); - if (toActiveTerm) { + if (toTermCode.isActive()) { /** * The `updateCourseTerm` API won't force cart validation which we want * if we're adding a course to the cart. Calling the `addCourseToCart` @@ -119,13 +116,11 @@ export class CourseEffects { flatMap(([action, visibleDegreePlan]) => { // TODO error handle the API calls const roadmapId = (visibleDegreePlan as DegreePlan).roadmapId; - const activeTerms = this.constants.activeTermCodes(); const { subjectCode, termCode, courseId, newIndex } = action.payload; - const isActiveTerm = activeTerms.some(term => term.equals(termCode)); const isPrimaryPlan = (visibleDegreePlan as DegreePlan).primary; const addCourse$ = - isActiveTerm && isPrimaryPlan + termCode.isActive() && isPrimaryPlan ? this.api.addCourseToCart(subjectCode, courseId, termCode) : this.api.addCourse(roadmapId, subjectCode, courseId, termCode); @@ -137,7 +132,7 @@ export class CourseEffects { ); const toSuccessAction$ = courseBaseToCourse$.pipe( - map(course => new AddCourseSuccess({ course, newIndex })), + map(course => new AddCourseSuccess({ termCode, course, newIndex })), ); return toSuccessAction$; @@ -145,7 +140,7 @@ export class CourseEffects { tap(state => { const touchedCourse = state.payload.course; - const touchedTerm = new TermCode(touchedCourse.termCode).description; + const touchedTerm = TermCode.describe(touchedCourse.termCode); const subject = this.constants.subjectDescription( touchedCourse.subjectCode, ).short; diff --git a/src/app/degree-planner/store/effects/note.effects.ts b/src/app/degree-planner/store/effects/note.effects.ts index 30de10d1f67b21d53830be0c5d6bc3b87cd0f801..ebb3a95a246dd330639d42459d24af410ef14d43 100644 --- a/src/app/degree-planner/store/effects/note.effects.ts +++ b/src/app/degree-planner/store/effects/note.effects.ts @@ -31,7 +31,7 @@ import { } from '@app/degree-planner/store/actions/note.actions'; import * as selectors from '@app/degree-planner/store/selectors'; import { GlobalState } from '@app/core/state'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; @Injectable() export class NoteEffects { @@ -66,17 +66,21 @@ export class NoteEffects { if (existingNote !== undefined && existingNote.isLoaded) { // Since the term DOES have a note, update the existing note const noteId = existingNote.id; - return this.api.updateNote(planId, termCode, noteText, noteId); + return this.api + .updateNote(planId, termCode, noteText, noteId) + .pipe( + map(updatedNote => new WriteNoteSuccess({ termCode, updatedNote })), + ); } else { // Since the term DOES NOT have a note, create a new note - return this.api.createNote(planId, termCode, noteText); + return this.api + .createNote(planId, termCode, noteText) + .pipe( + map(updatedNote => new WriteNoteSuccess({ termCode, updatedNote })), + ); } }), - // Dispatch an `WriteNoteSuccess` action so that the State - // object can be updated with the new Note data. - map(updatedNote => new WriteNoteSuccess({ updatedNote })), - tap(() => { const message = 'Note has been saved'; this.snackBar.open(message, undefined, {}); diff --git a/src/app/degree-planner/store/effects/plan.effects.ts b/src/app/degree-planner/store/effects/plan.effects.ts index 33fc2352a55bf40bfa5fbaeb7d18322eecbd3a0f..16722c5bb555d2320b2e149f68a46c03af6fce3b 100644 --- a/src/app/degree-planner/store/effects/plan.effects.ts +++ b/src/app/degree-planner/store/effects/plan.effects.ts @@ -33,18 +33,16 @@ import { DeletePlanSuccess, ChangeGradeVisibility, } from '@app/degree-planner/store/actions/plan.actions'; -import * as utils from '@app/degree-planner/shared/utils'; import { DegreePlan } from '@app/core/models/degree-plan'; import { PlannedTerm, PlannedTermNote } from '@app/core/models/planned-term'; import { INITIAL_DEGREE_PLANNER_STATE } from '@app/degree-planner/store/state'; import { YearMapping, MutableYearMapping } from '@app/core/models/year'; import { Note } from '@app/core/models/note'; import { CourseBase, Course } from '@app/core/models/course'; -import { pickTermEra } from '@app/degree-planner/shared/utils'; -import { TermCode, YearCode } from '@app/core/models/termcode'; -import { ConstantsService } from '@app/degree-planner/services/constants.service'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; import { Alert } from '@app/core/models/alert'; import { UpdateUserPreferences } from '../actions/userPreferences.actions'; +import { TermCodeFactory } from '@app/degree-planner/services/termcode.factory'; @Injectable() export class DegreePlanEffects { @@ -53,7 +51,7 @@ export class DegreePlanEffects { private api: DegreePlannerApiService, private store$: Store<GlobalState>, private snackBar: MatSnackBar, - private constants: ConstantsService, + private termCodeService: TermCodeFactory, ) {} @Effect() @@ -64,11 +62,13 @@ export class DegreePlanEffects { console.log('loading all degree plans'); return forkJoinWithKeys({ allDegreePlans: this.api.getAllDegreePlans(), + activeTermCodes: this.api.getActiveTermCodes(), userPreferences: this.api.getUserPreferences(), }); }), - // Load data specific to the primary degree plan. - flatMap(({ allDegreePlans, userPreferences }) => { + + flatMap(({ allDegreePlans, activeTermCodes, userPreferences }) => { + this.termCodeService.setActiveTermCodes(activeTermCodes); const savedForLaterCourses = this.api.getSavedForLaterCourses(); const visibleDegreePlan = userPreferences.degreePlannerSelectedPlan ? pickDegreePlanById( @@ -79,7 +79,7 @@ export class DegreePlanEffects { const visibleYears = loadPlanYears( this.api, visibleDegreePlan.roadmapId, - this.constants, + this.termCodeService, ); const alerts: Alert[] = []; @@ -120,7 +120,6 @@ export class DegreePlanEffects { return new InitialLoadSuccess({ ...INITIAL_DEGREE_PLANNER_STATE, ...payload, - activeTermCodes: this.constants.activeTermCodes(), isLoadingPlan: false, }); }), @@ -147,7 +146,7 @@ export class DegreePlanEffects { const visibleYears = loadPlanYears( this.api, visibleDegreePlan.roadmapId, - this.constants, + this.termCodeService, ); return forkJoinWithKeys({ @@ -271,7 +270,7 @@ export class DegreePlanEffects { const newYears = loadPlanYears( this.api, newPlan.roadmapId, - this.constants, + this.termCodeService, ); return forkJoinWithKeys({ @@ -356,10 +355,6 @@ const matchesTermCode = (termCode: TermCode) => (thing: { return thing.termCode === termCode.toString(); }; -const toYearCode = (termCode: string) => { - return termCode.substr(0, 3); -}; - const buildTerm = ( roadmapId: number, termCode: TermCode, @@ -368,7 +363,6 @@ const buildTerm = ( termCode: string; courses: ReadonlyArray<CourseBase>; }>, - constants: ConstantsService, ): PlannedTerm => { const baseNote = notes.find(matchesTermCode(termCode)); const note: PlannedTermNote | undefined = baseNote @@ -390,11 +384,9 @@ const buildTerm = ( plannedCourses.push(course); }); - const era = pickTermEra(termCode, constants.activeTermCodes()); return { roadmapId, termCode, - era, note, plannedCourses, enrolledCourses, @@ -404,7 +396,7 @@ const buildTerm = ( const loadPlanYears = ( api: DegreePlannerApiService, roadmapId: number, - constants: ConstantsService, + termCodeService: TermCodeFactory, ): Observable<YearMapping> => { const notesAndCourses$ = forkJoinWithKeys({ notes: api.getAllNotes(roadmapId), @@ -418,11 +410,11 @@ const loadPlanYears = ( const allTermCodes = [ ...noteTermCodes, ...courseTermCodes, - ...constants.activeTermCodes().map(tc => tc.toString()), - ].map(TermCode.fromString); + ...termCodeService.active.map(t => t.toString()), + ].map(t => termCodeService.fromString(t)); const uniqueYearCodes = unique( allTermCodes.map(tc => tc.yearCode.toString()), - ).map(yearCodeStr => new YearCode(yearCodeStr)); + ).map(yearCodeStr => termCodeService.fromRawYearCode(yearCodeStr)); return { uniqueYearCodes, notes, @@ -435,25 +427,13 @@ const loadPlanYears = ( map(({ uniqueYearCodes, notes, courses }) => { const mapping: MutableYearMapping = {}; uniqueYearCodes.forEach(yearCode => { + const { fall, spring, summer } = termCodeService.fromYear(yearCode); mapping[yearCode.toString()] = { yearCode, - isExpanded: - utils.pickYearEra(yearCode, constants.activeTermCodes()) !== 'past', - fall: buildTerm(roadmapId, yearCode.fall, notes, courses, constants), - spring: buildTerm( - roadmapId, - yearCode.spring, - notes, - courses, - constants, - ), - summer: buildTerm( - roadmapId, - yearCode.summer, - notes, - courses, - constants, - ), + isExpanded: !(fall.isPast() && spring.isPast() && summer.isPast()), + fall: buildTerm(roadmapId, fall, notes, courses), + spring: buildTerm(roadmapId, spring, notes, courses), + summer: buildTerm(roadmapId, summer, notes, courses), }; }); diff --git a/src/app/degree-planner/store/reducer.ts b/src/app/degree-planner/store/reducer.ts index a0ed618a3a860e390700df3a00ee9298ef9e274a..22ccb8202c366106649e733f829d2005f7aba444 100644 --- a/src/app/degree-planner/store/reducer.ts +++ b/src/app/degree-planner/store/reducer.ts @@ -1,4 +1,3 @@ -import { pickTermEra } from '@app/degree-planner/shared/utils'; import { UIActionTypes, CloseSidenav, OpenSidenav } from './actions/ui.actions'; import { @@ -59,13 +58,9 @@ import { SavedForLaterCourse } from '@app/core/models/saved-for-later-course'; import { DegreePlan } from '@app/core/models/degree-plan'; import { Year, YearMapping } from '@app/core/models/year'; import { Course } from '@app/core/models/course'; -import { - PlannedTerm, - PlannedTermEra, - PlannedTermNote, -} from '@app/core/models/planned-term'; -import * as utils from '@app/degree-planner/shared/utils'; -import { TermCode, YearCode } from '@app/core/models/termcode'; +import { PlannedTerm, PlannedTermNote } from '@app/core/models/planned-term'; +import { YearCode } from '@app/degree-planner/shared/term-codes/yearcode'; +import { TermCode, Era } from '@app/degree-planner/shared/term-codes/termcode'; type SupportedActions = | PlanError @@ -150,11 +145,15 @@ export function degreePlannerReducer( return parseInt(yearCode, 10); }), ); - const nextYearCode = new YearCode(`${largestYearCode + 1}`); + const nextYearCode = new YearCode( + `${largestYearCode + 1}`, + Era.Future, + Era.Future, + Era.Future, + ); const nextYear = emptyYear( (state.visibleDegreePlan as DegreePlan).roadmapId, nextYearCode, - state.activeTermCodes, ); const visibleYears: YearMapping = { ...state.visibleYears, @@ -304,7 +303,6 @@ export function degreePlannerReducer( (state.visibleDegreePlan as DegreePlan).roadmapId, termCode, newNote, - state.activeTermCodes, state.visibleYears[yearCode.toString()], ), }; @@ -322,8 +320,7 @@ export function degreePlannerReducer( * - *OR* adds a new term with the given note if no term exists with the note's termCode. */ case NoteActionTypes.WriteNoteSuccess: { - const { updatedNote } = action.payload; - const termCode = new TermCode(updatedNote.termCode); + const { termCode, updatedNote } = action.payload; const { yearCode } = termCode; const visibleYears: YearMapping = { @@ -332,7 +329,6 @@ export function degreePlannerReducer( (state.visibleDegreePlan as DegreePlan).roadmapId, termCode, { isLoaded: true, text: updatedNote.note, id: updatedNote.id }, - state.activeTermCodes, state.visibleYears[yearCode.toString()], ), }; @@ -354,7 +350,6 @@ export function degreePlannerReducer( [yearCode.toString()]: createYearWithoutNote( (state.visibleDegreePlan as DegreePlan).roadmapId, termCode, - state.activeTermCodes, state.visibleYears[yearCode.toString()], ), }; @@ -426,7 +421,6 @@ export function degreePlannerReducer( (state.visibleDegreePlan as DegreePlan).roadmapId, fromTermCode, course.id, - state.activeTermCodes, state.visibleYears[fromYearCode.toString()], ); @@ -434,7 +428,6 @@ export function degreePlannerReducer( (state.visibleDegreePlan as DegreePlan).roadmapId, toTermCode, { ...course, termCode: toTermCode.toString() }, - state.activeTermCodes, fromYearCode.equals(toYearCode) ? fromYear : state.visibleYears[toYearCode.toString()], @@ -466,7 +459,6 @@ export function degreePlannerReducer( (state.visibleDegreePlan as DegreePlan).roadmapId, termCode, course, - state.activeTermCodes, state.visibleYears[termCode.yearCode.toString()], newIndex, ); @@ -480,15 +472,13 @@ export function degreePlannerReducer( } case CourseActionTypes.AddCourseSuccess: { - const { course, newIndex } = action.payload; - const termCode = new TermCode(course.termCode); + const { termCode, course, newIndex } = action.payload; const { yearCode } = termCode; const year: Year = createYearWithCourse( (state.visibleDegreePlan as DegreePlan).roadmapId, termCode, course, - state.activeTermCodes, state.visibleYears[yearCode.toString()], newIndex, ); @@ -509,7 +499,6 @@ export function degreePlannerReducer( (state.visibleDegreePlan as DegreePlan).roadmapId, fromTermCode, recordId, - state.activeTermCodes, state.visibleYears[yearCode.toString()], ); @@ -656,54 +645,36 @@ export function degreePlannerReducer( } } -const emptyTerm = ( - roadmapId: number, - termCode: TermCode, - era: PlannedTermEra, -): PlannedTerm => { - return { roadmapId, termCode, era, plannedCourses: [], enrolledCourses: [] }; +const emptyTerm = (roadmapId: number, termCode: TermCode): PlannedTerm => { + return { roadmapId, termCode, plannedCourses: [], enrolledCourses: [] }; }; -const emptyYear = ( - roadmapId: number, - yearCode: YearCode, - activeTermCodes: ReadonlyArray<TermCode>, -): Year => { +const emptyYear = (roadmapId: number, yearCode: YearCode): Year => { return { yearCode, - isExpanded: utils.pickYearEra(yearCode, activeTermCodes) !== 'past', - fall: emptyTerm( - roadmapId, - yearCode.fall, - pickTermEra(yearCode.fall, activeTermCodes), - ), - spring: emptyTerm( - roadmapId, - yearCode.spring, - pickTermEra(yearCode.spring, activeTermCodes), - ), - summer: emptyTerm( - roadmapId, - yearCode.summer, - pickTermEra(yearCode.summer, activeTermCodes), + isExpanded: !( + yearCode.fall().isPast() && + yearCode.spring().isPast() && + yearCode.summer().isPast() ), + fall: emptyTerm(roadmapId, yearCode.fall()), + spring: emptyTerm(roadmapId, yearCode.spring()), + summer: emptyTerm(roadmapId, yearCode.summer()), }; }; const generateYearForTermCode = ( roadmapId: number, termCode: TermCode, - activeTermCodes: ReadonlyArray<TermCode>, ): Year => { - return emptyYear(roadmapId, termCode.yearCode, activeTermCodes); + return emptyYear(roadmapId, termCode.yearCode); }; const createYearWithNote = ( roadmapId: number, termCode: TermCode, note: PlannedTermNote | undefined, - activeTermCodes: ReadonlyArray<TermCode>, - year = generateYearForTermCode(roadmapId, termCode, activeTermCodes), + year = generateYearForTermCode(roadmapId, termCode), ): Year => { const term = year[termCode.termName]; return { ...year, [termCode.termName]: { ...term, note } }; @@ -712,16 +683,9 @@ const createYearWithNote = ( const createYearWithoutNote = ( roadmapId: number, termCode: TermCode, - activeTermCodes: ReadonlyArray<TermCode>, year?: Year, ) => { - return createYearWithNote( - roadmapId, - termCode, - undefined, - activeTermCodes, - year, - ); + return createYearWithNote(roadmapId, termCode, undefined, year); }; const findCourse = ( @@ -740,8 +704,7 @@ const createYearWithCourse = ( roadmapId: number, termCode: TermCode, course: Course, - activeTermCodes: ReadonlyArray<TermCode>, - year = generateYearForTermCode(roadmapId, termCode, activeTermCodes), + year = generateYearForTermCode(roadmapId, termCode), newIndex?: number, ): Year => { const term = year[termCode.termName]; @@ -757,8 +720,7 @@ const createYearWithoutCourse = ( roadmapId: number, termCode: TermCode, recordId: number, - activeTermCodes: ReadonlyArray<TermCode>, - year = generateYearForTermCode(roadmapId, termCode, activeTermCodes), + year = generateYearForTermCode(roadmapId, termCode), ): Year => { const term = year[termCode.termName]; const courses = term.plannedCourses.filter(course => course.id !== recordId); diff --git a/src/app/degree-planner/store/selectors.ts b/src/app/degree-planner/store/selectors.ts index 3d44099450e88d52a8a2e630b8ce5d9cd4b4a934..444768c5c6ac52ff63a354a3562061a0a52f8bbb 100644 --- a/src/app/degree-planner/store/selectors.ts +++ b/src/app/degree-planner/store/selectors.ts @@ -1,7 +1,8 @@ import { createSelector } from '@ngrx/store'; import { GlobalState } from '@app/core/state'; import { DegreePlannerState } from './state'; -import { TermCode, YearCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; +import { YearCode } from '@app/degree-planner/shared/term-codes/yearcode'; import { YearMapping, Year } from '@app/core/models/year'; import { UserPreferences } from '@app/core/models/user-preferences'; @@ -95,12 +96,8 @@ export const getSelectedSearchTerm = createSelector( export const getActiveSelectedSearchTerm = createSelector( getDegreePlannerState, (state: DegreePlannerState) => { - const { selectedTerm } = state.search; - if ( - selectedTerm && - state.activeTermCodes.some(tc => tc.equals(selectedTerm)) - ) { - return selectedTerm; + if (state.search.selectedTerm && state.search.selectedTerm.isActive()) { + return state.search.selectedTerm; } else { return undefined; } diff --git a/src/app/degree-planner/store/state.ts b/src/app/degree-planner/store/state.ts index 1697917e23c1786c9deae8b1827c2a197ae46415..0976a2a4fd72643f5c44229a7416a4c355fb332f 100644 --- a/src/app/degree-planner/store/state.ts +++ b/src/app/degree-planner/store/state.ts @@ -2,7 +2,7 @@ import { YearMapping } from '@app/core/models/year'; import { DegreePlan } from '@app/core/models/degree-plan'; import { SavedForLaterCourse } from '@app/core/models/saved-for-later-course'; import { SubjectCodesTo, SubjectDescription } from '@app/core/models/course'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; import { Alert } from '@app/core/models/alert'; import { UserPreferences } from '@app/core/models/user-preferences'; @@ -10,7 +10,6 @@ export interface DegreePlannerState { visibleDegreePlan: DegreePlan | undefined; visibleYears: YearMapping; savedForLaterCourses: ReadonlyArray<SavedForLaterCourse>; - activeTermCodes: ReadonlyArray<TermCode>; allDegreePlans: ReadonlyArray<DegreePlan>; subjectDescriptions: SubjectCodesTo<SubjectDescription>; search: { visible: boolean; selectedTerm?: TermCode }; @@ -25,7 +24,6 @@ export const INITIAL_DEGREE_PLANNER_STATE: DegreePlannerState = { visibleDegreePlan: undefined, visibleYears: {}, savedForLaterCourses: [], - activeTermCodes: [], allDegreePlans: [], subjectDescriptions: {}, search: { visible: false }, diff --git a/src/app/degree-planner/term-container/term-container.component.html b/src/app/degree-planner/term-container/term-container.component.html index 343124bb86bf95ec47ec9a5fa39f1c0d10668721..2ca5f0a89dd15284eedd34e6a993a64ef55efc47 100644 --- a/src/app/degree-planner/term-container/term-container.component.html +++ b/src/app/degree-planner/term-container/term-container.component.html @@ -3,10 +3,10 @@ <div class="course-list"> <div class="course-list-inner"> <ng-container *ngIf="enrolledCourses.length === 0"> - <p *ngIf="(term$ | async).era === 'past'" class="no-courses"> + <p *ngIf="(term$ | async).termCode.isPast()" class="no-courses"> No courses taken </p> - <p *ngIf="(term$ | async).era === 'active'" class="no-courses"> + <p *ngIf="(term$ | async).termCode.isActive()" class="no-courses"> Not enrolled in any courses </p> </ng-container> @@ -39,13 +39,13 @@ <div class="course-list-inner term-body"> <ng-container *ngIf="plannedCourses.length === 0 && !hasItemDraggedOver"> - <p *ngIf="(term$ | async).era === 'active'" class="no-courses"> + <p *ngIf="(term$ | async).termCode.isActive()" class="no-courses"> No Courses in cart </p> - <p *ngIf="(term$ | async).era === 'future'" class="no-courses"> + <p *ngIf="(term$ | async).termCode.isFuture()" class="no-courses"> No courses planned </p> - <p *ngIf="(term$ | async).era === 'past'" class="no-courses"> + <p *ngIf="(term$ | async).termCode.isPast()" class="no-courses"> No courses planned </p> </ng-container> @@ -70,7 +70,7 @@ </div> <!-- Add course --> - <div class="add-new-wrapper" *ngIf="(term$ | async).era !== 'past'"> + <div class="add-new-wrapper" *ngIf="(term$ | async).termCode.isPast() == false"> <button mat-raised-button attr.aria-label="Add course to {{ termCode | getTermDescription }}" @@ -163,7 +163,7 @@ </div> <!-- If this term is an active term --> - <ng-container *ngIf="(term$ | async).era === 'active'"> + <ng-container *ngIf="(term$ | async).termCode.isActive()"> <mat-tab-group (selectedTabChange)="changeVisibleCredits($event)" [selectedIndex]="(enrolledCourses.length > 0) ? 0 : 1"> <mat-tab [label]="'In Progress (' + enrolledCourses.length + ')'" aria-label="In progress courses"> <ng-container cdkFocusinitial *ngTemplateOutlet="enrolled"></ng-container> @@ -175,7 +175,7 @@ </ng-container> <!-- If this term is a past term --> - <ng-container *ngIf="(term$ | async).era === 'past'"> + <ng-container *ngIf="(term$ | async).termCode.isPast()"> <mat-tab-group (selectedTabChange)="changeVisibleCredits($event)" [selectedIndex]="0"> <mat-tab [label]="'Completed (' + enrolledCourses.length + ')'" aria-label="Completed courses"> <ng-container cdkFocusinitial *ngTemplateOutlet="enrolled"></ng-container> @@ -187,7 +187,7 @@ </ng-container> <!-- If this term is a future term --> - <ng-container *ngIf="(term$ | async).era === 'future'"> + <ng-container *ngIf="(term$ | async).termCode.isFuture()"> <ng-container cdkFocusinitial *ngTemplateOutlet="planned"></ng-container> </ng-container> </mat-card> diff --git a/src/app/degree-planner/term-container/term-container.component.ts b/src/app/degree-planner/term-container/term-container.component.ts index 84eea72148f61a5487455582c55f1f4df02c5ffd..b21a889f0bc4ec54861dd5c42baa76bf7b27d4e5 100644 --- a/src/app/degree-planner/term-container/term-container.component.ts +++ b/src/app/degree-planner/term-container/term-container.component.ts @@ -16,7 +16,7 @@ import { NotesDialogData, } from '@app/degree-planner/dialogs/notes-dialog/notes-dialog.component'; import * as utils from '@app/degree-planner/shared/utils'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode, Era } from '@app/degree-planner/shared/term-codes/termcode'; import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component'; import { MediaMatcher } from '@angular/cdk/layout'; import { ConstantsService } from '../services/constants.service'; @@ -72,7 +72,6 @@ export class TermContainerComponent implements OnInit, OnDestroy { // 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; @@ -156,19 +155,14 @@ export class TermContainerComponent implements OnInit, OnDestroy { } 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 = term.era === 'past' ? 'enrolled' : 'planned'; + this.visibleCredits = + term.termCode.era === Era.Past ? 'enrolled' : 'planned'; }); this.note$ = this.term$.pipe( @@ -247,11 +241,12 @@ export class TermContainerComponent implements OnInit, OnDestroy { if (newContainer === previousContainer) { const newIndex = event.currentIndex; - const { id: recordId, termCode } = event.item.data as Course; + const termCode = event.container.data; + const { id: recordId } = event.item.data as Course; if (recordId !== null) { const action = new actions.MoveCourseInsideTerm({ - termCode: new TermCode(termCode), + termCode, recordId, newIndex, }); diff --git a/src/app/degree-planner/year-container/year-container.component.html b/src/app/degree-planner/year-container/year-container.component.html index ab9ed4a8913de9e3d0c7efbdd597cd84f19328f7..f6854171e5636ac8c75ce2d3dc06087c9b4cc06a 100644 --- a/src/app/degree-planner/year-container/year-container.component.html +++ b/src/app/degree-planner/year-container/year-container.component.html @@ -14,11 +14,11 @@ fxLayoutGap="20px" fxLayoutAlign="start stretch" class="term-container-wrapper"> - <cse-term-container fxFlex="33%" [termCode]="yearCode.fall"> + <cse-term-container fxFlex="33%" [termCode]="yearCode.fall()"> </cse-term-container> - <cse-term-container fxFlex="33%" [termCode]="yearCode.spring"> + <cse-term-container fxFlex="33%" [termCode]="yearCode.spring()"> </cse-term-container> - <cse-term-container fxFlex="33%" [termCode]="yearCode.summer"> + <cse-term-container fxFlex="33%" [termCode]="yearCode.summer()"> </cse-term-container> </div> </mat-expansion-panel> diff --git a/src/app/degree-planner/year-container/year-container.component.ts b/src/app/degree-planner/year-container/year-container.component.ts index 3c3064a3d81bdf7927057dd868e2ea07279f86b3..be7ab44185f71675e3dff7c551c0293934cca842 100644 --- a/src/app/degree-planner/year-container/year-container.component.ts +++ b/src/app/degree-planner/year-container/year-container.component.ts @@ -7,7 +7,7 @@ import { ExpandAcademicYear, CollapseAcademicYear, } from '@app/degree-planner/store/actions/ui.actions'; -import { YearCode } from '@app/core/models/termcode'; +import { YearCode } from '../shared/term-codes/yearcode'; @Component({ selector: 'cse-year-container', diff --git a/src/app/shared/components/course-details/course-details.component.html b/src/app/shared/components/course-details/course-details.component.html index c5b9bdd1ae66be89a3c716b6d9a24e6846e8c157..9cff49b386f4b56d394bab73ff336ec813074d3e 100644 --- a/src/app/shared/components/course-details/course-details.component.html +++ b/src/app/shared/components/course-details/course-details.component.html @@ -34,6 +34,7 @@ <form [formGroup]="termSelector" (ngSubmit)="addCourseToPlan($event)"> <mat-form-field style="margin-right:20px;"> <mat-select + [compareWith]="sameTermCodes" placeholder="Term" aria-label="Term" matInput diff --git a/src/app/shared/components/course-details/course-details.component.ts b/src/app/shared/components/course-details/course-details.component.ts index e6e31cd146dcd4aa88e67fb6ae66ebf65dc9fc11..ef6d419efb4c1cd90a66fedbc051b7a688e11995 100644 --- a/src/app/shared/components/course-details/course-details.component.ts +++ b/src/app/shared/components/course-details/course-details.component.ts @@ -3,14 +3,14 @@ import { Component, OnInit, OnDestroy, Inject, Input } from '@angular/core'; import { MAT_DIALOG_DATA } from '@angular/material'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Observable, Subscription } from 'rxjs'; -import { distinctUntilChanged, map, filter } from 'rxjs/operators'; +import { distinctUntilChanged, filter, take } from 'rxjs/operators'; import { MatDialog } from '@angular/material'; import { Store, select } from '@ngrx/store'; import { CourseDetails } from '@app/core/models/course-details'; import * as selectors from '@app/degree-planner/store/selectors'; import * as utils from '@app/degree-planner/shared/utils'; -import { TermCode } from '@app/core/models/termcode'; +import { TermCode } from '@app/degree-planner/shared/term-codes/termcode'; import { GlobalState } from '@app/core/state'; import { Course } from '@app/core/models/course'; import { PlannedTerm } from '@app/core/models/planned-term'; @@ -34,13 +34,13 @@ export class CourseDetailsComponent implements OnInit, OnDestroy { @Input() termCode: TermCode; public courseDetails: CourseDetails; public type: 'course' | 'search' | 'saved'; - public selectedSearchTerm: string; + public selectedSearchTerm: TermCode | undefined; public term$: Observable<PlannedTerm>; public termSelector: FormGroup; public mobileView: MediaQueryList; - public selectedSearchTerm$: Observable<string>; - public droppableTermCodes$: Observable<string[]>; + public selectedSearchTerm$: Observable<TermCode | undefined>; + public droppableTermCodes$: Observable<TermCode[]>; public searchTermSubscription: Subscription; public termSubscription: Subscription; public plannedCourses: ReadonlyArray<Course>; @@ -58,26 +58,16 @@ export class CourseDetailsComponent implements OnInit, OnDestroy { } ngOnInit() { - this.selectedSearchTerm$ = this.store.pipe( - select(selectors.getSelectedSearchTerm), - map(termCode => (termCode ? termCode.toString() : '0000')), - ); + this.searchTermSubscription = this.store + .select(selectors.getSelectedSearchTerm) + .pipe(take(1)) + .subscribe(term => (this.termSelector = this.fb.group({ term }))); this.droppableTermCodes$ = this.store.pipe( select(selectors.selectAllVisibleYears), utils.yearsToDroppableTermCodes(), - distinctUntilChanged(utils.compareStringArrays), - ); - - this.searchTermSubscription = this.selectedSearchTerm$.subscribe( - termCode => { - this.selectedSearchTerm = termCode; - }, + distinctUntilChanged(utils.compareArrays((a, b) => a.equals(b))), ); - - this.termSelector = this.fb.group({ - term: this.selectedSearchTerm, - }); } ngOnDestroy() { @@ -87,9 +77,14 @@ export class CourseDetailsComponent implements OnInit, OnDestroy { addCourseToPlan($event) { $event.preventDefault(); - const termCode = new TermCode(this.termSelector.value.term); + const termCode: TermCode | undefined = this.termSelector.value.term; const subjectCode = this.courseDetails.subject.subjectCode; const courseId = this.courseDetails.courseId; + + if (termCode === undefined) { + return; + } + const payload = { courseId, termCode, @@ -100,7 +95,7 @@ export class CourseDetailsComponent implements OnInit, OnDestroy { this.term$ = this.store.pipe( select(selectors.selectVisibleTerm, { - termCode: new TermCode(this.termSelector.value.term), + termCode, }), filter(isntUndefined), distinctUntilChanged(), @@ -117,7 +112,7 @@ export class CourseDetailsComponent implements OnInit, OnDestroy { this.dialog .open(ConfirmDialogComponent, { data: { - title: "Can't add course to term", + title: `Can't add course to term`, confirmText: 'OK', dialogClass: 'alertDialog', text: `This course already exists in selected term`, @@ -139,4 +134,14 @@ export class CourseDetailsComponent implements OnInit, OnDestroy { } } } + + public sameTermCodes(a: TermCode | undefined, b: TermCode | undefined) { + if (a === undefined && b === undefined) { + return true; + } else if (!a || !b) { + return false; + } else { + return a.equals(b); + } + } } diff --git a/src/app/shared/pipes/academic-year-state.pipe.ts b/src/app/shared/pipes/academic-year-state.pipe.ts index a4a132872ecfaa9c09efbf6538e7685cdbcbcc8c..6e22ae665cfcc38d922f6b609d39fdd7b56e4253 100644 --- a/src/app/shared/pipes/academic-year-state.pipe.ts +++ b/src/app/shared/pipes/academic-year-state.pipe.ts @@ -1,25 +1,17 @@ import { Pipe, PipeTransform } from '@angular/core'; -import * as utils from '@app/degree-planner/shared/utils'; -import { YearCode } from '@app/core/models/termcode'; -import { ConstantsService } from '@app/degree-planner/services/constants.service'; +import { YearCode } from '@app/degree-planner/shared/term-codes/yearcode'; @Pipe({ name: 'academicYearState' }) export class AcademicYearStatePipe implements PipeTransform { - constructor(private constants: ConstantsService) {} - - transform(yearCode: string | YearCode): string { - if (typeof yearCode === 'string') { - yearCode = new YearCode(yearCode); + transform(yearCode: YearCode): string { + if (yearCode.summer().isPast()) { + return `Past year: ${yearCode.fromYear}-${yearCode.toYear}`; } - const era = utils.pickYearEra(yearCode, this.constants.activeTermCodes()); - switch (era) { - case 'past': - return `Past year: ${yearCode.fromYear}-${yearCode.toYear}`; - case 'future': - return `Future year: ${yearCode.fromYear}-${yearCode.toYear}`; - default: - return `Active year: ${yearCode.fromYear}-${yearCode.toYear}`; + if (yearCode.fall().isFuture()) { + return `Future year: ${yearCode.fromYear}-${yearCode.toYear}`; } + + return `Active year: ${yearCode.fromYear}-${yearCode.toYear}`; } } diff --git a/src/app/shared/pipes/get-term-description.pipe.ts b/src/app/shared/pipes/get-term-description.pipe.ts index 917f1bb6bfda95c587d761a61cc63852f15d4ff1..21d62baf574c916eaa37db1d0a47c86eb32aee23 100644 --- a/src/app/shared/pipes/get-term-description.pipe.ts +++ b/src/app/shared/pipes/get-term-description.pipe.ts @@ -1,13 +1,11 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { TermCode } from '@app/core/models/termcode'; +import { RawTermCode } from '@app/degree-planner/shared/term-codes/without-era'; -@Pipe({ - name: 'getTermDescription', -}) +@Pipe({ name: 'getTermDescription' }) export class GetTermDescriptionPipe implements PipeTransform { - transform(termCode: string | TermCode): string { + transform(termCode: RawTermCode | string): string { if (typeof termCode === 'string') { - termCode = new TermCode(termCode); + termCode = new RawTermCode(termCode); } return termCode.description;