import { filter, map, withLatestFrom, distinctUntilChanged, } from 'rxjs/operators'; import { OnInit, OnDestroy } from '@angular/core'; import { Observable, of, Subscription } from 'rxjs'; import { select } from '@ngrx/store'; import { Component } from '@angular/core'; import { MatSelectChange } from '@angular/material'; import { MatDialog } from '@angular/material'; import { Store } from '@ngrx/store'; import { MediaMatcher } from '@angular/cdk/layout'; import { MatSnackBar } from '@angular/material'; import { GlobalState } from '@app/core/state'; import { DegreePlan } from '@app/core/models/degree-plan'; import { Year } from '@app/core/models/year'; import * as selectors from '@app/degree-planner/store/selectors'; import * as utils from '@app/degree-planner/shared/utils'; // Actions import { SwitchPlan, MakePlanPrimary, CreatePlan, ChangePlanName, DeletePlan, } from '@app/degree-planner/store/actions/plan.actions'; import { PromptDialogComponent } from '@app/shared/dialogs/prompt-dialog/prompt-dialog.component'; import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component'; import { ToggleAcademicYear, CloseCourseSearch, } from './store/actions/ui.actions'; // Pipes import { GetTermDescriptionPipe } from '@app/shared/pipes/get-term-description.pipe'; @Component({ selector: 'cse-degree-planner', templateUrl: './degree-planner.component.html', styleUrls: ['./degree-planner.component.scss'], }) export class DegreePlannerComponent implements OnInit, OnDestroy { public termsByAcademicYear: Object; public mobileView: MediaQueryList; public coursesData$: any; public degreePlan$: Observable<DegreePlan | undefined>; public allDegreePlans$: Observable<DegreePlan[]>; public firstActiveTermCode$: Observable<string | undefined>; public termsByYear$: Observable<Year[]>; public yearCodes$: Observable<string[]>; public isCourseSearchOpen$: Observable<boolean>; public isLoadingPlan$: Observable<boolean>; public activeTermSubscription: Subscription; public firstActiveTerm: string; constructor( private store: Store<GlobalState>, public mediaMatcher: MediaMatcher, public dialog: MatDialog, private snackBar: MatSnackBar, ) { this.mobileView = mediaMatcher.matchMedia('(max-width: 900px)'); } public ngOnInit() { this.degreePlan$ = this.store.pipe( select(selectors.selectVisibleDegreePlan), filter(isntUndefined), ); this.allDegreePlans$ = this.store.pipe( select(selectors.selectAllDegreePlans), ); // Get observable for the search open state this.isCourseSearchOpen$ = this.store.pipe( select(selectors.isCourseSearchOpen), ); this.isLoadingPlan$ = this.store.pipe(select(selectors.isLoadingPlan)); this.yearCodes$ = this.store.pipe( select(selectors.selectAllVisibleYears), map(years => Object.keys(years)), distinctUntilChanged(utils.compareStringArrays), ); this.activeTermSubscription = this.store .pipe(select(selectors.getActiveTerms)) .subscribe(terms => { this.firstActiveTerm = terms[0]; }); } // Unsubscribe from subs to prevent memeory leaks public ngOnDestroy() { this.activeTermSubscription.unsubscribe(); } public handleAcademicYearToggle(year: Year): void { // this.store.dispatch( // new ToggleAcademicYear({ year: year.twoDigitYearCode.toString() }), // ); } public handleDegreePlanChange(event: MatSelectChange): void { if (typeof event.value === 'number') { this.store.dispatch(new SwitchPlan({ newVisibleRoadmapId: event.value })); } } public onCreatePlanClick() { this.dialog .open(PromptDialogComponent, { data: { initialValue: name, title: 'Add degree plan', confirmText: 'Save', inputName: 'i.e. Psychology', }, }) .afterClosed() .subscribe((result: { confirmed: boolean; value: string }) => { const { confirmed, value } = result; if (confirmed) { const action = new CreatePlan({ name: value, primary: false }); this.store.dispatch(action); } }); } public onRenamePlanClick(currentPlan: DegreePlan) { this.dialog .open(PromptDialogComponent, { data: { initialValue: currentPlan.name, title: 'Rename plan', confirmText: 'Save', inputName: 'Plan name', }, }) .afterClosed() .subscribe((result: { confirmed: boolean; value: string }) => { const { confirmed, value } = result; if (confirmed) { const { roadmapId } = currentPlan; const action = new ChangePlanName({ roadmapId, newName: value }); this.store.dispatch(action); } }); } public onMakePrimayClick(currentPlan: DegreePlan) { const currentTerm: any = new GetTermDescriptionPipe().transform( this.firstActiveTerm, ); this.dialog .open(ConfirmDialogComponent, { data: { title: 'Are you sure?', confirmText: 'Change plan', text: `This will change your primary plan and replace the current courses in your cart with the courses in this plan's ${currentTerm} term`, }, }) .afterClosed() .subscribe((result: { confirmed: true } | undefined) => { if (result !== undefined && result.confirmed === true) { const action = new MakePlanPrimary(); this.store.dispatch(action); } }); } public onDeletePlanClick(currentPlan: DegreePlan) { if (currentPlan.primary) { this.snackBar.open('The primary degree plan cannot be deleted'); return; } this.dialog .open(ConfirmDialogComponent, { data: { title: 'Are you sure?', confirmText: 'Delete', text: 'This will delete this plan and course information related to this plan.', }, }) .afterClosed() .pipe(withLatestFrom(this.store)) .subscribe(([result, state]) => { if (typeof result === 'object' && result.confirmed === true) { const { roadmapId } = currentPlan; const deleteAction = new DeletePlan({ roadmapId }); this.store.dispatch(deleteAction); const primaryPlan = state.degreePlanner.allDegreePlans.find(plan => { return plan.primary; }) as DegreePlan; const newVisibleRoadmapId = primaryPlan.roadmapId; const switchPlanAction = new SwitchPlan({ newVisibleRoadmapId }); this.store.dispatch(switchPlanAction); } }); } public onPrintPlanClick() { window.print(); } public onSharePlanClick() { // TODO console.log('onSharePlanClick'); } public getTermDropZone() { const termCodes = ['favoriteCourse-dropZone']; for (const yearCode in this.termsByAcademicYear) { if (this.termsByAcademicYear[yearCode]) { const year = this.termsByAcademicYear[yearCode]; for (const termKey in year.terms) { if (year.terms[termKey]) { const term = year.terms[termKey]; termCodes.push('term-' + term.termCode); } } } } // console.log(termCodes); return termCodes; } public closeCourseSearch() { this.store.dispatch(new CloseCourseSearch()); } } const isntUndefined = <T>(anything: T | undefined): anything is T => { return anything !== undefined; };