From 3136cd3b30dcc1c6a724bfc8f7cb89c19074d0ff Mon Sep 17 00:00:00 2001 From: punwai Date: Wed, 6 May 2020 22:24:09 +0100 Subject: [PATCH] feat: add server-side pagination sort and search for centers component (#506) - Add server-side pagination, sort and search for centers component - Refactor code to meet current guidelines See: #234 --- src/app/centers/centers.component.html | 95 +++++++++-------- src/app/centers/centers.component.scss | 33 ++---- src/app/centers/centers.component.ts | 141 ++++++++++++++++++++----- src/app/centers/centers.datasource.ts | 61 +++++++++++ src/app/centers/centers.module.ts | 39 +++---- src/app/centers/centers.service.ts | 42 ++++++-- 6 files changed, 289 insertions(+), 122 deletions(-) create mode 100644 src/app/centers/centers.datasource.ts diff --git a/src/app/centers/centers.component.html b/src/app/centers/centers.component.html index 3da12bdc2..9b57839cc 100644 --- a/src/app/centers/centers.component.html +++ b/src/app/centers/centers.component.html @@ -1,53 +1,60 @@ -
+ +
+ +
+ + +
+
- - - + -
-
+ + + + - - - - - + + + + - - - - - + + + + - - - - - + + + + - - - - - - + + + + + + +
Name {{ center.name }} Name {{row?.name}} Account # {{ center.accountNo }} Account # {{row?.accountNo}} ExternalID {{ center.externalId }} ExternalID {{row?.externalId}} Status +
+ +
+
Status
- -
Office Name {{ center.officeName }}
+ - - - Office - {{row?.officeName}} - - - - - - - - - - -
\ No newline at end of file + diff --git a/src/app/centers/centers.component.scss b/src/app/centers/centers.component.scss index 73124c9c0..15e12ff37 100644 --- a/src/app/centers/centers.component.scss +++ b/src/app/centers/centers.component.scss @@ -1,27 +1,10 @@ - - table { - width: 100%; +.container { + .action-button{ + margin-left: auto; } - - .mat-form-field { - font-size: 14px; - width: 100%; + .search-button{ + min-width: 30px; + margin: 4px; + } - - td, th { - width: 15%; - } - - .filter{ - width: 50%; - } - - .inactive { - color: red; - } - - .active { - color: green; - } - - \ No newline at end of file +} diff --git a/src/app/centers/centers.component.ts b/src/app/centers/centers.component.ts index 68cc6720a..3b40bc31d 100644 --- a/src/app/centers/centers.component.ts +++ b/src/app/centers/centers.component.ts @@ -1,45 +1,132 @@ -import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material'; -import { CentersService } from 'app/centers/centers.service'; -import {DataSource} from '@angular/cdk/collections'; -import { Observable } from 'rxjs'; +/** Angular Imports */ +import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; +import { MatPaginator, MatSort, MatCheckbox } from '@angular/material'; +import { FormControl } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +/** rxjs Imports */ +import { merge } from 'rxjs'; +import { tap, startWith, map, distinctUntilChanged, debounceTime} from 'rxjs/operators'; + +/** Custom Services */ +import { CentersService } from './centers.service'; + +/** Custom Data Source */ +import { CentersDataSource } from './centers.datasource'; + +/** + * Centers component. + */ @Component({ selector: 'mifosx-app-centers', templateUrl: './centers.component.html', styleUrls: ['./centers.component.scss'], - }) -export class CentersComponent implements OnInit { - private ELEMENT_DATA: any = undefined; - displayedColumns = ['name', 'accountno', 'externalid', 'status', 'office']; - dataSource = new MatTableDataSource(); +export class CentersComponent implements OnInit, AfterViewInit { + @ViewChild('showClosedCenters') showClosedCenters: MatCheckbox; + /** Name form control. */ + name = new FormControl(); + /** ExternalId form control. */ + externalId = new FormControl(); + /** Columns to be displayed in centers table. */ + displayedColumns = ['name', 'accountNo', 'externalId', 'status', 'officeName']; + /** Data source for centers table. */ + dataSource: CentersDataSource; + /** Centers filter. */ + filterCentersBy = [ + { + type: 'name', + value: '' + }, + { + type: 'externalId', + value: '' + } + ]; + + /** Paginator for centers table. */ @ViewChild(MatPaginator) paginator: MatPaginator; + /** Sorter for centers table. */ @ViewChild(MatSort) sort: MatSort; - constructor(private centerService: CentersService) { } + + constructor(private centersService: CentersService) { } ngOnInit() { - this.centerService.getCenters() - .subscribe( - (res => { - console.log(res); - // console.log(res.active); - res.active = !!res.active; - this.dataSource = new MatTableDataSource(res); - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; + this.getCenters(); + } + + /** + * Subscribes to all search filters: + * Name, ExternalId + * sort change and page change. + */ + ngAfterViewInit() { + + this.name.valueChanges + .pipe( + debounceTime(500), + distinctUntilChanged(), + tap((filterValue) => { + this.applyFilter(filterValue, 'name'); }) - ); + ) + .subscribe(); + + this.externalId.valueChanges + .pipe( + debounceTime(500), + distinctUntilChanged(), + tap((filterValue) => { + this.applyFilter(filterValue, 'externalId'); + }) + ) + .subscribe(); + + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + tap(() => this.loadCentersPage()) + ) + .subscribe(); } + /** + * Reloads page on changing show closed centers checkbox + */ + changeShowClosedCenters() { + this.loadCentersPage(); + } - applyFilter(filterValue: string) { - filterValue = filterValue.trim(); // Remove whitespace - filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches - this.dataSource.filter = filterValue; - if (this.dataSource.paginator) { - this.dataSource.paginator.firstPage(); + /** + * Loads a page of centers. + */ + loadCentersPage() { + if (!this.sort.direction) { + delete this.sort.active; } + this.dataSource.getCenters(this.filterCentersBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize, !this.showClosedCenters.checked); } + + /** + * Filters data in centers table based on passed value and poperty. + * @param {string} filterValue Value to filter data. + * @param {string} property Property to filter data by. + */ + applyFilter(filterValue: string, property: string) { + this.paginator.pageIndex = 0; + const findIndex = this.filterCentersBy.findIndex(filter => filter.type === property); + this.filterCentersBy[findIndex].value = filterValue; + this.loadCentersPage(); + } + + /** + * Initializes the data source for centers table and loads the first page. + */ + getCenters() { + this.dataSource = new CentersDataSource(this.centersService); + this.dataSource.getCenters(this.filterCentersBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize); + } + } diff --git a/src/app/centers/centers.datasource.ts b/src/app/centers/centers.datasource.ts new file mode 100644 index 000000000..9e9304aed --- /dev/null +++ b/src/app/centers/centers.datasource.ts @@ -0,0 +1,61 @@ +/** Angular Imports */ +import { CollectionViewer, DataSource } from '@angular/cdk/collections'; + +/** rxjs Imports */ +import { Observable, BehaviorSubject } from 'rxjs'; + +/** Custom Services */ +import { CentersService } from './centers.service'; + +/** + * Centers custom data source to implement server side filtering, pagination and sorting. + */ +export class CentersDataSource implements DataSource { + + /** centers behavior subject to represent loaded centers page. */ + private centersSubject = new BehaviorSubject([]); + /** Records subject to represent total number of filtered centers records. */ + private recordsSubject = new BehaviorSubject(0); + /** Records observable which can be subscribed to get the value of total number of filtered centers records. */ + public records$ = this.recordsSubject.asObservable(); + + /** + * @param {CentersService} centersService Centers Service + */ + constructor(private centersService: CentersService) { } + + /** + * Gets centers on the basis of provided parameters and emits the value. + * @param {any} filterBy Properties by which entries should be filtered. + * @param {string} orderBy Property by which entries should be sorted. + * @param {string} sortOrder Sort order: ascending or descending. + * @param {number} pageIndex Page number. + * @param {number} limit Number of entries within the page. + * @param {boolean} centerActive Specify whether to only filter active centers. + */ + getCenters(filterBy: any, orderBy: string = '', sortOrder: string = '', pageIndex: number = 0, limit: number = 10, centerActive: boolean = true) { + this.centersSubject.next([]); + this.centersService.getCenters(filterBy, orderBy, sortOrder, pageIndex * limit, limit) + .subscribe((centers: any) => { + centers.pageItems = (centerActive) ? (centers.pageItems.filter((center: any) => center.active)) : centers.pageItems; + this.recordsSubject.next(centers.totalFilteredRecords); + this.centersSubject.next(centers.pageItems); + }); + } + + /** + * @param {CollectionViewer} collectionViewer + */ + connect(collectionViewer: CollectionViewer): Observable { + return this.centersSubject.asObservable(); + } + + /** + * @param {CollectionViewer} collectionViewer + */ + disconnect(collectionViewer: CollectionViewer): void { + this.centersSubject.complete(); + this.recordsSubject.complete(); + } + +} diff --git a/src/app/centers/centers.module.ts b/src/app/centers/centers.module.ts index 070d18be8..e3442d422 100644 --- a/src/app/centers/centers.module.ts +++ b/src/app/centers/centers.module.ts @@ -1,28 +1,29 @@ -import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { HttpModule } from '@angular/http'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { CoreModule } from '../core/core.module'; -import { MatTableModule } from '@angular/material/table'; -import { FlexLayoutModule } from '@angular/flex-layout'; +/** Angular Imports */ +import { NgModule } from '@angular/core'; +import { DatePipe } from '@angular/common'; +/** Custom Modules */ +import { CentersRoutingModule } from './centers-routing.module'; +import { SharedModule } from 'app/shared/shared.module'; +import { PipesModule } from '../pipes/pipes.module'; + +/** Custom Components */ import { CentersComponent } from './centers.component'; -import { CentersRoutingModule } from 'app/centers/centers-routing.module'; -import { CentersService } from './centers.service'; +/** + * Centers Module + * + * All components related to Centers should be declared here. + */ @NgModule({ imports: [ - CoreModule, - CommonModule, + SharedModule, CentersRoutingModule, - FormsModule, - MatTableModule, - FlexLayoutModule - + PipesModule ], - declarations: [CentersComponent], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] + declarations: [ + CentersComponent + ], + providers: [DatePipe] }) export class CentersModule { } diff --git a/src/app/centers/centers.service.ts b/src/app/centers/centers.service.ts index a9108efa5..3c240bcb6 100644 --- a/src/app/centers/centers.service.ts +++ b/src/app/centers/centers.service.ts @@ -1,17 +1,45 @@ +/** Angular Imports */ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; + +/** rxjs Imports */ import { Observable } from 'rxjs'; +/** + * Clients service. + */ @Injectable({ providedIn: 'root' - }) - - +}) export class CentersService { - constructor(private http: HttpClient) { } + /** + * @param {HttpClient} http Http Client to send requests. + */ + constructor(private http: HttpClient) { } - getCenters(): Observable { - return this.http.get('/centers'); - } + /** + * @param {any} filterBy Properties by which entries should be filtered. + * @param {string} orderBy Property by which entries should be sorted. + * @param {string} sortOrder Sort order: ascending or descending. + * @param {number} offset Page offset. + * @param {number} limit Number of entries within the page. + * @returns {Observable} Centers. + */ + getCenters(filterBy: any, orderBy: string, sortOrder: string, offset: number, limit: number): Observable { + let httpParams = new HttpParams() + .set('offset', offset.toString()) + .set('limit', limit.toString()) + .set('sortOrder', sortOrder) + .set('orderBy', orderBy) + .set('paged', 'true'); + // filterBy: name, externalId + filterBy.forEach(function (filter: any) { + if (filter.value) { + console.log(filter.value); + httpParams = httpParams.set(filter.type, filter.value); + } + }); + return this.http.get('/centers', { params: httpParams }); + } }