--- /dev/null
+<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>
+
--- /dev/null
+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));
+ });
+ }
+}
+
+