diff --git a/src/app/degree-planner/degree-planner.component.html b/src/app/degree-planner/degree-planner.component.html index e9d1a549420d7562a4671622165a8847d4a06483..404f0293048dbe9a539aa23bf3b104595d24453b 100644 --- a/src/app/degree-planner/degree-planner.component.html +++ b/src/app/degree-planner/degree-planner.component.html @@ -34,8 +34,8 @@ <mat-menu #degreePlanMenu="matMenu"> <button mat-menu-item (click)="onCreatePlanClick()">Create new plan</button> <button mat-menu-item (click)="onRenamePlanClick(degreePlan)">Rename plan</button> - <button mat-menu-item (click)="onDeletePlanClick(degreePlan)">Delete plan</button> - <button mat-menu-item (click)="onMakePrimayClick(degreePlan)">Make primary</button> + <button mat-menu-item (click)="onDeletePlanClick(degreePlan)" [disabled]="degreePlan.primary">Delete plan</button> + <button mat-menu-item (click)="onMakePrimayClick(degreePlan)" [disabled]="degreePlan.primary">Make primary</button> <hr> <button mat-menu-item (click)="onPrintPlanClick(degreePlan)">Print plan</button> <button mat-menu-item (click)="onSharePlanClick(degreePlan)">Share plan</button> diff --git a/src/app/degree-planner/degree-planner.component.ts b/src/app/degree-planner/degree-planner.component.ts index ac315541db6209c5b3c670c7e9cd8f9a0bd296ec..baaf6aac2ae56ef7ae95c62d66a8ca83d35039c9 100644 --- a/src/app/degree-planner/degree-planner.component.ts +++ b/src/app/degree-planner/degree-planner.component.ts @@ -1,4 +1,4 @@ -import { tap } from 'rxjs/operators'; +import { tap, withLatestFrom } from 'rxjs/operators'; // Libraries import { OnInit } from '@angular/core'; import { Observable } from 'rxjs'; @@ -13,6 +13,7 @@ import { MatDialog } from '@angular/material'; import { Store } from '@ngrx/store'; import { MediaMatcher } from '@angular/cdk/layout'; import { filter } from 'rxjs/operators'; +import { MatSnackBar } from '@angular/material'; // Models import { GlobalState } from '@app/core/state'; @@ -34,6 +35,7 @@ import { MakePlanPrimary, CreatePlan, ChangePlanName, + DeletePlan, } from '@app/degree-planner/store/actions/plan.actions'; import { @@ -62,6 +64,7 @@ export class DegreePlannerComponent implements OnInit { private store: Store<GlobalState>, public mediaMatcher: MediaMatcher, public dialog: MatDialog, + private snackBar: MatSnackBar, ) { this.mobileView = mediaMatcher.matchMedia('(max-width: 900px)'); } @@ -118,15 +121,44 @@ export class DegreePlannerComponent implements OnInit { }); } - public onMakePrimayClick(currentPlan: DegreePlan) { - // TODO - console.warn('onMakePrimayClick'); + public onMakePrimayClick(_currentPlan: DegreePlan) { + const data: DialogMode = { mode: 'makePrimary' }; + this.dialog + .open(ModifyPlanDialogComponent, { data }) + .afterClosed() + .subscribe((result: { areYouSure: true } | undefined) => { + if (result !== undefined && result.areYouSure === true) { + const action = new MakePlanPrimary(); + this.store.dispatch(action); + } + }); } - public onDeletePlanClick() { - // TODO - // open confirm dialog - console.log('onDeletePlanClick'); + public onDeletePlanClick(currentPlan: DegreePlan) { + if (currentPlan.primary) { + this.snackBar.open('The primary degree plan cannot be deleted'); + return; + } + + const data: DialogMode = { mode: 'delete', name: currentPlan.name }; + this.dialog + .open(ModifyPlanDialogComponent, { data }) + .afterClosed() + .pipe(withLatestFrom(this.store)) + .subscribe(([result, state]) => { + if (typeof result === 'object' && result.areYouSure === 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() { diff --git a/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.html b/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.html index 901be641ff6d05625be80822dd4f33186cc0e162..5b13369847c4796d6987cb2df29c9f464d335045 100644 --- a/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.html +++ b/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.html @@ -7,6 +7,8 @@ <mat-toolbar color="primary" class="dialog-toolbar"> <span class="dialog-toolbar-title" [ngSwitch]="data.mode"> + <span *ngSwitchCase="'makePrimary'">Set as the primary plan</span> + <span *ngSwitchCase="'delete'">Delete plan</span> <span *ngSwitchCase="'rename'">Rename plan</span> <span *ngSwitchCase="'create'">Create plan</span> </span> @@ -17,6 +19,12 @@ <mat-dialog-content class="mat-typography dialog-with-toolbar"> <ng-container [ngSwitch]="data.mode"> + <div class="delete-plan-content" *ngSwitchCase="'delete'"> + <mat-dialog-content> + <p>Are you sure you want to delete the degree plan named <strong>{{data.name}}</strong>?</p> + </mat-dialog-content> + </div> + <div class="rename-plan-content" *ngSwitchCase="'rename'"> <mat-dialog-content [formGroup]="form"> <mat-form-field class="form-field-stretch"> @@ -41,6 +49,8 @@ <button mat-rasied-button class="btn-primary mat-button" [disabled]="form.invalid" (click)="onSubmit()"> <ng-container [ngSwitch]="data.mode"> + <span *ngSwitchCase="'makePrimary'">Set as the primary plan</span> + <span *ngSwitchCase="'delete'">Delete this plan</span> <span *ngSwitchCase="'rename'">Rename plan</span> <span *ngSwitchCase="'create'">Create plan</span> </ng-container> diff --git a/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.ts b/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.ts index 15814424e3996b0f058cde5236f40160278253ba..bcc0321a2e33d35cbdb615518a8cfaef074b05af 100644 --- a/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.ts +++ b/src/app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component.ts @@ -3,6 +3,8 @@ import { Component, Input, Inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; export type DialogMode = + | { mode: 'makePrimary' } + | { mode: 'delete'; name: string } | { mode: 'rename'; oldName: string } | { mode: 'create' }; @@ -18,19 +20,28 @@ export class ModifyPlanDialogComponent { @Inject(MAT_DIALOG_DATA) public data: DialogMode, ) { const initialName = this.data.mode === 'rename' ? this.data.oldName : ''; - this.form = new FormGroup({ - planName: new FormControl(initialName, Validators.required), - }); + if (this.data.mode === 'rename' || this.data.mode === 'create') { + this.form = new FormGroup({ + planName: new FormControl(initialName, Validators.required), + }); + } else { + this.form = new FormGroup({}); + } } onSubmit() { switch (this.data.mode) { - case 'rename': - this.dialogRef.close({ name: `${this.form.value.planName}` }); + case 'makePrimary': + case 'delete': { + this.dialogRef.close({ areYouSure: true }); break; - case 'create': - this.dialogRef.close({ name: `${this.form.value.planName}` }); + } + case 'rename': + case 'create': { + const name = `${this.form.value.planName}`; + this.dialogRef.close({ name }); break; + } default: this.dialogRef.close(); } diff --git a/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.html b/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.html index f0160683835b06a45864d88bd53d9a8bb213eecb..06242c2ac3fe9880fa910ccec7644c2914b1d6ba 100644 --- a/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.html +++ b/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.html @@ -31,8 +31,8 @@ <div> <button mat-button class="sidenav-link-btn"><i class="material-icons">print</i>Print</button> <button mat-button class="sidenav-link-btn"><i class="material-icons">email</i> Share Plan</button> - <button mat-button class="sidenav-link-btn"><i class="material-icons">add_box</i> Add Degree Plan</button> + <button mat-button class="sidenav-link-btn" (click)="addDegreePlan()"><i class="material-icons">add_box</i> Add Degree Plan</button> <button mat-button class="sidenav-link-btn" (click)="addAcademicYear()"><i class="material-icons">add_box</i> Add Academic Year</button> </div> </mat-expansion-panel> -</div> \ No newline at end of file +</div> diff --git a/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.ts b/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.ts index de404e61fdc9bd0b21404f8bee256d26aa9ad340..6fa1ed91e4aac02935f3348d0f01979cf1f36679 100644 --- a/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.ts +++ b/src/app/degree-planner/sidenav-menu-item/sidenav-menu-item.component.ts @@ -1,21 +1,40 @@ // Libraries import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; +import { MatDialog } from '@angular/material'; // State management import { GlobalState } from '@app/core/state'; import { AddAcademicYearRequest } from '@app/degree-planner/store/actions/addAcademicYear.actions'; +import { + ModifyPlanDialogComponent, + DialogMode, +} from '@app/degree-planner/dialogs/modify-plan-dialog/modify-plan-dialog.component'; +import { CreatePlan } from '@app/degree-planner/store/actions/plan.actions'; @Component({ selector: 'cse-sidenav-menu-item', templateUrl: './sidenav-menu-item.component.html', styleUrls: ['./sidenav-menu-item.component.scss'], }) - export class SidenavMenuItemComponent { - constructor(private store: Store<GlobalState>) {} + constructor(private store: Store<GlobalState>, public dialog: MatDialog) {} public addAcademicYear() { this.store.dispatch(new AddAcademicYearRequest()); } + + public addDegreePlan() { + const data: DialogMode = { mode: 'create' }; + this.dialog + .open(ModifyPlanDialogComponent, { data }) + .afterClosed() + .subscribe((result: { name: string } | undefined) => { + if (result !== undefined && typeof result.name === 'string') { + const name = result.name; + const action = new CreatePlan({ name, primary: false }); + this.store.dispatch(action); + } + }); + } } diff --git a/src/app/degree-planner/store/reducer.ts b/src/app/degree-planner/store/reducer.ts index a239988ac51ad25e37074c7ad6d56dd85f7b3801..9e82926547961ac9331cdbbc8a14b73c03003579 100644 --- a/src/app/degree-planner/store/reducer.ts +++ b/src/app/degree-planner/store/reducer.ts @@ -12,8 +12,8 @@ import { MakePlanPrimaryFailure, ChangePlanNameSuccess, ChangePlanNameFailure, - CreatePlan, CreatePlanSuccess, + DeletePlanSuccess, } from '@app/degree-planner/store/actions/plan.actions'; import { CourseActionTypes, @@ -53,6 +53,7 @@ type SupportedActions = | MakePlanPrimaryFailure | ChangePlanNameSuccess | ChangePlanNameFailure + | DeletePlanSuccess | ToggleAcademicYear; export function degreePlannerReducer( @@ -347,6 +348,13 @@ export function degreePlannerReducer( }; } + case PlanActionTypes.DeletePlanSuccess: { + const allDegreePlans = state.allDegreePlans.filter(plan => { + return plan.roadmapId !== action.payload.roadmapId; + }); + return { ...state, allDegreePlans }; + } + /** * It's okay if the action didn't match any of the cases above. If that's * the case, just return the existing state object.