LP#1942220: (follow-up) catch unsaved LI copy changes
authorGalen Charlton <gmc@equinoxOLI.org>
Tue, 17 May 2022 19:38:18 +0000 (19:38 +0000)
committerGalen Charlton <gmc@equinoxOLI.org>
Tue, 17 May 2022 19:38:18 +0000 (19:38 +0000)
The interface now discourages letting leaving the LI copy
page if there are unsaved changes.

Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/acq/lineitem/batch-copies.component.html
Open-ILS/src/eg2/src/app/staff/acq/lineitem/batch-copies.component.ts
Open-ILS/src/eg2/src/app/staff/acq/lineitem/copies.component.html
Open-ILS/src/eg2/src/app/staff/acq/lineitem/copies.component.ts
Open-ILS/src/eg2/src/app/staff/acq/lineitem/copy-attrs.component.ts
Open-ILS/src/eg2/src/app/staff/acq/po/routing.module.ts

index 7359c24..a654c87 100644 (file)
@@ -51,6 +51,7 @@
       (deleteRequested)="deleteCopy($event)" 
       (cancelRequested)="cancelCopy($event)"
       [showReceiver]="!hasEditableCopies()"
+      (becameDirty)="becameDirty.emit(true)"
       [rowIndex]="idx + 1" [lineitem]="lineitem" [copy]="copy">
     </eg-lineitem-copy-attrs>
   </div>
index 370ed34..f312b43 100644 (file)
@@ -30,6 +30,8 @@ export class LineitemBatchCopiesComponent implements OnInit {
     @Input() lineitem: IdlObject;
     @Input() batchAdd = false;
 
+    @Output() becameDirty = new EventEmitter<Boolean>();
+
     @ViewChild('confirmAlertsDialog') confirmAlertsDialog: LineitemAlertDialogComponent;
     @ViewChild('cancelDialog') cancelDialog: CancelDialogComponent;
 
@@ -64,6 +66,7 @@ export class LineitemBatchCopiesComponent implements OnInit {
             this.lineitem.lineitem_details().forEach(copy => {
                 copy[field](val);
                 copy.ischanged(true); // isnew() takes precedence
+                this.becameDirty.emit(true);
             });
         });
     }
@@ -77,6 +80,7 @@ export class LineitemBatchCopiesComponent implements OnInit {
         } else {
             // Requires a Save Changes action.
             copy.isdeleted(true);
+            this.becameDirty.emit(true);
         }
     }
 
index 380336b..d9301f1 100644 (file)
@@ -1,4 +1,11 @@
 <h3 *ngIf="mode !== 'multiAdd'" class="mt-3" i18n>Items for Line Item {{lineitem.id()}} ({{getTitle(lineitem)}})</h3>  
+
+<eg-confirm-dialog #leaveConfirm
+  i18n-dialogTitle i18n-dialogBody
+  dialogTitle="Unsaved Changes Warning"
+  dialogBody="There are unsaved changes. Are you sure you want to leave?">
+</eg-confirm-dialog>
+
 <div class="row mt-3 mb-1">
   <div class="col-lg-12 form-inline">
 
     </div>
   </div>
 
-  <eg-lineitem-batch-copies [lineitem]="lineitem" [batchAdd]="mode === 'multiAdd'"></eg-lineitem-batch-copies>
+  <eg-lineitem-batch-copies
+    [lineitem]="lineitem" [batchAdd]="mode === 'multiAdd'"
+    (becameDirty)="dirty = true"
+  ></eg-lineitem-batch-copies>
 </ng-container>
 
 
index 3ac060a..d644483 100644 (file)
@@ -1,6 +1,7 @@
 import {Component, OnInit, AfterViewInit, Input, Output, EventEmitter,
   ViewChild} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {Observable, of} from 'rxjs';
 import {tap} from 'rxjs/operators';
 import {Pager} from '@eg/share/util/pager';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
@@ -11,6 +12,7 @@ import {AuthService} from '@eg/core/auth.service';
 import {LineitemService, FleshCacheParams} from './lineitem.service';
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 import {ItemLocationService} from '@eg/share/item-location-select/item-location-select.service';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
 
 const FORMULA_FIELDS = [
     'owning_lib',
@@ -49,6 +51,7 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
     batchOwningLib: IdlObject;
     batchFund: ComboboxEntry;
     batchCopyLocId: number;
+    dirty = false;
     saving = false;
     progressMax = 0;
     progressValue = 0;
@@ -59,6 +62,8 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
     // Can any changes be applied?
     liLocked = false;
 
+    @ViewChild('leaveConfirm', { static: true }) leaveConfirm: ConfirmDialogComponent;
+
     constructor(
         private route: ActivatedRoute,
         private idl: IdlService,
@@ -135,6 +140,7 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
             copy.isnew(true);
             copy.lineitem(this.lineitem.id());
             copies.push(copy);
+            this.dirty = true;
         }
 
         if (copies.length > this.copyCount) {
@@ -195,6 +201,7 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
         if (this.mode === 'multiAdd') {
             app.isnew(true);
             this.lineitem.distribution_formulas().push(app);
+            this.dirty = true;
         } else {
             this.pcrud.create(app).toPromise().then(a => {
                 a.creator(this.auth.user());
@@ -262,6 +269,7 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
 
             } else {
                 copy[field](val);
+                this.dirty = true;
             }
         });
 
@@ -282,6 +290,7 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
             () => this.load({toCache: true}).then(_ => {
                 this.liService.activateStateChange.emit(this.lineitem.id());
                 this.saving = false;
+                this.dirty = false;
             })
         );
     }
@@ -298,6 +307,14 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
     getTitle(li: IdlObject): string {
         return this.liService.getFirstAttributeValue(li, 'title');
     }
+
+    canDeactivate(): Observable<boolean> {
+        if (this.dirty) {
+            return this.leaveConfirm.open();
+        } else {
+            return of(true);
+        }
+    }
 }
 
 
index b85c40e..b7359d2 100644 (file)
@@ -20,6 +20,8 @@ export class LineitemCopyAttrsComponent implements OnInit {
     @Input() rowIndex: number;
     @Input() batchAdd = false;
 
+    @Output() becameDirty = new EventEmitter<Boolean>();
+
     fundEntries: ComboboxEntry[];
     circModEntries: ComboboxEntry[];
 
@@ -100,6 +102,17 @@ export class LineitemCopyAttrsComponent implements OnInit {
 
         const announce: any = {};
         this.copy.ischanged(true);
+        if (!this.batchMode) {
+            if (field !== 'owning_lib') {
+                this.becameDirty.emit(true);
+            } else {
+                // FIXME eg-org-select current send needless change
+                //       events, so we need to check
+                if (entry && this.copy[field]() !== entry.id()) {
+                    this.becameDirty.emit(true);
+                }
+            }
+        }
 
         switch (field) {
 
index d294a53..8f3508d 100644 (file)
@@ -1,5 +1,8 @@
-import {NgModule} from '@angular/core';
+import {NgModule, Injectable} from '@angular/core';
 import {RouterModule, Routes} from '@angular/router';
+import {Router, Resolve, RouterStateSnapshot,
+        ActivatedRouteSnapshot, CanDeactivate} from '@angular/router';
+import {Observable} from 'rxjs';
 import {PoComponent} from './po.component';
 import {PrintComponent} from './print.component';
 import {PoSummaryComponent} from './summary.component';
@@ -14,6 +17,18 @@ import {PoHistoryComponent} from './history.component';
 import {PoEdiMessagesComponent} from './edi.component';
 import {PoCreateComponent} from './create.component';
 
+// following example of https://www.concretepage.com/angular-2/angular-candeactivate-guard-example
+export interface PoChildDeactivationGuarded {
+    canDeactivate(): Observable<boolean> | Promise<boolean> | boolean;
+}
+
+@Injectable()
+export class CanLeavePoChildGuard implements CanDeactivate<PoChildDeactivationGuarded> {
+    canDeactivate(component: PoChildDeactivationGuarded):  Observable<boolean> | Promise<boolean> | boolean {
+        return component.canDeactivate ? component.canDeactivate() : true;
+    }
+}
+
 const routes: Routes = [{
   path: 'create',
   component: PoCreateComponent
@@ -43,7 +58,8 @@ const routes: Routes = [{
     component: LineitemHistoryComponent
   }, {
     path: 'lineitem/:lineitemId/items',
-    component: LineitemCopiesComponent
+    component: LineitemCopiesComponent,
+    canDeactivate: [CanLeavePoChildGuard]
   }, {
     path: 'lineitem/:lineitemId/worksheet',
     component: LineitemWorksheetComponent
@@ -62,7 +78,7 @@ const routes: Routes = [{
 @NgModule({
   imports: [RouterModule.forChild(routes)],
   exports: [RouterModule],
-  providers: []
+  providers: [CanLeavePoChildGuard]
 })
 
 export class PoRoutingModule {}