From 0f14ce0a3ec098b2b793c0ad39a95bda49aa527d Mon Sep 17 00:00:00 2001 From: Jason Etheridge Date: Mon, 13 Mar 2023 14:51:20 -0400 Subject: [PATCH] lp1993824: grids enhancement; support for saving filter sets turned off by default Signed-off-by: Jason Etheridge --- .../share/grid/grid-filter-control.component.ts | 3 +- .../grid/grid-manage-filters-dialog.component.html | 82 +++++++ .../grid/grid-manage-filters-dialog.component.ts | 238 +++++++++++++++++++++ .../src/app/share/grid/grid-toolbar.component.html | 22 +- .../src/eg2/src/app/share/grid/grid.component.ts | 11 + Open-ILS/src/eg2/src/app/share/grid/grid.module.ts | 4 +- Open-ILS/src/eg2/src/app/share/grid/grid.ts | 83 ++++++- Open-ILS/src/eg2/tsconfig.json | 2 +- 8 files changed, 436 insertions(+), 9 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.ts diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts index 69893613be..3714258341 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts @@ -49,12 +49,13 @@ export class GridFilterControlComponent implements OnInit { } applyOrgFilter(col: GridColumn) { - const org: IdlObject = (col.filterValue as unknown) as IdlObject; + let org: IdlObject = (col.filterValue as unknown) as IdlObject; if (org == null) { this.clearFilter(col); return; } + org = this.org.get(org); // if coming from a Named Filter Set, filterValue would be an an org id const ous: any[] = new Array(); if (col.filterIncludeOrgDescendants || col.filterIncludeOrgAncestors) { if (col.filterIncludeOrgAncestors) { diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.html new file mode 100644 index 0000000000..10d46dcdac --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.html @@ -0,0 +1,82 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.ts new file mode 100644 index 0000000000..a0a22a0a5f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-manage-filters-dialog.component.ts @@ -0,0 +1,238 @@ +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {AuthService} from '@eg/core/auth.service'; +import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component'; +import {Component, Input, OnInit, ViewChild, Renderer2} from '@angular/core'; +import {GridContext} from '@eg/share/grid/grid'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {EventService} from '@eg/core/event.service'; +import {FormControl} from '@angular/forms'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {NgForm} from '@angular/forms'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {ProgressDialogComponent} from '@eg/share/dialog/progress.component'; +import {ServerStoreService} from '@eg/core/server-store.service'; +import {StringComponent} from '@eg/share/string/string.component'; +import {Subject, Subscription, Observable, from, EMPTY, throwError} from 'rxjs'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {debounceTime, distinctUntilChanged, switchMap, takeLast, finalize} from 'rxjs/operators'; + +@Component({ + selector: 'eg-grid-manage-filters-dialog', + templateUrl: './grid-manage-filters-dialog.component.html' +}) + +export class GridManageFiltersDialogComponent extends DialogComponent implements OnInit { + + @Input() gridContext: GridContext; + + subscriptions: Subscription[] = []; + + saveFilterName: string = ''; + saveFilterNameModelChanged: Subject = new Subject(); + nameCollision: boolean = false; + + filterSetEntries: ComboboxEntry[] = []; + + @ViewChild('manageFiltersForm', { static: false}) manageFiltersForm: NgForm; + @ViewChild('namedFilterSetSelector', { static: true}) namedFilterSetSelector: ComboboxComponent; + + constructor( + private modal: NgbModal, + private auth: AuthService, + private evt: EventService, + private net: NetService, + private toast: ToastService, + private idl: IdlService, + private pcrud: PcrudService, + private renderer: Renderer2, + private store: ServerStoreService, + ) { + super(modal); + } + + ngOnInit() { + + this.subscriptions.push( this.onOpen$.subscribe( + _ => { + const el = this.renderer.selectRootElement('#session_name'); + if (el) { el.focus(); el.select(); } + } + )); + + this.subscriptions.push( + this.saveFilterNameModelChanged + .pipe( + debounceTime(300), + distinctUntilChanged() + ) + .subscribe( newText => { + this.saveFilterName = newText; + this.nameCollision = false; + this.store.getItem('eg.grid.filters.' + this.gridContext.persistKey).then( setting => { + if (setting) { + if (setting[newText]) { + this.nameCollision = true; + } + } + }); + }) + ); + + this.refreshEntries(); + + console.log('manage-filters-dialog this', this); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } + + refreshEntries() { + this.filterSetEntries = []; + this.store.getItem('eg.grid.filters.' + this.gridContext.persistKey).then( setting => { + console.log('getItem, setting =',setting); + if (setting /* for testing only: && Object.keys( setting ).length > 0 */) { + Object.keys(setting).forEach( key => { + this.filterSetEntries.push({ id: key, label: key }); + }); + } else { + if (this.gridContext.migrateLegacyFilterSets) { + this.attemptLegacyFilterSetMigration(); + } + } + if (this.namedFilterSetSelector && this.filterSetEntries.length > 0) { + this.namedFilterSetSelector.selected = this.filterSetEntries[0]; + } + }); + } + + legacyFieldMap(legacy_field: string): string { + if (this.gridContext.idlClass === 'uvuv') { + if (legacy_field === 'url_id') { return 'url'; } + if (legacy_field === 'attempt_id') { return 'id'; } + if (legacy_field === 'res_time') { return 'res_time'; } + if (legacy_field === 'res_code') { return 'res_code'; } + if (legacy_field === 'res_text') { return 'res_text'; } + if (legacy_field === 'req_time') { return 'req_time'; } + return 'url.' + legacy_field; + } else { + if (legacy_field === 'url_id') { return 'id'; } + } + + return legacy_field; + } + + legacyOperatorValueMap(field_name: string, field_datatype: string, legacy_operator: string, legacy_value: any): any { + let operator = legacy_operator; + let value = legacy_value; + let filterOperator = legacy_operator; + let filterValue = legacy_value; + let filterInputDisabled = false; + let filterIncludeOrgAncestors = false; + let filterIncludeOrgDescendants = false; + let notSupported = false; + switch(legacy_operator) { + case '=': case '!=': case '>': case '<': case '>=': case '<=': + /* same */ + break; + case 'in': case 'not in': + case 'between': case 'not between': + /* not supported, warn user */ + operator = undefined; + value = undefined; + filterOperator = '='; + filterValue = undefined; + notSupported = true; + break; + case 'null': + operator = '='; + value = undefined; + filterOperator = '='; + filterValue = null; + break; + case 'not null': + operator = '!='; + value = undefined; + filterOperator = '!='; + filterValue = null; + break; + case 'like': case 'not like': + value = '%' + filterValue + '%'; + /* not like needs special handling further below */ + break; + } + if (notSupported) { + return undefined; + } + + let filter = {} + let mappedFieldName = this.legacyFieldMap(field_name); + filter[mappedFieldName] = {}; + if (operator === 'not like') { + filter[mappedFieldName]['-not'] = {}; + filter[mappedFieldName]['-not'][mappedFieldName] = {}; + filter[mappedFieldName]['-not'][mappedFieldName]['like'] = value; + } else { + filter[mappedFieldName][operator] = value; + } + + let control = { + isFiltered: true, + filterValue: filterValue, + filterOperator: filterOperator, + filterInputDisabled: filterInputDisabled, + filterIncludeOrgAncestors: filterIncludeOrgAncestors, + filterIncludeOrgDescendants: filterIncludeOrgDescendants + } + + return [ filter, control ]; + } + + attemptLegacyFilterSetMigration() { + // The legacy interface allows you to define multiple filters for the same column, which our current filters + // do not support (well, the dataSource.filters part can, but not the grid.context.filterControls). The legacy + // filters also have an unintuitive additive behavior if you do that. We should take the last filter and warn + // the user if this happens. None of the filters for date columns is working correctly in the legacy UI, so no + // need to map those. We also not able to support between, not between, in, and not in. + this.pcrud.search('cfdfs', {'interface':this.gridContext.migrateLegacyFilterSets},{},{'atomic':true}).subscribe( + (legacySets) => { + legacySets.forEach( s => { + let obj = { + 'filters' : {}, + 'controls' : {} + }; + console.log('migrating legacy set ' + s.name(), s ); + JSON.parse( s.filters() ).forEach( f => { + let mappedFieldName = this.legacyFieldMap(f.field); + let c = this.gridContext.columnSet.getColByName( mappedFieldName ); + if (c) { + let r = this.legacyOperatorValueMap(f.field, c.datatype, f.operator, f.value || f.values); + console.log(f.field, r); + obj['filters'][mappedFieldName] = [ r[0] ]; + obj['controls'][mappedFieldName] = r[1]; + } else { + console.log('with legacy set ' + s.name() + + ', column not found for ' + f.field + ' (' + this.legacyFieldMap( f.field) + ')'); + } + }); + if (Object.keys(obj.filters).length > 0) { + this.store.getItem('eg.grid.filters.' + this.gridContext.persistKey).then( setting => { + console.log('saveFilters, setting = ', setting); + setting ||= {}; + setting[s.name()] = obj; + console.log('saving ' + s.name(), JSON.stringify(obj)); + this.store.setItem('eg.grid.filters.' + this.gridContext.persistKey, setting).then( res => { + this.refreshEntries(); + console.log('save toast here',res); + }); + }); + } + }); + } + ); + } +} diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html index 8e998d150c..a37b5aacba 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html @@ -1,8 +1,12 @@ + + +
+
{{gridContext.toolbarLabel}} @@ -11,11 +15,19 @@
- + + + +