From 8ff41548cb6a38e2a70dfd11fde6e77af751b6d1 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Mon, 31 Aug 2020 22:40:52 -0400 Subject: [PATCH] LH#17: warn if attempting to leave provider edits in flight Signed-off-by: Galen Charlton --- .../staff/acq/provider/acq-provider.component.html | 8 ++++- .../staff/acq/provider/acq-provider.component.ts | 36 ++++++++++++++++++---- .../acq/provider/provider-details.component.ts | 4 +++ .../acq/provider/provider-holdings.component.ts | 14 ++++++++- .../staff/acq/provider/provider-record.service.ts | 2 ++ .../src/app/staff/acq/provider/resolver.service.ts | 14 ++++++++- .../src/app/staff/acq/provider/routing.module.ts | 5 +-- 7 files changed, 72 insertions(+), 11 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.html b/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.html index a480646420..c96092f949 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.html +++ b/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.html @@ -3,6 +3,12 @@ + + +
@@ -92,7 +98,7 @@ (click)="setDefaultTab()" i18n>Set Default View
- + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.ts index a3aa44fe96..708819ca1b 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/provider/acq-provider.component.ts @@ -1,6 +1,6 @@ import {Component, OnInit, AfterViewInit, ViewChild, ChangeDetectorRef, OnDestroy} from '@angular/core'; import {filter, takeUntil} from 'rxjs/operators'; -import {Subject} from 'rxjs'; +import {Subject, Observable, of} from 'rxjs'; import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap'; import {Router, ActivatedRoute, ParamMap, RouterEvent, NavigationEnd} from '@angular/router'; import {StaffCommonModule} from '@eg/staff/common.module'; @@ -8,6 +8,7 @@ import {IdlService, IdlObject} from '@eg/core/idl.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {AcqProviderSummaryPaneComponent} from './summary-pane.component'; import {ProviderDetailsComponent} from './provider-details.component'; +import {ProviderHoldingsComponent} from './provider-holdings.component'; import {ProviderResultsComponent} from './provider-results.component'; import {ProviderRecordService} from './provider-record.service'; import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component'; @@ -15,6 +16,7 @@ import {StringComponent} from '@eg/share/string/string.component'; import {ToastService} from '@eg/share/toast/toast.service'; import {AuthService} from '@eg/core/auth.service'; import {StoreService} from '@eg/core/store.service'; +import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; @Component({ templateUrl: './acq-provider.component.html' @@ -30,9 +32,11 @@ export class AcqProviderComponent implements OnInit, AfterViewInit { @ViewChild('acqSearchProviderSummary', { static: true }) providerSummaryPane: AcqProviderSummaryPaneComponent; @ViewChild('acqProviderResults', { static: true }) acqProviderResults: ProviderResultsComponent; @ViewChild('providerDetails', { static: false }) providerDetails: ProviderDetailsComponent; + @ViewChild('providerHoldings', { static: false }) providerHoldings: ProviderHoldingsComponent; @ViewChild('createDialog', { static: true }) createDialog: FmRecordEditorComponent; @ViewChild('createString', { static: false }) createString: StringComponent; @ViewChild('createErrString', { static: false }) createErrString: StringComponent; + @ViewChild('leaveConfirm', { static: true }) leaveConfirm: ConfirmDialogComponent; onTabChange: ($event: NgbTabChangeEvent) => void; @@ -41,6 +45,7 @@ export class AcqProviderComponent implements OnInit, AfterViewInit { previousUrl: string = null; public destroyed = new Subject(); + _alreadyDeactivated = false; constructor( private router: Router, @@ -99,11 +104,16 @@ export class AcqProviderComponent implements OnInit, AfterViewInit { } this.onTabChange = ($event) => { - if (this.validTabTypes.includes($event.nextId)) { - this.activeTab = $event.nextId; - const id = this.route.snapshot.paramMap.get('id'); - this.router.navigate(['/staff', 'acq', 'provider', this.id, $event.nextId]); - } + $event.preventDefault(); + this.canDeactivate().subscribe(canLeave => { + if (!canLeave) { return; } + this._alreadyDeactivated = true; // don't trigger again on the route change + if (this.validTabTypes.includes($event.nextId)) { + this.activeTab = $event.nextId; + const id = this.route.snapshot.paramMap.get('id'); + this.router.navigate(['/staff', 'acq', 'provider', this.id, $event.nextId]); + } + }); }; this.onDesireSummarize = ($event, updateSummaryOnly = false) => { @@ -165,4 +175,18 @@ export class AcqProviderComponent implements OnInit, AfterViewInit { } ); } + + canDeactivate(): Observable { + if (this._alreadyDeactivated) { + // one freebie + this._alreadyDeactivated = false; + return of(true); + } + if ((this.providerHoldings && this.providerHoldings.isDirty()) || + (this.providerDetails && this.providerDetails.isDirty())) { + return this.leaveConfirm.open(); + } else { + return of(true); + } + } } diff --git a/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-details.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-details.component.ts index df3158cb70..2261a16e97 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-details.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-details.component.ts @@ -20,6 +20,7 @@ export class ProviderDetailsComponent implements OnInit { @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent; @ViewChild('deleteFailedString', { static: true }) deleteFailedString: StringComponent; @ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent; + @ViewChild('editDialog', { static: false}) editDialog: FmRecordEditorComponent; provider: IdlObject; @@ -75,4 +76,7 @@ export class ProviderDetailsComponent implements OnInit { return this.providerRecord.currentProviderRecord().canAdmin ? 'update' : 'view'; } + isDirty(): boolean { + return (this.editDialog) ? this.editDialog.isDirty() : false; + } } diff --git a/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-holdings.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-holdings.component.ts index d79a954d74..8923c9469d 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-holdings.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-holdings.component.ts @@ -1,4 +1,5 @@ import {Component, OnInit, AfterViewInit, OnDestroy, Input, ViewChild} from '@angular/core'; +import {NgForm} from '@angular/forms'; import {empty, throwError, Observable, from, Subscription} from 'rxjs'; import {map} from 'rxjs/operators'; import {Router, ActivatedRoute, ParamMap} from '@angular/router'; @@ -35,6 +36,7 @@ export class ProviderHoldingsComponent implements OnInit, AfterViewInit { @ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent; @ViewChild('successTagString', { static: true }) successTagString: StringComponent; @ViewChild('updateFailedTagString', { static: false }) updateFailedTagString: StringComponent; + @ViewChild('holdingTagForm', { static: false}) holdingTagForm: NgForm; cellTextGenerator: GridCellTextGenerator; provider: IdlObject; @@ -94,7 +96,12 @@ export class ProviderHoldingsComponent implements OnInit, AfterViewInit { } ngAfterViewInit() { - console.log('this.providerRecord',this.providerRecord); + if (this.providerRecord.current()) { + // sometimes needs to force a refresh in case we updated that tag, + // navigated away (and confirmed that we wanted to abandon the change), + // then navigated back + this.providerRecord.current()['_holding_tag'] = this.providerRecord.current().holding_tag(); + } } @@ -209,4 +216,9 @@ export class ProviderHoldingsComponent implements OnInit, AfterViewInit { } ); } + + isDirty() : boolean { + return (this.providerRecord.current()['_holding_tag'] == this.providerRecord.current().holding_tag()) ? false : + (this.holdingTagForm && this.holdingTagForm.dirty) ? this.holdingTagForm.dirty : false; + } } diff --git a/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-record.service.ts b/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-record.service.ts index be775f0132..e16a9d539a 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-record.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/provider/provider-record.service.ts @@ -72,6 +72,8 @@ export class ProviderRecordService { {} ).pipe(map(acqpro => { const provider = new ProviderRecord(acqpro); + // make a copy of holding_tag for use by the holdings definitions tab + acqpro['_holding_tag'] = acqpro.holding_tag(); acqpro.edi_accounts().forEach(acct => { acct['_is_default'] = false; if (acqpro.edi_default()) { diff --git a/Open-ILS/src/eg2/src/app/staff/acq/provider/resolver.service.ts b/Open-ILS/src/eg2/src/app/staff/acq/provider/resolver.service.ts index f1f0de5505..30200916c1 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/provider/resolver.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/provider/resolver.service.ts @@ -1,6 +1,7 @@ import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; import {Router, Resolve, RouterStateSnapshot, - ActivatedRouteSnapshot} from '@angular/router'; + ActivatedRouteSnapshot, CanDeactivate} from '@angular/router'; import {ProviderRecordService} from './provider-record.service'; @Injectable() @@ -36,3 +37,14 @@ export class ProviderResolver implements Resolve> { } +// following example of https://www.concretepage.com/angular-2/angular-candeactivate-guard-example +export interface DeactivationGuarded { + canDeactivate(): Observable | Promise | boolean; +} + +@Injectable() +export class CanLeaveAcqProviderGuard implements CanDeactivate { + canDeactivate(component: DeactivationGuarded): Observable | Promise | boolean { + return component.canDeactivate ? component.canDeactivate() : true; + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/provider/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/provider/routing.module.ts index 8bd2b6a9c8..c8e0686816 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/provider/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/provider/routing.module.ts @@ -1,7 +1,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; import {AcqProviderComponent} from './acq-provider.component'; -import {ProviderResolver} from './resolver.service'; +import {ProviderResolver, CanLeaveAcqProviderGuard} from './resolver.service'; const routes: Routes = [ { path: '', @@ -16,6 +16,7 @@ const routes: Routes = [ { path: ':id/:tab', component: AcqProviderComponent, resolve: { providerResolver : ProviderResolver }, + canDeactivate: [CanLeaveAcqProviderGuard], runGuardsAndResolvers: 'always' } ]; @@ -23,7 +24,7 @@ const routes: Routes = [ @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], - providers: [ProviderResolver] + providers: [ProviderResolver, CanLeaveAcqProviderGuard] }) export class AcqProviderRoutingModule {} -- 2.11.0