]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
LP2045292 Color contrast for AngularJS patron bills
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / sandbox / sandbox.component.ts
1 /* eslint-disable no-magic-numbers */
2
3 import {timer as observableTimer, Observable} from 'rxjs';
4 import {Component, OnInit, ViewChild, Input, TemplateRef} from '@angular/core';
5 import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
6 import {ToastService} from '@eg/share/toast/toast.service';
7 import {StringService} from '@eg/share/string/string.service';
8 import {map, take} from 'rxjs/operators';
9 import {GridDataSource, GridColumn, GridRowFlairEntry, GridCellTextGenerator} from '@eg/share/grid/grid';
10 import {IdlService, IdlObject} from '@eg/core/idl.service';
11 import {PcrudService} from '@eg/core/pcrud.service';
12 import {OrgService} from '@eg/core/org.service';
13 import {Pager} from '@eg/share/util/pager';
14 import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
15 import {PrintService} from '@eg/share/print/print.service';
16 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
17 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
18 import {NgbDate} from '@ng-bootstrap/ng-bootstrap';
19 import {FormGroup, FormControl} from '@angular/forms';
20 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
21 import {FormatService} from '@eg/core/format.service';
22 import {StringComponent} from '@eg/share/string/string.component';
23 import {GridComponent} from '@eg/share/grid/grid.component';
24 import * as Moment from 'moment-timezone';
25 import {SampleDataService} from '@eg/share/util/sample-data.service';
26 import {HtmlToTxtService} from '@eg/share/util/htmltotxt.service';
27
28 @Component({
29     templateUrl: 'sandbox.component.html',
30     styles: ['.date-time-input.ng-invalid {border: 5px purple solid;}',
31         '.date-time-input.ng-valid {border: 5px green solid; animation: slide 5s linear 1s infinite alternate;}',
32         '@keyframes slide {0% {margin-left:0px;} 50% {margin-left:200px;}}']
33 })
34 export class SandboxComponent implements OnInit {
35
36     @ViewChild('progressDialog', { static: true })
37     private progressDialog: ProgressDialogComponent;
38
39     @ViewChild('dateSelect', { static: false })
40     private dateSelector: DateSelectComponent;
41
42     @ViewChild('printTemplate', { static: true })
43     private printTemplate: TemplateRef<any>;
44
45     @ViewChild('fmRecordEditor', { static: true })
46     private fmRecordEditor: FmRecordEditorComponent;
47
48     @ViewChild('numConfirmDialog', { static: true })
49     private numConfirmDialog: ConfirmDialogComponent;
50
51     public numThings = 0;
52
53     @ViewChild('bresvEditor', { static: true })
54     private bresvEditor: FmRecordEditorComponent;
55
56     @ViewChild('penaltyDialog', {static: false}) penaltyDialog;
57
58
59     // @ViewChild('helloStr') private helloStr: StringComponent;
60
61     gridDataSource: GridDataSource = new GridDataSource();
62
63     cbEntries: ComboboxEntry[];
64     // supplier of async combobox data
65     cbAsyncSource: (term: string) => Observable<ComboboxEntry>;
66
67     btSource: GridDataSource = new GridDataSource();
68     btGridCellTextGenerator: GridCellTextGenerator;
69     acpSource: GridDataSource = new GridDataSource();
70     eventsDataSource: GridDataSource = new GridDataSource();
71     editSelected: (rows: IdlObject[]) => void;
72     @ViewChild('acpGrid', { static: true }) acpGrid: GridComponent;
73     @ViewChild('acpEditDialog', { static: true }) editDialog: FmRecordEditorComponent;
74     @ViewChild('successString', { static: true }) successString: StringComponent;
75     @ViewChild('updateFailedString', { static: true }) updateFailedString: StringComponent;
76     world = 'world'; // for local template version
77     btGridTestContext: any = {hello : this.world};
78
79     renderLocal = false;
80
81     testDate: any;
82
83     testStr: string;
84     @Input() set testString(str: string) {
85         this.testStr = str;
86     }
87
88     oneBtype: IdlObject;
89
90     name = 'Jane';
91
92     dynamicTitleText: string;
93
94     badOrgForm: FormGroup;
95
96     ranganathan: FormGroup;
97
98     dateObject: Date = new Date();
99
100     simpleCombo: ComboboxEntry;
101     kingdom: ComboboxEntry;
102
103     complimentEvergreen: (rows: IdlObject[]) => void;
104     notOneSelectedRow: (rows: IdlObject[]) => boolean;
105
106     // selector field value on metarecord object
107     aMetarecord: string;
108
109     // file-reader example
110     fileContents:  Array<string>;
111
112     // cross-tab communications example
113     private sbChannel: any;
114     sbChannelText: string;
115     myTimeForm: FormGroup;
116
117     locId = 1; // Stacks
118     aLocation: IdlObject; // acpl
119     orgClassCallback: (orgId: number) => string;
120
121     circDaily: IdlObject;
122     circHourly: IdlObject;
123
124     constructor(
125         private idl: IdlService,
126         private org: OrgService,
127         private pcrud: PcrudService,
128         private strings: StringService,
129         private toast: ToastService,
130         private format: FormatService,
131         private printer: PrintService,
132         private samples: SampleDataService,
133         private h2txt: HtmlToTxtService
134     ) {
135         // BroadcastChannel is not yet defined in PhantomJS and elsewhere
136         this.sbChannel = (typeof BroadcastChannel === 'undefined') ?
137             {} : new BroadcastChannel('eg.sbChannel');
138         this.sbChannel.onmessage = (e) => this.sbChannelHandler(e);
139
140         this.orgClassCallback = (orgId: number): string => {
141             if (orgId === 1) { return 'font-weight-bold'; }
142             return orgId <= 3 ? 'text-info' : 'text-danger';
143         };
144     }
145
146     ngOnInit() {
147         this.badOrgForm = new FormGroup({
148             'badOrgSelector': new FormControl(
149                 {'id': 4, 'includeAncestors': false, 'includeDescendants': true}, (c: FormControl) => {
150                     // An Angular custom validator
151                     if (c.value.orgIds && c.value.orgIds.length > 5) {
152                         return { tooMany: 'That\'s too many fancy libraries!' };
153                     } else {
154                         return null;
155                     }
156                 } )
157         });
158
159         this.ranganathan = new FormGroup({
160             'law': new FormControl('second', (c: FormControl) => {
161                 // An Angular custom validator
162                 if ('wrong' === c.value.id || c.value.freetext) {
163                     return { notALaw: 'That\'s not a real law of library science!' };
164                 } else {
165                     return null;
166                 }
167             } )
168         });
169
170         this.badOrgForm.get('badOrgSelector').valueChanges.subscribe(bad => {
171             this.toast.danger('The fanciest libraries are: ' + JSON.stringify(bad.orgIds));
172         });
173
174         this.ranganathan.get('law').valueChanges.subscribe(l => {
175             this.toast.success('You chose: ' + l.label);
176         });
177
178         this.kingdom = {id: 'Bacteria', label: 'Bacteria'};
179
180         this.gridDataSource.data = [
181             {name: 'Jane', state: 'AZ'},
182             {name: 'Al', state: 'CA'},
183             {name: 'The Tick', state: 'TX'}
184         ];
185
186         this.pcrud.retrieveAll('cmrcfld', {order_by: {cmrcfld: 'name'}})
187             .subscribe(format => {
188                 if (!this.cbEntries) { this.cbEntries = []; }
189                 this.cbEntries.push({id: format.id(), label: format.name()});
190             });
191
192         this.cbAsyncSource = term => {
193             return this.pcrud.search(
194                 'cmrcfld',
195                 {name: {'ilike': `%${term}%`}}, // could -or search on label
196                 {order_by: {cmrcfld: 'name'}}
197             ).pipe(map(marcField => {
198                 return {id: marcField.id(), label: marcField.name()};
199             }));
200         };
201
202         this.btSource.getRows = (pager: Pager, sort: any[]) => {
203
204             const orderBy: any = {cbt: 'name'};
205             if (sort.length) {
206                 orderBy.cbt = sort[0].name + ' ' + sort[0].dir;
207             }
208
209             return this.pcrud.retrieveAll('cbt', {
210                 offset: pager.offset,
211                 limit: pager.limit,
212                 order_by: orderBy
213             }).pipe(map(cbt => {
214                 // example of inline fleshing
215                 cbt.owner(this.org.get(cbt.owner()));
216                 cbt.datetime_test = new Date();
217                 this.oneBtype = cbt;
218                 return cbt;
219             }));
220         };
221
222         // GridCellTextGenerator for the btGrid; note that this
223         // also demonstrates that a GridCellTextGenerator only has
224         // access to the row, and does not have access to any additional
225         // context that might be passed to a cellTemplate
226         this.btGridCellTextGenerator = {
227             test: row => 'HELLO universe ' + row.id()
228         };
229
230         this.acpSource.getRows = (pager: Pager, sort: any[]) => {
231             const orderBy: any = {acp: 'id'};
232             if (sort.length) {
233                 orderBy.acp = sort[0].name + ' ' + sort[0].dir;
234             }
235
236             // base query to grab everything
237             const base: Object = {};
238             base[this.idl.classes['acp'].pkey] = {'!=' : null};
239             const query: any = new Array();
240             query.push(base);
241
242             // and add any filters
243             Object.keys(this.acpSource.filters).forEach(key => {
244                 Object.keys(this.acpSource.filters[key]).forEach(key2 => {
245                     query.push(this.acpSource.filters[key][key2]);
246                 });
247             });
248             return this.pcrud.search('acp',
249                 query, {
250                     flesh: 1,
251                     flesh_fields: {acp: ['location', 'status', 'creator', 'editor']},
252                     offset: pager.offset,
253                     limit: pager.limit,
254                     order_by: orderBy
255                 });
256         };
257
258         this.eventsDataSource.getRows = (pager: Pager, sort: any[]) => {
259
260             const orderEventsBy: any = {atevdef: 'name'};
261             if (sort.length) {
262                 orderEventsBy.atevdef = sort[0].name + ' ' + sort[0].dir;
263             }
264
265             const base: Object = {};
266             base[this.idl.classes['atevdef'].pkey] = {'!=' : null};
267             const query: any = new Array();
268             query.push(base);
269
270             console.log(JSON.stringify(this.eventsDataSource.filters));
271
272             Object.keys(this.eventsDataSource.filters).forEach(key => {
273                 Object.keys(this.eventsDataSource.filters[key]).forEach(key2 => {
274                     query.push(this.eventsDataSource.filters[key][key2]);
275                 });
276             });
277
278             return this.pcrud.search('atevdef', query, {
279                 flesh: 1,
280                 flesh_fields: {atevdef: ['hook', 'validator', 'reactor']},
281                 offset: pager.offset,
282                 limit: pager.limit,
283                 order_by: orderEventsBy
284             });
285         };
286
287         this.editSelected = (idlThings: IdlObject[]) => {
288
289             // Edit each IDL thing one at a time
290             const editOneThing = (thing: IdlObject) => {
291                 if (!thing) { return; }
292
293                 this.showEditDialog(thing).then(
294                     () => editOneThing(idlThings.shift()));
295             };
296
297             editOneThing(idlThings.shift());
298         };
299         this.acpGrid.onRowActivate.subscribe(
300             (acpRec: IdlObject) => { this.showEditDialog(acpRec); }
301         );
302
303         this.complimentEvergreen = (rows: IdlObject[]) => alert('Evergreen is great!');
304         this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1);
305
306         this.pcrud.retrieve('bre', 1, {}, {fleshSelectors: true})
307             .subscribe(bib => {
308             // Format service will automatically find the selector
309             // value to display from our fleshed metarecord field.
310                 this.aMetarecord = this.format.transform({
311                     value: bib.metarecord(),
312                     idlClass: 'bre',
313                     idlField: 'metarecord'
314                 });
315             });
316
317         const b = this.idl.create('bresv');
318         b.cancel_time('2019-03-25T11:07:59-0400');
319         this.bresvEditor.mode = 'create';
320         this.bresvEditor.record = b;
321
322         this.myTimeForm = new FormGroup({
323             'datetime': new FormControl(Moment([]), (c: FormControl) => {
324                 // An Angular custom validator
325                 if (c.value.year() < 2019) {
326                     return { tooLongAgo: 'That\'s before 2019' };
327                 } else {
328                     return null;
329                 }
330             } )
331         });
332
333         const str = 'C&#xe9;sar&nbsp;&amp;&nbsp;Me';
334         console.log(this.h2txt.htmlToTxt(str));
335
336         const org =
337             this.org.list().filter(o => o.ou_type().can_have_vols() === 't')[0];
338         this.circDaily = this.idl.create('circ');
339         this.circDaily.duration('1 day');
340         this.circDaily.due_date(new Date().toISOString());
341         this.circDaily.circ_lib(org.id());
342
343         this.circHourly = this.idl.create('circ');
344         this.circHourly.duration('1 hour');
345         this.circHourly.due_date(new Date().toISOString());
346         this.circHourly.circ_lib(org.id());
347     }
348
349     sbChannelHandler = msg => {
350         setTimeout(() => { this.sbChannelText = msg.data.msg; });
351     };
352
353     sendMessage($event) {
354         this.sbChannel.postMessage({msg : $event.target.value});
355     }
356
357     // Example of click handler for row action
358     complimentEvergreen2(rows: IdlObject[]) {
359         alert('I know, right?');
360     }
361
362     openEditor() {
363         this.fmRecordEditor.open({size: 'lg'}).subscribe(
364             pcrudResult => console.debug('Record editor performed action'),
365             (err: unknown) => console.error(err),
366             () => console.debug('Dialog closed')
367         );
368     }
369
370     btGridRowClassCallback(row: any): string {
371         if (row.id() === 1) {
372             return 'text-uppercase font-weight-bold text-danger';
373         }
374     }
375
376     btGridRowFlairCallback(row: any): GridRowFlairEntry {
377         const flair = {icon: null, title: null};
378         if (row.id() === 2) {
379             flair.icon = 'priority_high';
380             flair.title = 'I Am ID 2';
381         } else if (row.id() === 3) {
382             flair.icon = 'not_interested';
383         }
384         return flair;
385     }
386
387     // apply to all 'name' columns regardless of row
388     btGridCellClassCallback(row: any, col: GridColumn): string {
389         if (col.name === 'name') {
390             if (row.id() === 7) {
391                 return 'text-lowercase font-weight-bold text-info';
392             }
393             return 'text-uppercase font-weight-bold text-success';
394         }
395     }
396
397     doPrint() {
398         this.printer.print({
399             template: this.printTemplate,
400             contextData: {world : this.world},
401             printContext: 'default'
402         });
403
404         this.printer.print({
405             text: '<b>hello</b>',
406             printContext: 'default'
407         });
408     }
409
410     printWithDialog() {
411         this.printer.print({
412             template: this.printTemplate,
413             contextData: {world : this.world},
414             printContext: 'default',
415             showDialog: true
416         });
417     }
418
419     changeDate(date) {
420         console.log('HERE WITH ' + date);
421         this.testDate = date;
422     }
423
424     showProgress() {
425         this.progressDialog.open();
426
427         // every 250ms emit x*10 for 0-10
428         observableTimer(0, 250).pipe(
429             map(x => x * 10),
430             take(11)
431         ).subscribe(
432             val => this.progressDialog.update({value: val, max: 100}),
433             (err: unknown) => {},
434             ()  => this.progressDialog.close()
435         );
436     }
437
438     testToast() {
439         this.toast.success('HELLO TOAST TEST');
440         setTimeout(() => this.toast.danger('DANGER TEST AHHH!'), 4000);
441     }
442
443     testStrings() {
444         this.strings.interpolate('staff.sandbox.test', {name : 'janey'})
445             .then(txt => this.toast.success(txt));
446
447         setTimeout(() => {
448             this.strings.interpolate('staff.sandbox.test', {name : 'johnny'})
449                 .then(txt => this.toast.success(txt));
450         }, 4000);
451     }
452
453     confirmNumber(num: number): void {
454         this.numThings = num;
455         console.log(this.numThings);
456         this.numConfirmDialog.open();
457     }
458
459     showEditDialog(idlThing: IdlObject): Promise<any> {
460         this.editDialog.mode = 'update';
461         this.editDialog.recordId = idlThing['id']();
462         return new Promise((resolve, reject) => {
463             this.editDialog.open({size: 'lg'}).subscribe(
464                 ok => {
465                     this.successString.current()
466                         .then(str => this.toast.success(str));
467                     this.acpGrid.reloadWithoutPagerReset();
468                     resolve(ok);
469                 },
470                 (rejection: unknown) => {
471                     this.updateFailedString.current()
472                         .then(str => this.toast.danger(str));
473                     reject(rejection);
474                 }
475             );
476         });
477     }
478
479     allFutureDates(date: NgbDate, current: { year: number; month: number; }) {
480         const currentTime = new Date();
481         const today = new NgbDate(currentTime.getFullYear(), currentTime.getMonth() + 1, currentTime.getDate());
482         return date.after(today);
483     }
484
485     sevenDaysAgo() {
486         const d = new Date();
487         d.setDate(d.getDate() - 7);
488         return d;
489     }
490
491     testServerPrint() {
492
493         // Note these values can be IDL objects or plain hashes.
494         const templateData = {
495             patron:  this.samples.listOfThings('au')[0],
496             address: this.samples.listOfThings('aua')[0]
497         };
498
499         // NOTE: eventually this will be baked into the print service.
500         this.printer.print({
501             templateName: 'patron_address',
502             contextData: templateData,
503             printContext: 'default'
504         });
505     }
506
507     openPenalty() {
508         this.penaltyDialog.open()
509             .subscribe(val => console.log('penalty value', val));
510     }
511 }
512