From 1367233b198c9e9204a224df5e3a0fa08695517f Mon Sep 17 00:00:00 2001 From: Patrick Sun Date: Fri, 14 May 2021 03:38:25 +1000 Subject: [PATCH] first reusable filter for numbers --- src/app/app.component.ts | 19 +- src/app/app.module.ts | 2 + src/app/auth/auth-http-interceptor.service.ts | 8 +- src/app/auth/auth.component.ts | 2 +- .../number-range-filter.component.html | 46 ++ .../number-range-filter.component.scss | 63 +++ .../number-range-filter.component.spec.ts | 25 + .../number-range-filter.component.ts | 473 ++++++++++++++++++ src/app/models/grid.state.model.ts | 35 ++ .../models/websocket/ws.login.event.model.ts | 2 + src/app/pay-in/pay-in.component.html | 17 +- src/app/pay-in/pay-in.component.ts | 37 +- src/app/service/auth-guard.service.ts | 4 +- src/app/service/pay-in.service.ts | 5 + src/app/service/session.service.ts | 29 +- src/app/websocket.ts | 7 + tsconfig.json | 2 +- 17 files changed, 752 insertions(+), 24 deletions(-) create mode 100644 src/app/grid-filter/number-range-filter/number-range-filter.component.html create mode 100644 src/app/grid-filter/number-range-filter/number-range-filter.component.scss create mode 100644 src/app/grid-filter/number-range-filter/number-range-filter.component.spec.ts create mode 100644 src/app/grid-filter/number-range-filter/number-range-filter.component.ts create mode 100644 src/app/models/grid.state.model.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c980769..b50c8e0 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -21,6 +21,7 @@ export class AppComponent implements OnInit , OnDestroy { @ViewChild('loanEditComponent', {static: true}) loanEdit: LoanEditComponent; webSocketSubscription: Subscription; + wsAssignSocketIdSub: Subscription; wsLoginSub: Subscription; constructor(private menuService: MenuService, @@ -36,6 +37,10 @@ export class AppComponent implements OnInit , OnDestroy { this.onWSLogin(m); }); + this.wsAssignSocketIdSub = this.wsService.assignSocketIdEvent.subscribe(m =>{ + this.onWSAssignSocketId(m); + }); + this.titleService.setTitle(this.title); } @@ -57,13 +62,18 @@ export class AppComponent implements OnInit , OnDestroy { ngOnDestroy(): void { this.webSocketSubscription.unsubscribe(); this.wsLoginSub.unsubscribe(); + this.wsAssignSocketIdSub.unsubscribe(); } onWSLogin(e: WsLoginEventModel): void { + if ( e.SocketId === this.ss.SocketId ) { // our own message + return; + } + console.log('on login out', this); if ( e.T === 'logout' ) { // regardless where are they, logout means logout if ( this.ss.isLoggedIn() && this.ss.isCurrentUser(e.Uid) ) { - if ( e.Sid === this.ss.SessionId ) { - this.ss.logoutAndClearLocalStorage(); + if ( e.Mid === this.ss.MachineId ) { + this.ss.logoutWithoutPersistingStorage(); }else{ this.ss.logout(); } @@ -76,5 +86,10 @@ export class AppComponent implements OnInit , OnDestroy { } } } + + onWSAssignSocketId(id: string): void{ + console.log('get registration id', id); + this.ss.SocketId = id; + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index cfe1641..e5f24cd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -112,6 +112,7 @@ import { RewardSelectComponent } from './reward-select/reward-select.component'; import { RewardsAllComponent } from './rewards-all/rewards-all.component'; import { SinglePayoutRewardsListComponent } from './single-payout-rewards-list/single-payout-rewards-list.component'; import {SessionService} from './service/session.service'; +import { NumberRangeFilterComponent } from './grid-filter/number-range-filter/number-range-filter.component'; @@ -192,6 +193,7 @@ export function initializeApp(appConfig: AppConfig): () => Promise { RewardSelectComponent, RewardsAllComponent, SinglePayoutRewardsListComponent, + NumberRangeFilterComponent, ], imports: [ BrowserModule, diff --git a/src/app/auth/auth-http-interceptor.service.ts b/src/app/auth/auth-http-interceptor.service.ts index 9685aad..b9129a9 100644 --- a/src/app/auth/auth-http-interceptor.service.ts +++ b/src/app/auth/auth-http-interceptor.service.ts @@ -20,6 +20,9 @@ export class AuthHttpInterceptor implements HttpInterceptor { if (this.ss.MachineId !== '') { h = h.set('Biukop-Mid', this.ss.MachineId); } + if (this.ss.SocketId !== '') { + h = h.set('Biukop-Socket', this.ss.SocketId); + } const authReq = req.clone({ headers: h, @@ -43,9 +46,8 @@ export class AuthHttpInterceptor implements HttpInterceptor { public setSession(bs: string): void { // console.log('receive session:' , bs); if (bs){ - if ( this.ss.loggedIn.session !== bs ){ - this.ss.loggedIn.session = bs; - this.ss.saveSessionInfo(); + if ( this.ss.SessionId !== bs ){ + this.ss.SessionId = bs; console.log('switch session:' , bs); } } diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index d383add..0a69c9e 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -48,7 +48,7 @@ export class AuthComponent implements OnInit, OnDestroy{ public onLogin(rsp: ApiV1LoginResponse): void { this.loading = false; - // console.log ('found login ' , rsp ); + // console.log (' auth event login ' , rsp ); if (rsp.login) { switch ( rsp.role ) { case 'admin': diff --git a/src/app/grid-filter/number-range-filter/number-range-filter.component.html b/src/app/grid-filter/number-range-filter/number-range-filter.component.html new file mode 100644 index 0000000..e3e759b --- /dev/null +++ b/src/app/grid-filter/number-range-filter/number-range-filter.component.html @@ -0,0 +1,46 @@ +
+
+ + {{operator.op}} + + +
+ + +
+ + + + + {{operator.op}} + + +
+
+ + + + diff --git a/src/app/grid-filter/number-range-filter/number-range-filter.component.scss b/src/app/grid-filter/number-range-filter/number-range-filter.component.scss new file mode 100644 index 0000000..8e143d5 --- /dev/null +++ b/src/app/grid-filter/number-range-filter/number-range-filter.component.scss @@ -0,0 +1,63 @@ +div.filter{ + display:flex; + width: 100%; + background: white; + + kendo-textbox{ + display: flex; + width: 100%; + margin:1px; + background: transparent; + } + + div.single{ + display:flex; + flex-direction: row; + width: 100%; + .thin { + width: 1px; + z-index:1; + } + .full{ + width: 100%; + border-left:0; + border-right:0; + border-top:0; + border-bottom:1px solid darkgrey; + } + .full.with-padding{ + padding-left: 18px; + } + } + + div.multiple{ + display:flex; + width: 100%; + flex-direction: column; + align-items: center; + .minimum { + height: 10px; + } + .first-half{ + + border-left: 1px solid lightblue; + border-right: 1px solid lightblue; + border-top: 3px solid lightblue; + border-bottom: 1px dotted darkgray; + } + + .second-half{ + border-left: 1px solid lightblue; + border-right: 1px solid lightblue; + border-top: 1px dotted lightblue; + border-bottom: 3px solid darkgray; + } + } + +} +.popup-content{ + padding: 10px; + background: black; + opacity: 0.5; + color: white +} diff --git a/src/app/grid-filter/number-range-filter/number-range-filter.component.spec.ts b/src/app/grid-filter/number-range-filter/number-range-filter.component.spec.ts new file mode 100644 index 0000000..c87dfae --- /dev/null +++ b/src/app/grid-filter/number-range-filter/number-range-filter.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NumberRangeFilterComponent } from './number-range-filter.component'; + +describe('NumberRangeFilterComponent', () => { + let component: NumberRangeFilterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NumberRangeFilterComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NumberRangeFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/grid-filter/number-range-filter/number-range-filter.component.ts b/src/app/grid-filter/number-range-filter/number-range-filter.component.ts new file mode 100644 index 0000000..7714742 --- /dev/null +++ b/src/app/grid-filter/number-range-filter/number-range-filter.component.ts @@ -0,0 +1,473 @@ +import {Component, Input, OnChanges, OnInit, ViewChild} from '@angular/core'; +import {CompositeFilterDescriptor, FilterDescriptor} from '@progress/kendo-data-query'; +import {BaseFilterCellComponent, FilterService} from '@progress/kendo-angular-grid'; +import {trim} from '@progress/kendo-angular-editor/dist/es2015/util'; +import {debounce} from 'ts-debounce'; +import {TextBoxComponent} from '@progress/kendo-angular-inputs'; + + + +class Operator { op: string; value: string; } + +@Component({ + selector: 'app-number-range-filter', + templateUrl: './number-range-filter.component.html', + styleUrls: ['./number-range-filter.component.scss'] +}) +export class NumberRangeFilterComponent extends BaseFilterCellComponent implements OnInit, OnChanges { + + constructor(filterService: FilterService) { super(filterService); } + + @Input() public filter: CompositeFilterDescriptor; + @Input() public min = -1; + @Input() public max = -1; + @Input() public required = false; + @Input() public fieldName = ''; + @Input() public options = ['=', '≠', '>', '≥', '<', '≤', '⇩']; + // ngModel control + @ViewChild('single') ctlSingle: TextBoxComponent; + @ViewChild('rangeFrom') ctlRangeFrom: TextBoxComponent; + @ViewChild('rangeTo') ctlRangeTo: TextBoxComponent; + + public operator: Operator = {op: '=', value: 'eq'}; + private defaultOperator: Operator = {op: '=', value: 'eq'}; + public availableOperators: Operator[] = []; + private AllOperatorMap = [ + {op: '=', value: 'eq'}, + {op: '≠', value: 'neq'}, + {op: '>', value: 'gt'}, + {op: '≥', value: 'gte'}, + {op: '<', value: 'lt'}, + {op: '≤', value: 'lte'}, + {op: '⇩', value: 'range'} + ]; + + public singleMode = true; + + public valueFrom = ''; + public valueTo = ''; + public valueFromValid = true; + public valueToValid = true; + public fromTouched = false; + public toTouched = false; + public currentFocus = ''; + + public showOperatorChoice = true; + public showError = false; + + public validateResult = new Map(); + public errorMessage = ''; + + private debouncedSingleFrom = debounce(this.valueFromChanged, 500); + private debouncedRangeFrom = debounce(this.rangeFromChanged, 500); + private debouncedUpdateTo = debounce(this.rangeToChanged, 500); + + private isEmpty(v: string): boolean{ + return trim(v) === ''; + } + + private isNumber(v: string): boolean{ + const vf = Number(v); + return v !== null && ! isNaN(vf) ; + } + + ngOnInit(): void { + console.log(this); + this.initAvailableOperators(); + this.showOperatorChoice = this.availableOperators.length > 1 || + ( this.availableOperators[0].op !== '=' && this.availableOperators[0].op !== ' '); + } + + ngOnChanges(changes): void { + if ( changes.options ){ + this.initAvailableOperators(); + } + } + + private initAvailableOperators(): void { + this.availableOperators = this.AllOperatorMap.filter( v => this.options.indexOf(v.value) !== -1 ); + if ( this.availableOperators.length === 0) { + this.availableOperators = [this.defaultOperator]; + this.operator = this.defaultOperator; + }else{ + this.operator = this.availableOperators[0]; + } + this.singleMode = this.operator.value !== 'range'; + } + + + // + // Events handling + // + + public onOperatorClick(op: Operator): void { + if ( this.operator === op ) { // same op + return; + } + this.operator = op; + this.singleMode = op.value !== 'range'; + + if (!this.toTouched && ! this.fromTouched) { + return ; + } + + this.clearError(); // start validation + if ( this.singleMode ){ + this.valueTo = ''; // clear to + if (this.fromTouched ){ + this.onChangeFrom(this.valueFrom); + } + }else if (!this.singleMode) { + if (this.fromTouched ){ + this.validateFromAsRange(this.valueFrom); + } + if (this.toTouched ){ + this.validateTo(this.valueTo); + } + } + + this.showError = this.buildErrorMessage(); + + if ( ! this.showError ){ + this.buildFilter(); + } + } + + public onChangeFrom(v: string): void { + this.clearError(); + this.valueFromValid = this.validateFrom(v); + if ( this.singleMode ){ + this.debouncedSingleFrom(v).then(); + }else{ + this.debouncedRangeFrom(v).then(); + } + this.showError = this.buildErrorMessage(); + } + + public onFromTouched(): void { + this.fromTouched = true; + this.currentFocus = 'from'; + } + + public onToTouched(): void { + this.toTouched = false; + this.currentFocus = 'to'; + } + + public onChangeTo(v: string): void { + this.clearError(); + this.valueToValid = this.validateTo(v); + this.debouncedUpdateTo(v).then(); + this.showError = this.buildErrorMessage(); + } + + // + // debounced function + // + + private valueFromChanged(v: string): void{ + if ( this.validateResult.size === 0 ) { + this.buildFilter(); + return; + } + // remove filter + this.clearOurFilter(); + } + + private rangeFromChanged(v): void{ + if ( this.validateResult.size === 0 ) { + this.buildFilter(); + return; + } + // remove filter + this.clearOurFilter(); + } + + private rangeToChanged(v: string): void { + if ( this.validateResult.size === 0 ) { + this.buildFilter(); + return; + } + // remove filter + this.clearOurFilter(); + + if ( this.valueFromValid && this.valueToValid) { + this.buildFilter(); + }else{ + if (this.fieldName && trim(this.fieldName) !== ''){ + const f = this.removeFilter(this.fieldName); + this.applyFilter(f); + } + } + } + + + private clearOurFilter(): void { + if (this.fieldName && trim(this.fieldName) !== ''){ + const f = this.removeFilter(this.fieldName); + this.applyFilter(f); + } + } + + + protected buildFilter(): void { + if (this.fieldName === '') { + console.warn('filed name is not specified, skip update filter', this); + return; + } + + if (this.singleMode) { + this.buildSingleFilter(); + } else { + this.buildRangeFilter(); + } + + } + + private buildSingleFilter(): void { + if ( this.valueFrom === null ) { return; } + const filter: FilterDescriptor = { + field: this.fieldName, + operator: this.operator.value, + value: this.valueFrom, + }; + this.applyFilter(this.updateFilter(filter)); + } + + private buildRangeFilter(): void { + const fs: FilterDescriptor[] = []; + this.valueFromValid = this.validateFromAsRange(this.valueFrom); + this.valueToValid = this.validateTo(this.valueTo); + if ( this.valueFromValid && this.valueFrom !== null && this.valueFrom !== '') { + fs.push({ + field: this.fieldName, + operator: 'gte', + value: this.valueFrom + }); + } + + if ( this.valueToValid && this.valueTo !== null && this.valueTo !== '') { + fs.push({ + field: this.fieldName, + operator: 'lte', + value: this.valueTo + }); + } + this.removeFilter(this.fieldName); + const root: CompositeFilterDescriptor = this.filter || { logic: 'and', + filters: [], + }; + + if (fs.length) { + root.filters.push(...fs); + } + this.filterService.filter(root); + } + + private validateCommon(v: string, field?: string ): boolean { + const f = field || ''; + if ( v === null ){ + this.validateResult.set(f + 'null', ' value is null'); + return false; + } + + if (! this.isNumber(v) ) { + this.validateResult.set(f + 'number', 'should be number'); + return false; + } + + return this.isWithinRange(v, field); + + } + + private validateFrom(v: string): boolean { + if ( this.singleMode ) { + return this.validateFromAsSingle(v); + } else { + return this.validateFromAsRange(v); + } + } + + private validateFromAsSingle(v: string): boolean { + if ( ! this.validateCommon(v) ) { + return false; + } + + if (this.required && this.isEmpty(v) ) { + this.validateResult.set('required', 'value is required'); + return false; + } + return true; + } + + private validateFromAsRange(v: string): boolean { + if ( ! this.validateCommon(v, 'from_') ){ + return false; + } + + if ( !this.isFromLessThanTo() ){ + return false; + } + return true; + } + + private validateTo(v: string): boolean{ + if ( ! this.validateCommon(v, 'to_') ) { + return false; + } + + if ( ! this.isFromLessThanTo() ) { + return false; + } + + return true; + } + + private isFromLessThanTo(): boolean{ + if (this.isEmpty( this.valueFrom) || this.isEmpty(this.valueTo)){ + return true; // if one of them is empty, we dont care about who is bigger + } + + if ( ! (Number(this.valueFrom) <= Number(this.valueTo)) ){ + this.validateResult.set('min>max', 'min must ≤ max'); + return false; + } + return true; + } + + private buildErrorMessage(): boolean { + if (this.singleMode) { + return this.buildSingleError(); + } else { + return this.buildRangeError(); + } + } + + private clearError(): void { + this.showError = false; + this.validateResult.clear(); + this.errorMessage = '' ; + } + + private buildSingleError(): boolean { + + if ( this.validateResult.has('null') ){ + this.errorMessage = 'can not be null'; + return true; + } + + if (this.validateResult.has('required')){ + this.errorMessage = 'value is required' + this.hint(); + return true; + } + + if (this.validateResult.has('number')){ + this.errorMessage = 'should be a number'; + return true; + } + + if (this.validateResult.has('min')){ + this.errorMessage = 'should ≥ ' + this.min; + return true; + } + + if (this.validateResult.has('max')){ + + this.errorMessage = 'should ≤ ' + this.max; + return true; + } + + const touched = this.fromTouched || this.toTouched; // this.ctlRangeFrom + this.showError = this.errorMessage !== '' && ! touched; + return this.showError; + } + + private buildRangeError(): boolean { + + if ( this.validateResult.has('from_null') && this.validateResult.has('to_null') ){ + this.errorMessage = 'can not be null'; + return true; + } + + if (this.validateResult.has('from_number') || this.validateResult.has( 'to_number')) { + this.errorMessage = 'should be a number'; + return true; + } + + if (this.validateResult.has('from_min') || this.validateResult.has('to_min')) { + this.errorMessage = 'should ≥ ' + this.min; + return true; + } + + if (this.validateResult.has('from_max') || this.validateResult.has('to_max')){ + this.errorMessage = 'should ≤ ' + this.max; + return true; + } + + if (this.validateResult.has('min>max')){ + if (this.currentFocus === 'to'){ + this.errorMessage = 'should ≥ ' + this.valueFrom; + }else{ + this.errorMessage = 'should ≤ ' + this.valueTo; + } + + return true; + } + + return false; + } + + private hint(): string{ + let hint = ''; + + if ( this.min !== -1 && this.max === -1) { + hint = ' > ' + this.min + ' '; + } + + if (this.max !== -1 && this.min === -1 ) { + hint = ' < ' + this.max + ' '; + } + + if (this.min !== -1 && this.max !== -1) { + hint = this.min + ' - ' + this.max; + } + + if ( hint !== '' ){ + hint = ' (' + hint + ') '; + } + return hint; + } + + + private isWithinRange(v: string, field?: string): boolean{ + const f = field || ''; + if ( this.isEmpty(v) ){ + if (this.singleMode && this.required){ + this.validateResult.set(f + 'required', 'must input something'); + return false; + } + return true; + } + + const vf = Number(v); + if (! this.isNumber(v)) { + this.validateResult.set(f + 'number', 'must be a number'); + return false; + } + + if ( this.min !== -1 && vf < this.min ){ + this.validateResult.set(f + 'min', 'cannot be lower than min'); + return false; + } + + if (this.max !== -1 && vf > this.max ) { + this.validateResult.set(f + 'max', 'cannot be greater than max'); + return false; + } + + // if we reached here + return true; + } + + +} diff --git a/src/app/models/grid.state.model.ts b/src/app/models/grid.state.model.ts new file mode 100644 index 0000000..15c6769 --- /dev/null +++ b/src/app/models/grid.state.model.ts @@ -0,0 +1,35 @@ +import {CompositeFilterDescriptor, GroupDescriptor, SortDescriptor} from '@progress/kendo-data-query'; +import {DataStateChangeEvent} from '@progress/kendo-angular-grid'; + + +export class GridStateModel implements DataStateChangeEvent { + /** + * The number of records to skip. + */ + skip: number; + /** + * The number of records to take. + */ + take: number; + /** + * The sort descriptors by which the data is sorted. + */ + sort?: Array; + /** + * The group descriptors by which the data is grouped. + */ + group?: Array; + /** + * The filter descriptor by which the data is filtered. + */ + filter?: CompositeFilterDescriptor; + + public constructor(payload?: Partial) { + if ( ! payload ) payload = {}; + this.skip = payload.skip || 0; + this.take = payload.take || 0; + this.sort = payload.sort || []; + this.group = payload.group || []; + this.filter = payload.filter || { logic: 'and', filters: [] }; + } +} diff --git a/src/app/models/websocket/ws.login.event.model.ts b/src/app/models/websocket/ws.login.event.model.ts index 1c296bb..3578bf7 100644 --- a/src/app/models/websocket/ws.login.event.model.ts +++ b/src/app/models/websocket/ws.login.event.model.ts @@ -3,6 +3,7 @@ export class WsLoginEventModel{ T: string; Mid: string; Sid: string; + SocketId: string; Uid: string; Role: string; constructor( payload: Partial) { @@ -11,5 +12,6 @@ export class WsLoginEventModel{ this.Sid = payload.Sid || ''; this.Uid = payload.Uid || ''; this.Role = payload.Uid || ''; + this.SocketId = payload.SocketId || ''; } } diff --git a/src/app/pay-in/pay-in.component.html b/src/app/pay-in/pay-in.component.html index 7966c3a..1519e9c 100644 --- a/src/app/pay-in/pay-in.component.html +++ b/src/app/pay-in/pay-in.component.html @@ -1,13 +1,15 @@ - + - - - + + + diff --git a/src/app/pay-in/pay-in.component.ts b/src/app/pay-in/pay-in.component.ts index 3af40b4..7d073f7 100644 --- a/src/app/pay-in/pay-in.component.ts +++ b/src/app/pay-in/pay-in.component.ts @@ -6,8 +6,8 @@ import {PayInService} from '../service/pay-in.service'; import {PayInListResult} from '../models/pay-in-list-result.model'; import {Router} from '@angular/router'; import {PopupIncomeFilterComponent} from '../popup-income-filter/popup-income-filter.component'; -import {GridComponent, PageChangeEvent, RowClassArgs, SortSettings} from '@progress/kendo-angular-grid'; -import {SortDescriptor} from '@progress/kendo-data-query'; +import {GridComponent, PageChangeEvent, RowClassArgs, SortSettings, DataStateChangeEvent} from '@progress/kendo-angular-grid'; +import {CompositeFilterDescriptor, FilterDescriptor, SortDescriptor} from '@progress/kendo-data-query'; import {UploadMetaModel} from '../models/uploadMetaModel'; import {debounce} from 'ts-debounce'; import {LoanModel} from '../models/loan.model'; @@ -15,6 +15,7 @@ import {LoanSingleService} from '../service/loan.single.service'; import {PayInModelEx} from '../models/pay-in-ex.model'; import {Observable} from 'rxjs'; import {LenderNameService} from '../service/lender-name.service'; +import {GridStateModel} from '../models/grid.state.model'; @@ -32,6 +33,7 @@ export class PayInComponent implements OnInit { private filterUploadMeta: UploadMetaModel = new UploadMetaModel({}); public filterLoan = new LoanModel({}); @Input() filter: PayInListFilterModel = new PayInListFilterModel({}); + public state = new GridStateModel(); @Output() errorOccurred = new EventEmitter(); @Output() Updated = new EventEmitter(); @ViewChild('filterDialog', {static: true}) filterDialog: PopupIncomeFilterComponent; @@ -98,6 +100,24 @@ export class PayInComponent implements OnInit { ); } + public loadFilteredPayInList(): void { + this.loading = true; + this.pis.getFilteredPayInList(this.state).subscribe( + ( resp: PayInListResult) => { + this.gridData.total = resp.total; + this.gridData.data = []; + resp.data.forEach(v => { + this.gridData.data.push(new PayInModelEx(v)); + }); + this.loading = false; + }, err => { + this.loading = false; + }, () => { + this.loading = false; + } + ); + } + // Upload Filter @Input() set uploadMeta(value: UploadMetaModel) { this.filterUploadMeta = value; @@ -316,12 +336,21 @@ export class PayInComponent implements OnInit { public pageChange(event: PageChangeEvent): void { this.filter.Skip = event.skip; - this.loadFilteredData(); + // this.loadFilteredData(); } public sortChange(sort: SortDescriptor[]): void { this.filter.Sort = sort; - this.loadFilteredData(); + // this.loadFilteredData(); + } + + public filterChange( filter: CompositeFilterDescriptor): void { + // console.log (filter, this.state.filter); + } + public dataStateChange(state: DataStateChangeEvent): void { + this.state = state; + this.loadFilteredPayInList(); + console.log(state, this.state); } public onLoanChange(loan: LoanModel): void { diff --git a/src/app/service/auth-guard.service.ts b/src/app/service/auth-guard.service.ts index 3959572..ac53f92 100644 --- a/src/app/service/auth-guard.service.ts +++ b/src/app/service/auth-guard.service.ts @@ -15,7 +15,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Observable | Promise | boolean { + state: RouterStateSnapshot): Observable | Promise | boolean { if (this.authService.isAuthenticated()) { return true; } else { @@ -24,7 +24,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { } canActivateChild(route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Observable | Promise | boolean { + state: RouterStateSnapshot): Observable | Promise | boolean { return this.canActivate(route, state); } } diff --git a/src/app/service/pay-in.service.ts b/src/app/service/pay-in.service.ts index 97af5ca..fa1705b 100644 --- a/src/app/service/pay-in.service.ts +++ b/src/app/service/pay-in.service.ts @@ -4,6 +4,7 @@ import {PayInListFilterModel} from '../models/pay-in-list.filter.model'; import {HttpClient} from '@angular/common/http'; import {AuthService} from './auth.service'; import {PayInListResult} from '../models/pay-in-list-result.model'; +import {DataStateChangeEvent} from '@progress/kendo-angular-grid'; @@ -14,4 +15,8 @@ export class PayInService { public getPayInList(filter: PayInListFilterModel): Observable { return this.http.post(this.auth.getUrl('pay-in-list/'), filter); } + + public getFilteredPayInList(state: DataStateChangeEvent): Observable { + return this.http.post(this.auth.getUrl('pay-in-filtered-list/'), state); + } } diff --git a/src/app/service/session.service.ts b/src/app/service/session.service.ts index 1385b07..9ab39cc 100644 --- a/src/app/service/session.service.ts +++ b/src/app/service/session.service.ts @@ -15,6 +15,7 @@ export class SessionService { loginResult = new EventEmitter (); logoutEvent = new EventEmitter (); private machineId = localStorage.getItem('mid') || ''; + private socketId = ''; // only lives in memory debouncedLocalStorageMonitor = debounce( this.localStorageChange, 1000); // to avoid to frequent local storage changes constructor(private config: AppConfig, private http: HttpClient, @@ -41,9 +42,10 @@ export class SessionService { public login( resp: ApiV1LoginResponse): void{ + // console.log( 'login in session', this); this.loggedIn = new ApiV1LoginResponse(resp); - if ( this.MachineId !== '' && resp.machineId !== '' && this.MachineId !== resp.machineId ) { + if ( this.MachineId === '' || (resp.machineId !== '' && this.MachineId !== resp.machineId )) { this.MachineId = resp.machineId; // update machine id } @@ -59,10 +61,12 @@ export class SessionService { private localStorageChange(event: StorageEvent): void { console.log('local storage change', event); - const sfm: ApiV1LoginResponse = JSON.parse(localStorage.getItem(this.config.storageKey)); - if ( sfm && sfm.session && sfm.User && sfm.User.Id && this.loggedIn.User.Id) { - if ( sfm.session === this.loggedIn.session && sfm.User.Id !== this.loggedIn.User.Id){ - this.logoutWithoutPersistingStorage(); // silently logout without touching any storage + if ( event.key === this.config.storageKey ){ + const newSigin: ApiV1LoginResponse = JSON.parse(localStorage.getItem(this.config.storageKey)); + if ( newSigin && newSigin.session && newSigin.User && newSigin.User.Id && this.loggedIn.User.Id) { + if ( newSigin.machineId === this.loggedIn.machineId && newSigin.User.Id !== this.loggedIn.User.Id){ + this.logoutWithoutPersistingStorage(); // silently logout without touching any storage + } } } } @@ -71,7 +75,6 @@ export class SessionService { localStorage.setItem(this.config.storageKey, JSON.stringify(this.loggedIn)); } - public isAdmin(): boolean { return this.loggedIn.role === 'admin'; } @@ -190,4 +193,18 @@ export class SessionService { return this.loggedIn.session || ''; } + public set SessionId(sid: string){ + if ( this.loggedIn.session === '' || this.loggedIn.session !== sid ){ + this.loggedIn.session = sid; + this.saveSessionInfo(); + this.ws.SessionId = sid; + } + } + + public get SocketId(): string{ + return this.socketId; + } + public set SocketId(socketId: string ) { + this.socketId = socketId; + } } diff --git a/src/app/websocket.ts b/src/app/websocket.ts index a396aa0..da3e805 100644 --- a/src/app/websocket.ts +++ b/src/app/websocket.ts @@ -10,9 +10,11 @@ export class WebSocketService extends Subject{ private connected = false; private sessionId = ''; + private socketId = ''; public ws: WebSocket; private readonly url: string; public LoginEvent: Subject; + public assignSocketIdEvent: Subject; // cannot have session as service, as session use us as building block constructor(private config: AppConfig) { @@ -20,6 +22,7 @@ export class WebSocketService extends Subject{ this.url = config.apiWsUrl; this.startWebsocket(); this.LoginEvent = new Subject(); + this.assignSocketIdEvent = new Subject(); } private startWebsocket(): void { @@ -72,6 +75,10 @@ export class WebSocketService extends Subject{ if ( e.T === 'login' || e.T === 'logout' ){ this.LoginEvent.next(new WsLoginEventModel(e)); } + if (e.T === 'assign-socketId') { + this.socketId = e.socketId; + this.assignSocketIdEvent.next(e.socketId); + } }catch (e) { console.log(e); } diff --git a/tsconfig.json b/tsconfig.json index cf9f03c..00072af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, - "target": "es5", + "target": "Es6", "module": "es2020", "lib": [ "es2018",