attr sets: functionally complete
authorGalen Charlton <gmc@equinoxinitiative.org>
Sun, 28 Mar 2021 01:24:27 +0000 (21:24 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Sun, 28 Mar 2021 01:24:27 +0000 (21:24 -0400)
Note the TODO about the onRowActivate subscription, which will require
a change to the base component

Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.component.html
Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.component.ts
Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.module.ts

diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.html
new file mode 100644 (file)
index 0000000..81e7cc3
--- /dev/null
@@ -0,0 +1,46 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h3 *ngIf="mode === 'create'" class="modal-title" i18n>New EDI Attribute Set</h3>
+    <h3 *ngIf="mode === 'update'" class="modal-title" i18n>Modify EDI Attribute Set</h3>
+    <h3 *ngIf="mode === 'clone'" class="modal-title" i18n>Clone EDI Attribute Set (from {{clonedLabel}})</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 #myForm="ngForm" role="form" class="form-validated">
+      <div class="form-group row mt-2" *ngIf="attrSet">
+        <label for="attr-set-label" class="col-sm-3 col-form-label" i18n>Attribute Set Label
+        </label>
+        <div class="col-sm-3">
+          <input class="form-control" type="text" id="attr-set-label"
+            required="required"
+            [ngModel]="attrSet.label()" name="label"
+            (ngModelChange)="attrSet.label($event)">
+        </div>
+      </div>
+      <table class="table table-striped table-sm col-lg-10 offset-lg-1">
+        <thead>
+          <tr>
+            <td>&nbsp;</td>
+            <td i81n>Attribute</td>
+            <td i81n>Description</td>
+          </tr>
+        </thead>
+        <tr *ngFor="let inp of attrInputs">
+          <td><input type="checkbox" [(ngModel)]="inp.selected"
+               id="attr-map-{{inp.key}}" name="attr-map-{{inp.key}}"></td>
+          <td><label for="attr-map-{{inp.key}}">{{inp.key}}</label></td>
+          <td>{{inp.label}}</td>
+        </tr>
+      </table>
+    </form>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-info" [disabled]="!myForm?.valid || !(myForm?.dirty)"
+      (click)="save()" i18n>Save</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.ts
new file mode 100644 (file)
index 0000000..656a0b5
--- /dev/null
@@ -0,0 +1,151 @@
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgForm} from '@angular/forms';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {EventService} from '@eg/core/event.service';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {Pager} from '@eg/share/util/pager';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PermService} from '@eg/core/perm.service';
+
+@Component({
+  selector: 'eg-edi-attr-set-edit-dialog',
+  templateUrl: './edi-attr-set-edit-dialog.component.html'
+})
+
+export class EdiAttrSetEditDialogComponent
+  extends DialogComponent implements OnInit {
+
+    @Input() mode: string = 'create';
+    @Input() attrSetId: number;
+    @Input() cloneSource: number;
+    attrSet: IdlObject;
+    attrInputs: any = [];
+    clonedLabel = '';
+
+    constructor(
+        private idl: IdlService,
+        private evt: EventService,
+        private net: NetService,
+        private auth: AuthService,
+        private pcrud: PcrudService,
+        private perm: PermService,
+        private toast: ToastService,
+        private modal: NgbModal
+    ) {
+        super(modal);
+    }
+
+    ngOnInit() {
+        this.onOpen$.subscribe(() => this._initRecord());
+    }
+
+    private _initRecord() {
+        this.attrSet = null;
+        this.attrInputs = [];
+        this.clonedLabel = '';
+        if (this.mode === 'update') {
+            this.pcrud.retrieve('aeas', this.attrSetId, {
+                flesh: 1,
+                flesh_fields: { aeas: ['attr_maps'] }
+            }).subscribe(res => {
+                this.attrSet = res;
+                this._generateAttrInputs();
+            });
+        } else if (this.mode === 'clone') {
+            this.pcrud.retrieve('aeas', this.cloneSource, {
+                flesh: 1,
+                flesh_fields: { aeas: ['attr_maps'] }
+            }).subscribe(res => {
+                this.clonedLabel = res.label();
+                this.attrSet = this.idl.create('aeas');
+                this.attrSet.attr_maps([]);
+                res.attr_maps().forEach((m) => {
+                    const newMap = this.idl.create('aeasm');
+                    newMap.attr(m.attr());
+                    this.attrSet.attr_maps().push(newMap);
+                });
+                this._generateAttrInputs();
+            });
+        } else if (this.mode === 'create') {
+            this.attrSet = this.idl.create('aeas');
+            this.attrSet.attr_maps([]);
+            this._generateAttrInputs();
+        }
+    }
+
+    _generateAttrInputs() {
+        const hasAttr: {[key: string]: boolean} = {};
+        const hasAttrId: {[key: string]: number} = {};
+        this.attrSet.attr_maps().forEach((m) => {
+            hasAttr[m.attr()] = true;
+            hasAttrId[m.attr()] = m.id();
+        });
+        this.pcrud.retrieveAll('aea', {order_by: {aea: 'key'}}).subscribe(attr => {
+            const inp = {
+                key: attr.key(),
+                label: attr.label(),
+                id: null,
+                selected: false
+            };
+            if (attr.key() in hasAttr) {
+                inp.selected = true;
+                inp.id = hasAttrId[attr.key()];
+            }
+            this.attrInputs.push(inp);
+        });
+    }
+
+    save() {
+        if (this.attrSet.id() === undefined || this.attrSet.id() === null) {
+            this.attrSet.isnew(true);
+        } else {
+            this.attrSet.ischanged(true);
+        }
+        this.pcrud.autoApply([this.attrSet]).subscribe(res => {
+            const setId = this.mode === 'update' ? res : res.id();
+            const updates: IdlObject[] = [];
+            if (this.mode === 'create' || this.mode === 'clone') {
+                this.attrInputs.forEach((inp) => {
+                    if (inp.selected) {
+                        const aesm = this.idl.create('aeasm');
+                        aesm.attr(inp.key);
+                        aesm.attr_set(setId);
+                        aesm.isnew(true);
+                        updates.push(aesm);
+                    }
+                });
+            } else {
+                // updating an existing set
+                this.attrInputs.forEach((inp) => {
+                    if (inp.id) {
+                        if (!inp.selected) {
+                            // used to be wanted, but no longer
+                            const aesm = this.idl.create('aeasm');
+                            aesm.id(inp.id);
+                            aesm.isdeleted(true);
+                            updates.push(aesm);
+                        }
+                    } else if (inp.selected) {
+                        // no ID, must be newly checked
+                        const aesm = this.idl.create('aeasm');
+                        aesm.attr(inp.key);
+                        aesm.attr_set(setId);
+                        aesm.isnew(true);
+                        updates.push(aesm);
+                    }
+                });
+            }
+            this.pcrud.autoApply(updates).subscribe(
+                res => this.close(true),
+                err => this.close(err),
+                () => this.close(true)
+            );
+        }, err => this.close(false));
+    }
+
+}
index 3a36c23..6fdf7f0 100644 (file)
@@ -58,7 +58,9 @@
   </eg-grid-toolbar-button>
   <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
   </eg-grid-toolbar-action>
-  <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
+  <eg-grid-toolbar-action label="Clone Selected" i18n-label (onClick)="cloneSelected($event)">
+  </eg-grid-toolbar-action>
+  <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteIfPossible($event)">
   </eg-grid-toolbar-action>
 
   <eg-grid-column path="label"></eg-grid-column>
 </eg-fm-record-editor>
 
 <eg-edi-attr-set-providers-dialog #ediAttrSetProvidersDialog></eg-edi-attr-set-providers-dialog>
+<eg-edi-attr-set-edit-dialog #ediAttrSetEditDialog></eg-edi-attr-set-edit-dialog>
+
+<eg-confirm-dialog #confirmDel
+  dialogTitle="Delete?" i18n-dialogTitle
+  dialogBody="Delete EDI attribute set?" i18n-dialogBody>
+</eg-confirm-dialog>
+<eg-alert-dialog #alertDialog
+ i18n-dialogBody
+  dialogBody="EDI attribute set cannot be deleted: it is in use by at least one provider">
+</eg-alert-dialog>
index d1529d7..2ee9a14 100644 (file)
@@ -15,7 +15,11 @@ import {AuthService} from '@eg/core/auth.service';
 import {NetService} from '@eg/core/net.service';
 import {Observable, of} from 'rxjs';
 import {map, mergeMap} from 'rxjs/operators';
+import {StringComponent} from '@eg/share/string/string.component';
 import {EdiAttrSetProvidersDialogComponent} from './edi-attr-set-providers-dialog.component';
+import {EdiAttrSetEditDialogComponent} from './edi-attr-set-edit-dialog.component';
+import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
 
 @Component({
     templateUrl: './edi-attr-sets.component.html'
@@ -27,6 +31,9 @@ export class EdiAttrSetsComponent extends AdminPageComponent implements OnInit {
 
     @ViewChild('grid', { static: true }) grid: GridComponent;
     @ViewChild('ediAttrSetProvidersDialog', { static: false }) ediAttrSetProvidersDialog: EdiAttrSetProvidersDialogComponent;
+    @ViewChild('ediAttrSetEditDialog', { static: false }) ediAttrSetEditDialog: EdiAttrSetEditDialogComponent;
+    @ViewChild('alertDialog', {static: false}) private alertDialog: AlertDialogComponent;
+    @ViewChild('confirmDel', { static: true }) confirmDel: ConfirmDialogComponent;
 
     cellTextGenerator: GridCellTextGenerator;
 
@@ -105,6 +112,10 @@ export class EdiAttrSetsComponent extends AdminPageComponent implements OnInit {
 
         this.classLabel = this.idlClassDef.label;
         this.includeOrgDescendants = true;
+        // TODO - this works, but doesn't clear the base component's subscription
+        //this.grid.onRowActivate.subscribe(
+        //    (idlThing: IdlObject) => this.editSelected([idlThing])
+        //);
     }
 
     countProviders(row: IdlObject): Observable<IdlObject> {
@@ -117,4 +128,53 @@ export class EdiAttrSetsComponent extends AdminPageComponent implements OnInit {
         this.ediAttrSetProvidersDialog.open({size: 'lg'});
     }
 
+    deleteIfPossible(rows: IdlObject[]) {
+        if (rows.length > 0) {
+            if (rows[0].num_providers > 0) {
+                this.alertDialog.open();
+            } else {
+                this.confirmDel.open().subscribe(confirmed => {
+                    if (!confirmed) { return; }
+                    super.deleteSelected([ rows[0] ]);
+                });
+            }
+        }
+    }
+
+    showEditAttrSetDialog(successString: StringComponent, failString: StringComponent): Promise<any> {
+        return new Promise((resolve, reject) => {
+            this.ediAttrSetEditDialog.open({size: 'lg', scrollable: true}).subscribe(
+                result => {
+                    this.successString.current()
+                        .then(str => this.toast.success(str));
+                    this.grid.reload();
+                    resolve(result);
+                },
+                error => {
+                    this.updateFailedString.current()
+                        .then(str => this.toast.danger(str));
+                    reject(error);
+                }
+            );
+        });
+    }
+
+    createNew() {
+        this.ediAttrSetEditDialog.mode = 'create';
+        this.showEditAttrSetDialog(this.createString, this.createErrString);
+    }
+
+    editSelected(rows: IdlObject[]) {
+        if (rows.length <= 0) { return; }
+        this.ediAttrSetEditDialog.mode = 'update';
+        this.ediAttrSetEditDialog.attrSetId = rows[0].id();
+        this.showEditAttrSetDialog(this.successString, this.updateFailedString);
+    }
+
+    cloneSelected(rows: IdlObject[]) {
+        if (rows.length <= 0) { return; }
+        this.ediAttrSetEditDialog.mode = 'clone';
+        this.ediAttrSetEditDialog.cloneSource = rows[0].id();
+        this.showEditAttrSetDialog(this.createString, this.createErrString);
+    }
 }
index e5fb854..53d0967 100644 (file)
@@ -5,12 +5,14 @@ import {EdiAttrSetsRoutingModule} from './routing.module';
 import {EdiAttrSetsComponent} from './edi-attr-sets.component';
 import {EdiAttrSetProvidersDialogComponent} from './edi-attr-set-providers-dialog.component';
 import {EdiAttrSetProvidersComponent} from './edi-attr-set-providers.component';
+import {EdiAttrSetEditDialogComponent} from './edi-attr-set-edit-dialog.component';
 
 @NgModule({
   declarations: [
     EdiAttrSetsComponent,
     EdiAttrSetProvidersDialogComponent,
-    EdiAttrSetProvidersComponent
+    EdiAttrSetProvidersComponent,
+    EdiAttrSetEditDialogComponent
   ],
   imports: [
     StaffCommonModule,