sb-dashlets
Version:
A Simple extensible angular library to render JSON data into different chart formats
1,068 lines (1,041 loc) • 46.3 kB
JavaScript
import { InjectionToken, EventEmitter, ɵɵdefineInjectable, ɵɵinject, Injectable, Inject, ViewChild, Component, ViewContainerRef, Directive, TemplateRef, Input, ComponentFactoryResolver, Output, ContentChildren, ChangeDetectorRef, NgModule } from '@angular/core';
import { of, Subject, zip } from 'rxjs';
import { tap, takeUntil, debounceTime, distinctUntilChanged, map, startWith, pairwise } from 'rxjs/operators';
import * as jsonexport from 'jsonexport/dist';
import { __decorate, __awaiter, __rest, __param } from 'tslib';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { BaseChartDirective, ThemeService, ChartsModule } from 'ng2-charts';
import { groupBy, get, mapValues, sumBy, remove, toNumber, minBy, maxBy, omitBy, isEmpty, compact, sortBy, uniq, forEach, filter, every, some, trim, toLower } from 'lodash-es';
import { v4 } from 'uuid';
import { DataTableDirective, DataTablesModule } from 'angular-datatables';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import * as momentImported from 'moment';
import { CommonModule } from '@angular/common';
import { NgMultiSelectDropDownModule } from 'ng-multiselect-dropdown';
import { NgxDaterangepickerMd } from 'ngx-daterangepicker-material';
var ChartType;
(function (ChartType) {
ChartType["BAR"] = "bar";
ChartType["LINE"] = "line";
ChartType["PIE"] = "pie";
ChartType["DOUGHNUT"] = "doughnut";
ChartType["POLAR"] = "polar";
ChartType["RADAR"] = "radar";
ChartType["BUBBLE"] = "bubble";
ChartType["SCATTER"] = "scatter";
ChartType["AREA"] = "area";
ChartType["HORIZONTAL_BAR"] = "bar";
ChartType["VERTICAL_BAR"] = "verticalBar";
ChartType["BIG_NUMBER"] = "bigNumber";
})(ChartType || (ChartType = {}));
var IReportType;
(function (IReportType) {
IReportType["CHART"] = "chart";
IReportType["TABLE"] = "table";
})(IReportType || (IReportType = {}));
var ReportState;
(function (ReportState) {
ReportState["PENDING"] = "pending";
ReportState["DONE"] = "done";
})(ReportState || (ReportState = {}));
var TableType;
(function (TableType) {
TableType["TABLE"] = "table";
})(TableType || (TableType = {}));
const DASHLET_CONSTANTS = new InjectionToken('CONSTANTS', {
factory() {
return constants;
}
});
const constants = {
INVALID_INPUT: "invalid input",
METHOD_NOT_IMPLEMENTED: "Method not implemented",
CHART_NOT_INITIALIZED: "Chart is not initialized"
};
const jsonExport = jsonexport;
class BaseComponent {
constructor(dataService) {
this.dataService = dataService;
this.data = [];
this._isInitialized = false;
this.state = new EventEmitter();
this.events = new EventEmitter();
this.exportOptions = [];
}
fetchData(config) {
const { values = null, location: { url = null, options = {}, method = 'GET' } = {} } = config || {};
if (values)
return of(values);
if (!url)
throw new Error('invalid input');
this.state.emit(ReportState.PENDING);
return this.dataService.fetchData({ method, url, options }).pipe(tap(_ => this.state.emit(ReportState.DONE)));
}
getConfigValue(key) {
return this.config && this.config[key];
}
checkIfInitialized() {
if (!this._isInitialized) {
throw Error(constants.CHART_NOT_INITIALIZED);
}
}
_downloadFile(url, filename) {
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", filename);
link.click();
}
exportAsCsv(data) {
jsonExport(data || this.data, (error, csv) => {
if (!error && csv) {
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
var url = URL.createObjectURL(blob);
this._downloadFile(url, 'data.csv');
}
});
}
}
let DataService = class DataService {
constructor(httpClient) {
this.httpClient = httpClient;
this.cachedData = new Map();
}
fetchData(config) {
const stringifiedConfig = JSON.stringify(config);
if (this.cachedData.has(stringifiedConfig))
return this.cachedData.get(stringifiedConfig);
const { method, url, options } = config;
return this.httpClient.request(method, url, options).pipe(tap(response => {
this.cachedData.set(stringifiedConfig, response);
}));
}
};
DataService.ctorParameters = () => [
{ type: HttpClient }
];
DataService.ngInjectableDef = ɵɵdefineInjectable({ factory: function DataService_Factory() { return new DataService(ɵɵinject(HttpClient)); }, token: DataService, providedIn: "root" });
DataService = __decorate([
Injectable({
providedIn: 'root'
})
], DataService);
const DEFAULT_CONFIG = new InjectionToken('DEFAULT_CONFIG');
const CHART_DEFAULT_CONFIG = {
labels: [],
datasets: [],
legend: true,
options: {},
colors: []
};
const ɵ0 = CHART_DEFAULT_CONFIG;
/**
* @dynamic
*/
let ChartJsComponent = class ChartJsComponent extends BaseComponent {
constructor(dataService, defaultConfig, CONSTANTS) {
super(dataService);
this.dataService = dataService;
this.CONSTANTS = CONSTANTS;
this.reportType = IReportType.CHART;
this.inputParameters = {};
this.exportOptions = ['png', 'csv', 'jpg'];
this._defaultConfig = defaultConfig;
}
/**
* @description initializes the component with the passed config and data
* @param {InputParams} { config, type, data }
* @return {*} {Promise<any>}
* @memberof ChartJsComponent
*/
initialize({ config, type, data }) {
return __awaiter(this, void 0, void 0, function* () {
if (!(config && type && data))
throw new SyntaxError(this.CONSTANTS.INVALID_INPUT);
this.config = config = Object.assign({}, config, { type });
const fetchedJSON = this.data = yield this.fetchData(data).toPromise().catch(err => []);
this.builder(config, fetchedJSON);
this._isInitialized = true;
this.state.emit(ReportState.DONE);
});
}
/**
* @description It's a high order function responsible for getting labels and datasets, addition and removal of data.
* @private
* @param {string} labelExpr
* @param {IDataset[]} datasets
* @return {*}
* @memberof ChartJsComponent
*/
getLabelsAndDatasetsClosure(labelExpr, datasets) {
return (data) => {
const getDataGroupedByLabelExpr = data => groupBy(data, val => {
const value = get(val, labelExpr);
return value && typeof value === 'string' ? value.toLowerCase().trim() : '';
});
const getLabels = (data) => Object.keys(data);
const getDatasets = (data) => datasets.map(dataset => {
return Object.assign({}, dataset, (dataset.dataExpr && {
data: Object.values(mapValues(data, rows => sumBy(rows, row => +(row[dataset.dataExpr] || 0))))
}));
});
const findDataByLabelPredicate = (label) => (row) => row[labelExpr] === label;
return {
addData(newData) {
data = data.concat(newData);
return this.getData(data);
},
getData(overriddenData) {
data = overriddenData || data;
const groupedData = getDataGroupedByLabelExpr(data);
return {
labels: getLabels(groupedData),
datasets: getDatasets(groupedData)
};
},
removeData(label) {
remove(data, findDataByLabelPredicate(label));
return this.getData(data);
}
};
};
}
/**
* @description prepared the chart data using the configuration passed
* @param {Partial<IChartOptions>} config
* @param {*} data
* @memberof ChartJsComponent
*/
builder(config, data) {
let { labels = [], labelExpr = null, type = null, legend = true, colors = [], datasets = [], options = {} } = config, others = __rest(config, ["labels", "labelExpr", "type", "legend", "colors", "datasets", "options"]);
options = Object.assign({}, others, options);
if (labelExpr) {
this._labelsAndDatasetsClosure = this.getLabelsAndDatasetsClosure(labelExpr, datasets)(data);
const { getData } = this._labelsAndDatasetsClosure;
({ labels, datasets } = getData());
}
this.setChartData({ labels, datasets, options, type, legend, colors });
}
setChartData(config = {}) {
this.inputParameters = Object.assign({}, this._defaultConfig, this.inputParameters, config);
this.$context = { data: this.data, config: this.config, inputParameters: this.inputParameters, exportOptions: this.exportOptions };
}
reset() {
// throw new Error('Method not implemented.');
}
destroy() {
this.baseChartDirective.chart.destroy();
}
ngOnDestroy() {
this.destroy();
}
/**
* @description updates the type, data or Dashlet configuration
* @param {InputParams} input
* @memberof ChartJsComponent
*/
update(input) {
this.checkIfInitialized();
if (!input)
throw new Error(this.CONSTANTS.INVALID_INPUT);
const { type = null, config = {}, data = null } = input;
let labels, datasets;
if (data) {
const { labelExpr, datasets: datasetsConfig } = config;
if (labelExpr || datasets) {
this._labelsAndDatasetsClosure = this.getLabelsAndDatasetsClosure(labelExpr || this.getConfigValue(labelExpr), datasetsConfig || this.getConfigValue(datasets))(data);
}
({ labels, datasets } = this._labelsAndDatasetsClosure.getData(data));
}
this.setChartData(Object.assign({}, config, (type && { type }), (labels && datasets && { labels, datasets })));
this.baseChartDirective.update();
}
addData(data) {
this.checkIfInitialized();
if (!data)
throw new Error(this.CONSTANTS.INVALID_INPUT);
if (this._labelsAndDatasetsClosure) {
data = Array.isArray(data) ? data : [data];
const { labels, datasets } = this._labelsAndDatasetsClosure.addData(data);
this.setChartData({ labels, datasets });
}
}
refreshChart() {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
/**
* @description Removes data associated with a label
* @param {string} label
* @memberof ChartJsComponent
*/
removeData(label) {
this.checkIfInitialized();
const { labels, datasets } = this._labelsAndDatasetsClosure.removeData(label);
this.setChartData({ labels, datasets });
}
getTelemetry() {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
getCurrentSelection() {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
getDatasetAtIndex(index) {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
onChartClicked(event) {
this.events.emit({
type: 'CLICK',
event
});
}
onChartHovered(event) {
this.events.emit({
type: 'HOVER',
event
});
}
exportAsImage(format = 'jpg') {
const dataUrl = document.getElementById(this.id).toDataURL(`image/${format}`, 1);
const fileName = `image.${format}`;
this._downloadFile(dataUrl, fileName);
}
exportAs(format) {
if (!this.exportOptions.includes(format)) {
throw new Error('given type not supported');
}
switch (format) {
case 'csv': {
this.exportAsCsv();
break;
}
default: {
this.exportAsImage(format);
break;
}
}
}
};
ChartJsComponent.ctorParameters = () => [
{ type: DataService },
{ type: Object, decorators: [{ type: Inject, args: [DEFAULT_CONFIG,] }] },
{ type: undefined, decorators: [{ type: Inject, args: [DASHLET_CONSTANTS,] }] }
];
__decorate([
ViewChild(BaseChartDirective, { static: false })
], ChartJsComponent.prototype, "baseChartDirective", void 0);
ChartJsComponent = __decorate([
Component({
selector: 'sb-chart-js',
template: "<ng-template #defaultFilterTemplate let-context>\n <sb-dashlets-filters [data]=\"context?.data\" [config]=\"context?.config?.filters\"\n (filteredData)=\"update({data: $event})\">\n </sb-dashlets-filters>\n</ng-template>\n\n<ng-container *ngIf=\"$context?.config?.filters\" [ngTemplateOutlet]=\"templateRefs?.filter || defaultFilterTemplate\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>\n\n<ng-container *ngIf=\"templateRefs?.header && $context\" [ngTemplateOutlet]=\"templateRefs?.header\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>\n\n<div *ngIf=\"$context?.inputParameters as data\">\n <canvas [id]=\"id\" baseChart width=\"400\" height=\"400\" [datasets]=\"data?.datasets\" [labels]=\"data?.labels\"\n [options]=\"data?.options\" [colors]=\"data?.colors\" [legend]=\"data?.legend\" [chartType]=\"data?.type\"\n (chartClick)=\"onChartClicked($event)\" (chartHover)=\"onChartHovered($event)\"></canvas>\n</div>\n\n<ng-container *ngIf=\"templateRefs?.footer && $context\" [ngTemplateOutlet]=\"templateRefs?.footer\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>",
providers: [
ThemeService,
{
provide: DEFAULT_CONFIG,
useValue: ɵ0
}
],
styles: [""]
}),
__param(1, Inject(DEFAULT_CONFIG)), __param(2, Inject(DASHLET_CONSTANTS))
], ChartJsComponent);
var chartJs_component = /*#__PURE__*/Object.freeze({
__proto__: null,
get ChartJsComponent () { return ChartJsComponent; },
ɵ0: ɵ0
});
let ReportWrapperDirective = class ReportWrapperDirective {
constructor(viewContainerRef) {
this.viewContainerRef = viewContainerRef;
}
};
ReportWrapperDirective.ctorParameters = () => [
{ type: ViewContainerRef }
];
ReportWrapperDirective = __decorate([
Directive({
selector: '[sbReportWrapper]'
})
], ReportWrapperDirective);
let TemplateRefsDirective = class TemplateRefsDirective {
constructor(templateRef) {
this.templateRef = templateRef;
}
};
TemplateRefsDirective.ctorParameters = () => [
{ type: TemplateRef }
];
__decorate([
Input('sbTemplateRef')
], TemplateRefsDirective.prototype, "slot", void 0);
TemplateRefsDirective = __decorate([
Directive({
selector: '[sbTemplateRef]'
})
], TemplateRefsDirective);
const TYPE_TO_COMPONENT_MAPPING = {
[ChartType.LINE]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.BAR]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.PIE]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.AREA]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.BUBBLE]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.DOUGHNUT]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.POLAR]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.SCATTER]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.DOUGHNUT]: () => Promise.resolve().then(function () { return chartJs_component; }).then(module => module.ChartJsComponent),
[ChartType.BIG_NUMBER]: () => Promise.resolve().then(function () { return bigNumber_component; }).then(module => module.BigNumberComponent),
[TableType.TABLE]: () => Promise.resolve().then(function () { return dtTable_component; }).then(module => module.DtTableComponent)
};
const transformTemplates = (result, current) => {
result[current.slot] = current.templateRef;
return result;
};
const ɵ0$1 = transformTemplates;
let DashletComponent = class DashletComponent {
constructor(componentFactoryResolver) {
this.componentFactoryResolver = componentFactoryResolver;
this.events = new EventEmitter();
this._typeToComponentMapping = Object.freeze(TYPE_TO_COMPONENT_MAPPING);
}
get instance() {
return this._componentInstance;
}
set instance(componentInstance) {
this._componentInstance = componentInstance;
}
ngOnInit() {
this.id = v4();
if (!this.type && !this.config && !this.data) {
throw new SyntaxError('Syntax Error. Please check configuration');
}
this.loadComponent(this.type).catch(err => {
console.error(err);
throw err;
});
}
loadComponent(type) {
return __awaiter(this, void 0, void 0, function* () {
const componentResolver = this._typeToComponentMapping[type];
if (!componentResolver) {
throw new Error('Given Type not supported');
}
const component = yield componentResolver();
this.reportWrapper.viewContainerRef.clear();
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
const componentRef = this.reportWrapper.viewContainerRef.createComponent(componentFactory);
const instance = this.instance = componentRef.instance;
if (this.templateRefs.length) {
instance['templateRefs'] = this.templateRefs.toArray().reduce(transformTemplates, {});
}
instance.id = this.id;
instance.initialize({ config: this.config, type: this.type, data: this.data });
instance.state.subscribe(this._stateEventsHandler.bind(this));
instance.events.subscribe(this._eventsHandler.bind(this));
});
}
_stateEventsHandler(event) {
this.events.emit({ type: 'STATE', event });
}
_eventsHandler(event) {
this.events.emit(event);
}
filter(filteredData) {
this.instance.update({ data: filteredData });
}
};
DashletComponent.ctorParameters = () => [
{ type: ComponentFactoryResolver }
];
__decorate([
Input()
], DashletComponent.prototype, "type", void 0);
__decorate([
Input()
], DashletComponent.prototype, "config", void 0);
__decorate([
Input()
], DashletComponent.prototype, "data", void 0);
__decorate([
Output()
], DashletComponent.prototype, "events", void 0);
__decorate([
ViewChild(ReportWrapperDirective, { static: true })
], DashletComponent.prototype, "reportWrapper", void 0);
__decorate([
ContentChildren(TemplateRefsDirective)
], DashletComponent.prototype, "templateRefs", void 0);
DashletComponent = __decorate([
Component({
selector: 'sb-dashlet',
template: "<div>\n <ng-template sbReportWrapper></ng-template>\n</div>",
styles: [""]
})
], DashletComponent);
const iterateeFn = key => row => toNumber(row[key]);
const ɵ0$2 = iterateeFn;
const SUM = data => key => sumBy(data, iterateeFn(key));
const ɵ1 = SUM;
const MIN = data => key => minBy(data, iterateeFn(key))[key];
const ɵ2 = MIN;
const MAX = data => key => maxBy(data, iterateeFn(key))[key];
const ɵ3 = MAX;
const AVG = data => key => {
const length = data.length || 0;
if (length === 0)
return 0;
const totalSum = SUM(data)(key);
return (totalSum / length).toFixed(2);
};
const ɵ4 = AVG;
const $operations = new Map([
['SUM', SUM],
['MIN', MIN],
['MAX', MAX],
['AVG', AVG]
]);
const runAggregator = (aggregateFn, data, key) => {
const aggregateFnUpper = aggregateFn.toUpperCase();
if ($operations.has(aggregateFnUpper)) {
return $operations.get(aggregateFnUpper)(data)(key);
}
throw new Error('Specified Aggregator function does not exists');
};
const ɵ0$3 = {
header: '',
footer: '',
operation: 'SUM'
};
let BigNumberComponent = class BigNumberComponent extends BaseComponent {
constructor(dataService, defaultConfig, cdr, CONSTANTS) {
super(dataService);
this.dataService = dataService;
this.cdr = cdr;
this.CONSTANTS = CONSTANTS;
this.reportType = IReportType.CHART;
this.type = ChartType.BIG_NUMBER;
this.inputParameters = {};
this.exportOptions = ['csv'];
this.bigNumberDataClosure = (dataExpr) => $aggregateFn => (data) => {
return {
getData(overriddenData) {
data = overriddenData || data;
return runAggregator($aggregateFn, data, dataExpr);
},
addData(newData) {
data = data.concat(newData);
return this.getData();
}
};
};
this._defaultConfig = defaultConfig;
}
initialize({ config, data, type = "bigNumber" }) {
return __awaiter(this, void 0, void 0, function* () {
if (!(config && data))
throw new SyntaxError(this.CONSTANTS.INVALID_INPUT);
this.config = config = Object.assign({}, config, { type });
const fetchedJSON = this.data = yield this.fetchData(data).toPromise().catch(err => []);
this.builder(config, fetchedJSON);
this._isInitialized = true;
this.state.emit(ReportState.DONE);
});
}
builder(config, JSONData) {
const { header = this._defaultConfig.header, footer = this._defaultConfig.footer, dataExpr, operation = this._defaultConfig.operation } = config;
if (!dataExpr || !JSONData) {
throw Error(this.CONSTANTS.INVALID_INPUT);
}
this._bigNumberClosure = this.bigNumberDataClosure(dataExpr)(operation)(JSONData);
const bigNumberObj = { header, footer, data: this._bigNumberClosure.getData() };
this.setBigNumberData(bigNumberObj);
}
setBigNumberData(config = {}) {
this.inputParameters = Object.assign({}, this._defaultConfig, this.inputParameters, config);
this.$context = { data: this.data, config: this.config, inputParameters: this.inputParameters, exportOptions: this.exportOptions };
this.cdr.detectChanges();
}
reset() {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
destroy() {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
update(input) {
this.checkIfInitialized();
if (!input)
throw new Error(this.CONSTANTS.INVALID_INPUT);
const { config = {}, data = null } = input;
const { header, footer, dataExpr, operation = 'SUM' } = config;
let bigNumber;
if (data) {
this._bigNumberClosure = (dataExpr && this.bigNumberDataClosure(dataExpr)(operation)(data)) || this._bigNumberClosure;
bigNumber = this._bigNumberClosure.getData(data);
}
this.setBigNumberData(Object.assign({}, (header && { header }), (footer && { footer }), (bigNumber && {
data: bigNumber
})));
}
addData(data) {
if (!data)
throw new Error(this.CONSTANTS.INVALID_INPUT);
data = Array.isArray(data) ? data : [data];
const bigNumber = this._bigNumberClosure.addData(data);
this.setBigNumberData({
data: bigNumber
});
}
refreshChart() {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
getTelemetry() {
throw new Error(this.CONSTANTS.METHOD_NOT_IMPLEMENTED);
}
exportAs(format) {
if (!this.exportOptions.includes(format)) {
throw new Error('given type not supported');
}
this.exportAsCsv();
}
};
BigNumberComponent.ctorParameters = () => [
{ type: DataService },
{ type: undefined, decorators: [{ type: Inject, args: [DEFAULT_CONFIG,] }] },
{ type: ChangeDetectorRef },
{ type: undefined, decorators: [{ type: Inject, args: [DASHLET_CONSTANTS,] }] }
];
BigNumberComponent = __decorate([
Component({
selector: 'sb-big-number',
template: "<ng-template #defaultTemplate let-config>\n <div class=\"ui cards\">\n <div class=\"card\">\n <div class=\"content\">\n <div class=\"header\">{{config?.inputParameters?.header}}</div>\n <div class=\"meta\">{{config?.inputParameters?.footer}}</div>\n <div class=\"description\">\n {{config?.inputParameters?.data}}\n </div>\n </div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #defaultFilterTemplate let-context>\n <sb-dashlets-filters [data]=\"context?.data\" [config]=\"context?.config?.filters\"\n (filteredData)=\"update({data: $event})\">\n </sb-dashlets-filters>\n</ng-template>\n\n<ng-container *ngIf=\"$context?.config?.filters\" [ngTemplateOutlet]=\"templateRefs?.filter || defaultFilterTemplate\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>\n\n\n<ng-container *ngIf=\"templateRefs?.header && $context\" [ngTemplateOutlet]=\"templateRefs?.header\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>\n\n<ng-container *ngIf=\"$context\" [ngTemplateOutlet]=\"templateRefs?.body || defaultTemplate\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>\n\n<ng-container *ngIf=\"templateRefs?.footer && $context\" [ngTemplateOutlet]=\"templateRefs?.footer\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>",
providers: [
{
provide: DEFAULT_CONFIG,
useValue: ɵ0$3
}
],
styles: [""]
}),
__param(1, Inject(DEFAULT_CONFIG)), __param(3, Inject(DASHLET_CONSTANTS))
], BigNumberComponent);
var bigNumber_component = /*#__PURE__*/Object.freeze({
__proto__: null,
get BigNumberComponent () { return BigNumberComponent; },
ɵ0: ɵ0$3
});
const TABLE_DEFAULT_CONFIG = {
tableLevelConfig: {
autoWidth: true,
paging: false,
bFilter: false,
bInfo: false,
info: false,
searchable: false,
bLengthChange: false
},
columnConfig: {
searchable: true,
orderable: true,
visible: true,
autoWidth: true
}
};
const jsonExport$1 = jsonexport;
const ɵ0$4 = TABLE_DEFAULT_CONFIG;
let DtTableComponent = class DtTableComponent extends BaseComponent {
constructor(dataService, defaultConfig, CONSTANTS) {
super(dataService);
this.dataService = dataService;
this.CONSTANTS = CONSTANTS;
this.reportType = IReportType.TABLE;
this.inputParameters = {};
this.exportOptions = ['csv'];
this.rowClickHandler = (row, data, index) => {
const self = this;
$('td', row).off('click');
$('td', row).on('click', () => {
this.events.emit({
type: 'CLICK',
event: data
});
});
return row;
};
this._addDefaultToColumn = column => {
return Object.assign({}, this._defaultConfig['columnConfig'], column);
};
this._defaultConfig = defaultConfig;
}
initialize({ config, type, data }) {
return __awaiter(this, void 0, void 0, function* () {
if (!(config && type && data))
throw new SyntaxError(this.CONSTANTS.INVALID_INPUT);
this.config = config = Object.assign({}, config, { type });
const fetchedJSON = this.data = yield this.fetchData(data).toPromise().catch(err => []);
this.builder(config, fetchedJSON);
this._isInitialized = true;
this.state.emit(ReportState.DONE);
});
}
ngAfterViewInit() {
this._dtClosure = this._tableOpsClosure(this.dataTableElement && this.dataTableElement.dtInstance);
}
builder(config, data) {
const { columnConfig } = config, others = __rest(config, ["columnConfig"]);
const columns = columnConfig.map(this._addDefaultToColumn);
this._setTableOptions(Object.assign({}, others, { data,
columns, rowCallback: this.rowClickHandler.bind(this) }));
}
_setTableOptions(config = {}) {
this.inputParameters = Object.assign({}, this._defaultConfig['tableLevelConfig'], this.inputParameters, config);
this.$context = { data: this.data, config: this.config, inputParameters: this.inputParameters, exportOptions: this.exportOptions };
}
getRowsCount() {
return this._dtClosure && this._dtClosure.rowsCount();
}
// resets to the original state.
reset() {
this._dtClosure.updateData(this.data);
}
destroy() {
const { destroy } = this._dtClosure;
if (destroy && typeof destroy === 'function') {
try {
destroy.call(this._dtClosure);
}
catch (err) {
console.error('component not destroyed', err);
}
}
}
update(input) {
this.checkIfInitialized();
if (!input)
throw new Error(this.CONSTANTS.INVALID_INPUT);
const { config = {}, data = null } = input;
const _a = config, { columnConfig: columns } = _a, others = __rest(_a, ["columnConfig"]);
if (data && this._dtClosure) {
this._dtClosure.updateData(data);
}
this._setTableOptions(Object.assign({}, others, (data && { data }), (columns && { columns })));
}
;
addRows(data) {
this.addData(data);
}
getRowAtIndex(index) {
const { getRowAtIndex } = this._dtClosure;
if (getRowAtIndex) {
return getRowAtIndex.bind(this._dtClosure, index);
}
}
removeRow(index) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this._dtClosure.getData();
data.splice(index, 1);
yield this._dtClosure.updateData(data);
});
}
addData(data) {
const { addData } = this._dtClosure;
if (addData && typeof addData === 'function') {
try {
addData.call(this._dtClosure, data);
}
catch (error) {
console.error('addition of data failed', error);
}
}
}
_tableOpsClosure(tableInstance) {
return {
get instance() {
return tableInstance;
},
addData(data) {
return __awaiter(this, void 0, void 0, function* () {
const instance = yield this.instance;
instance.row.add(data);
instance.draw();
});
},
draw() {
return __awaiter(this, void 0, void 0, function* () {
const instance = yield this.instance;
instance.draw();
return instance;
});
},
destroy() {
return __awaiter(this, void 0, void 0, function* () {
const instance = yield this.instance;
instance.destroy();
return instance;
});
},
updateData(data) {
return __awaiter(this, void 0, void 0, function* () {
const instance = yield this.instance;
instance.clear();
instance.rows.add(data);
instance.draw();
});
},
rowsCount() {
return __awaiter(this, void 0, void 0, function* () {
const instance = yield this.instance;
return instance.rows().count();
});
},
getData() {
return __awaiter(this, void 0, void 0, function* () {
const instance = yield this.instance;
return instance.rows().data();
});
},
getRowAtIndex(index) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.getData();
return data[index];
});
}
};
}
// Returns the csv string for the mobile platform
exportCsv() {
return new Promise((resolve, reject) => {
jsonExport$1(this.data, (error, csv) => {
if (csv) {
resolve(csv);
}
else {
reject(error);
}
});
});
}
exportAs(format) {
if (!this.exportOptions.includes(format)) {
throw new Error('given type not supported');
}
switch (format) {
case 'csv': {
this.exportAsCsv();
break;
}
}
}
};
DtTableComponent.ctorParameters = () => [
{ type: DataService },
{ type: undefined, decorators: [{ type: Inject, args: [DEFAULT_CONFIG,] }] },
{ type: undefined, decorators: [{ type: Inject, args: [DASHLET_CONSTANTS,] }] }
];
__decorate([
ViewChild(DataTableDirective, { static: false })
], DtTableComponent.prototype, "dataTableElement", void 0);
DtTableComponent = __decorate([
Component({
selector: 'sb-dt-table',
template: "<ng-template #defaultFilterTemplate let-context>\n <sb-dashlets-filters [data]=\"context?.data\" [config]=\"context?.config?.filters\"\n (filteredData)=\"update({data: $event})\">\n </sb-dashlets-filters>\n</ng-template>\n\n<ng-container *ngIf=\"$context?.config?.filters\" [ngTemplateOutlet]=\"templateRefs?.filter || defaultFilterTemplate\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>\n\n<ng-container *ngIf=\"templateRefs?.header && $context\" [ngTemplateOutlet]=\"templateRefs?.header\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>\n\n<table datatable [dtOptions]=\"$context?.inputParameters\" class=\"row-border hover\"></table>\n\n<ng-container *ngIf=\"templateRefs?.footer && $context\" [ngTemplateOutlet]=\"templateRefs?.footer\"\n [ngTemplateOutletContext]=\"{'$implicit': $context}\">\n</ng-container>",
providers: [
{
provide: DEFAULT_CONFIG,
useValue: ɵ0$4
}
],
styles: [""]
}),
__param(1, Inject(DEFAULT_CONFIG)), __param(2, Inject(DASHLET_CONSTANTS))
], DtTableComponent);
var dtTable_component = /*#__PURE__*/Object.freeze({
__proto__: null,
get DtTableComponent () { return DtTableComponent; },
ɵ0: ɵ0$4
});
const FILTER_DEFAULT_CONFIG = {
config: {
controlType: 'multi-select',
searchable: true,
default: [],
placeholder: 'Select Option',
options: [],
dateFormat: 'DD-MM-YYYY'
},
dropdownSettings: {
singleSelection: false,
idField: 'item_id',
textField: 'item_text',
selectAllText: 'Select All',
unSelectAllText: 'UnSelect All',
itemsShowLimit: 3,
allowSearchFilter: true
}
};
const moment = momentImported;
const ranges = {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
};
const sortDates = (dates, format = 'DD-MM-YYYY') => {
return dates.sort((pre, next) => {
const preDate = moment(pre, format), nextDate = moment(next, format);
if (preDate.isSame(nextDate))
return 0;
if (preDate.isBefore(nextDate))
return -1;
return 1;
});
};
const ɵ0$5 = sortDates;
const ɵ1$1 = FILTER_DEFAULT_CONFIG;
let FiltersComponent = class FiltersComponent {
constructor(fb, defaultConfig) {
this.fb = fb;
this.defaultConfig = defaultConfig;
this.config = [];
this.filteredData = new EventEmitter();
this.unsubscribe$ = new Subject();
this.ranges = ranges;
this.locale = { applyLabel: 'Set Date', format: 'DD-MM-YYYY' };
this._omitEmptyFilters = filters => omitBy(filters, isEmpty);
this.transformFilterValues = filters => mapValues(filters, values => Array.isArray(values) ? values : [values]);
this.getSelectedFiltersObservable = () => {
return this.filtersFormGroup.valueChanges
.pipe(takeUntil(this.unsubscribe$), debounceTime(1000), distinctUntilChanged(), map(this._omitEmptyFilters.bind(this)), map(this.transformFilterValues.bind(this)), startWith({}), pairwise());
};
}
ngOnInit() {
this._data = this.data;
this.init(this.config, this._data);
this.handleFilterValueChanges();
}
_setDropdownSettings(config = {}) {
return Object.assign({}, this.defaultConfig.dropdownSettings, config);
}
_getFilterOptions(dataExpr, data) {
const getFilterValue = dataExpr => row => (row && row[dataExpr]) || '';
const inputDataArr = (data && Array.isArray(data) && data.map(getFilterValue(dataExpr))) || [];
return compact(sortBy(uniq(inputDataArr)));
}
init(config, data) {
this.filters = [];
this.filtersFormGroup = this.fb.group({});
config.forEach(filter => {
const filterObj = Object.assign({}, this.defaultConfig.config, filter);
const { reference, default: defaultValue, searchable, controlType, dropdownSettings = {}, dateFormat } = filterObj;
const options = this._getFilterOptions(reference, data);
if (filter.controlType === 'date' || /date/i.test(reference)) {
const sortedDateRange = sortDates([...options], dateFormat);
filterObj['minDate'] = moment(sortedDateRange[0], dateFormat);
filterObj['maxDate'] = moment(sortedDateRange[sortedDateRange.length - 1], dateFormat);
}
else {
filterObj['dropdownSettings'] = this._setDropdownSettings(Object.assign({ singleSelection: controlType === 'single-select' ? true : false, allowSearchFilter: searchable }, dropdownSettings));
}
filterObj.options = options;
this.filtersFormGroup.addControl(reference, this.fb.control(defaultValue));
this.filters.push(filterObj);
});
}
handleFilterValueChanges() {
const selectedFilters$ = this.getSelectedFiltersObservable();
const filteredData$ = selectedFilters$.pipe(tap(console.log), map(([_, currentFilters]) => currentFilters), map(this.filterDataBySelectedFilters(this._data)));
zip(selectedFilters$, filteredData$)
.subscribe(([[previousFilters, currentFilters], filteredData]) => {
forEach(this.filters, filter => {
const { reference } = filter;
const options = this._getFilterOptions(reference, filteredData);
const referenceExistsInPreviousFilters = reference in previousFilters;
const referenceExistsInCurrentFilters = reference in currentFilters;
if (!referenceExistsInCurrentFilters || (referenceExistsInPreviousFilters && JSON.stringify(previousFilters[reference]) === JSON.stringify(currentFilters[reference]))) {
filter.options = options;
if (filter.controlType === 'date' || /date/i.test(reference)) {
const sortedDateRange = sortDates([...options], 'DD-MM-YYYY');
filter['minDate'] = moment(sortedDateRange[0], 'DD-MM-YYYY');
filter['maxDate'] = moment(sortedDateRange[sortedDateRange.length - 1], 'DD-MM-YYYY');
}
}
});
this.filteredData.emit(filteredData);
});
}
filterDataBySelectedFilters(JSON) {
return selectedFilters => filter(JSON, data => {
return every(selectedFilters, (values, key) => {
if (data[key]) {
return some(values, value => trim(toLower(value)) === trim(toLower(data[key])));
}
return false;
});
});
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
updateDateRange({ startDate, endDate }, columnRef, dateFormat) {
const selectedStartDate = moment(startDate).subtract(1, 'day');
const selectedEndDate = moment(endDate).add(1, 'day');
const dateRange = [];
const currDate = moment(selectedStartDate).startOf('day');
const lastDate = moment(selectedEndDate).startOf('day');
while (currDate.add(1, 'days').diff(lastDate) < 0) {
dateRange.push(currDate.clone().format(dateFormat));
}
this.filtersFormGroup.get(columnRef).setValue(dateRange);
}
reset() {
this.filtersFormGroup.reset();
if (this.datepicker) {
this.datepicker.nativeElement.value = '';
}
}
};
FiltersComponent.ctorParameters = () => [
{ type: FormBuilder },
{ type: undefined, decorators: [{ type: Inject, args: [DEFAULT_CONFIG,] }] }
];
__decorate([
Input()
], FiltersComponent.prototype, "config", void 0);
__decorate([
Input()
], FiltersComponent.prototype, "data", void 0);
__decorate([
Output()
], FiltersComponent.prototype, "filteredData", void 0);
__decorate([
ViewChild('datePickerForFilters', { static: false })
], FiltersComponent.prototype, "datepicker", void 0);
FiltersComponent = __decorate([
Component({
selector: 'sb-dashlets-filters',
template: "\n<ng-container *ngIf=\"filters?.length && filtersFormGroup\">\n <form [formGroup]=\"filtersFormGroup\">\n <div class=\"sb-filter-g\">\n <ng-container *ngFor=\"let filter of filters\">\n <ng-container\n *ngIf=\"filter?.controlType === 'multi-select' || filter?.controlType === 'single-select'\">\n <ng-multiselect-dropdown class=\"sb-filter-g__item\"\n [placeholder]=\"filter?.placeholder || filter?.displayName || filter?.label\"\n [settings]=\"filter?.dropdownSettings\" [data]=\"filter?.options\"\n [formControlName]=\"filter.reference\">\n </ng-multiselect-dropdown>\n </ng-container>\n <ng-container *ngIf=\"filter?.controlType === 'date'\">\n <div class=\"sb-filter-g__item\">\n <input type=\"text\"\n [placeholder]=\"filter?.placeholder || filter?.displayName || filter?.label\"\n ngxDaterangepickerMd [minDate]=\"filter?.minDate\" [maxDate]=\"filter?.maxDate\"\n [ranges]=\"ranges\" [alwaysShowCalendars]=\"true\" [locale]=\"locale\"\n [linkedCalendars]=\"true\" [showCustomRangeLabel]=\"true\"\n (change)=\"updateDateRange($event,filter.reference, filter?.dateFormat)\"\n #datePickerForFilters />\n </div>\n </ng-container>\n </ng-container>\n </div>\n </form>\n</ng-container>",
providers: [{
provide: DEFAULT_CONFIG,
useValue: ɵ1$1
}],
styles: [".sb-filter-g{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:.5rem}.sb-filter-g__item{margin-bottom:.5rem;display:block;--white:#fff;background-color:var(--white)}.sb-filter-g__item input{width:100%;border:1px solid #adadad;height:35px;border-radius:.25rem;padding-left:.75rem;color:#333}"]
}),
__param(1, Inject(DEFAULT_CONFIG))
], FiltersComponent);
let DashletModule = class DashletModule {
};
DashletModule = __decorate([
NgModule({
declarations: [ChartJsComponent, DashletComponent, ReportWrapperDirective, BigNumberComponent, DtTableComponent, TemplateRefsDirective, FiltersComponent],
imports: [HttpClientModule, ChartsModule, CommonModule, DataTablesModule, ReactiveFormsModule, NgMultiSelectDropDownModule, NgxDaterangepickerMd.forRoot()],
exports: [DashletComponent, TemplateRefsDirective],
entryComponents: [ChartJsComponent, BigNumberComponent, DtTableComponent]
})
], DashletModule);
/*
* Public API Surface of my-lib
*/
/**
* Generated bundle index. Do not edit.
*/
export { BaseComponent, BigNumberComponent, CHART_DEFAULT_CONFIG, ChartJsComponent, ChartType, DASHLET_CONSTANTS, DEFAULT_CONFIG, DashletComponent, DashletModule, DataService, DtTableComponent, FILTER_DEFAULT_CONFIG, FiltersComponent, IReportType, ReportState, ReportWrapperDirective, TABLE_DEFAULT_CONFIG, TYPE_TO_COMPONENT_MAPPING, TableType, TemplateRefsDirective, constants, runAggregator, ɵ0, ɵ1$1 as ɵ1, ɵ2, ɵ3, ɵ4 };
//# sourceMappingURL=sb-dashlets.js.map