feat: add dashboard component (#811)

Fixes: #808
This commit is contained in:
Karan Takalkar
2020-05-30 22:15:29 +05:30
committed by GitHub
parent ca9404213c
commit ab3bef463d
27 changed files with 1025 additions and 13 deletions
@@ -0,0 +1,30 @@
<mat-card class="card">
<mat-card-header class="header">
<h3 fxFlex="100%">
<fa-icon icon="donate"></fa-icon>
&nbsp;&nbsp; Amount Collected
<div>
<mat-form-field id="office" fxFlex="94%">
<mat-label>Office</mat-label>
<mat-select [formControl]="officeId">
<mat-option *ngFor="let office of officeData" [value]="office.id">
{{ office.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</h3>
</mat-card-header>
<mat-card-content>
<!-- Structural directives like *ngIf break the canvas -->
<div [ngStyle]="{'display': (hideOutput) ? 'none' : 'block'}">
<canvas id="collection-pie" width="800" height="600"></canvas>
</div>
<div *ngIf="showFallback" class="fallback" fxLayoutAlign="center center">
<p>No Data</p>
</div>
</mat-card-content>
</mat-card>
@@ -0,0 +1,12 @@
.card {
width: 27.5rem;
padding: 0;
.header {
#office{
margin-bottom: -1.20em;
}
}
.fallback {
height: 20rem;
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AmountCollectedPieComponent } from './amount-collected-pie.component';
describe('AmountCollectedPieComponent', () => {
let component: AmountCollectedPieComponent;
let fixture: ComponentFixture<AmountCollectedPieComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AmountCollectedPieComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AmountCollectedPieComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,104 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
/** Custom Services */
import { HomeService } from '../../home.service';
/** Charting Imports */
import Chart from 'chart.js';
/**
* Amount Collected Pie Chart Component
*/
@Component({
selector: 'mifosx-amount-collected-pie',
templateUrl: './amount-collected-pie.component.html',
styleUrls: ['./amount-collected-pie.component.scss']
})
export class AmountCollectedPieComponent implements OnInit {
/** Static Form control for office Id */
officeId = new FormControl();
/** Office Data */
officeData: any;
/** Chart.js chart */
chart: any;
/** Substitute for resolver */
hideOutput = true;
/** Shows fallback element */
showFallback = false;
/**
* Fetches offices data from `resolve`.
* @param {HomeService} homeService Home Service.
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private homeService: HomeService,
private route: ActivatedRoute) {
this.route.data.subscribe( (data: { offices: any }) => {
this.officeData = data.offices;
});
}
/**
* Sets the pie chart
* Initialize with office Id 1 for better UX.
*/
ngOnInit() {
this.getChartData();
this.officeId.patchValue(1);
}
/**
* Subscribes to value changes of office Id fetches chart data accordingly.
*/
getChartData() {
this.officeId.valueChanges.subscribe((value: number) => {
this.homeService.getCollectedAmount(value).subscribe((response: any) => {
const data = Object.entries(response[0]).map(entry => entry[1]);
if (!(data[0] === 0 && data[1] === 0)) {
this.setChart(data);
this.hideOutput = false;
this.showFallback = false;
} else {
this.showFallback = true;
this.hideOutput = true;
}
});
});
}
/**
* Creates an instance of Chart.js pie chart
* Refer: https://www.chartjs.org/docs/latest/charts/doughnut.html for configuration details.
* @param {any} data Chart Data.
*/
setChart(data: any) {
if (!this.chart) {
this.chart = new Chart('collection-pie', {
type: 'pie',
data: {
labels: ['Pending', 'Collected'],
datasets: [{
backgroundColor: ['red', 'green'],
data: data
}]
},
options: {
layout: {
padding: {
top: 10,
bottom: 15
}
}
}
});
} else {
this.chart.data.datasets[0].data = data;
this.chart.update();
}
}
}
@@ -0,0 +1,30 @@
<mat-card class="card">
<mat-card-header class="header">
<h3 fxFlex="100%">
<fa-icon icon="money-bill"></fa-icon>
&nbsp;&nbsp; Amount Disbursed
<div>
<mat-form-field id="office" fxFlex="94%">
<mat-label>Office</mat-label>
<mat-select [formControl]="officeId">
<mat-option *ngFor="let office of officeData" [value]="office.id">
{{ office.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</h3>
</mat-card-header>
<mat-card-content>
<!-- Structural directives like *ngIf break the canvas -->
<div [ngStyle]="{'display': (hideOutput) ? 'none' : 'block'}">
<canvas id="disbursement-pie" width="800" height="600"></canvas>
</div>
<div *ngIf="showFallback" class="fallback" fxLayoutAlign="center center">
<p>No Data</p>
</div>
</mat-card-content>
</mat-card>
@@ -0,0 +1,12 @@
.card {
width: 27.5rem;
padding: 0;
.header {
#office{
margin-bottom: -1.20em;
}
}
.fallback {
height: 20rem;
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AmountDisbursedPieComponent } from './amount-disbursed-pie.component';
describe('AmountDisbursedPieComponent', () => {
let component: AmountDisbursedPieComponent;
let fixture: ComponentFixture<AmountDisbursedPieComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AmountDisbursedPieComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AmountDisbursedPieComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,104 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
/** Custom Services */
import { HomeService } from '../../home.service';
/** Charting Imports */
import Chart from 'chart.js';
/**
* Amount Disbursed Pie Chart Component
*/
@Component({
selector: 'mifosx-amount-disbursed-pie',
templateUrl: './amount-disbursed-pie.component.html',
styleUrls: ['./amount-disbursed-pie.component.scss']
})
export class AmountDisbursedPieComponent implements OnInit {
/** Static Form control for office Id */
officeId = new FormControl();
/** Office Data */
officeData: any;
/** Chart.js chart */
chart: any;
/** Substitute for resolver */
hideOutput = true;
/** Shows fallback element */
showFallback = false;
/**
* Fetches offices data from `resolve`.
* @param {HomeService} homeService Home Service.
* @param {ActivatedRoute} route Activated Route.
*/
constructor(private homeService: HomeService,
private route: ActivatedRoute) {
this.route.data.subscribe( (data: { offices: any }) => {
this.officeData = data.offices;
});
}
/**
* Sets the pie chart with initial office Id 1.
* Initialize with office Id 1 for better UX.
*/
ngOnInit() {
this.getChartData();
this.officeId.patchValue(1);
}
/**
* Subscribes to value changes of office Id fetches chart data accordingly.
*/
getChartData() {
this.officeId.valueChanges.subscribe((value: number) => {
this.homeService.getDisbursedAmount(value).subscribe((response: any) => {
const data = Object.entries(response[0]).map(entry => entry[1]);
if (!(data[0] === 0 && data[1] === 0)) {
this.setChart(data);
this.showFallback = false;
this.hideOutput = false;
} else {
this.showFallback = true;
this.hideOutput = true;
}
});
});
}
/**
* Creates an instance of Chart.js pie chart
* Refer: https://www.chartjs.org/docs/latest/charts/doughnut.html for configuration details.
* @param {any} data Chart Data.
*/
setChart(data: any) {
if (!this.chart) {
this.chart = new Chart('disbursement-pie', {
type: 'pie',
data: {
labels: ['Pending', 'Disbursed'],
datasets: [{
backgroundColor: ['red', 'yellow'],
data: data
}]
},
options: {
layout: {
padding: {
top: 10,
bottom: 15
}
}
}
});
} else {
this.chart.data.datasets[0].data = data;
this.chart.update();
}
}
}
@@ -0,0 +1,32 @@
<mat-card class="card">
<mat-card-header class="header">
<h3 fxFlex="100%">
<fa-icon icon="chart-line"></fa-icon>
&nbsp;&nbsp; Client Trends
<div>
<mat-form-field id="office" fxFlex="94%">
<mat-label>Office</mat-label>
<mat-select [formControl]="officeId">
<mat-option *ngFor="let office of officeData" [value]="office.id">
{{ office.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</h3>
</mat-card-header>
<mat-card-content>
<!-- Structural directives like *ngIf break the canvas -->
<div [ngStyle]="{'display': (hideOutput) ? 'none' : 'block'}">
<canvas id="client-trends-bar" width="800" height="465"></canvas>
</div>
<mat-button-toggle-group [formControl]="timescale" id='timescale' appearance="legacy">
<mat-button-toggle value="Day">Day</mat-button-toggle>
<mat-button-toggle value="Week">Week</mat-button-toggle>
<mat-button-toggle value="Month">Month</mat-button-toggle>
</mat-button-toggle-group>
</mat-card-content>
</mat-card>
@@ -0,0 +1,13 @@
.card {
width: 41.25rem;
padding: 0;
.header {
#office{
margin-bottom: -1.20em;
}
}
#timescale {
margin-block: 2%;
margin-inline: 35%;
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ClientTrendsBarComponent } from './client-trends-bar.component';
describe('ClientTrendsBarComponent', () => {
let component: ClientTrendsBarComponent;
let fixture: ComponentFixture<ClientTrendsBarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ClientTrendsBarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ClientTrendsBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,255 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { DatePipe } from '@angular/common';
/** rxjs Imports */
import { merge, forkJoin } from 'rxjs';
import { skip } from 'rxjs/operators';
/** Custom Services */
import { HomeService } from '../../home.service';
/** Charting Imports */
import Chart from 'chart.js';
/**
* Client Trends Bar Chart Component.
*/
@Component({
selector: 'mifosx-client-trends-bar',
templateUrl: './client-trends-bar.component.html',
styleUrls: ['./client-trends-bar.component.scss']
})
export class ClientTrendsBarComponent implements OnInit {
/** Static Form control for office Id */
officeId = new FormControl();
/** Static Form control for time scale */
timescale = new FormControl();
/** Office Data */
officeData: any;
/** Chart.js chart */
chart: any;
/** Substitute for resolver */
hideOutput = true;
/**
* Fetches offices data from `resolve`
* @param {HomeService} homeService Home Service
* @param {ActivatedRoute} route Activated Route
* @param {DatePipe} datePipe Date Pipe
*/
constructor(private homeService: HomeService,
private route: ActivatedRoute,
private datePipe: DatePipe) {
this.route.data.subscribe( (data: { offices: any }) => {
this.officeData = data.offices;
});
}
ngOnInit() {
this.getChartData();
this.initializeControls();
}
/**
* Initialize the form controls for better UX.
*/
initializeControls() {
this.officeId.patchValue(1);
this.timescale.patchValue('Day');
}
/**
* Subscribes to value changes of officeID and timescale controls,
* Fetches data accordingly and sets charts based on fetched data.
*/
getChartData() {
merge(this.officeId.valueChanges, this.timescale.valueChanges).pipe(skip(1))
.subscribe(() => {
const officeId = this.officeId.value;
const timescale = this.timescale.value;
switch (timescale) {
case 'Day':
const clientsByDay = this.homeService.getClientTrendsByDay(officeId);
const loansByDay = this.homeService.getLoanTrendsByDay(officeId);
forkJoin([clientsByDay, loansByDay]).subscribe((data: any[]) => {
const dayLabels = this.getLabels(timescale);
const clientCounts = this.getCounts(data[0], dayLabels, timescale, 'client');
const loanCounts = this.getCounts(data[1], dayLabels, timescale, 'loan');
this.setChart(dayLabels, clientCounts, loanCounts);
this.hideOutput = false;
});
break;
case 'Week':
const clientsByWeek = this.homeService.getClientTrendsByWeek(officeId);
const loansByWeek = this.homeService.getLoanTrendsByWeek(officeId);
forkJoin([clientsByWeek, loansByWeek]).subscribe((data: any[]) => {
const weekLabels = this.getLabels(timescale);
const clientCounts = this.getCounts(data[0], weekLabels, timescale, 'client');
const loanCounts = this.getCounts(data[1], weekLabels, timescale, 'loan');
this.setChart(weekLabels, clientCounts, loanCounts);
this.hideOutput = false;
});
break;
case 'Month':
const clientsByMonth = this.homeService.getClientTrendsByMonth(officeId);
const loansByMonth = this.homeService.getLoanTrendsByMonth(officeId);
forkJoin([clientsByMonth, loansByMonth]).subscribe((data: any[]) => {
const monthLabels = this.getLabels(timescale);
const clientCounts = this.getCounts(data[0], monthLabels, timescale, 'client');
const loanCounts = this.getCounts(data[1], monthLabels, timescale, 'loan');
this.setChart(monthLabels, clientCounts, loanCounts);
this.hideOutput = false;
});
break;
}
});
}
/**
* Gets Abscissa Labels.
* @param {string} timescale User's timescale choice.
*/
getLabels(timescale: string) {
const date = new Date();
const labelsArray = [];
switch (timescale) {
case 'Day':
while (labelsArray.length < 12) {
date.setDate(date.getDate() - 1);
const transformedDate = this.datePipe.transform(date, 'd/M');
labelsArray.push(transformedDate);
}
break;
case 'Week':
/** 1st January of present year */
const onejan = new Date(date.getFullYear(), 0, 1);
while (labelsArray.length < 12) {
date.setDate(date.getDate() - 7);
/** Gets current week number */
const weekNumber = Math.ceil(
(((date.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7
);
labelsArray.push(weekNumber);
}
break;
case 'Month':
while (labelsArray.length < 12) {
const transformedDate = this.datePipe.transform(date, 'MMMM');
labelsArray.push(transformedDate);
date.setMonth(date.getMonth() - 1);
}
break;
}
return labelsArray.reverse();
}
/**
* Get bar heights for clients/loans trends.
* @param {any[]} response API response array.
* @param {any[]} labels Abscissa Labels.
* @param {string} timescale User's timescale choice.
* @param {string} type 'client' or 'loan'.
*/
getCounts(response: any[], labels: any[], timescale: string, type: string) {
let counts: number[] = [];
switch (timescale) {
case 'Day':
labels.forEach((label: any) => {
const day = response.find((entry: any) => {
const transformedDate = this.datePipe.transform(entry.days, 'd/M');
return transformedDate === label;
});
counts = this.updateCount(day, counts, type);
});
break;
case 'Week':
labels.forEach((label: any) => {
const week = response.find((entry: any) => {
return entry.Weeks === label;
});
counts = this.updateCount(week, counts, type);
});
break;
case 'Month':
labels.forEach((label: any) => {
const month = response.find((entry: any) => {
return entry.Months === label;
});
counts = this.updateCount(month, counts, type);
});
break;
}
return counts;
}
/**
* Updates the counts array.
* @param {any} span Time span.
* @param {any[]} counts Counts.
* @param {string} type 'client' or 'loan'
*/
updateCount(span: any, counts: any[], type: string) {
if (span) {
switch (type) {
case 'client':
counts.push(span.count);
break;
case 'loan':
counts.push(span.lcount);
break;
}
} else {
counts.push(0);
}
return counts;
}
/**
* Creates an instance of Chart.js multi-bar chart.
* Refer: https://www.chartjs.org/docs/latest/charts/bar.html for configuration details.
* @param {any[]} labels Abscissa Labels.
* @param {number[]} clientCounts Clients Ordinate.
* @param {number[]} loanCounts Loans Ordinate.
*/
setChart(labels: any[], clientCounts: number[], loanCounts: number[]) {
if (!this.chart) {
this.chart = new Chart('client-trends-bar', {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'New Clients',
backgroundColor: 'dodgerblue',
data: clientCounts
},
{
label: 'Loans Disbursed',
backgroundColor: 'green',
data: loanCounts
}
]
},
options: {
layout: {
padding: {
top: 5,
left: 10,
right: 10
}
}
}
});
} else {
this.chart.data.labels = labels;
this.chart.data.datasets[0].data = clientCounts;
this.chart.data.datasets[1].data = loanCounts;
this.chart.update();
}
}
}
@@ -0,0 +1,36 @@
@import "~@angular/material/theming";
@mixin dashboard-component-theme($theme) {
$primary: map-get($theme, primary);
mifosx-dashboard {
.recent-activities, .frequent-activities {
.header {
background-color: mat-color($primary, 500);
color: mat-contrast($primary, 500);
}
}
mifosx-amount-collected-pie,
mifosx-amount-disbursed-pie,
mifosx-client-trends-bar {
.card {
.header {
background-color: mat-color($primary, 500);
color: mat-contrast($primary, 500);
mat-label,
.mat-select-value,
.mat-select-arrow {
color: mat-contrast($primary, 500);
}
.mat-form-field-underline,
.mat-form-field-ripple {
background-color: mat-contrast($primary, 500);
}
}
}
}
}
}
@@ -1,7 +1,53 @@
<div class="container">
<p>
TODO: dashboard component.
</p>
<mat-card class="card" fxLayout="column" fxLayoutGap="40px">
<div fxLayout="row" fxLayoutGap="40px">
<mat-card class="recent-activities">
<mat-card-header class="header">
<h3><fa-icon icon="bolt"></fa-icon>
&nbsp;&nbsp; Recent Activities</h3>
</mat-card-header>
<mat-card-content>
<mat-action-list class="list">
<button mat-list-item *ngFor="let activity of recentActivities"
[routerLink]="activity"> {{ activity | urlToString }} </button>
</mat-action-list>
</mat-card-content>
</mat-card>
<mat-card class="frequent-activities">
<mat-card-header class="header">
<h3><fa-icon icon="laptop"></fa-icon>
&nbsp;&nbsp; Frequent Activities</h3>
</mat-card-header>
<mat-card-content>
<mat-action-list class="list">
<button mat-list-item *ngFor="let activity of frequentActivities"
[routerLink]="activity"> {{ activity | urlToString }} </button>
</mat-action-list>
</mat-card-content>
</mat-card>
<mifosx-amount-disbursed-pie></mifosx-amount-disbursed-pie>
</div>
<div fxLayout="row" fxLayoutGap="40px">
<mifosx-client-trends-bar></mifosx-client-trends-bar>
<mifosx-amount-collected-pie></mifosx-amount-collected-pie>
</div>
</mat-card>
</div>
@@ -0,0 +1,10 @@
.card {
padding: 2%;
.recent-activities, .frequent-activities {
width: 20rem;
padding: 0;
.list {
padding-top: 0px;
}
}
}
+52 -2
View File
@@ -11,10 +11,60 @@ import { Component, OnInit } from '@angular/core';
})
export class DashboardComponent implements OnInit {
constructor() { }
/** Array of all user activities */
userActivity: string[];
/** Array of most recent user activities */
recentActivities: string[];
/** Array of most frequent user activities */
frequentActivities: string[];
/**
* Gets user activities from local storage.
*/
constructor() {
this.userActivity = JSON.parse(localStorage.getItem('mifosXLocation'));
}
ngOnInit() {
this.recentActivities = this.getRecentActivities();
this.frequentActivities = this.getFrequentActivities();
}
/**
* Returns top eight recent activities.
*/
getRecentActivities() {
const reverseActivities = this.userActivity.reverse();
const uniqueActivities: string[] = [];
reverseActivities.forEach((activity: string) => {
if (!uniqueActivities.includes(activity)) {
uniqueActivities.push(activity);
}
});
const topEightRecentActivities =
uniqueActivities
.filter((activity: string) => !['/', '/login', '/home', '/dashboard'].includes(activity))
.slice(0, 8);
return topEightRecentActivities;
}
/**
* Returns top eight frequent activities.
*/
getFrequentActivities() {
const frequencyCounts: any = {};
let index = this.userActivity.length;
while (index) {
frequencyCounts[this.userActivity[--index]] = (frequencyCounts[this.userActivity[index]] || 0) + 1;
}
const frequencyCountsArray = Object.entries(frequencyCounts);
const topEigthFrequentActivities =
frequencyCountsArray
.sort((a: any, b: any) => b[1] - a[1])
.map((entry: any[]) => entry[0])
.filter((activity: string) => !['/', '/login', '/home', '/dashboard'].includes(activity))
.slice(0, 8);
return topEigthFrequentActivities;
}
}
+8 -2
View File
@@ -12,6 +12,9 @@ import { extract } from '../core/i18n/i18n.service';
import { HomeComponent } from './home.component';
import { DashboardComponent } from './dashboard/dashboard.component';
/** Custom Resolvers */
import { OfficesResolver } from '../accounting/common-resolvers/offices.resolver';
/** Home and Dashboard Routes */
const routes: Routes = [
Route.withShell([
@@ -28,7 +31,10 @@ const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
data: { title: extract('Dashboard'), breadcrumb: 'Dashboard' }
data: { title: extract('Dashboard'), breadcrumb: 'Dashboard' },
resolve: {
offices: OfficesResolver
}
}
])
];
@@ -41,6 +47,6 @@ const routes: Routes = [
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: []
providers: [OfficesResolver]
})
export class HomeRoutingModule { }
+13 -1
View File
@@ -1,13 +1,18 @@
/** Angular Imports */
import { NgModule } from '@angular/core';
import { DatePipe } from '@angular/common';
/** Custom Modules */
import { SharedModule } from '../shared/shared.module';
import { HomeRoutingModule } from './home-routing.module';
import { PipesModule } from '../pipes/pipes.module';
/** Custom Components */
import { HomeComponent } from './home.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { AmountCollectedPieComponent } from './dashboard/amount-collected-pie/amount-collected-pie.component';
import { AmountDisbursedPieComponent } from './dashboard/amount-disbursed-pie/amount-disbursed-pie.component';
import { ClientTrendsBarComponent } from './dashboard/client-trends-bar/client-trends-bar.component';
/**
* Home Component
@@ -17,11 +22,18 @@ import { DashboardComponent } from './dashboard/dashboard.component';
@NgModule({
imports: [
SharedModule,
PipesModule,
HomeRoutingModule
],
declarations: [
HomeComponent,
DashboardComponent
DashboardComponent,
AmountCollectedPieComponent,
AmountDisbursedPieComponent,
ClientTrendsBarComponent
],
providers: [
DatePipe
]
})
export class HomeModule { }
+12
View File
@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { HomeService } from './home.service';
describe('HomeService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: HomeService = TestBed.get(HomeService);
expect(service).toBeTruthy();
});
});
+109
View File
@@ -0,0 +1,109 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs';
/**
* Home Service
*/
@Injectable({
providedIn: 'root'
})
export class HomeService {
/**
* @param {HttpClient} http Http Client to send requests.
*/
constructor(private http: HttpClient) { }
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getCollectedAmount(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/Demand_Vs_Collection', { params: httpParams });
}
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getDisbursedAmount(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/Disbursal_Vs_Awaitingdisbursal', { params: httpParams });
}
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getClientTrendsByDay(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/ClientTrendsByDay', { params: httpParams });
}
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getClientTrendsByWeek(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/ClientTrendsByWeek', { params: httpParams });
}
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getClientTrendsByMonth(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/ClientTrendsByMonth', { params: httpParams });
}
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getLoanTrendsByDay(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/LoanTrendsByDay', { params: httpParams });
}
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getLoanTrendsByWeek(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/LoanTrendsByWeek', { params: httpParams });
}
/**
* @param {number} officeId Office Id.
* @returns {Observable<any>}
*/
getLoanTrendsByMonth(officeId: number): Observable<any> {
const httpParams = new HttpParams()
.set('R_officeId', officeId.toString())
.set('genericResultSet', 'false');
return this.http.get('/runreports/LoanTrendsByMonth', { params: httpParams });
}
}
+4 -3
View File
@@ -5,13 +5,14 @@ import { AccountsFilterPipe } from './accounts-filter.pipe';
import { ChargesFilterPipe } from './charges-filter.pipe';
import { ChargesPenaltyFilterPipe } from './charges-penalty-filter.pipe';
import { FindPipe } from './find.pipe';
import { UrlToStringPipe } from './url-to-string.pipe';
@NgModule({
imports: [
CommonModule
],
declarations: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe],
providers: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe],
exports: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe]
declarations: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe, UrlToStringPipe],
providers: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe, UrlToStringPipe],
exports: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe, UrlToStringPipe]
})
export class PipesModule { }
+8
View File
@@ -0,0 +1,8 @@
import { UrlToStringPipe } from './url-to-string.pipe';
describe('UrlToStringPipe', () => {
it('create an instance', () => {
const pipe = new UrlToStringPipe();
expect(pipe).toBeTruthy();
});
});
+28
View File
@@ -0,0 +1,28 @@
/** Angular Imports */
import { Pipe, PipeTransform } from '@angular/core';
/**
* URL to String pipe.
* Transform eg: `self-service/users` :: `Self Service | Users`
*/
@Pipe({
name: 'urlToString'
})
export class UrlToStringPipe implements PipeTransform {
transform(url: string): any {
const urlSubstrings: string[] = url.slice(1).split('/');
const stringRepresentation =
urlSubstrings
.map((path: string) => {
return path.split('-')
.map((str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
})
.join(' ');
})
.join(' | ');
return stringRepresentation;
}
}
+12 -2
View File
@@ -83,7 +83,12 @@ import {
faTimes,
faStickyNote,
faStop,
faBan
faBan,
faBolt,
faLaptop,
faDonate,
faMoneyBill,
faChartLine
} from '@fortawesome/free-solid-svg-icons';
/** Add icons to the library for convenient access in other components. */
@@ -166,7 +171,12 @@ library.add(
faTimes,
faStickyNote,
faStop,
faBan
faBan,
faBolt,
faLaptop,
faDonate,
faMoneyBill,
faChartLine
);
/**
+13
View File
@@ -99,6 +99,19 @@ export class WebAppComponent implements OnInit {
}
});
// Stores top 100 user activites as local storage object.
let activities: string[] = [];
if (localStorage.getItem('mifosXLocation')) {
const activitiesArray: string[] = JSON.parse(localStorage.getItem('mifosXLocation'));
const length = activitiesArray.length;
activities = length > 100 ? activitiesArray.slice(length - 100) : activitiesArray;
}
// Store route URLs array in local storage on navigation end.
onNavigationEnd.subscribe(() => {
activities.push(this.router.url);
localStorage.setItem('mifosXLocation', JSON.stringify(activities));
});
// Setup theme
const theme = this.themeStorageService.getTheme();
if (theme) {
+2
View File
@@ -3,6 +3,7 @@
@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';
@import '../../app/home/dashboard/dashboard.component-theme.scss';
// Custom material palette
$mat-denim: (
@@ -51,3 +52,4 @@ $mifosx-app-theme: mat-light-theme($mifosx-app-primary, $mifosx-app-accent, $mif
@include clients-view-component-theme($mifosx-app-theme);
@include groups-view-component-theme($mifosx-app-theme);
@include centers-view-component-theme($mifosx-app-theme);
@include dashboard-component-theme($mifosx-app-theme);
@@ -3,6 +3,7 @@
@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';
@import '../../app/home/dashboard/dashboard.component-theme.scss';
// Custom material palette
$mat-picton-blue: (
@@ -51,3 +52,4 @@ $mifosx-app-theme: mat-light-theme($mifosx-app-primary, $mifosx-app-accent, $mif
@include clients-view-component-theme($mifosx-app-theme);
@include groups-view-component-theme($mifosx-app-theme);
@include centers-view-component-theme($mifosx-app-theme);
@include dashboard-component-theme($mifosx-app-theme);