feat: add centers view page (general tab) (#782)

Fixes: #218
This commit is contained in:
Muskan Khedia
2020-05-20 15:57:02 +05:30
committed by GitHub
parent 3fe5a05fed
commit 2e89538432
22 changed files with 654 additions and 5 deletions
+33 -1
View File
@@ -10,6 +10,12 @@ import { extract } from '../core/i18n/i18n.service';
import { CentersComponent } from './centers.component';
import { CreateCenterComponent } from './create-center/create-center.component';
import { OfficesResolver } from 'app/accounting/common-resolvers/offices.resolver';
import { CentersViewComponent } from './centers-view/centers-view.component';
import { GeneralTabComponent } from './centers-view/general-tab/general-tab.component';
import { CenterViewResolver } from './common-resolvers/center-view.resolver';
import { SavingsAccountResolver } from './common-resolvers/savings-account.resolver';
import { CenterResourceResolver } from './common-resolvers/center-resource.resolver';
import { CenterSummaryResolver } from './common-resolvers/center-summary.resolver';
const routes: Routes = [
Route.withShell([
@@ -29,6 +35,26 @@ const routes: Routes = [
resolve: {
offices: OfficesResolver,
}
},
{
path: ':centerId',
component: CentersViewComponent,
data: { title: extract('Centers View'), routeParamBreadcrumb: 'centerId' },
resolve: {
centerViewData: CenterViewResolver
},
children: [
{
path: 'general',
component: GeneralTabComponent,
data: { title: extract('General'), breadcrumb: 'General', routeParamBreadcrumb: false },
resolve: {
centerSummaryData: CenterSummaryResolver,
centerViewData: CenterResourceResolver,
savingsAccountData: SavingsAccountResolver
}
}
]
}
]
}
@@ -38,6 +64,12 @@ const routes: Routes = [
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [OfficesResolver]
providers: [
OfficesResolver,
CenterViewResolver,
SavingsAccountResolver,
CenterResourceResolver,
CenterSummaryResolver
]
})
export class CentersRoutingModule { }
@@ -0,0 +1,13 @@
@import "~@angular/material/theming";
@mixin centers-view-component-theme($theme) {
$primary: map-get($theme, primary);
mifosx-centers-view {
.center-card {
.header {
background-color: mat-color($primary, 500);
}
}
}
}
@@ -0,0 +1,93 @@
<mat-card class="center-card">
<mat-card-header fxLayout="column" class="header">
<mat-card-title-group class="header-title-group">
<div class="profile-image-container">
<div>
<img mat-card-md-image class="profile-image"
src='../../../assets/images/center_placeholder.png'>
</div>
</div>
<div class="mat-typography center-card-title">
<mat-card-title>
<h3>
<i class="fa fa-stop" matTooltip= "{{ centerViewData.status.value }}"
[ngClass]="centerViewData.status.code |statusLookup"></i>
Center Name : {{centerViewData.name}}
</h3>
</mat-card-title>
<mat-card-subtitle>
<div fxLayout="row" fxLayout.lt-md="column">
<div fxFlex="50%">
<p>
Account #:{{centerViewData.accountNo}} <br />
Office: {{centerViewData.officeName}} <br />
<span *ngIf="centerViewData.externalId">
External Id: {{centerViewData.externalId}} <br/>
</span>
<span *ngIf="centerViewData.staffName">
Staff: {{centerViewData.staffName}} <br/>
</span>
Activation Date :
{{(centerViewData.activationDate)?(centerViewData.activationDate|date) :'Not Activated'}}<br />
</p>
</div>
<div fxFlex="50%">
<p class="meetingDetails">
Next Meeting On:
<span *ngIf="meetingData">
{{centerViewData.collectionMeetingCalendar.nextTenRecurringDates[0]|date}}
<span *ngIf="centerViewData.collectionMeetingCalendar">
<a href="#" *mifosxHasPermission="'UPDATE_MEETING'"> <i class="fa fa-edit" style="color:#ffffff; padding: 5px;"></i> </a>
</span> <br/>
<span *ngIf="centerViewData.collectionMeetingCalendar.humanReadable"> Meeting Frequency: {{centerViewData.collectionMeetingCalendar.humanReadable}} </span>
</span>
<span *ngIf="!(meetingData)"> Unassigned </span><br/>
<span *ngIf="centerViewData.status.value === 'Closed'"> Closed Date: {{centerViewData.timeline.closedOnDate|date}}</span>
</p>
</div>
</div>
</mat-card-subtitle>
</div>
</mat-card-title-group>
<mat-card-actions class="center-actions" *ngIf="!(centerViewData.status.value==='Closed')">
<span *ngIf="!(centerViewData.status.value=='Active')">
<button mat-raised-button *mifosxHasPermission="'ACTIVATE_CENTER'" ><i class="fa fa-check-sign"></i> Activate </button>
</span>
<button mat-raised-button *mifosxHasPermission="'UPDATE_CENTER'"><i class="fa fa-edit"></i> Edit </button>
<button mat-raised-button *mifosxHasPermission="'CREATE_GROUP'"><i class="fa fa-plus"></i> Add Group </button>
<button mat-raised-button *mifosxHasPermission="'ASSOCIATEGROUPS_CENTER'"> <i class="fa fa-edit"></i> Manage Groups </button>
<span *ngIf="centerViewData.active">
<button mat-raised-button *mifosxHasPermission="'CREATE_SAVINGSACCOUNT'"> <i class="fa fa-file"></i> Centers Saving Application</button>
</span>
<button mat-raised-button [matMenuTriggerFor]="More"> More </button>
<mat-menu #More="matMenu">
<span *ngIf="centerViewData.collectionMeetingCalendar">
<button mat-menu-item *mifosxHasPermission="'SAVEORUPDATEATTENDANCE_MEETING'"> Attendance </button>
</span>
<span *ngIf="!(centerViewData.staffId)">
<button mat-menu-item *mifosxHasPermission="'ASSIGNSTAFF_GROUP'" > Assign Staff </button>
</span>
<span *ngIf="centerViewData.staffId">
<button mat-menu-item *mifosxHasPermission="'UNASSIGNSTAFF_GROUP'" > Unassign Staff </button>
</span>
<span *ngIf="centerViewData.status.value==='Active'">
<button mat-menu-item *mifosxHasPermission="'DELETE_CENTER'" > Delete </button>
</span>
<button mat-menu-item *mifosxHasPermission="'CLOSE_CENTER'" > Close </button>
<span *ngIf="centerViewData.status.value==='Active' && !(centerViewData.collectionMeetingCalendar)">
<button mat-menu-item *mifosxHasPermission="'CREATE_MEETING'"> Attach Meeting </button>
</span>
<button mat-menu-item *mifosxHasPermission="'READ_Staff Assignment History'" > Staff Assignment History </button>
</mat-menu>
</mat-card-actions>
</mat-card-header>
<mat-card-content>
<nav mat-tab-nav-bar class="navigation-tabs">
<a mat-tab-link [routerLink]="['./general']" routerLinkActive #general="routerLinkActive"
[active]="general.isActive">
General
</a>
</nav>
<router-outlet></router-outlet>
</mat-card-content>
</mat-card>
@@ -0,0 +1,39 @@
.center-card {
margin: 0 auto;
max-width: 80rem;
width: 90%;
padding: 0;
.header {
padding: 1%;
.header-title-group {
.center-card-title {
color: white;
width: 90%;
p {
color: white;
}
}
}
.profile-image-container {
margin: 1%;
.profile-image {
object-fit: cover;
border-radius: 20px;
}
}
.center-actions {
align-self: flex-end;
margin: 0 1%;
}
}
.navigation-tabs {
overflow: auto;
}
}
.meetingDetails {
float:right;
margin-right: 10px;
border: 1px black solid;
padding: 10px;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CentersViewComponent } from './centers-view.component';
describe('CentersViewComponent', () => {
let component: CentersViewComponent;
let fixture: ComponentFixture<CentersViewComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CentersViewComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CentersViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,37 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
/**
* Create Center View
*/
@Component({
selector: 'mifosx-centers-view',
templateUrl: './centers-view.component.html',
styleUrls: ['./centers-view.component.scss']
})
export class CentersViewComponent implements OnInit {
/** Stores Center View Data */
centerViewData: any;
meetingData: boolean;
/**
* Retrieves the data for center
* @param route route Activated Route.
*/
constructor(private route: ActivatedRoute) {
this.route.data.subscribe((data: {
centerViewData: any,
}) => {
this.centerViewData = data.centerViewData;
});
}
ngOnInit() {
if (this.centerViewData.collectionMeetingCalendar) {
this.meetingData = true;
} else { this.meetingData = false; }
}
}
@@ -0,0 +1,140 @@
<div class="tab-container mat-typography">
<div *ngIf="centerViewData.status.value=='Closed'">
<h3 class="closedCenter">Center Closed</h3>
</div>
<h3>Summary Details</h3>
<div fxLayout="row" fxLayoutGap="32px" class="summary-details-container">
<p>
Active Clients: {{centerSummaryData.activeClients}}<br />
Active Group Loans: {{centerSummaryData.activeGroupLoans}}<br />
Active Client Loans: {{centerSummaryData.activeClientLoans}}<br />
Active Overdue Group Loans: {{centerSummaryData.overdueGroupLoans}}<br />
</p>
<p>
Active Group Borrowers: {{centerSummaryData.activeGroupBorrowers}}<br />
Active Client Borrowers: {{centerSummaryData.activeClientBorrowers}}<br />
Active Overdue Client Loans: {{centerSummaryData.overdueClientLoans}}<br />
</p>
</div>
<!-- Groups -->
<ng-container *ngIf="groupResourceData.length > 0">
<h3>Groups</h3>
<table mat-table [dataSource]="groupResourceData" matSort>
<ng-container matColumnDef="Account No">
<th mat-header-cell *matHeaderCellDef> Account Number </th>
<td mat-cell *matCellDef="let element"> <i class="fa fa-stop" matTooltip="{{ element.status.value }}"
[ngClass]="element.status.code|statusLookup"></i>
{{element.accountNo}} </td>
</ng-container>
<ng-container matColumnDef="Group Name">
<th mat-header-cell *matHeaderCellDef> Group Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<ng-container matColumnDef="Office Name">
<th mat-header-cell *matHeaderCellDef> Office Name </th>
<td mat-cell *matCellDef="let element"> {{element.officeName}} </td>
</ng-container>
<ng-container matColumnDef="Submitted On">
<th mat-header-cell *matHeaderCellDef> Submitted On </th>
<td mat-cell *matCellDef="let element"> {{element.timeline.submittedOnDate|date}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="groupsColumns"></tr>
<tr mat-row *matRowDef="let row; columns: groupsColumns;"></tr>
</table>
</ng-container>
<!-- savings overview table -->
<ng-container *ngIf="!(savingsAccountData === undefined)">
<ng-container *ngIf="savingsAccountData.length > 0">
<h3>Savings Account Overview</h3>
<table mat-table [dataSource]="savingsAccountData" matSort>
<ng-container matColumnDef="Account No">
<th mat-header-cell *matHeaderCellDef> Account No </th>
<td mat-cell *matCellDef="let element"> <i class="fa fa-stop" matTooltip="{{ element.status.value }}"
[ngClass]="element.status.code|statusLookup"></i>
{{element.accountNo}} </td>
</ng-container>
<ng-container matColumnDef="Products">
<th mat-header-cell *matHeaderCellDef> Products </th>
<td mat-cell *matCellDef="let element"> {{element.productName}} </td>
</ng-container>
<ng-container matColumnDef="Balance">
<th mat-header-cell *matHeaderCellDef> Balance</th>
<td mat-cell *matCellDef="let element"> {{element.accountBalance}} </td>
</ng-container>
<ng-container matColumnDef="Actions">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let element">
<ng-container *ngIf="element.status.active">
<button class="account-action-button" mat-raised-button color="primary" *ngIf="element.depositType.id == '100'">
<i class="fa fa-arrow-right" matTooltip="Deposit"></i>
</button>
<button class="account-action-button" mat-raised-button color="primary" *ngIf="element.depositType.id == '300'">
<i class="fa fa-arrow-right" matTooltip[="Deposit"]></i>
</button>
<button class="account-action-button" mat-raised-button color="primary" *ngIf="element.depositType.id == '100'">
<i class="fa fa-arrow-left" matTooltip="Withdraw"></i>
</button>
<button class="account-action-button" mat-raised-button color="primary" *ngIf="element.depositType.id == '300'">
<i class="fa fa-arrow-left" matTooltip="Withdraw"></i>
</button>
</ng-container>
<ng-container *ngIf="element.status.submittedAndPendingApproval">
<button class="account-action-button" mat-raised-button color="primary">
<i class="fa fa-check" matTooltip="Approve"></i>
</button>
</ng-container>
<ng-container *ngIf="!element.status.submittedAndPendingApproval && !element.status.active">
<button class="account-action-button" mat-raised-button color="primary">
<i class="fa fa-undo" matTooltip="Undo Approve"></i>
</button>
<button class="account-action-button" mat-raised-button color="primary">
<i class="fa fa-power-off" matTooltip="Activate"></i>
</button>
</ng-container>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="savingsAccountColumns"></tr>
<tr mat-row *matRowDef="let row; columns: savingsAccountColumns;"></tr>
</table>
</ng-container>
</ng-container>
</div>
@@ -0,0 +1,28 @@
.tab-container {
padding: 1%;
margin: 1%;
h3{
margin:1% auto;
}
.summary-details-container {
border: 1px solid;
padding: 1%;
}
table {
width: 100%;
.account-action-button{
min-width: 26px;
padding: 0 6px;
margin: 4px;
line-height: 25px;
}
}
}
.closedCenter {
text-align: center;
color: red;
font-size: 2rem;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { GeneralTabComponent } from './general-tab.component';
describe('GeneralTabComponent', () => {
let component: GeneralTabComponent;
let fixture: ComponentFixture<GeneralTabComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ GeneralTabComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(GeneralTabComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,48 @@
/** Angular Imports */
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
/**
* Create Center General Tab Component
*/
@Component({
selector: 'mifosx-general-tab',
templateUrl: './general-tab.component.html',
styleUrls: ['./general-tab.component.scss']
})
export class GeneralTabComponent implements OnInit {
/** Savings Account Table Columns */
savingsAccountColumns: string[] = ['Account No', 'Products', 'Balance', 'Actions'];
/** Groups Table Columns */
groupsColumns: string[] = ['Account No', 'Group Name', 'Office Name', 'Submitted On'];
/** Stores the summary of center */
centerSummaryData: any;
/** Stores Center Data for particular center */
centerViewData: any;
/** Stores Saving Account for particular center */
savingsAccountData: any;
/** Stores Group Data */
groupResourceData: any;
/**
* Retrieves the data for centers
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private route: ActivatedRoute) {
this.route.data.subscribe((data: {
centerSummaryData: any,
centerViewData: any,
savingsAccountData: any
}) => {
this.centerSummaryData = data.centerSummaryData[0];
this.centerViewData = data.centerViewData;
this.savingsAccountData = data.savingsAccountData.savingsAccounts;
this.groupResourceData = data.centerViewData.groupMembers;
});
}
ngOnInit() {
}
}
+1 -1
View File
@@ -52,7 +52,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="[ ]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="[row.id, 'general']" class="select-row"></tr>
</table>
<mat-paginator [length]="dataSource?.records$ | async" [pageSize]="10" [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
+8 -1
View File
@@ -5,6 +5,13 @@
.search-button{
min-width: 30px;
margin: 4px;
}
}
table {
width: 100%;
.select-row:hover {
cursor: pointer;
}
}
+8 -2
View File
@@ -5,10 +5,13 @@ import { NgModule } from '@angular/core';
import { CentersRoutingModule } from './centers-routing.module';
import { SharedModule } from 'app/shared/shared.module';
import { PipesModule } from '../pipes/pipes.module';
import { DirectivesModule } from '../directives/directives.module';
/** Custom Components */
import { CentersComponent } from './centers.component';
import { CreateCenterComponent } from './create-center/create-center.component';
import { CentersViewComponent } from './centers-view/centers-view.component';
import { GeneralTabComponent } from './centers-view/general-tab/general-tab.component';
/**
* Centers Module
@@ -19,11 +22,14 @@ import { CreateCenterComponent } from './create-center/create-center.component';
imports: [
SharedModule,
CentersRoutingModule,
PipesModule
PipesModule,
DirectivesModule
],
declarations: [
CentersComponent,
CreateCenterComponent
CreateCenterComponent,
CentersViewComponent,
GeneralTabComponent
]
})
export class CentersModule { }
+27
View File
@@ -61,4 +61,31 @@ export class CentersService {
return this.http.get('/centers/template', { params: httpParams });
}
/**
* @param centerId CenterID
* @returns {Observable<any>} Group Data for Center
*/
getCenterData(centerId: string): Observable<any> {
const httpParams = new HttpParams()
.set('associations', 'groupMembers,collectionMeetingCalendar');
return this.http.get(`/centers/${centerId}`, { params: httpParams } );
}
/**
* @param centerId Center ID
* @returns {Observable<any>} Returns the entire details of the Center ID
*/
getCenterSummary(centerId: string): Observable<any> {
const httpParams = new HttpParams().set('R_groupId', centerId)
.set('genericResultSet', 'false');
return this.http.get(`/runreports/GroupSummaryCounts`, { params: httpParams });
}
/**
* @param centerId Center ID
* @returns {Observable<any>} Returns the saving account for that center
*/
getSavingsAccountDetails(centerId: string): Observable<any> {
return this.http.get(`/centers/${centerId}/accounts`);
}
}
@@ -0,0 +1,31 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { CentersService } from '../centers.service';
/**
* Centers data resolver.
*/
@Injectable()
export class CenterResourceResolver implements Resolve<Object> {
/**
* @param {CentersService} CentersService Centers service.
*/
constructor(private centersService: CentersService) { }
/**
* Returns the Centers data for General Tab.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const centerId = route.parent.paramMap.get('centerId');
return this.centersService.getCenterData(centerId);
}
}
@@ -0,0 +1,31 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { CentersService } from '../centers.service';
/**
* Centers data resolver.
*/
@Injectable()
export class CenterSummaryResolver implements Resolve<Object> {
/**
* @param {CentersService} CentersService Centers service.
*/
constructor(private centersService: CentersService) { }
/**
* Returns the Centers Summary Data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const centerId = route.parent.paramMap.get('centerId');
return this.centersService.getCenterSummary(centerId);
}
}
@@ -0,0 +1,31 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { CentersService } from '../centers.service';
/**
* Centers data resolver.
*/
@Injectable()
export class CenterViewResolver implements Resolve<Object> {
/**
* @param {CentersService} CentersService Centers service.
*/
constructor(private centersService: CentersService) { }
/**
* Returns the Centers data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const centerId = route.paramMap.get('centerId');
return this.centersService.getCenterData(centerId);
}
}
@@ -0,0 +1,31 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { CentersService } from '../centers.service';
/**
* Centers data resolver.
*/
@Injectable()
export class SavingsAccountResolver implements Resolve<Object> {
/**
* @param {CentersService} CentersService Centers service.
*/
constructor(private centersService: CentersService) { }
/**
* Returns the Center Savings Account data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const centerId = route.parent.paramMap.get('centerId');
return this.centersService.getSavingsAccountDetails(centerId);
}
}
+1
View File
@@ -39,6 +39,7 @@ export class StatusLookupPipe implements PipeTransform {
'smsCampaignStatus.active': 'status-active',
'smsCampaignStatus.pending': 'status-pending',
'smsCampaignStatus.closed': 'status-closed',
'groupingStatusType.active': 'status-active'
};
return cssClassNameLookup[status];
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+2
View File
@@ -2,6 +2,7 @@
// Plus imports for other components in your app.
@import '../../app/clients/clients-view/clients-view.component-theme.scss';
@import '../../app/groups/groups-view/groups-view.component-theme.scss';
@import '../../app/centers/centers-view/centers-view.component-theme.scss';
// Custom material palette
$mat-denim: (
@@ -49,3 +50,4 @@ $mifosx-app-theme: mat-light-theme($mifosx-app-primary, $mifosx-app-accent, $mif
@include angular-material-theme($mifosx-app-theme);
@include clients-view-component-theme($mifosx-app-theme);
@include groups-view-component-theme($mifosx-app-theme);
@include centers-view-component-theme($mifosx-app-theme);
@@ -2,6 +2,7 @@
// Plus imports for other components in your app.
@import '../../app/clients/clients-view/clients-view.component-theme.scss';
@import '../../app/groups/groups-view/groups-view.component-theme.scss';
@import '../../app/centers/centers-view/centers-view.component-theme.scss';
// Custom material palette
$mat-picton-blue: (
@@ -49,3 +50,4 @@ $mifosx-app-theme: mat-light-theme($mifosx-app-primary, $mifosx-app-accent, $mif
@include angular-material-theme($mifosx-app-theme);
@include clients-view-component-theme($mifosx-app-theme);
@include groups-view-component-theme($mifosx-app-theme);
@include centers-view-component-theme($mifosx-app-theme);