LP1846552 Shelving Location Order Angular UI
authorBill Erickson <berickxx@gmail.com>
Mon, 13 Sep 2021 18:17:16 +0000 (14:17 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 16 Mar 2022 20:41:23 +0000 (16:41 -0400)
Ports the copy shelving location order interface to Angular.

Admin => Local Admin => Shelving Location Order.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/src/eg2/src/app/core/pcrud.service.ts
Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order-routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.css [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts

index 7fb0c7c..d95869e 100644 (file)
@@ -299,7 +299,7 @@ export class PcrudContext {
 
             if (action === 'auto') {
                 // object does not need updating; move along
-                this.nextCudRequest();
+                return this.nextCudRequest();
             }
         }
 
index b852b98..5ab2496 100644 (file)
@@ -61,7 +61,7 @@
     <eg-link-table-link i18n-label label="Shelving Location Groups" 
       routerLink="/staff/admin/local/asset/shelving_location_groups"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Shelving Location Order" 
-      url="/eg/staff/admin/local/asset/copy_location_order"></eg-link-table-link>
+      routerLink="/staff/admin/local/asset/copy_location_order"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Shelving Locations Editor" 
       routerLink="/staff/admin/local/asset/copy_location"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Standing Penalties" 
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order-routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order-routing.module.ts
new file mode 100644 (file)
index 0000000..7afef5d
--- /dev/null
@@ -0,0 +1,15 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {CopyLocOrderComponent} from './copy-loc-order.component';
+
+const routes: Routes = [{
+    path: '',
+    component: CopyLocOrderComponent
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+
+export class CopyLocOrderRoutingModule {}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.css b/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.css
new file mode 100644 (file)
index 0000000..4652259
--- /dev/null
@@ -0,0 +1,7 @@
+
+li:nth-child(even) {
+  background-color: rgba(0,0,0,.03);
+  border-top: 1px solid rgba(0,0,0,.125);
+  border-bottom: 1px solid rgba(0,0,0,.125);
+}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.html
new file mode 100644 (file)
index 0000000..c37cf0a
--- /dev/null
@@ -0,0 +1,59 @@
+<eg-staff-banner i18n-bannerText bannerText="Shelving Location Order">
+</eg-staff-banner>
+
+<eg-string #editString i18n-text text="Changes Saved"></eg-string>
+
+<div class="row">
+  <div class="col-lg-12 d-flex">
+    <div class="mr-2" i18n>Context Org Unit</div>
+    <div>
+      <eg-org-select (onChange)="orgChanged($event)" [initialOrgId]="contextOrg">
+      </eg-org-select>
+    </div>
+    <div class="ml-3">
+      <button (click)="up()" i18n-title title="Move Selected Location Up"
+        class="mr-2 btn btn-sm btn-outline-dark .mat-icon-shrunk-in-button">
+        <span class="material-icons">arrow_upward</span>
+      </button>
+      <button (click)="down()" i18n-title title="Move Selected Location Down"
+        class="mr-2 btn btn-sm btn-outline-dark .mat-icon-shrunk-in-button">
+        <span class="material-icons">arrow_downward</span>
+      </button>
+      <button (click)="up(true)" i18n-title title="Move Selected Location To Top"
+        class="mr-2 btn btn-sm btn-outline-dark .mat-icon-shrunk-in-button">
+        <span class="material-icons">vertical_align_top</span>
+      </button>
+      <button (click)="down(true)" i18n-title title="Move Selected Location To Bottom"
+        class="mr-2 btn btn-sm btn-outline-dark .mat-icon-shrunk-in-button">
+        <span class="material-icons">vertical_align_bottom</span>
+      </button>
+      <span class="ml-2 font-italic" *ngIf="selectedEntry">
+        Selected: {{selected().location().name()}} 
+          ({{orgSn(selected().location().owning_lib())}})
+      </span>
+    </div>
+    <div class="flex-1"></div>
+    <button class="btn btn-outline-dark" (click)="save()" 
+      [disabled]="!changesPending()" i18n>Save Changes</button>
+  </div>
+</div>
+
+<div class="mt-2 mb-2 font-italic" i18n>
+  Select a shelving location below and use the arrows above to change its position.
+</div>
+
+<ol class="mt-3">
+  <li *ngFor="let entry of entries" (click)="selectedEntry = entry.id()">
+    <div class="row p-1">
+      <div class="col-lg-12">
+        <input type="radio" name="selected-entry" 
+          [value]="entry.id()" [(ngModel)]="selectedEntry"/>
+        <span class="flex-1 ml-1 p-1" i18n
+          [ngClass]="{'border border-primary rounded': selectedEntry == entry.id()}">
+          {{entry.location().name()}} ({{orgSn(entry.location().owning_lib())}})
+        </span>
+      </div>
+    </div>
+  </li>
+</ol>
+
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.component.ts
new file mode 100644 (file)
index 0000000..56e9b1a
--- /dev/null
@@ -0,0 +1,198 @@
+import {Component, Input, ViewChild, OnInit} from '@angular/core';
+import {tap, concatMap} from 'rxjs/operators';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {StringService} from '@eg/share/string/string.service';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+    templateUrl: './copy-loc-order.component.html',
+    styleUrls: ['copy-loc-order.component.css']
+})
+export class CopyLocOrderComponent implements OnInit {
+
+    @ViewChild('editString') editString: StringComponent;
+    /*
+    @ViewChild('errorString') errorString: StringComponent;
+    @ViewChild('delConfirm') delConfirm: ConfirmDialogComponent;
+    */
+
+    locations: {[id: number]: IdlObject} = {};
+    entries: IdlObject[] = [];
+    contextOrg: number;
+    selectedEntry: number;
+
+    constructor(
+        private idl: IdlService,
+        private org: OrgService,
+        private auth: AuthService,
+        private pcrud: PcrudService,
+        private strings: StringService,
+        private toast: ToastService
+    ) {}
+
+    ngOnInit() {
+        this.contextOrg = Number(this.auth.user().ws_ou());
+        this.load();
+    }
+
+    load(): Promise<any> {
+
+        this.entries = [];
+        this.locations = {};
+
+        return this.pcrud.search('acpl',
+            {owning_lib: this.org.ancestors(this.contextOrg, true)})
+        .pipe(tap(loc => this.locations[loc.id()] = loc))
+        .toPromise()
+
+        .then(_ => {
+
+            return this.pcrud.search('acplo',
+                {org: this.contextOrg},
+                {order_by: {acplo: 'position'}},
+                {authoritative: true}
+            )
+            .pipe(tap(e => {
+                e.position(Number(e.position()));
+                e.location(this.locations[e.location()]);
+                this.entries.push(e);
+            }))
+            .toPromise();
+        })
+
+        .then(_ => {
+
+            if (this.entries.length > 0) { return; }
+
+            // If we have no position entries, create some now so they
+            // can become the basis of our new list.
+
+            const locs = Object.values(this.locations)
+                .sort((o1, o2) => o1.name() < o2.name() ? -1 : 1);
+
+            let pos = 1;
+            locs.forEach(loc => {
+                const entry = this.idl.create('acplo');
+                entry.isnew(true);
+                entry.id(-pos); // avoid using '0' as an ID
+                entry.location(loc);
+                entry.position(pos++);
+                entry.org(this.contextOrg);
+                this.entries.push(entry);
+            });
+        });
+    }
+
+    orgChanged(org: IdlObject) {
+        if (org && org.id() !== this.contextOrg) {
+            this.contextOrg = org.id();
+            this.load();
+        }
+    }
+
+    orgSn(id: number): string {
+        return this.org.get(id).shortname();
+    }
+
+    setPositions() {
+        let pos = 1;
+        this.entries.forEach(e => {
+            if (e.position() !== pos) {
+                e.ischanged(true);
+                e.position(pos);
+            }
+            pos++;
+        });
+    }
+
+    up(toTop?: boolean) {
+        if (!this.selectedEntry) { return; }
+
+        for (let idx = 0; idx < this.entries.length; idx++) {
+            const entry = this.entries[idx];
+
+            if (entry.id() === this.selectedEntry) {
+
+                if (toTop) {
+                    this.entries.splice(idx, 1);
+                    this.entries.unshift(entry);
+
+                } else {
+
+                    if (idx === 0) {
+                        // We're already at the top of the list.
+                        // No where to go but down.
+                        return;
+                    }
+
+                    // Swap places with the preceding entry
+                    this.entries[idx] = this.entries[idx - 1];
+                    this.entries[idx - 1] = entry;
+                }
+
+                break;
+            }
+        }
+
+        this.setPositions();
+    }
+
+    down(toBottom?: boolean) {
+        if (!this.selectedEntry) { return; }
+
+        for (let idx = 0; idx < this.entries.length; idx++) {
+            const entry = this.entries[idx];
+
+            if (entry.id() === this.selectedEntry) {
+
+                if (toBottom) {
+                    this.entries.splice(idx, 1);
+                    this.entries.push(entry);
+
+                } else {
+
+                    if (idx === this.entries.length - 1) {
+                        // We're already at the bottom of the list.
+                        // No where to go but up.
+                        return;
+                    }
+
+                    this.entries[idx] = this.entries[idx + 1];
+                    this.entries[idx + 1] = entry;
+                }
+                break;
+            }
+        }
+
+        this.setPositions();
+    }
+
+    changesPending(): boolean {
+        return this.entries.filter(e => (e.isnew() || e.ischanged())).length > 0;
+    }
+
+    selected(): IdlObject {
+        return this.entries.filter(e => e.id() === this.selectedEntry)[0];
+    }
+
+    save() {
+        // Scrub our temp ID's
+        this.entries.forEach(e => { if (e.isnew()) { e.id(null); } });
+
+        this.pcrud.autoApply(this.entries).toPromise()
+        .then(_ => {
+            this.selectedEntry = null;
+            this.load().then(__ => this.toast.success(this.editString.text));
+        });
+    }
+}
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/copy-loc-order/copy-loc-order.module.ts
new file mode 100644 (file)
index 0000000..ba8c501
--- /dev/null
@@ -0,0 +1,23 @@
+import {NgModule} from '@angular/core';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {CopyLocOrderRoutingModule} from './copy-loc-order-routing.module';
+import {CopyLocOrderComponent} from './copy-loc-order.component';
+
+@NgModule({
+  declarations: [
+    CopyLocOrderComponent
+  ],
+  imports: [
+    AdminCommonModule,
+    CopyLocOrderRoutingModule
+  ],
+  exports: [
+  ],
+  providers: [
+  ]
+})
+
+export class CopyLocOrderModule {
+}
+
+
index c3dd98e..a2eb2ff 100644 (file)
@@ -18,6 +18,10 @@ const routes: Routes = [{
     path: 'actor/address_alert',
     component: AddressAlertComponent
 }, {
+    path: 'asset/copy_location_order',
+    loadChildren: () =>
+      import('./copy-loc-order/copy-loc-order.module').then(m => m.CopyLocOrderModule)
+}, {
     path: 'asset/copy_location',
     component: BasicAdminPageComponent,
     data: [{