UNPKG

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
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