staff portal: implement clone action
authorGalen Charlton <gmc@equinoxOLI.org>
Wed, 1 Sep 2021 20:37:55 +0000 (16:37 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Wed, 1 Sep 2021 20:37:55 +0000 (16:37 -0400)
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/admin/local/admin-local.module.ts
Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/clone-portal-entries-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/clone-portal-entries-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/staff-portal-page.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/staff-portal-page.component.ts

index 099b071..2fff294 100644 (file)
@@ -6,6 +6,7 @@ import {AdminCommonModule} from '@eg/staff/admin/common.module';
 import {AdminLocalSplashComponent} from './admin-local-splash.component';
 import {AddressAlertComponent} from './address-alert.component';
 import {AdminCarouselComponent} from './admin-carousel.component';
+import {ClonePortalEntriesDialogComponent} from './staff_portal_page/clone-portal-entries-dialog.component';
 import {AdminStaffPortalPageComponent} from './staff_portal_page/staff-portal-page.component';
 import {StandingPenaltyComponent} from './standing-penalty.component';
 
@@ -15,6 +16,7 @@ import {StandingPenaltyComponent} from './standing-penalty.component';
       AddressAlertComponent,
       AdminCarouselComponent,
       StandingPenaltyComponent,
+      ClonePortalEntriesDialogComponent,
       AdminStaffPortalPageComponent
   ],
   imports: [
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/clone-portal-entries-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/clone-portal-entries-dialog.component.html
new file mode 100644 (file)
index 0000000..322c036
--- /dev/null
@@ -0,0 +1,50 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h3 class="modal-title" i18n>Clone a Library's Portal Page Entries</h3>
+    <button type="button" class="close"
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body">
+    <form #cloneForm="ngForm" role="form" class="form-validated common-form striped-odd">
+      <div class="form-group row mt-2">
+        <label for="source_library" class="col-sm-6 col-form-label" i18n>Source Library</label>
+        <div class="col-sm-6">
+          <eg-org-select
+            placeholder="Source Library..."
+            domId="source_library"
+            i18n-placeholder
+            [limitPerms]="['STAFF_LOGIN']"
+            (onChange)="result.source_library = $event.id(); cloneForm.form.markAsDirty()">
+          </eg-org-select>
+        </div>
+      </div>
+      <div class="form-group row mt-2">
+        <label for="target_library" class="col-sm-6 col-form-label" i18n>Target Library</label>
+        <div class="col-sm-6">
+          <eg-org-select
+            placeholder="Target Library..."
+            domId="target_library"
+            i18n-placeholder
+            [limitPerms]="['ADMIN_STAFF_PORTAL_PAGE']"
+            (onChange)="result.target_library = $event.id(); cloneForm.form.markAsDirty();">
+          </eg-org-select>
+        </div>
+      </div>
+      <div class="form-group row mt-2">
+        <label for="overwrite_target" class="col-sm-6 col-form-label" i18n>Clear Entries at Target Library?</label>
+        <div class="col-sm-6">
+          <input type="checkbox" id="overwrite_target" name="overwrite_target" [(ngModel)]="result.overwrite_target" />
+        </div>
+      </div>
+    </form>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-info"
+      [disabled]="!result.source_library || !result.target_library || (result.target_library === result.source_library)"
+      (click)="close(result)" i18n>Clone</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Close</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/clone-portal-entries-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/staff_portal_page/clone-portal-entries-dialog.component.ts
new file mode 100644 (file)
index 0000000..f630e39
--- /dev/null
@@ -0,0 +1,34 @@
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgForm, NG_VALIDATORS} from '@angular/forms';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+  selector: 'eg-clone-portal-entries-dialog',
+  templateUrl: './clone-portal-entries-dialog.component.html'
+})
+
+export class ClonePortalEntriesDialogComponent
+  extends DialogComponent implements OnInit {
+
+    result = { };
+
+    constructor(
+        private modal: NgbModal
+    ) {
+        super(modal);
+    }
+
+    ngOnInit() {
+        this.onOpen$.subscribe(() => this._initRecord());
+    }
+
+    private _initRecord() {
+        this.result = {
+            source_library: null,
+            target_library: null,
+            overwrite_target: false
+        }
+    }
+
+}
index ecfd722..043615b 100644 (file)
 <ng-template #createErrStrTmpl i18n>Failed to create new {{idlClassDef.label}}</ng-template>
 <eg-string #createErrString [template]="createErrStrTmpl"></eg-string>
 
+<eg-string #cloneSuccessString i18n-text text="Portal Page Entries Cloning Succeeded"></eg-string>
+<eg-string #cloneFailedString i18n-text text="Portal Page Entries Cloning Failed"></eg-string>
+
+<eg-clone-portal-entries-dialog #cloneDialog></eg-clone-portal-entries-dialog>
+
 <ng-container *ngIf="orgField || gridFilters">
   <div class="row">
     <div class="col-lg-6">
@@ -59,6 +64,9 @@
   <eg-grid-toolbar-button [disabled]="!canCreate" 
     label="New {{idlClassDef.label}}" i18n-label (onClick)="createNew()">
   </eg-grid-toolbar-button>
+  <eg-grid-toolbar-button [disabled]="!canCreate" 
+    label="Clone a Library's Portal Page Entries" i18n-label (onClick)="cloneEntries()">
+  </eg-grid-toolbar-button>
   <eg-grid-toolbar-button [disabled]="translatableFields.length == 0" 
     label="Apply Translations" i18n-label (onClick)="translate()">
   </eg-grid-toolbar-button>
index e312249..574f540 100644 (file)
@@ -13,6 +13,8 @@ import {NetService} from '@eg/core/net.service';
 import {GridCellTextGenerator} from '@eg/share/grid/grid';
 import {StringComponent} from '@eg/share/string/string.component';
 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {ClonePortalEntriesDialogComponent} from './clone-portal-entries-dialog.component';
+import {merge, Observable, empty} from 'rxjs';
 
 @Component({
     templateUrl: './staff-portal-page.component.html'
@@ -31,6 +33,9 @@ export class AdminStaffPortalPageComponent extends AdminPageComponent implements
 
     @ViewChild('refreshString', { static: true }) refreshString: StringComponent;
     @ViewChild('refreshErrString', { static: true }) refreshErrString: StringComponent;
+    @ViewChild('cloneSuccessString', { static: true }) cloneSuccessString: StringComponent;
+    @ViewChild('cloneFailedString', { static: true }) cloneFailedString: StringComponent;
+    @ViewChild('cloneDialog', { static: true}) cloneDialog: ClonePortalEntriesDialogComponent;
 
     constructor(
         route: ActivatedRoute,
@@ -54,4 +59,48 @@ export class AdminStaffPortalPageComponent extends AdminPageComponent implements
         this.defaultNewRecord.owner(this.auth.user().ws_ou());
     }
 
+    cloneEntries() {
+        this.cloneDialog.open().subscribe(
+            result => {
+                this._handleClone(result.source_library, result.target_library, result.overwrite_target);
+            }
+        );
+    }
+
+    _handleClone(src: number, tgt: number, overwrite: Boolean) {
+        const updates: IdlObject[] = [];
+
+        const delObs = (overwrite) ?
+            this.pcrud.search('cusppe', { owner: tgt }, {}, {}) :
+            empty();
+        const newObs = this.pcrud.search('cusppe', { owner: src }, {}, {});
+        merge(delObs, newObs).subscribe(
+            entry => {
+                if (entry.owner() === tgt) {
+                    entry.isdeleted(true);
+                } else {
+                    entry.owner(tgt);
+                    entry.id(null);
+                    entry.isnew(true);
+                }
+                updates.push(entry);
+            },
+            err => {},
+        ).add(() => {
+            this.pcrud.autoApply(updates).subscribe(
+                val => {},
+                err => {
+                    this.cloneFailedString.current()
+                        .then(str => this.toast.danger(str));
+                },
+                () => {
+                    this.cloneSuccessString.current()
+                        .then(str => this.toast.success(str));
+                    this.searchOrgs = {primaryOrgId: tgt}; // change the org filter to the
+                                                           // the one we just cloned into
+                    this.grid.reload();
+                }
+            );
+        });
+    }
 }