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 {IdlService, IdlObject} from '@eg/core/idl.service';
6 import {EventService} from '@eg/core/event.service';
7 import {NetService} from '@eg/core/net.service';
8 import {AuthService} from '@eg/core/auth.service';
9 import {OrgService} from '@eg/core/org.service';
10 import {PcrudService} from '@eg/core/pcrud.service';
11 import {ServerStoreService} from '@eg/core/server-store.service';
12 import {ToastService} from '@eg/share/toast/toast.service';
13 import {StringComponent} from '@eg/share/string/string.component';
14 import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
15 import {GridComponent} from '@eg/share/grid/grid.component';
16 import {GridDataSource} from '@eg/share/grid/grid';
17 import {Pager} from '@eg/share/util/pager';
19 const DEFAULT_SORT = [
21 'first_given_name ASC',
22 'second_given_name ASC',
26 const DEFAULT_FLESH = [
27 'card', 'settings', 'standing_penalties', 'addresses', 'billing_address',
28 'mailing_address', 'stat_cat_entries', 'waiver_entries', 'usr_activity',
32 const EXPAND_FORM = 'eg.circ.patron.search.show_extras';
33 const INCLUDE_INACTIVE = 'eg.circ.patron.search.include_inactive';
36 selector: 'eg-patron-search',
37 templateUrl: './search.component.html'
40 export class PatronSearchComponent implements OnInit, AfterViewInit {
42 @ViewChild('searchGrid', {static: false}) searchGrid: GridComponent;
44 // Fired on dbl-click of a search result row.
45 @Output() patronsSelected: EventEmitter<any>;
50 dataSource: GridDataSource;
51 profileGroups: IdlObject[] = [];
54 private renderer: Renderer2,
55 private net: NetService,
56 private org: OrgService,
57 private auth: AuthService,
58 private store: ServerStoreService
60 this.patronsSelected = new EventEmitter<any>();
61 this.dataSource = new GridDataSource();
62 this.dataSource.getRows = (pager: Pager, sort: any[]) => {
63 return this.getRows(pager, sort);
68 this.searchOrg = this.org.root();
69 this.store.getItemBatch([EXPAND_FORM, INCLUDE_INACTIVE])
71 this.expandForm = settings[EXPAND_FORM];
72 this.search.inactive = settings[INCLUDE_INACTIVE];
77 this.renderer.selectRootElement('#focus-this-input').focus();
81 this.expandForm = !this.expandForm;
82 if (this.expandForm) {
83 this.store.setItem(EXPAND_FORM, true);
85 this.store.removeItem(EXPAND_FORM);
89 toggleIncludeInactive() {
90 if (this.search.inactive) { // value set by ngModel
91 this.store.setItem(INCLUDE_INACTIVE, true);
93 this.store.removeItem(INCLUDE_INACTIVE);
97 rowsSelected(rows: IdlObject | IdlObject[]) {
98 this.patronsSelected.emit([].concat(rows));
101 getSelected(): IdlObject[] {
102 return this.searchGrid ?
103 this.searchGrid.context.getSelectedRows() : [];
107 this.searchGrid.reload();
111 this.search = {profile: null};
112 this.searchOrg = this.org.root();
115 getRows(pager: Pager, sort: any[]): Observable<any> {
117 let observable: Observable<IdlObject>;
119 if (this.search.id) {
120 observable = this.searchById();
122 observable = this.searchByForm(pager, sort);
125 return observable.pipe(map(user => this.localFleshUser(user)));
128 localFleshUser(user: IdlObject): IdlObject {
129 user.home_ou(this.org.get(user.home_ou()));
133 searchByForm(pager: Pager, sort: any[]): Observable<IdlObject> {
135 const search = this.compileSearch();
136 if (!search) { return of(); }
138 const sorter = this.compileSort(sort);
140 return this.net.request(
142 'open-ils.actor.patron.search.advanced.fleshed',
144 this.compileSearch(),
154 searchById(): Observable<IdlObject> {
155 return this.net.request(
157 'open-ils.actor.user.fleshed.retrieve',
158 this.auth.token(), this.search.id, DEFAULT_FLESH
162 compileSort(sort: any[]): string[] {
163 if (!sort || sort.length === 0) { return DEFAULT_SORT; }
164 return sort.map(def => `${def.name} ${def.dir}`);
167 compileSearch(): any {
169 let hasSearch = false;
170 const search: Object = {};
172 Object.keys(this.search).forEach(field => {
173 search[field] = this.mapSearchField(field);
174 if (search[field]) { hasSearch = true; }
177 return hasSearch ? search : null;
180 isValue(val: any): boolean {
181 return (val !== null && val !== undefined && val !== '');
184 mapSearchField(field: string): any {
186 const value = this.search[field];
187 if (!this.isValue(value)) { return null; }
189 const chunk = {value: value, group: 0};
193 case 'name': // name keywords
217 chunk.value = chunk.value.id(); // pgt object
224 chunk.value = chunk.value.replace(/\D/g, '');
226 if (!field.match(/year/)) {
227 // force day/month to be 2 digits
228 chunk[field].value = ('0' + value).slice(-2);
233 // Confirm the value wasn't scrubbed away above
234 if (!this.isValue(chunk.value)) { return null; }