funds: implement fund tags component
authorGalen Charlton <gmc@equinoxinitiative.org>
Sun, 28 Mar 2021 21:17:02 +0000 (17:17 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Sun, 28 Mar 2021 21:17:02 +0000 (17:17 -0400)
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-details-dialog.component.html
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-details-dialog.component.ts
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-tags.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-tags.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.module.ts

index a2f30b8..1ee4a0f 100644 (file)
         <a ngbNavLink i18n>Tags</a>
         <ng-template ngbNavContent>
           <div class="mt-2">
+            <eg-fund-tags [fundId]="fundId" [fundOwner]="fund.org()"></eg-fund-tags>
            </div>
         </ng-template>
       </li>
index f1cca33..0c985db 100644 (file)
@@ -13,6 +13,7 @@ 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 {FundTagsComponent} from './fund-tags.component';
 
 @Component({
   selector: 'eg-fund-details-dialog',
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-tags.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-tags.component.html
new file mode 100644 (file)
index 0000000..c971577
--- /dev/null
@@ -0,0 +1,34 @@
+<ng-template #addSuccessStrTmpl i18n>Added tag</ng-template>
+<eg-string #addSuccessString [template]="removeSuccessStrTmpl"></eg-string>
+<ng-template #addErrorStrTmpl i18n>Failed to add tag</ng-template>
+<eg-string #addErrorString [template]="removeErrorStrTmpl"></eg-string>
+<ng-template #removeSuccessStrTmpl i18n>Removed tag</ng-template>
+<eg-string #removeSuccessString [template]="removeSuccessStrTmpl"></eg-string>
+<ng-template #removeErrorStrTmpl i18n>Failed to remove tag</ng-template>
+<eg-string #removeErrorString [template]="removeErrorStrTmpl"></eg-string>
+
+<div class="row">
+  <div class="col-sm-2" *ngFor="let ftm of tagMaps">
+    <button class="btn btn-sm material-icon-button" type="button"
+      (click)="removeTagMap(ftm)"
+      i18n-title title="Remove Tag"><span class="sr-only">Remove Tag</span>
+      <span class="material-icons" aria-hidden="true">delete</span>
+    </button>
+    {{ftm.tag().name()}} ({{ftm.tag().owner().shortname()}})
+  </div>
+</div>
+<div class="row mt-3">
+  <div class="col-sm-2">
+    <eg-combobox #tagSelector [asyncSupportsEmptyTermClick]="true"
+      [(ngModel)]="newTag" [asyncDataSource]="tagSelectorDataSource"
+      i18n-placeholder placeholder="Select tag"></eg-combobox>
+  </div>
+  <div class="col-sm-1">
+    <button class="btn btn-success" [disabled]="!newTag || checkNewTagAlreadyMapped()"
+      (click)="addTagMap()" i18n>Add Tag
+    </button>
+  </div>
+  <div class="col-sm-2" *ngIf="newTag && checkNewTagAlreadyMapped()">
+    <span class="alert-warning" i18n>(tag is already assigned to this fund)</span>
+  </div>
+</div> 
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-tags.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-tags.component.ts
new file mode 100644 (file)
index 0000000..529bca2
--- /dev/null
@@ -0,0 +1,124 @@
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+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 {OrgService} from '@eg/core/org.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {Observable} from 'rxjs';
+import {map} from 'rxjs/operators';
+
+@Component({
+    selector: 'eg-fund-tags',
+    templateUrl: './fund-tags.component.html'
+})
+export class FundTagsComponent implements OnInit {
+
+    @Input() fundId: number;
+    @Input() fundOwner: number;
+
+    @ViewChild('addSuccessString', { static: true }) addSuccessString: StringComponent;
+    @ViewChild('addErrorString', { static: true }) addErrorString: StringComponent;
+    @ViewChild('removeSuccessString', { static: true }) removeSuccessString: StringComponent;
+    @ViewChild('removeErrorString', { static: true }) removeErrorString: StringComponent;
+    @ViewChild('tagSelector', { static: false }) tagSelector: ComboboxComponent;
+
+    tagMaps: IdlObject[];
+    newTag: ComboboxEntry = null;
+    tagSelectorDataSource: (term: string) => Observable<ComboboxEntry>
+
+    constructor(
+        private idl: IdlService,
+        private evt: EventService,
+        private net: NetService,
+        private auth: AuthService,
+        private pcrud: PcrudService,
+        private org: OrgService,
+        private toast: ToastService
+    ) {}
+
+    ngOnInit() {
+        this._loadTagMaps();
+        this.tagSelectorDataSource = term => {
+            const field = 'name';
+            const args = {};
+            const extra_args = { order_by : {} };
+            args[field] = {'ilike': `%${term}%`}; // could -or search on label
+            args['owner'] = this.org.ancestors(this.fundOwner, true)
+            extra_args['order_by']['acqft'] = field;
+            extra_args['limit'] = 100;
+            extra_args['flesh'] = 2;
+            const flesh_fields: Object = {};
+            flesh_fields['acqft'] = ['owner'];
+            extra_args['flesh_fields'] = flesh_fields;
+            return this.pcrud.search('acqft', args, extra_args).pipe(map(data => {
+                return {
+                    id: data.id(),
+                    label: data.name() + ' (' + data.owner().shortname() + ')',
+                    fm: data
+                };
+            }));
+        };
+    }
+
+    _loadTagMaps() {
+        this.tagMaps = [];
+        this.pcrud.search('acqftm', { fund: this.fundId }, {
+            flesh: 2,
+            flesh_fields: {
+                acqftm: ['tag'],
+                acqft:  ['owner']
+            }
+        }).subscribe(
+            res => this.tagMaps.push(res),
+            err => {},
+            () => this.tagMaps.sort((a, b) => {
+                return a.tag().name() < b.tag().name() ? -1 : 1
+            })
+        );
+    }
+
+    checkNewTagAlreadyMapped(): boolean {
+        if ( this.newTag == null) { return false; }
+        const matches: IdlObject[] = this.tagMaps.filter(tm => tm.tag().id() === this.newTag.id);
+        return matches.length > 0 ? true : false;
+    }
+
+    addTagMap() {
+        const ftm = this.idl.create('acqftm');
+        ftm.tag(this.newTag.id);
+        ftm.fund(this.fundId);
+        this.pcrud.create(ftm).subscribe(
+            ok => {
+              this.addSuccessString.current()
+                .then(str => this.toast.success(str));
+            },
+            err => {
+              this.addErrorString.current()
+                .then(str => this.toast.danger(str));
+            },
+            () => {
+                this.newTag = null;
+                this.tagSelector.selectedId = null;
+                this._loadTagMaps();
+            }
+        );
+    }
+    removeTagMap(ftm: IdlObject) {
+        this.pcrud.remove(ftm).subscribe(
+            ok => {
+              this.removeSuccessString.current()
+                .then(str => this.toast.success(str));
+            },
+            err => {
+              this.removeErrorString.current()
+                .then(str => this.toast.danger(str));
+            },
+            () => this._loadTagMaps()
+        )
+    }
+}
index a74dadd..5f3892b 100644 (file)
@@ -7,6 +7,7 @@ import {FundsManagerComponent} from './funds-manager.component';
 import {FundDetailsDialogComponent} from './fund-details-dialog.component';
 import {FundingSourcesComponent} from './funding-sources.component';
 import {FundingSourceTransactionsDialogComponent} from './funding-source-transactions-dialog.component';
+import {FundTagsComponent} from './fund-tags.component';
 
 @NgModule({
   declarations: [
@@ -14,7 +15,8 @@ import {FundingSourceTransactionsDialogComponent} from './funding-source-transac
     FundsManagerComponent,
     FundDetailsDialogComponent,
     FundingSourcesComponent,
-    FundingSourceTransactionsDialogComponent
+    FundingSourceTransactionsDialogComponent,
+    FundTagsComponent
   ],
   imports: [
     StaffCommonModule,