@@ -0,0 +1,30 @@
|
|||||||
|
<mat-card class="card">
|
||||||
|
|
||||||
|
<mat-card-header class="header">
|
||||||
|
<h3 fxFlex="100%">
|
||||||
|
<fa-icon icon="donate"></fa-icon>
|
||||||
|
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>
|
||||||
|
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>
|
||||||
|
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">
|
<div class="container">
|
||||||
|
|
||||||
<p>
|
<mat-card class="card" fxLayout="column" fxLayoutGap="40px">
|
||||||
TODO: dashboard component.
|
|
||||||
</p>
|
<div fxLayout="row" fxLayoutGap="40px">
|
||||||
|
|
||||||
|
<mat-card class="recent-activities">
|
||||||
|
|
||||||
|
<mat-card-header class="header">
|
||||||
|
<h3><fa-icon icon="bolt"></fa-icon>
|
||||||
|
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>
|
||||||
|
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>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.card {
|
||||||
|
padding: 2%;
|
||||||
|
.recent-activities, .frequent-activities {
|
||||||
|
width: 20rem;
|
||||||
|
padding: 0;
|
||||||
|
.list {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,10 +11,60 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
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() {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import { extract } from '../core/i18n/i18n.service';
|
|||||||
import { HomeComponent } from './home.component';
|
import { HomeComponent } from './home.component';
|
||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
|
|
||||||
|
/** Custom Resolvers */
|
||||||
|
import { OfficesResolver } from '../accounting/common-resolvers/offices.resolver';
|
||||||
|
|
||||||
/** Home and Dashboard Routes */
|
/** Home and Dashboard Routes */
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
Route.withShell([
|
Route.withShell([
|
||||||
@@ -28,7 +31,10 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
component: DashboardComponent,
|
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({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
providers: []
|
providers: [OfficesResolver]
|
||||||
})
|
})
|
||||||
export class HomeRoutingModule { }
|
export class HomeRoutingModule { }
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
/** Angular Imports */
|
/** Angular Imports */
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { DatePipe } from '@angular/common';
|
||||||
|
|
||||||
/** Custom Modules */
|
/** Custom Modules */
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { HomeRoutingModule } from './home-routing.module';
|
import { HomeRoutingModule } from './home-routing.module';
|
||||||
|
import { PipesModule } from '../pipes/pipes.module';
|
||||||
|
|
||||||
/** Custom Components */
|
/** Custom Components */
|
||||||
import { HomeComponent } from './home.component';
|
import { HomeComponent } from './home.component';
|
||||||
import { DashboardComponent } from './dashboard/dashboard.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
|
* Home Component
|
||||||
@@ -17,11 +22,18 @@ import { DashboardComponent } from './dashboard/dashboard.component';
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
PipesModule,
|
||||||
HomeRoutingModule
|
HomeRoutingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
HomeComponent,
|
HomeComponent,
|
||||||
DashboardComponent
|
DashboardComponent,
|
||||||
|
AmountCollectedPieComponent,
|
||||||
|
AmountDisbursedPieComponent,
|
||||||
|
ClientTrendsBarComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
DatePipe
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class HomeModule { }
|
export class HomeModule { }
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,13 +5,14 @@ import { AccountsFilterPipe } from './accounts-filter.pipe';
|
|||||||
import { ChargesFilterPipe } from './charges-filter.pipe';
|
import { ChargesFilterPipe } from './charges-filter.pipe';
|
||||||
import { ChargesPenaltyFilterPipe } from './charges-penalty-filter.pipe';
|
import { ChargesPenaltyFilterPipe } from './charges-penalty-filter.pipe';
|
||||||
import { FindPipe } from './find.pipe';
|
import { FindPipe } from './find.pipe';
|
||||||
|
import { UrlToStringPipe } from './url-to-string.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule
|
CommonModule
|
||||||
],
|
],
|
||||||
declarations: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe],
|
declarations: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe, UrlToStringPipe],
|
||||||
providers: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe],
|
providers: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe, UrlToStringPipe],
|
||||||
exports: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe]
|
exports: [StatusLookupPipe, AccountsFilterPipe, ChargesFilterPipe, ChargesPenaltyFilterPipe, FindPipe, UrlToStringPipe]
|
||||||
})
|
})
|
||||||
export class PipesModule { }
|
export class PipesModule { }
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { UrlToStringPipe } from './url-to-string.pipe';
|
||||||
|
|
||||||
|
describe('UrlToStringPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new UrlToStringPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -83,7 +83,12 @@ import {
|
|||||||
faTimes,
|
faTimes,
|
||||||
faStickyNote,
|
faStickyNote,
|
||||||
faStop,
|
faStop,
|
||||||
faBan
|
faBan,
|
||||||
|
faBolt,
|
||||||
|
faLaptop,
|
||||||
|
faDonate,
|
||||||
|
faMoneyBill,
|
||||||
|
faChartLine
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
/** Add icons to the library for convenient access in other components. */
|
/** Add icons to the library for convenient access in other components. */
|
||||||
@@ -166,7 +171,12 @@ library.add(
|
|||||||
faTimes,
|
faTimes,
|
||||||
faStickyNote,
|
faStickyNote,
|
||||||
faStop,
|
faStop,
|
||||||
faBan
|
faBan,
|
||||||
|
faBolt,
|
||||||
|
faLaptop,
|
||||||
|
faDonate,
|
||||||
|
faMoneyBill,
|
||||||
|
faChartLine
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
// Setup theme
|
||||||
const theme = this.themeStorageService.getTheme();
|
const theme = this.themeStorageService.getTheme();
|
||||||
if (theme) {
|
if (theme) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@import '../../app/clients/clients-view/clients-view.component-theme.scss';
|
@import '../../app/clients/clients-view/clients-view.component-theme.scss';
|
||||||
@import '../../app/groups/groups-view/groups-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/centers/centers-view/centers-view.component-theme.scss';
|
||||||
|
@import '../../app/home/dashboard/dashboard.component-theme.scss';
|
||||||
|
|
||||||
// Custom material palette
|
// Custom material palette
|
||||||
$mat-denim: (
|
$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 clients-view-component-theme($mifosx-app-theme);
|
||||||
@include groups-view-component-theme($mifosx-app-theme);
|
@include groups-view-component-theme($mifosx-app-theme);
|
||||||
@include centers-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/clients/clients-view/clients-view.component-theme.scss';
|
||||||
@import '../../app/groups/groups-view/groups-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/centers/centers-view/centers-view.component-theme.scss';
|
||||||
|
@import '../../app/home/dashboard/dashboard.component-theme.scss';
|
||||||
|
|
||||||
// Custom material palette
|
// Custom material palette
|
||||||
$mat-picton-blue: (
|
$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 clients-view-component-theme($mifosx-app-theme);
|
||||||
@include groups-view-component-theme($mifosx-app-theme);
|
@include groups-view-component-theme($mifosx-app-theme);
|
||||||
@include centers-view-component-theme($mifosx-app-theme);
|
@include centers-view-component-theme($mifosx-app-theme);
|
||||||
|
@include dashboard-component-theme($mifosx-app-theme);
|
||||||
|
|||||||
Reference in New Issue
Block a user