LP1904036 Dupe searches, help/example text
authorBill Erickson <berickxx@gmail.com>
Mon, 29 Mar 2021 19:42:31 +0000 (15:42 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:28 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts
Open-ILS/src/eg2/src/app/staff/share/patron/search.component.ts

index 5af7748..580a745 100644 (file)
   </div>
 </div>
 
-<div class="row pb-2 pt-2" *ngIf="dupeSearches.length > 0">
+<div class="row pb-1 pt-1" *ngIf="dupesFound().length > 0">
   <div class="col-lg-12 d-flex">
     <div class="ml-auto mr-2">
-      <div *ngFor="let dupe of dupeSearches"
-        class="alert alert-danger ml-auto p-2 mt-2" i18n>
-        {{dupe.count}} patron(s) with same
-        <ng-container [ngSwitch]="dupe.category">
-          <span *ngSwitchCase="'phone'">phone</span>
-          <span *ngSwitchCase="'email'">email</span>
-          <span *ngSwitchCase="'name'">name</span>
-          <span *ngSwitchCase="'ident'">identifier</span>
-          <span *ngSwitchCase="'address'">address</span>
-        </ng-container>
+      <div *ngFor="let dupe of dupesFound()"
+        class="alert alert-danger ml-auto p-2 mt-2">
+          <a routerLink="/staff/circ/patron/search" 
+            target="_blank" [queryParams]="{search: dupe.json}" i18n>
+          {{dupe.count}} patron(s) with same
+          <ng-container [ngSwitch]="dupe.category">
+            <span *ngSwitchCase="'phone'">phone</span>
+            <span *ngSwitchCase="'email'">email</span>
+            <span *ngSwitchCase="'name'">name</span>
+            <span *ngSwitchCase="'ident'">identifier</span>
+            <span *ngSwitchCase="'address'">address</span>
+          </ng-container>
+        </a>
       </div>
     </div>
   </div>
index 32a3329..d715cbc 100644 (file)
@@ -20,6 +20,7 @@ interface DupeSearch {
     category: SearchCategory;
     count: number;
     search: PatronSearchFieldSet;
+    json: string;
 }
 
 @Component({
@@ -39,7 +40,7 @@ export class EditToolbarComponent implements OnInit {
     saveCloneClicked: EventEmitter<void> = new EventEmitter<void>();
     printClicked: EventEmitter<void> = new EventEmitter<void>();
 
-    dupeSearches: DupeSearch[] = [];
+    searches: {[category: string]: DupeSearch} = {};
 
     constructor(
         private org: OrgService,
@@ -58,6 +59,12 @@ export class EditToolbarComponent implements OnInit {
         this.visibilityLevel = v;
     }
 
+    dupesFound(): DupeSearch[] {
+        return Object.values(this.searches).filter(dupe => dupe.count > 0);
+    }
+
+
+
     checkDupes(category: string, search: PatronSearchFieldSet) {
 
         this.net.request(
@@ -70,25 +77,12 @@ export class EditToolbarComponent implements OnInit {
             true  // as id
         ).subscribe(ids => {
             ids = ids.filter(id => Number(id) !== this.patronId);
-            const count = ids.length;
-
-            if (count > 0) {
-                const existing =
-                    this.dupeSearches.filter(s => s.category === category)[0];
-                if (existing) {
-                    existing.search = search;
-                    existing.count = count;
-                } else {
-                    this.dupeSearches.push({
-                        category: category as SearchCategory,
-                        search: search,
-                        count: count
-                    });
-                }
-            } else {
-                this.dupeSearches =
-                    this.dupeSearches.filter(s => s.category !== category);
-            }
+            this.searches[category] = {
+                category: category as SearchCategory,
+                count: ids.length,
+                search: search,
+                json: JSON.stringify(search)
+            };
         });
     }
 }
index 75d06e0..5b05342 100644 (file)
   </div>
 </div>
 
+<ng-template #fieldExample let-args="args">
+  <span class="ml-2" *ngIf="exampleText(args.cls, args.field)" i18n>
+    Example: {{exampleText(args.cls, args.field)}}
+  </span>
+</ng-template>
+
 <!-- IDL-generated field labels.  Override with args.overrideLabel -->
 <ng-template #fieldLabel let-args="args">
   <div class="col-lg-3 field-label">
     <label for="{{getClass(args.cls)}}-{{args.field}}-input">
       {{getFieldLabel(getClass(args.cls), args.field, args.overrideLabel)}}
     </label>
-    <!-- TODO doc links -->
+    <eg-help-popover *ngIf="getFieldDoc(args.cls, args.field)"
+      buttonClass="btn-sm text-info p-0 m-0"
+      [placement]="'bottom'" [helpText]="getFieldDoc(args.cls, args.field)">
+    </eg-help-popover>
   </div>
 </ng-template>
 
     </ng-container>
     <ng-container *ngTemplateOutlet="args.template; context: {args: args}">
     </ng-container>
+    <ng-container *ngTemplateOutlet="fieldExample; context: {args: args}">
+    </ng-container>
   </div>
 </ng-template>
 
         <button class="btn btn-outline-dark" 
           (click)="invalidateField('day_phone')" i18n>Invalidate</button>
       </ng-container>
+      <ng-container *ngTemplateOutlet="fieldExample; context: {args: {field: 'day_phone'}}">
+      </ng-container>
     </div>
   </div>
 
index 50553da..d379718 100644 (file)
@@ -159,6 +159,8 @@ export class EditComponent implements OnInit, AfterViewInit {
 
     holdNotifyTypes: {email?: boolean, phone?: boolean, sms?: boolean} = {};
 
+    fieldDoc: {[cls: string]: {[field: string]: string}} = {};
+
     constructor(
         private org: OrgService,
         private net: NetService,
@@ -184,6 +186,7 @@ export class EditComponent implements OnInit, AfterViewInit {
     load(): Promise<any> {
         this.loading = true;
         return this.setStatCats()
+        .then(_ => this.getFieldDocs())
         .then(_ => this.setSurveys())
         .then(_ => this.loadPatron())
         .then(_ => this.getSecondaryGroups())
@@ -196,7 +199,28 @@ export class EditComponent implements OnInit, AfterViewInit {
         .then(_ => this.loading = false);
     }
 
-    setupToolbar() {
+    getFieldDocs(): Promise<any> {
+        return this.pcrud.search('fdoc', {
+            fm_class: ['au', 'ac', 'aua', 'actsc', 'asv', 'asvq', 'asva']})
+        .pipe(tap(doc => {
+            if (!this.fieldDoc[doc.fm_class()]) {
+                this.fieldDoc[doc.fm_class()] = {};
+            }
+            this.fieldDoc[doc.fm_class()][doc.field()] = doc.string();
+            console.log(this.fieldDoc);
+        })).toPromise();
+    }
+
+    getFieldDoc(cls: string, field: string): string {
+        cls = this.getClass(cls);
+        if (this.fieldDoc[cls]) {
+            return this.fieldDoc[cls][field];
+        }
+    }
+
+    exampleText(cls: string, field: string): string {
+        cls = this.getClass(cls);
+        return this.context.settingsCache[`ui.patron.edit.${cls}.${field}.example`];
     }
 
     setSurveys(): Promise<any> {
index d8a172c..88025d7 100644 (file)
@@ -1,5 +1,6 @@
 import {Component, Input, Output, OnInit, AfterViewInit,
-    EventEmitter, ViewChild, Renderer2} from '@angular/core';
+    EventEmitter, ViewChild} from '@angular/core';
+import {ActivatedRoute, ParamMap} from '@angular/router';
 import {Observable, of} from 'rxjs';
 import {map} from 'rxjs/operators';
 import {IdlObject} from '@eg/core/idl.service';
@@ -38,7 +39,7 @@ export interface PatronSearchFieldSet {
 
 export interface PatronSearch {
     search: PatronSearchFieldSet;
-    orgId: number;
+    orgId?: number;
 }
 
 @Component({
@@ -72,7 +73,7 @@ export class PatronSearchComponent implements OnInit, AfterViewInit {
     profileGroups: IdlObject[] = [];
 
     constructor(
-        private renderer: Renderer2,
+        private route: ActivatedRoute,
         private net: NetService,
         public org: OrgService,
         private auth: AuthService,
@@ -90,6 +91,18 @@ export class PatronSearchComponent implements OnInit, AfterViewInit {
     }
 
     ngOnInit() {
+
+        this.route.queryParamMap.subscribe((params: ParamMap) => {
+            const search = params.get('search');
+            if (search) {
+                try {
+                    this.startWithSearch = {search: JSON.parse(search)};
+                } catch (E) {
+                    console.error("Invalid JSON search value", search, E);
+                }
+            }
+        });
+
         this.searchOrg = this.org.root();
         this.store.getItemBatch([EXPAND_FORM, INCLUDE_INACTIVE])
             .then(settings => {
@@ -99,7 +112,8 @@ export class PatronSearchComponent implements OnInit, AfterViewInit {
     }
 
     ngAfterViewInit() {
-        this.renderer.selectRootElement('#focus-this-input').focus();
+        const node = document.getElementById('focus-this-input');
+        if (node) { node.focus(); }
     }
 
     toggleExpandForm() {