1 import {Component, Input, Output, OnInit, AfterViewInit,
2 EventEmitter, ViewChild, Renderer2} from '@angular/core';
3 import {Observable, of} from 'rxjs';
4 import {map} from 'rxjs/operators';
5 import {IdlObject} from '@eg/core/idl.service';
6 import {NetService} from '@eg/core/net.service';
7 import {AuthService} from '@eg/core/auth.service';
8 import {OrgService} from '@eg/core/org.service';
9 import {ServerStoreService} from '@eg/core/server-store.service';
10 import {GridComponent} from '@eg/share/grid/grid.component';
11 import {GridDataSource} from '@eg/share/grid/grid';
12 import {Pager} from '@eg/share/util/pager';
14 const DEFAULT_SORT = [
16 'first_given_name ASC',
17 'second_given_name ASC',
21 const DEFAULT_FLESH = [
22 'card', 'settings', 'standing_penalties', 'addresses', 'billing_address',
23 'mailing_address', 'stat_cat_entries', 'waiver_entries', 'usr_activity',
27 const EXPAND_FORM = 'eg.circ.patron.search.show_extras';
28 const INCLUDE_INACTIVE = 'eg.circ.patron.search.include_inactive';
31 selector: 'eg-patron-search',
32 templateUrl: './search.component.html'
35 export class PatronSearchComponent implements OnInit, AfterViewInit {
37 @ViewChild('searchGrid', {static: false}) searchGrid: GridComponent;
39 // Fired on dbl-click of a search result row.
40 @Output() patronsSelected: EventEmitter<any>;
45 dataSource: GridDataSource;
46 profileGroups: IdlObject[] = [];
49 private renderer: Renderer2,
50 private net: NetService,
51 public org: OrgService,
52 private auth: AuthService,
53 private store: ServerStoreService
55 this.patronsSelected = new EventEmitter<any>();
56 this.dataSource = new GridDataSource();
57 this.dataSource.getRows = (pager: Pager, sort: any[]) => {
58 return this.getRows(pager, sort);
63 this.searchOrg = this.org.root();
64 this.store.getItemBatch([EXPAND_FORM, INCLUDE_INACTIVE])
66 this.expandForm = settings[EXPAND_FORM];
67 this.search.inactive = settings[INCLUDE_INACTIVE];
72 this.renderer.selectRootElement('#focus-this-input').focus();
76 this.expandForm = !this.expandForm;
77 if (this.expandForm) {
78 this.store.setItem(EXPAND_FORM, true);
80 this.store.removeItem(EXPAND_FORM);
84 toggleIncludeInactive() {
85 if (this.search.inactive) { // value set by ngModel
86 this.store.setItem(INCLUDE_INACTIVE, true);
88 this.store.removeItem(INCLUDE_INACTIVE);
92 rowsSelected(rows: IdlObject | IdlObject[]) {
93 this.patronsSelected.emit([].concat(rows));
96 getSelected(): IdlObject[] {
97 return this.searchGrid ?
98 this.searchGrid.context.getSelectedRows() : [];
102 this.searchGrid.reload();
106 this.search = {profile: null};
109 getRows(pager: Pager, sort: any[]): Observable<any> {
111 let observable: Observable<IdlObject>;
113 if (this.search.id) {
114 observable = this.searchById();
116 observable = this.searchByForm(pager, sort);
119 return observable.pipe(map(user => this.localFleshUser(user)));
122 localFleshUser(user: IdlObject): IdlObject {
123 user.home_ou(this.org.get(user.home_ou()));
127 searchByForm(pager: Pager, sort: any[]): Observable<IdlObject> {
129 const search = this.compileSearch();
130 if (!search) { return of(); }
132 const sorter = this.compileSort(sort);
134 return this.net.request(
136 'open-ils.actor.patron.search.advanced.fleshed',
138 this.compileSearch(),
148 searchById(): Observable<IdlObject> {
149 return this.net.request(
151 'open-ils.actor.user.fleshed.retrieve',
152 this.auth.token(), this.search.id, DEFAULT_FLESH
156 compileSort(sort: any[]): string[] {
157 if (!sort || sort.length === 0) { return DEFAULT_SORT; }
158 return sort.map(def => `${def.name} ${def.dir}`);
161 compileSearch(): any {
163 let hasSearch = false;
164 const search: Object = {};
166 Object.keys(this.search).forEach(field => {
167 search[field] = this.mapSearchField(field);
168 if (search[field]) { hasSearch = true; }
171 return hasSearch ? search : null;
174 isValue(val: any): boolean {
175 return (val !== null && val !== undefined && val !== '');
178 mapSearchField(field: string): any {
180 const value = this.search[field];
181 if (!this.isValue(value)) { return null; }
183 const chunk = {value: value, group: 0};
187 case 'name': // name keywords
211 chunk.value = chunk.value.id(); // pgt object
218 chunk.value = chunk.value.replace(/\D/g, '');
220 if (!field.match(/year/)) {
221 // force day/month to be 2 digits
222 chunk[field].value = ('0' + value).slice(-2);
227 // Confirm the value wasn't scrubbed away above
228 if (!this.isValue(chunk.value)) { return null; }