LP1838341: Double-Click Setting to open Edit Dialog
[evergreen-equinox.git] / Open-ILS / src / eg2 / src / app / staff / admin / local / org-unit-settings / org-unit-settings.component.ts
1 import {Component, OnInit, Input, ViewChild, ViewEncapsulation
2     } from '@angular/core';
3 import {Router} from '@angular/router';
4 import {Observable, Observer, of} from 'rxjs';
5 import {map} from 'rxjs/operators';
6 import {Pager} from '@eg/share/util/pager';
7 import {IdlObject, IdlService} from '@eg/core/idl.service';
8 import {OrgService} from '@eg/core/org.service';
9 import {PcrudService} from '@eg/core/pcrud.service';
10 import {AuthService} from '@eg/core/auth.service';
11 import {NetService} from '@eg/core/net.service';
12 import {GridDataSource} from '@eg/share/grid/grid';
13 import {GridComponent} from '@eg/share/grid/grid.component';
14 import {GridToolbarCheckboxComponent
15     } from '@eg/share/grid/grid-toolbar-checkbox.component';
16 import {StoreService} from '@eg/core/store.service';
17 import {ServerStoreService} from '@eg/core/server-store.service';
18 import {ToastService} from '@eg/share/toast/toast.service';
19
20 import {EditOuSettingDialogComponent
21     } from '@eg/staff/admin/local/org-unit-settings/edit-org-unit-setting-dialog.component';
22 import {OuSettingHistoryDialogComponent
23     } from '@eg/staff/admin/local/org-unit-settings/org-unit-setting-history-dialog.component';
24 import {OuSettingJsonDialogComponent
25     } from '@eg/staff/admin/local/org-unit-settings/org-unit-setting-json-dialog.component';
26
27 export class OrgUnitSetting {
28     name: string;
29     label: string;
30     grp: string;
31     description: string;
32     value: any;
33     value_str: any;
34     dataType: string;
35     fmClass: string;
36     _idlOptions: IdlObject[];
37     _org_unit: IdlObject;
38     context: string;
39     view_perm: string;
40     _history: any[];
41 }
42
43 @Component({
44     templateUrl: './org-unit-settings.component.html'
45 })
46
47 export class OrgUnitSettingsComponent {
48
49     contextOrg: IdlObject;
50
51     initDone = false;
52     gridDataSource: GridDataSource;
53     gridTemplateContext: any;
54     prevFilter: string;
55     currentHistory: any[];
56     currentOptions: any[];
57     jsonFieldData: {};
58     @ViewChild('orgUnitSettingsGrid', { static:true }) orgUnitSettingsGrid: GridComponent;
59
60     @ViewChild('editOuSettingDialog', { static:true })
61         private editOuSettingDialog: EditOuSettingDialogComponent;
62     @ViewChild('orgUnitSettingHistoryDialog', { static:true })
63         private orgUnitSettingHistoryDialog: OuSettingHistoryDialogComponent;
64     @ViewChild('ouSettingJsonDialog', { static:true })
65         private ouSettingJsonDialog: OuSettingJsonDialogComponent;
66
67     refreshSettings: boolean;
68     renderFromPrefs: boolean;
69
70     settingTypeArr: any[];
71
72     @Input() filterString: string;
73
74     constructor(
75         private router: Router,
76         private org: OrgService,
77         private idl: IdlService,
78         private pcrud: PcrudService,
79         private auth: AuthService,
80         private store: ServerStoreService,
81         private localStore: StoreService,
82         private toast: ToastService,
83         private net: NetService,
84     ) {
85         this.gridDataSource = new GridDataSource();
86         this.refreshSettings = true;
87         this.renderFromPrefs = true;
88
89         this.contextOrg = this.org.get(this.auth.user().ws_ou());
90     }
91
92     ngOnInit() {
93         this.initDone = true;
94         this.settingTypeArr = [];
95         this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
96             return this.fetchSettingTypes(pager);
97         };
98         this.orgUnitSettingsGrid.onRowActivate.subscribe((setting:OrgUnitSetting) => {
99             this.showEditSettingValueDialog(setting);
100         });
101     }
102
103     fetchSettingTypes(pager: Pager): Observable<any> {
104         return new Observable<any>(observer => {
105             this.pcrud.retrieveAll('coust', {flesh: 3, flesh_fields: {
106                 'coust': ['grp', 'view_perm']
107             }},
108             { authoritative: true }).subscribe(
109                 settingTypes => this.allocateSettingTypes(settingTypes),
110                 err => {},
111                 ()  => {
112                     this.refreshSettings = false;
113                     this.mergeSettingValues().then(
114                         ok => {
115                             this.flattenSettings(observer);
116                         }
117                     );
118                 }
119             );
120         });
121     }
122
123     mergeSettingValues(): Promise<any> {
124         const settingNames = this.settingTypeArr.map(setting => setting.name);
125         return new Promise((resolve, reject) => {
126             this.net.request(
127                 'open-ils.actor',
128                 'open-ils.actor.ou_setting.ancestor_default.batch',
129                  this.contextOrg.id(), settingNames, this.auth.token()
130             ).subscribe(
131                 blob => {
132                     let settingVals = Object.keys(blob).map(key => {
133                         return {'name': key, 'setting': blob[key]}
134                     });
135                     settingVals.forEach(key => {
136                         if (key.setting) {
137                             let settingsObj = this.settingTypeArr.filter(
138                                 setting => setting.name == key.name
139                             )[0];
140                             settingsObj.value = key.setting.value;
141                             settingsObj.value_str = settingsObj.value;
142                             if (settingsObj.dataType == 'link' && (key.setting.value || key.setting.value == 0)) {
143                                 this.fetchLinkedField(settingsObj.fmClass, key.setting.value, settingsObj.value_str).then(res => {
144                                     settingsObj.value_str = res;
145                                 });
146                             }
147                             settingsObj._org_unit = this.org.get(key.setting.org);
148                             settingsObj.context = settingsObj._org_unit.shortname();
149                         }
150                     });
151                     resolve(this.settingTypeArr);
152                 },
153                 err => reject(err)
154             );
155         });
156     }
157
158     fetchLinkedField(fmClass, id, val) {
159         return new Promise((resolve, reject) => {
160             return this.pcrud.retrieve(fmClass, id).subscribe(linkedField => {
161                 val = linkedField.name();
162                 resolve(val);
163             });
164         });
165     }
166
167     fetchHistory(setting): Promise<any> {
168         let name = setting.name;
169         return new Promise((resolve, reject) => {
170             this.net.request(
171                 'open-ils.actor',
172                 'open-ils.actor.org_unit.settings.history.retrieve',
173                 this.auth.token(), name, this.contextOrg.id()
174             ).subscribe(res=> {
175                 this.currentHistory = [];
176                 if (!Array.isArray(res)) {
177                     res = [res];
178                 }
179                 res.forEach(log => {
180                     log.org = this.org.get(log.org);
181                     log.new_value_str = log.new_value;
182                     log.original_value_str = log.original_value;
183                     if (setting.dataType == "link") {
184                         if (log.new_value) {
185                             this.fetchLinkedField(setting.fmClass, parseInt(log.new_value), log.new_value_str).then(val => {
186                                 log.new_value_str = val;
187                             });
188                         }
189                         if (log.original_value) {
190                             this.fetchLinkedField(setting.fmClass, parseInt(log.original_value), log.original_value_str).then(val => {
191                                 log.original_value_str = val;
192                             });
193                         }
194                     }
195                     if (log.new_value_str) log.new_value_str = log.new_value_str.replace(/^"(.*)"$/, '$1');
196                     if (log.original_value_str) log.original_value_str = log.original_value_str.replace(/^"(.*)"$/, '$1');
197                 });
198                 this.currentHistory = res;
199                 this.currentHistory.sort((a, b) => {
200                     return a.date_applied < b.date_applied ? 1 : -1;
201                 });
202
203                 resolve(this.currentHistory);
204             }, err=>{reject(err);});
205         });
206     }
207
208     allocateSettingTypes(coust: IdlObject) {
209         let entry = new OrgUnitSetting();
210         entry.name = coust.name();
211         entry.label = coust.label();
212         entry.dataType = coust.datatype();
213         if (coust.fm_class()) entry.fmClass = coust.fm_class();
214         if (coust.description()) entry.description = coust.description();
215         // For some reason some setting types don't have a grp, should look into this...
216         if (coust.grp()) entry.grp = coust.grp().label();
217         if (coust.view_perm()) 
218             entry.view_perm = coust.view_perm().code();
219
220         this.settingTypeArr.push(entry);
221     }
222
223     flattenSettings(observer: Observer<any>) {
224         this.gridDataSource.data = this.settingTypeArr;
225         observer.complete();
226     }
227
228     contextOrgChanged(org: IdlObject) {
229         this.updateGrid(org);
230     }
231
232     applyFilter(clear?: boolean) {
233         if (clear) this.filterString = '';
234         this.updateGrid(this.contextOrg);
235     }
236     
237     updateSetting(obj, entry) {
238         this.net.request(
239             'open-ils.actor',
240             'open-ils.actor.org_unit.settings.update',
241             this.auth.token(), obj.context.id(), obj.setting
242         ).toPromise().then(res=> {
243             this.toast.success(entry.label + " Updated.");
244             if (!obj.setting[entry.name]) {
245                 let settingsObj = this.settingTypeArr.filter(
246                     setting => setting.name == entry.name
247                 )[0];
248                 settingsObj.value = null;
249                 settingsObj.value_str = null;
250                 settingsObj._org_unit = null;
251                 settingsObj.context = null;
252             }
253             this.mergeSettingValues();
254         },
255         err => {
256             this.toast.danger(entry.label + " failed to update: " + err.desc);
257         });
258     }
259
260     showEditSettingValueDialog(entry: OrgUnitSetting) {
261         this.editOuSettingDialog.entry = entry;
262         this.editOuSettingDialog.entryValue = entry.value;
263         this.editOuSettingDialog.entryContext = entry._org_unit || this.contextOrg;
264         this.editOuSettingDialog.open({size: 'lg'}).subscribe(
265             res => {
266                 this.updateSetting(res, entry);
267             }
268         );
269     }
270
271     showHistoryDialog(entry: OrgUnitSetting) {
272         if (entry) {
273             this.fetchHistory(entry).then(
274                 fetched => {
275                     this.orgUnitSettingHistoryDialog.history = this.currentHistory;
276                     this.orgUnitSettingHistoryDialog.gridDataSource.data = this.currentHistory;
277                     this.orgUnitSettingHistoryDialog.entry = entry;
278                     this.orgUnitSettingHistoryDialog.open({size: 'lg'}).subscribe(res => {
279                         if (res.revert) {
280                             this.updateSetting(res, entry);
281                         }
282                     });
283                 }
284             )
285         }
286     }
287
288     showJsonDialog(isExport: boolean) {
289         this.ouSettingJsonDialog.isExport = isExport;
290         this.ouSettingJsonDialog.jsonData = "";
291         if (isExport) {
292             this.ouSettingJsonDialog.jsonData = "{";
293             this.gridDataSource.data.forEach(entry => {
294                 this.ouSettingJsonDialog.jsonData +=
295                     "\"" + entry.name + "\": {\"org\": \"" +
296                     this.contextOrg.id() + "\", \"value\": ";
297                 if (entry.value) {
298                     this.ouSettingJsonDialog.jsonData += "\"" + entry.value + "\"";
299                 } else {
300                     this.ouSettingJsonDialog.jsonData += "null";
301                 }
302                 this.ouSettingJsonDialog.jsonData += "}";
303                 if (this.gridDataSource.data.indexOf(entry) != (this.gridDataSource.data.length - 1))
304                     this.ouSettingJsonDialog.jsonData += ",";
305             });
306             this.ouSettingJsonDialog.jsonData += "}";
307         }
308
309         this.ouSettingJsonDialog.open({size: 'lg'}).subscribe(res => {
310             if (res.apply && res.jsonData) {
311                 let jsonSettings = JSON.parse(res.jsonData);
312                 Object.entries(jsonSettings).forEach((fields) => {
313                     let entry = this.settingTypeArr.find(x => x.name == fields[0]);
314                     let obj = {setting: {}, context: {}};
315                     let val = this.parseValType(fields[1]['value'], entry.dataType);
316                     obj.setting[fields[0]] = val;
317                     obj.context = this.org.get(fields[1]['org']);
318                     this.updateSetting(obj, entry);
319                 });
320             }
321         });
322     }
323
324     parseValType(value, dataType) {
325         if (dataType == "integer" || "currency" || "link") {
326             return Number(value);
327         } else if (dataType == "bool") {
328             return (value === 'true');
329         } else {
330             return value;
331         }
332     }
333     
334     filterCoust() {
335         if (this.filterString != this.prevFilter) {
336             this.prevFilter = this.filterString;
337             if (this.filterString) {
338                 this.gridDataSource.data = [];
339                 let tempGrid = this.settingTypeArr;
340                 tempGrid.forEach(row => {
341                     let containsString =
342                          row.name.includes(this.filterString) ||
343                          row.label.includes(this.filterString) ||
344                          (row.grp && row.grp.includes(this.filterString)) ||
345                          (row.description && row.description.includes(this.filterString));
346                     if (containsString) {
347                         this.gridDataSource.data.push(row);
348                     }
349                 });
350             } else {
351                 this.gridDataSource.data = this.settingTypeArr;
352             }
353         }
354     }
355
356     updateGrid(org) {
357         if (this.contextOrg != org) {
358             this.contextOrg = org;
359             this.refreshSettings = true;
360         }
361
362         if (this.filterString != this.prevFilter) {
363             this.refreshSettings = true;
364         }
365
366         if (this.refreshSettings) { 
367             this.mergeSettingValues().then(
368                 res => this.filterCoust()
369             );
370         }
371     }
372 }