diff --git a/src/app/groups/groups-routing.module.ts b/src/app/groups/groups-routing.module.ts index 342428bc5..cb6a40976 100644 --- a/src/app/groups/groups-routing.module.ts +++ b/src/app/groups/groups-routing.module.ts @@ -1,3 +1,4 @@ +/** Angular Imports */ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; @@ -7,11 +8,12 @@ import { Route } from '../core/route/route.service'; /** Translation Imports */ import { extract } from '../core/i18n/i18n.service'; +/** Custom Imports */ import { GroupsComponent } from './groups.component'; +/** Groups Routes */ const routes: Routes = [ Route.withShell([ - { path: 'groups', component: GroupsComponent, @@ -20,6 +22,11 @@ const routes: Routes = [ ]) ]; +/** + * Groups Routing Module + * + * Configures the groups routes. + */ @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/src/app/groups/groups.component.html b/src/app/groups/groups.component.html index 38023c258..39f69fc76 100644 --- a/src/app/groups/groups.component.html +++ b/src/app/groups/groups.component.html @@ -1,51 +1,55 @@ -
+ +
+ +
+ +
+
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Name {{row?.name}} Account # {{row?.accountNo}} ExternalID {{row?.externalId}} Status
- -
Office {{row?.officeName}}
- - -
\ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name {{ group.name }} Account # {{ group.accountNo }} ExternalID {{ group.externalId }} Status +
+ +
+
Office Name {{ group.officeName }}
+ + + +
+ \ No newline at end of file diff --git a/src/app/groups/groups.component.scss b/src/app/groups/groups.component.scss index 73124c9c0..b917b27e6 100644 --- a/src/app/groups/groups.component.scss +++ b/src/app/groups/groups.component.scss @@ -1,27 +1,10 @@ +.container { + .action-button{ + margin-left: auto; + } - table { - width: 100%; + .search-button{ + min-width: 30px; + margin: 4px; } - - .mat-form-field { - font-size: 14px; - width: 100%; - } - - td, th { - width: 15%; - } - - .filter{ - width: 50%; - } - - .inactive { - color: red; - } - - .active { - color: green; - } - - \ No newline at end of file +} diff --git a/src/app/groups/groups.component.ts b/src/app/groups/groups.component.ts index 795acbff2..75869763b 100644 --- a/src/app/groups/groups.component.ts +++ b/src/app/groups/groups.component.ts @@ -1,44 +1,113 @@ -import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material'; +/** 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 { GroupsService } from './groups.service'; -import {DataSource} from '@angular/cdk/collections'; -import { Observable } from 'rxjs'; + +/** Custom Data Source */ +import { GroupsDataSource } from './groups.datasource'; + +/** + * Groups component. + */ @Component({ selector: 'mifosx-app-groups', templateUrl: './groups.component.html', styleUrls: ['./groups.component.scss'] }) -export class GroupsComponent implements OnInit { +export class GroupsComponent implements OnInit, AfterViewInit { + @ViewChild('showClosedGroups') showClosedGroups: MatCheckbox; - private ELEMENT_DATA: any = undefined; - displayedColumns = ['name', 'accountno', 'externalid', 'status', 'office']; - dataSource = new MatTableDataSource(); + /** Name form control. */ + name = new FormControl(); + /** Columns to be displayed in groups table. */ + displayedColumns = ['name', 'accountNo', 'externalId', 'status', 'officeName']; + /** Data source for groups table. */ + dataSource: GroupsDataSource; + /** Groups filter. */ + filterGroupsBy = [ + { + type: 'name', + value: '' + } + ]; + /** Paginator for groups table. */ @ViewChild(MatPaginator) paginator: MatPaginator; + /** Sorter for groups table. */ @ViewChild(MatSort) sort: MatSort; - constructor(private groupService: GroupsService) { } + + constructor(private groupsService: GroupsService) { } ngOnInit() { - this.groupService.getGroups() - .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.getGroups(); + } + + /** + * Subscribes to all search filters: + * Name + * sort change and page change. + */ + ngAfterViewInit() { + + this.name.valueChanges + .pipe( + debounceTime(500), + distinctUntilChanged(), + tap((filterValue) => { + this.applyFilter(filterValue, 'name'); }) - ); + ) + .subscribe(); + + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + tap(() => this.loadGroupsPage()) + ) + .subscribe(); } + changeShowClosedGroups() { + this.loadGroupsPage(); + } - 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 groups. + */ + loadGroupsPage() { + if (!this.sort.direction) { + delete this.sort.active; } + this.dataSource.getGroups(this.filterGroupsBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize, !this.showClosedGroups.checked); } + + /** + * Filters data in groups 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.filterGroupsBy.findIndex(filter => filter.type === property); + this.filterGroupsBy[findIndex].value = filterValue; + this.loadGroupsPage(); + } + + /** + * Initializes the data source for groups table and loads the first page. + */ + getGroups() { + this.dataSource = new GroupsDataSource(this.groupsService); + this.dataSource.getGroups(this.filterGroupsBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize); + } + } diff --git a/src/app/groups/groups.datasource.ts b/src/app/groups/groups.datasource.ts new file mode 100644 index 000000000..16e06d455 --- /dev/null +++ b/src/app/groups/groups.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 { GroupsService } from './groups.service'; + +/** + * Groups custom data source to implement server side filtering, pagination and sorting. + */ +export class GroupsDataSource implements DataSource { + + /** groups behavior subject to represent loaded groups page. */ + private groupsSubject = new BehaviorSubject([]); + /** Records subject to represent total number of filtered groups records. */ + private recordsSubject = new BehaviorSubject(0); + /** Records observable which can be subscribed to get the value of total number of filtered groups records. */ + public records$ = this.recordsSubject.asObservable(); + + /** + * @param {GroupsService} groupsService Groups Service + */ + constructor(private groupsService: GroupsService) { } + + /** + * Gets groups 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} groupActive Specify whether to only filter active groups. + */ + getGroups(filterBy: any, orderBy: string = '', sortOrder: string = '', pageIndex: number = 0, limit: number = 10, groupActive: boolean = true) { + this.groupsSubject.next([]); + this.groupsService.getGroups(filterBy, orderBy, sortOrder, pageIndex * limit, limit) + .subscribe((groups: any) => { + groups.pageItems = (groupActive) ? (groups.pageItems.filter((group: any) => group.active)) : groups.pageItems; + this.recordsSubject.next(groups.totalFilteredRecords); + this.groupsSubject.next(groups.pageItems); + }); + } + + /** + * @param {CollectionViewer} collectionViewer + */ + connect(collectionViewer: CollectionViewer): Observable { + return this.groupsSubject.asObservable(); + } + + /** + * @param {CollectionViewer} collectionViewer + */ + disconnect(collectionViewer: CollectionViewer): void { + this.groupsSubject.complete(); + this.recordsSubject.complete(); + } + +} diff --git a/src/app/groups/groups.module.ts b/src/app/groups/groups.module.ts index 6c2a9d0b3..c191dd9ce 100644 --- a/src/app/groups/groups.module.ts +++ b/src/app/groups/groups.module.ts @@ -1,23 +1,29 @@ -import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { CommonModule } from '@angular/common'; -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 { GroupsRoutingModule } from './groups-routing.module'; +import { SharedModule } from 'app/shared/shared.module'; +import { PipesModule } from '../pipes/pipes.module'; + +/** Custom Components */ import { GroupsComponent } from './groups.component'; -import { GroupsRoutingModule } from 'app/groups/groups-routing.module'; -import { GroupsService } from './groups.service'; + +/** + * Groups Module + * + * All components related to Groups should be declared here. + */ @NgModule({ imports: [ - CommonModule, + SharedModule, GroupsRoutingModule, - CoreModule, - MatTableModule, - FlexLayoutModule + PipesModule ], - declarations: [GroupsComponent], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] + declarations: [ + GroupsComponent + ], + providers: [DatePipe] }) export class GroupsModule { } diff --git a/src/app/groups/groups.service.ts b/src/app/groups/groups.service.ts index 6d777eeae..d671d84bc 100644 --- a/src/app/groups/groups.service.ts +++ b/src/app/groups/groups.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'; +/** + * Groups service. + */ @Injectable({ providedIn: 'root' - }) - - +}) export class GroupsService { - constructor(private http: HttpClient) { } + /** + * @param {HttpClient} http Http Client to send requests. + */ + constructor(private http: HttpClient) { } - getGroups(): Observable { - return this.http.get('/groups'); - } + /** + * @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} Groups. + */ + getGroups(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 + filterBy.forEach(function (filter: any) { + if (filter.value) { + console.log(filter.value); + httpParams = httpParams.set(filter.type, filter.value); + } + }); + return this.http.get('/groups', { params: httpParams }); + } }