feat: add create report page (#592)

Fixes: #233
This commit is contained in:
Karan Takalkar
2020-05-30 21:55:12 +05:30
committed by GitHub
parent 8c9bb25fd8
commit ca9404213c
25 changed files with 983 additions and 3 deletions
@@ -0,0 +1,16 @@
/** Chart Data Model */
export class ChartData {
keysLabel: string;
valuesLabel: string;
keys: string[];
values: number[];
constructor(response: any) {
this.keysLabel = response.columnHeaders[0].columnName;
this.valuesLabel = response.columnHeaders[1].columnName;
this.keys = response.data.map((object: any) => object.row[0]);
this.values = response.data.map((object: any) => object.row[1]);
}
}
@@ -0,0 +1,31 @@
/** Report Parameter Model */
export class ReportParameter {
name: string;
variable: string;
label: string;
displayType: string;
formatType: string;
defaultVal: any;
selectOne: any;
selectAll: any;
parentParameterName: string;
inputName: string;
selectOptions: any[] = [];
childParameter: any;
pentahoName: any;
constructor(options: any[]) {
this.name = options[0];
this.variable = options[1];
this.label = options[2];
this.displayType = options[3];
this.formatType = options[4];
this.defaultVal = options[5];
this.selectOne = options[6];
this.selectAll = options[7];
this.parentParameterName = options[8];
this.inputName = `R_${options[1]}`;
}
}
@@ -0,0 +1,11 @@
/** Select Option model */
export class SelectOption {
id: number;
name: string;
constructor(options: any[]) {
this.id = options[0],
this.name = options[1];
}
}
+27 -1
View File
@@ -10,9 +10,11 @@ import { extract } from '../core/i18n/i18n.service';
/** Custom Components */
import { ReportsComponent } from './reports.component';
import { RunReportComponent } from './run-report/run-report.component';
/** Custom Resolvers */
import { ReportsResolver } from './reports.resolver';
import { RunReportResolver } from './run-report/run-report.resolver';
/** Reports Routes */
const routes: Routes = [
@@ -34,6 +36,29 @@ const routes: Routes = [
component: ReportsComponent,
}
]
},
{
path: 'run-report',
data: { title: extract('Reports'), breadcrumb: 'Reports' },
children: [
{
path: '',
redirectTo: '/reports', pathMatch: 'full'
},
{
path: ':report-name/:report-type/:report-id',
data: { title: extract('Run Report'), routeParamBreadcrumb: 'report-name' },
children: [
{
path: '',
component: RunReportComponent,
resolve: {
params: RunReportResolver
}
}
]
}
]
}
])
];
@@ -47,7 +72,8 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
ReportsResolver
ReportsResolver,
RunReportResolver
]
})
export class ReportsRoutingModule { }
+1 -1
View File
@@ -28,7 +28,7 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="select-row"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="select-row" [routerLink]="['/run-report', row.reportName, row.reportType, row.id]"></tr>
</table>
+13 -1
View File
@@ -1,5 +1,6 @@
/** Angular Imports */
import { NgModule } from '@angular/core';
import { DecimalPipe } from '@angular/common';
/** Custom Modules */
import { SharedModule } from '../shared/shared.module';
@@ -7,6 +8,10 @@ import { ReportsRoutingModule } from 'app/reports/reports-routing.module';
/** Custom Components */
import { ReportsComponent } from './reports.component';
import { RunReportComponent } from './run-report/run-report.component';
import { TableAndSmsComponent } from './run-report/table-and-sms/table-and-sms.component';
import { ChartComponent } from './run-report/chart/chart.component';
import { PentahoComponent } from './run-report/pentaho/pentaho.component';
/**
* Reports Module
@@ -18,6 +23,13 @@ import { ReportsComponent } from './reports.component';
SharedModule,
ReportsRoutingModule
],
declarations: [ReportsComponent],
declarations: [
ReportsComponent,
RunReportComponent,
TableAndSmsComponent,
ChartComponent,
PentahoComponent
],
providers: [ DecimalPipe ]
})
export class ReportsModule { }
+84
View File
@@ -4,6 +4,12 @@ import { HttpClient, HttpParams } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
/** Custom Models */
import { ReportParameter } from './common-models/report-parameter.model';
import { SelectOption } from './common-models/select-option.model';
import { ChartData } from './common-models/chart-data.model';
/**
* Reports service.
@@ -25,4 +31,82 @@ export class ReportsService {
return this.http.get('/reports');
}
/**
* @param {string} reportName Report name for which parameters are needed.
* @returns {Observable<ReportParameter[]>}
*/
getReportParams(reportName: string): Observable<ReportParameter[]> {
const httpParams = new HttpParams()
.set('R_reportListing', `'${reportName}'`)
.set('parameterType', 'true');
return this.http.get(`/runreports/FullParameterList`, {params: httpParams})
.pipe(map((response: any) => response.data.map((entry: any) => new ReportParameter(entry.row)) ));
}
/**
* @param {string} inputString URL substring containing object details.
* @returns {Observable<SelectOption[]>}
*/
getSelectOptions(inputString: string): Observable<SelectOption[]> {
const httpParams = new HttpParams().set('parameterType', 'true');
return this.http.get(`/runreports/${inputString}`, {params: httpParams})
.pipe(map((response: any) => response.data.map((entry: any) => new SelectOption(entry.row)) ));
}
/**
* @param {number} reportId Report id for which pentaho parameters are needed.
* @returns {Observable<any>}
*/
getPentahoParams(reportId: number): Observable<any> {
const httpParams = new HttpParams().set('fields', 'reportParameters');
return this.http.get(`/reports/${reportId}`, {params: httpParams})
.pipe(map((response: any) => response.reportParameters));
}
/**
* Run Report Data for Table and SMS.
* @param {any} report report object containing 'id', 'name' & 'type' attributes.
* @param {object} formData Form Data.
* @returns {Observable<any>}
*/
getRunReportData(report: any, formData: object): Observable<any> {
let httpParams = new HttpParams();
for (const [key, value] of Object.entries(formData)) {
httpParams = httpParams.set(key, value);
}
return this.http.get(`/runreports/${report.name}`, {params: httpParams});
}
/**
* Run Report Data for Charts.
* @param {any} report report object containing 'id', 'name' & 'type' attributes.
* @param {object} formData Form Data.
* @returns {Observable<ChartData>}
*/
getChartRunReportData(report: any, formData: object): Observable<ChartData> {
let httpParams = new HttpParams();
for (const [key, value] of Object.entries(formData)) {
httpParams = httpParams.set(key, value);
}
return this.http.get(`/runreports/${report.name}`, {params: httpParams})
.pipe(map((response: any) => new ChartData(response)));
}
/**
* Run Report Data for Pentaho.
* @param {any} report report object containing 'id', 'name' & 'type' attributes.
* @param {object} formData Form Data.
* @returns {Observable<any>}
*/
getPentahoRunReportData(report: any, formData: object, tenantIdentifier: string, locale: string, dateFormat: string): Observable<any> {
let httpParams = new HttpParams()
.set('tenantIdentifier', tenantIdentifier)
.set('locale', locale)
.set('dateFormat', dateFormat);
for (const [key, value] of Object.entries(formData)) {
httpParams = httpParams.set(key, value);
}
return this.http.get(`/runreports/${report.name}`, {responseType: 'arraybuffer', observe: 'response', params: httpParams});
}
}
@@ -0,0 +1,10 @@
<div class="m-b-20" fxLayoutAlign="end">
<mat-button-toggle-group aria-label="Select Chart Type">
<mat-button-toggle value="Bar" (click) = "setBarChart(inputData)">Bar Chart</mat-button-toggle>
<mat-button-toggle value="Pie" (click) = "setPieChart(inputData)">Pie Chart</mat-button-toggle>
</mat-button-toggle-group>
</div>
<div [ngStyle]="{'display': (hideOutput) ? 'none' : 'block'}">
<canvas id="output"></canvas>
</div>
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChartComponent } from './chart.component';
describe('ChartComponent', () => {
let component: ChartComponent;
let fixture: ComponentFixture<ChartComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ChartComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,139 @@
/** Angular Imports */
import { Component, OnChanges, Input } from '@angular/core';
/** Custom Services */
import { ReportsService } from '../../reports.service';
/** Custom Models */
import { ChartData } from '../../common-models/chart-data.model';
/** Charting Imports */
import Chart from 'chart.js';
/**
* Chart Component
*/
@Component({
selector: 'mifosx-chart',
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.scss' ]
})
export class ChartComponent implements OnChanges {
/** Run Report Data */
@Input() dataObject: any;
/** chart data object */
chart: any;
/** substitute for resolver */
hideOutput = true;
/** Data object for witching charts in view. */
inputData: ChartData;
/**
* @param {ReportsService} reportsService Reports Service
*/
constructor(private reportsService: ReportsService) { }
/**
* Fetches run report data post changes in run report form.
*/
ngOnChanges() {
this.getRunReportData();
}
getRunReportData() {
this.reportsService.getChartRunReportData(this.dataObject.reportData, this.dataObject.formData)
.subscribe((response: ChartData) => {
this.inputData = response;
this.setPieChart(this.inputData);
this.hideOutput = false;
});
}
/**
* Creates instance of chart.js pie chart.
* Refer: https://www.chartjs.org/docs/latest/charts/doughnut.html for configuration details.
*/
setPieChart(inputData: ChartData) {
if (this.chart) {
this.chart.destroy();
}
this.chart = new Chart('output', {
type: 'pie',
data: {
labels: inputData.keys,
datasets: [{
label: inputData.valuesLabel,
data: inputData.values,
backgroundColor: this.randomColorArray(inputData.values.length)
}]
},
options: {
title: {
display: true,
text: inputData.keysLabel
}
}
});
}
/**
* Creates instance of chart.js bar chart.
* Refer: https://www.chartjs.org/docs/latest/charts/bar.html for configuration details.
*/
setBarChart(inputData: ChartData) {
if (this.chart) {
this.chart.destroy();
}
this.chart = new Chart('output', {
type: 'bar',
data: {
labels: inputData.keys,
datasets: [{
label: inputData.valuesLabel,
data: inputData.values,
backgroundColor: this.randomColorArray(inputData.values.length)
}]
},
options: {
legend: { display: false },
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: inputData.keysLabel
},
ticks: {
beginAtZero: true
}
}]
}
}
});
}
/**
* Generates bar/pie-slice colors array for dynamic charts.
* @param {number} length Length of dataset array.
*/
randomColorArray(length: number) {
const colorArray: any[] = [];
while (length--) {
const color = this.randomColor();
colorArray.push(color);
}
return colorArray;
}
/**
* Returns a random rgb color.
*/
randomColor() {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r},${g},${b},0.6)`;
}
}
@@ -0,0 +1 @@
<iframe *ngIf = "!hideOutput" [src]="pentahoUrl" frameborder="0" width=100% height="750px;"></iframe>
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PentahoComponent } from './pentaho.component';
describe('PentahoComponent', () => {
let component: PentahoComponent;
let fixture: ComponentFixture<PentahoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PentahoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PentahoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,52 @@
/** Angular Imports */
import { Component, OnChanges, Input } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
/** Custom Services */
import { ReportsService } from '../../reports.service';
/**
* Pentaho Component
*/
@Component({
selector: 'mifosx-pentaho',
templateUrl: './pentaho.component.html',
styleUrls: ['./pentaho.component.scss']
})
export class PentahoComponent implements OnChanges {
/** Run Report Data */
@Input() dataObject: any;
/** substitute for resolver */
hideOutput = true;
/** trusted resource url for pentaho output */
pentahoUrl: any;
/**
* @param {DomSanitizer} sanitizer DOM Sanitizer
* @param {ReportsService} reportsService Reports Service
*/
constructor(private sanitizer: DomSanitizer,
private reportsService: ReportsService) { }
/**
* Fetches run report data post changes in run report form.
*/
ngOnChanges() {
this.hideOutput = true;
this.getRunReportData();
}
getRunReportData() {
this.reportsService.getPentahoRunReportData(this.dataObject.reportData, this.dataObject.formData, 'default', 'en', 'dd MMMM yyyy')
.subscribe( (res: any) => {
const contentType = res.headers.get('Content-Type');
const file = new Blob([res.body], {type: contentType});
const filecontent = URL.createObjectURL(file);
this.pentahoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(filecontent);
this.hideOutput = false;
});
}
}
@@ -0,0 +1,113 @@
<div class="container input">
<mat-card *ngIf="!isCollapsed">
<form [formGroup]="ReportForm" (ngSubmit)="run()">
<mat-card-content>
<div fxLayout="column" *ngFor="let param of paramData">
<div [ngSwitch]="param.displayType">
<div *ngIf="ReportForm.contains(param.name)">
<mat-form-field fxFlex="100%" *ngSwitchCase="'text'">
<mat-label>{{param.label}}</mat-label>
<input matInput required [formControlName]="param.name">
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
<div *ngIf="ReportForm.contains(param.name)">
<mat-form-field fxFlex="100%" *ngSwitchCase="'date'">
<mat-label>{{param.label}}</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="runReportDatePicker" required [formControlName]="param.name">
<mat-datepicker-toggle matSuffix [for]="runReportDatePicker"></mat-datepicker-toggle>
<mat-datepicker #runReportDatePicker></mat-datepicker>
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
<div *ngIf="ReportForm.contains(param.name) ">
<mat-form-field fxFlex="100%" *ngSwitchCase="'select'" >
<mat-label>{{param.label}}</mat-label>
<mat-select required [formControlName]="param.name">
<mat-option *ngFor="let option of param.selectOptions" [value]="option" >
{{option.name}}
</mat-option>
</mat-select>
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
</div>
</div>
<div>
<mat-form-field fxFlex="100%">
<mat-label>Decimal Places</mat-label>
<mat-select [formControl]="decimalChoice">
<mat-option value="0">0</mat-option>
<mat-option value="1">1</mat-option>
<mat-option value="2">2</mat-option>
<mat-option value="3">3</mat-option>
<mat-option value="4">4</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="report.type === 'Pentaho' && ReportForm.contains('outputType')">
<mat-form-field fxFlex="100%">
<mat-label>Output Type</mat-label>
<mat-select formControlName="outputType" value="HTML">
<mat-option value="HTML">Normal format</mat-option>
<mat-option value="XLS">Excel format</mat-option>
<mat-option value="XLSX">Excel 2007 format</mat-option>
<mat-option value="CSV">CSV format</mat-option>
<mat-option value="PDF">PDF format</mat-option>
</mat-select>
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/reports']">Cancel</button>
<button mat-raised-button color="primary" [disabled]="!ReportForm.valid">
<fa-icon icon="cogs"></fa-icon>
&nbsp; Run Report
</button>
</mat-card-actions>
</form>
</mat-card>
</div>
<div class="container output" *ngIf="isCollapsed">
<mat-card>
<div class="m-b-20">
<button mat-raised-button color="primary" (click)="isCollapsed = false">
Parameters
</button>
</div>
<div>
<mifosx-table-and-sms *ngIf="!hideTable"[dataObject]="dataObject"></mifosx-table-and-sms>
<mifosx-chart *ngIf="!hideChart" [dataObject]="dataObject"></mifosx-chart>
<mifosx-pentaho *ngIf="!hidePentaho" [dataObject]="dataObject"></mifosx-pentaho>
</div>
</mat-card>
</div>
@@ -0,0 +1,3 @@
.input {
max-width: 37rem;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RunReportComponent } from './run-report.component';
describe('RunReportComponent', () => {
let component: RunReportComponent;
let fixture: ComponentFixture<RunReportComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RunReportComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RunReportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,201 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { DatePipe } from '@angular/common';
/** Custom Services */
import { ReportsService } from '../reports.service';
/** Custom Models */
import { ReportParameter } from '../common-models/report-parameter.model';
import { SelectOption } from '../common-models/select-option.model';
/**
* Run report component.
*/
@Component({
selector: 'mifosx-run-report',
templateUrl: './run-report.component.html',
styleUrls: ['./run-report.component.scss']
})
export class RunReportComponent implements OnInit {
/** Minimum date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum date allowed. */
maxDate = new Date();
/** Contains report specifications i.e: name, type and id */
report: any = {};
/** Formatted data post labeling of report parameters fetched from API */
paramData: any = [];
/** Array of all parent parameters */
parentParameters: any[] = [];
/** Parameter data to configure pentaho output */
pentahoReportParameters: any[] = [];
/** Data to be passed on to component selectors */
dataObject: any;
/** Initializes new form group 'ReportForm */
ReportForm = new FormGroup({});
/** Static Form control for decimal places in output */
decimalChoice = new FormControl();
/** Toggles Report form */
isCollapsed = false;
/** Toggles Table output. */
hideTable = true;
/** Toggles Chart output */
hideChart = true;
/** Toggles Pentaho output */
hidePentaho = true;
/**
* Fetches report specifications from route params and retrieves report parameters data from `resolve`.
* @param {ActivatedRoute} route ActivatedRoute.
* @param {DomSanitizer} sanitizer DomSanitizer.
* @param {ReportsService} reportsService ReportsService
* @param {DatePipe} datePipe Date Pipe
*/
constructor(private route: ActivatedRoute,
private reportsService: ReportsService,
private datePipe: DatePipe) {
this.report.name = this.route.snapshot.params['report-name'];
this.report.type = this.route.snapshot.params['report-type'];
this.report.id = this.route.snapshot.params['report-id'];
this.route.data.subscribe((data: any) => {
this.paramData = data.params;
});
}
/**
* Creates and sets the run report form.
*/
ngOnInit() {
this.createRunReportForm();
}
/**
* Establishes form controls for Report Parameter's name attribute,
* Fetches dropdown options and builds child dependencies.
*/
createRunReportForm() {
this.paramData.forEach(
(param: any) => {
if (!param.parentParameterName) {
this.ReportForm.addControl(param.name, new FormControl('', Validators.required));
if (param.displayType === 'select') {
this.fetchSelectOptions(param, param.name);
}
} else {
const parent: ReportParameter = this.paramData
.find((entry: any) => entry.name === param.parentParameterName);
parent.childParameter = param;
this.parentParameters.push(parent);
}
});
if (this.report.type === 'Pentaho') {
this.ReportForm.addControl('outputType', new FormControl(''));
this.mapPentahoParams();
}
this.decimalChoice.patchValue('0');
this.setChildControls();
}
/**
* Fetches Select Dropdown options for param type "Select".
* @param {ReportParameter} param Parameter for which dropdown options are required.
* @param {string} inputstring url substring for API call.
*/
fetchSelectOptions(param: ReportParameter, inputstring: string) {
this.reportsService.getSelectOptions(inputstring).subscribe((options: SelectOption[]) => {
param.selectOptions = options;
if (param.selectAll === 'Y') {
param.selectOptions.push({id: '-1', name: 'All'});
}
});
}
/**
* Maps pentaho specific names to form-control names.
*/
mapPentahoParams() {
this.reportsService.getPentahoParams(this.report.id).subscribe((data: any) => {
data.forEach((entry: any) => {
const param: ReportParameter = this.paramData
.find((_entry: any) => _entry.name === entry.parameterName);
param.pentahoName = `R_${entry.reportParameterName}`;
});
});
}
/**
* Subscribes to changes in parent parameters value, builds child parameter vis-a-vis parent's value.
*/
setChildControls() {
this.parentParameters.forEach((param: ReportParameter) => {
this.ReportForm.get(param.name).valueChanges.subscribe((option: any) => {
this.ReportForm.addControl(param.childParameter.name, new FormControl('', Validators.required));
if (param.childParameter.displayType === 'select') {
const inputstring = `${param.childParameter.name}?${param.inputName}=${option.id}`;
this.fetchSelectOptions(param.childParameter, inputstring);
}
});
});
}
/**
* Formats user response and readies it for utilization by run report function.
* @param {any} response Object containing formcontrol values.
*/
formatUserResponse(response: any) {
const formattedResponse: any = {};
let newKey: string;
for (const [key, value] of Object.entries(response)) {
if (key === 'outputType') {
formattedResponse['output-type'] = value;
continue;
}
const param: ReportParameter = this.paramData
.find((_entry: any) => _entry.name === key);
newKey = this.report.type === 'Pentaho' ? param.pentahoName : param.inputName;
switch (param.displayType) {
case 'text':
formattedResponse[newKey] = value;
break;
case 'select':
formattedResponse[newKey] = value['id'];
break;
case 'date':
const dateFormat = 'yyyy-MM-dd';
formattedResponse[newKey] = this.datePipe.transform(value, dateFormat);
break;
}
}
return formattedResponse;
}
/**
* Core run report functionality.
*/
run() {
this.isCollapsed = true;
const userResponse = this.formatUserResponse(this.ReportForm.value);
this.dataObject = {
formData: userResponse,
reportData: this.report,
decimalChoice: this.decimalChoice.value
};
switch (this.report.type) {
case 'SMS':
case 'Table':
this.hideTable = false;
break;
case 'Chart':
this.hideChart = false;
break;
case 'Pentaho':
this.hidePentaho = false;
break;
}
}
}
@@ -0,0 +1,30 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { ReportsService } from '../reports.service';
/**
* Reports data resolver.
*/
@Injectable()
export class RunReportResolver implements Resolve<Object> {
/**
* @param {ReportsService} reportsService Reports service.
*/
constructor(private reportsService: ReportsService) {}
/**
* Returns the reports data.
* @returns {Observable<any>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<any> {
return this.reportsService.getReportParams(route.params['report-name']);
}
}
@@ -0,0 +1,26 @@
<div *ngIf = "!hideOutput">
<div class="m-b-10" *ngIf = "dataObject.reportData.type === 'Table'" fxLayoutAlign="end">
<button mat-raised-button color="primary" (click)="downloadCSV()" >
Export CSV
</button>
</div>
<div class="table-output">
<table mat-table [dataSource]="dataSource">
<div *ngFor= "let column of displayedColumns index as i">
<ng-container [matColumnDef]="column">
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
<td mat-cell *matCellDef="let param" >
<span *ngIf="isDecimal(i)">{{toDecimal(param.row[i])}}</span>
<span *ngIf="!isDecimal(i)">{{param.row[i]}}</span>
</td>
</ng-container>
</div>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="select-row" ></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
@@ -0,0 +1,13 @@
.table-output {
overflow: auto;
}
.select-row:hover {
cursor: pointer;
}
.mat-header-cell, .mat-cell {
min-width: 150px;
margin-left: 5px;
margin-right: 5px;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TableAndSmsComponent } from './table-and-sms.component';
describe('TableAndSmsComponent', () => {
let component: TableAndSmsComponent;
let fixture: ComponentFixture<TableAndSmsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TableAndSmsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TableAndSmsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,110 @@
/** Angular Imports */
import { Component, Input, ViewChild, OnChanges } from '@angular/core';
import { MatPaginator, MatTableDataSource} from '@angular/material';
import { DecimalPipe } from '@angular/common';
/** Custom Servies */
import { ReportsService } from '../../reports.service';
/**
* Table and SMS Component
*/
@Component({
selector: 'mifosx-table-and-sms',
templateUrl: './table-and-sms.component.html',
styleUrls: ['./table-and-sms.component.scss']
})
export class TableAndSmsComponent implements OnChanges {
/** Run Report Data */
@Input() dataObject: any;
/** Columns to be displayed in mat-table */
displayedColumns: string[] = [];
/** Data source for run-report table. */
dataSource = new MatTableDataSource();
/** Maps column name to type */
columnTypes: any[] = [];
/** substitute for resolver */
hideOutput = true;
/** Data to be converted into CSV file */
csvData: any;
/** Paginator for run-report table. */
@ViewChild(MatPaginator) paginator: MatPaginator;
/**
* @param {ReportsService} reportsService Reports Service
* @param {DecimalPipe} decimalPipe Decimal Pipe
*/
constructor(private reportsService: ReportsService,
private decimalPipe: DecimalPipe ) { }
/**
* Fetches run report data post changes in run report form.
*/
ngOnChanges() {
this.hideOutput = true;
this.columnTypes = [];
this.displayedColumns = [];
this.getRunReportData();
}
getRunReportData() {
this.reportsService.getRunReportData(this.dataObject.reportData, this.dataObject.formData)
.subscribe( (res: any) => {
this.csvData = res.data;
this.setOutputTable(res.data);
res.columnHeaders.forEach((header: any) => {
this.columnTypes.push(header.columnDisplayType);
this.displayedColumns.push(header.columnName);
});
this.hideOutput = false;
});
}
/**
* Sets up a dynamic Mat Table.
* @param {any} data Mat Table data
*/
setOutputTable(data: any) {
this.dataSource = new MatTableDataSource(data);
setTimeout(() => {
this.dataSource.paginator = this.paginator;
});
}
/**
* Generates the CSV file dynamically for run report data.
*/
downloadCSV() {
const headers = this.displayedColumns;
let csv = this.csvData.map((object: any) => object.row.join());
csv.unshift(`data:text/csv;charset=utf-8,${headers.join()}`);
csv = csv.join('\r\n');
const link = document.createElement('a');
link.setAttribute('href', encodeURI(csv));
link.setAttribute('download', `${this.dataObject.reportData.name}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
/**
* Returns number formatted as per user's decimal choice.
* @param {number} value Value to be formatted as per decimal choice.
*/
toDecimal(value: number) {
const decimalChoice = this.dataObject.decimalChoice;
return this.decimalPipe.transform(value, `1.${decimalChoice}-${decimalChoice}`);
}
/**
* Checks the weather Mat-Table column has decimal display type.
* @param {number} index Index of column.
*/
isDecimal(index: number) {
return this.columnTypes[index] === 'DECIMAL';
}
}
+2
View File
@@ -10,3 +10,5 @@ declare var module: NodeModule;
interface NodeModule {
id: string;
}
declare module 'chart.js';