]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/share/patron/search.component.ts
LP1859241 Angular holds patron search dialog
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / patron / search.component.ts
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';
18
19 const DEFAULT_SORT = [
20    'family_name ASC',
21     'first_given_name ASC',
22     'second_given_name ASC',
23     'dob DESC'
24 ];
25
26 const DEFAULT_FLESH = [
27     'card', 'settings', 'standing_penalties', 'addresses', 'billing_address',
28     'mailing_address', 'stat_cat_entries', 'waiver_entries', 'usr_activity',
29     'notes', 'profile'
30 ];
31
32 const EXPAND_FORM = 'eg.circ.patron.search.show_extras';
33 const INCLUDE_INACTIVE = 'eg.circ.patron.search.include_inactive';
34
35 @Component({
36   selector: 'eg-patron-search',
37   templateUrl: './search.component.html'
38 })
39
40 export class PatronSearchComponent implements OnInit, AfterViewInit {
41
42     @ViewChild('searchGrid', {static: false}) searchGrid: GridComponent;
43
44     // Fired on dbl-click of a search result row.
45     @Output() patronsSelected: EventEmitter<any>;
46
47     search: any = {};
48     searchOrg: IdlObject;
49     expandForm: boolean;
50     dataSource: GridDataSource;
51     profileGroups: IdlObject[] = [];
52
53     constructor(
54         private renderer: Renderer2,
55         private net: NetService,
56         private org: OrgService,
57         private auth: AuthService,
58         private store: ServerStoreService
59     ) {
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);
64         };
65     }
66
67     ngOnInit() {
68         this.searchOrg = this.org.root();
69         this.store.getItemBatch([EXPAND_FORM, INCLUDE_INACTIVE])
70             .then(settings => {
71                 this.expandForm = settings[EXPAND_FORM];
72                 this.search.inactive = settings[INCLUDE_INACTIVE];
73             });
74     }
75
76     ngAfterViewInit() {
77         this.renderer.selectRootElement('#focus-this-input').focus();
78     }
79
80     toggleExpandForm() {
81         this.expandForm = !this.expandForm;
82         if (this.expandForm) {
83             this.store.setItem(EXPAND_FORM, true);
84         } else {
85             this.store.removeItem(EXPAND_FORM);
86         }
87     }
88
89     toggleIncludeInactive() {
90         if (this.search.inactive) { // value set by ngModel
91             this.store.setItem(INCLUDE_INACTIVE, true);
92         } else {
93             this.store.removeItem(INCLUDE_INACTIVE);
94         }
95     }
96
97     rowsSelected(rows: IdlObject | IdlObject[]) {
98         this.patronsSelected.emit([].concat(rows));
99     }
100
101     getSelected(): IdlObject[] {
102         return this.searchGrid ?
103             this.searchGrid.context.getSelectedRows() : [];
104     }
105
106     go() {
107         this.searchGrid.reload();
108     }
109
110     clear() {
111         this.search = {profile: null};
112         this.searchOrg = this.org.root();
113     }
114
115     getRows(pager: Pager, sort: any[]): Observable<any> {
116
117         let observable: Observable<IdlObject>;
118
119         if (this.search.id) {
120             observable = this.searchById();
121         } else {
122             observable = this.searchByForm(pager, sort);
123         }
124
125         return observable.pipe(map(user => this.localFleshUser(user)));
126     }
127
128     localFleshUser(user: IdlObject): IdlObject {
129         user.home_ou(this.org.get(user.home_ou()));
130         return user;
131     }
132
133     searchByForm(pager: Pager, sort: any[]): Observable<IdlObject> {
134
135         const search = this.compileSearch();
136         if (!search) { return of(); }
137
138         const sorter = this.compileSort(sort);
139
140         return this.net.request(
141             'open-ils.actor',
142             'open-ils.actor.patron.search.advanced.fleshed',
143             this.auth.token(),
144             this.compileSearch(),
145             pager.limit,
146             sorter,
147             null, // ?
148             this.searchOrg.id(),
149             DEFAULT_FLESH,
150             pager.offset
151         );
152     }
153
154     searchById(): Observable<IdlObject> {
155         return this.net.request(
156             'open-ils.actor',
157             'open-ils.actor.user.fleshed.retrieve',
158             this.auth.token(), this.search.id, DEFAULT_FLESH
159         );
160     }
161
162     compileSort(sort: any[]): string[] {
163         if (!sort || sort.length === 0) { return DEFAULT_SORT; }
164         return sort.map(def => `${def.name} ${def.dir}`);
165     }
166
167     compileSearch(): any {
168
169         let hasSearch = false;
170         const search: Object = {};
171
172         Object.keys(this.search).forEach(field => {
173             search[field] = this.mapSearchField(field);
174             if (search[field]) { hasSearch = true; }
175         });
176
177         return hasSearch ? search : null;
178     }
179
180     isValue(val: any): boolean {
181         return (val !== null && val !== undefined && val !== '');
182     }
183
184     mapSearchField(field: string): any {
185
186         const value = this.search[field];
187         if (!this.isValue(value)) { return null; }
188
189         const chunk = {value: value, group: 0};
190
191         switch (field) {
192
193             case 'name': // name keywords
194             case 'inactive':
195                 delete chunk.group;
196                 break;
197
198             case 'street1':
199             case 'street2':
200             case 'city':
201             case 'state':
202             case 'post_code':
203                 chunk.group = 1;
204                 break;
205
206             case 'phone':
207             case 'ident':
208                 chunk.group = 2;
209                 break;
210
211             case 'card':
212                 chunk.group = 3;
213                 break;
214
215             case 'profile':
216                 chunk.group = 5;
217                 chunk.value = chunk.value.id(); // pgt object
218                 break;
219
220             case 'dob_day':
221             case 'dob_month':
222             case 'dob_year':
223                 chunk.group = 4;
224                 chunk.value = chunk.value.replace(/\D/g, '');
225
226                 if (!field.match(/year/)) {
227                     // force day/month to be 2 digits
228                     chunk[field].value = ('0' + value).slice(-2);
229                 }
230                 break;
231         }
232
233         // Confirm the value wasn't scrubbed away above
234         if (!this.isValue(chunk.value)) { return null; }
235
236         return chunk;
237     }
238 }
239