ngx-t-reports
Version:
Angular module for creating dynamic reports and dashboards. Supports various data sources, custom templates, and real-time updates.
1,024 lines (1,013 loc) • 152 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, Output, Input, Component, Injectable, inject, ViewChild, ChangeDetectionStrategy, InjectionToken, Optional, Inject } from '@angular/core';
import * as i1 from 'ngx-echarts';
import { NgxEchartsModule, provideEchartsCore } from 'ngx-echarts';
import * as echarts from 'echarts/core';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import { BarChart, BoxplotChart, CandlestickChart, CustomChart, EffectScatterChart, FunnelChart, GaugeChart, GraphChart, HeatmapChart, LineChart, LinesChart, MapChart, ParallelChart, PictorialBarChart, PieChart, RadarChart, SankeyChart, ScatterChart, SunburstChart, ThemeRiverChart, TreeChart, TreemapChart } from 'echarts/charts';
import * as i4$1 from '@angular/material/progress-spinner';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import * as i3 from '@angular/cdk/drag-drop';
import { moveItemInArray, DragDropModule, CdkDropList, CdkDrag } from '@angular/cdk/drag-drop';
import * as i8 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import * as i1$1 from '@angular/material/card';
import { MatCardModule, MatCardContent, MatCardHeader, MatCardTitle, MatCardSubtitle } from '@angular/material/card';
import * as i7 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i5 from '@angular/material/menu';
import { MatMenuModule } from '@angular/material/menu';
import * as i9 from '@angular/material/toolbar';
import { MatToolbarModule, MatToolbar } from '@angular/material/toolbar';
import * as i2$1 from '@angular/material/divider';
import { MatDividerModule } from '@angular/material/divider';
import * as i4 from '@angular/material/chips';
import { MatChipsModule } from '@angular/material/chips';
import * as i12 from '@angular/material/table';
import { MatTableModule } from '@angular/material/table';
import * as i6 from '@angular/material/tooltip';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ComponentStore } from '@ngrx/component-store';
import { combineLatest, map, switchMap, tap, distinctUntilChanged, of, BehaviorSubject, Observable, Subscription, take, catchError } from 'rxjs';
import * as XLSX from 'xlsx';
import * as i13 from '@angular/cdk/scrolling';
import { ScrollingModule } from '@angular/cdk/scrolling';
import * as i1$2 from '@angular/material/dialog';
import * as i7$1 from '@angular/material/expansion';
import { MatExpansionModule } from '@angular/material/expansion';
import { assignDeepPropertyToObject, TDynamicDataEditComponent, NGX_T_FORMS_CONFIG_TOKEN, getSectionElements } from 'ngx-t-forms';
import { ElementEditorTypes, SpecialElementKeys, InputDataTypes, ElementTypes } from 'ngx-t-forms-types';
import * as i7$2 from '@angular/forms';
import { FormGroup, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import * as i6$1 from '@angular/material/datepicker';
import { MatDatepickerModule } from '@angular/material/datepicker';
import * as i5$1 from '@angular/material/form-field';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import moment from 'moment-timezone';
import moment$1 from 'moment';
echarts.use([
BarChart, BoxplotChart, CandlestickChart, CustomChart, EffectScatterChart,
FunnelChart, GaugeChart, GraphChart, HeatmapChart, LineChart, LinesChart,
MapChart, ParallelChart, PictorialBarChart, PieChart, RadarChart, SankeyChart,
ScatterChart, SunburstChart, ThemeRiverChart, TreeChart, TreemapChart,
GridComponent, CanvasRenderer
]);
class EChartsComponent {
constructor() {
this.chartClick = new EventEmitter();
}
ngOnChanges(changes) {
if (changes['options']) {
this.chartOption = this.options;
}
}
onChartClick(event) {
this.chartClick.emit(event);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EChartsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: EChartsComponent, isStandalone: true, selector: "app-e-charts", inputs: { loading: "loading", options: "options" }, outputs: { chartClick: "chartClick" }, providers: [
provideEchartsCore({ echarts }),
], usesOnChanges: true, ngImport: i0, template: "@if (chartOption ) {\r\n<div echarts style=\"height:338px;\" (chartClick)=\"onChartClick($event)\" [options]=\"chartOption\" [loading]=\"loading\"\r\n class=\"chart\"></div>\r\n}", styles: [""], dependencies: [{ kind: "ngmodule", type: NgxEchartsModule }, { kind: "directive", type: i1.NgxEchartsDirective, selector: "echarts, [echarts]", inputs: ["options", "theme", "initOpts", "merge", "autoResize", "loading", "loadingType", "loadingOpts"], outputs: ["chartInit", "optionsError", "chartClick", "chartDblClick", "chartMouseDown", "chartMouseMove", "chartMouseUp", "chartMouseOver", "chartMouseOut", "chartGlobalOut", "chartContextMenu", "chartHighlight", "chartDownplay", "chartSelectChanged", "chartLegendSelectChanged", "chartLegendSelected", "chartLegendUnselected", "chartLegendLegendSelectAll", "chartLegendLegendInverseSelect", "chartLegendScroll", "chartDataZoom", "chartDataRangeSelected", "chartGraphRoam", "chartGeoRoam", "chartTreeRoam", "chartTimelineChanged", "chartTimelinePlayChanged", "chartRestore", "chartDataViewChanged", "chartMagicTypeChanged", "chartGeoSelectChanged", "chartGeoSelected", "chartGeoUnselected", "chartAxisAreaSelected", "chartBrush", "chartBrushEnd", "chartBrushSelected", "chartGlobalCursorTaken", "chartRendered", "chartFinished"], exportAs: ["echarts"] }, { kind: "ngmodule", type: CommonModule }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EChartsComponent, decorators: [{
type: Component,
args: [{ selector: 'app-e-charts', standalone: true, imports: [
NgxEchartsModule,
CommonModule
], providers: [
provideEchartsCore({ echarts }),
], template: "@if (chartOption ) {\r\n<div echarts style=\"height:338px;\" (chartClick)=\"onChartClick($event)\" [options]=\"chartOption\" [loading]=\"loading\"\r\n class=\"chart\"></div>\r\n}" }]
}], propDecorators: { loading: [{
type: Input
}], options: [{
type: Input
}], chartClick: [{
type: Output
}] } });
function createADonutChart(id, title, dataSeries) {
return {
title,
id: id,
chart: {
toolbox: {
show: true,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
grid: {
top: 100,
},
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [{
name: title,
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true
},
emphasis: {
label: {
show: true,
fontSize: '40',
fontWeight: 'bold'
}
},
labelLine: {
lineStyle: {
color: '#235894'
}
},
data: dataSeries
}],
}
};
}
function createABarChart(id, title, dataSeries) {
return {
title,
id,
chart: {
toolbox: {
show: true,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: dataSeries.map(v => v.name),
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '',
type: 'bar',
label: {
show: true
},
emphasis: {
label: {
show: true,
fontSize: '40',
fontWeight: 'bold'
}
},
labelLine: {
lineStyle: {
color: '#235894'
}
},
data: dataSeries.map((v, index) => ({
...v,
itemStyle: {
color: getColors(dataSeries.length)[index]
}
}))
}
],
},
};
}
function createRoseTypeChart(id, title, dataSeries) {
return {
title,
id,
chart: {
legend: {
top: 'bottom'
},
toolbox: {
show: true,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
series: [
{
name: title,
type: 'pie',
radius: [50, 250],
center: ['50%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 8
},
data: dataSeries
}
]
}
};
}
function createAPolarChart(id, title, dataSeries) {
return {
title,
id,
chart: {
title: [
{
text: title
}
],
toolbox: {
show: true,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
polar: {
radius: [30, '80%']
},
angleAxis: {
startAngle: 75
},
radiusAxis: {
type: 'category',
data: dataSeries.map(v => v.name)
},
tooltip: {},
series: {
type: 'bar',
data: dataSeries.map(v => v.value),
coordinateSystem: 'polar',
label: {
show: true,
position: 'middle',
formatter: '{b}: {c}'
}
}
}
};
}
const colors = [
'#4285F4',
'#FBBC05',
'#34A853',
'#EA4335',
'#55ACEE',
'#3B5998',
'#7CBB00',
'#00A1F1',
'#7B0099',
'#146EB4'
];
function getColors(n) {
const result = [];
for (let i = 0; i < n; i++) {
result.push(colors[i % colors.length]);
}
return result;
}
class GroupingChartComponent {
constructor() {
this.groups = [];
this.numberProperties = [];
this.lastHash = null;
this.chartConfig = {};
this.chatTypesEnum = ChartTypes;
}
get totalDistributionPerGroup() {
// Create a hash or identifier based on the content of the groups to detect changes
const currentHash = JSON.stringify(this.groups);
if (this.lastHash === currentHash && this.cache) {
// If the hash hasn't changed, return the cached result
return this.cache;
}
// Compute the distribution if data has changed
const result = this.numberProperties?.map((property, index) => ({
title: property.label,
id: index.toString(),
dataSeries: this.groups.map(g => ({
name: g.name,
value: Number(g.totals?.[property.formControlName] || 0)
}))
}))
.map(chart => {
const type = this.chartConfig[chart.id]?.chartType || ChartTypes.Donut;
if (type === ChartTypes.Donut || !type) {
return createADonutChart(chart.id, chart.title, chart.dataSeries);
}
if (type === ChartTypes.Bar) {
return createABarChart(chart.id, chart.title, chart.dataSeries);
}
if (type === ChartTypes.Polar) {
return createAPolarChart(chart.id, chart.title, chart.dataSeries);
}
if (type === ChartTypes.RoseType) {
return createRoseTypeChart(chart.id, chart.title, chart.dataSeries);
}
return;
});
// Update cache and hash
this.cache = result;
this.lastHash = currentHash;
return result;
}
isExpanded() {
return this.chartConfig[this.activeChart || '']?.full;
}
chartType() {
return this.chartConfig[this.activeChart || '']?.chartType;
}
setChartType(type) {
if (!this.chartConfig[this.activeChart || '']) {
this.chartConfig[this.activeChart || ''] = {
full: false,
chartType: this.chatTypesEnum.Donut
};
}
if (!this.chartConfig[this.activeChart || ''])
return;
this.chartConfig[this.activeChart || '']['chartType'] = type;
this.lastHash = null;
}
toggleExpansion() {
const st = !!this.chartConfig[this.activeChart || '']?.full;
if (!this.chartConfig[this.activeChart || '']) {
this.chartConfig[this.activeChart || ''] = {
full: false,
chartType: this.chatTypesEnum.Donut
};
}
if (!this.chartConfig[this.activeChart || ''])
return;
return this.chartConfig[this.activeChart || ''].full = !st;
}
getChartSize(id) {
return this.chartConfig[id]?.full ? '12' : '4';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: GroupingChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: GroupingChartComponent, isStandalone: true, selector: "app-grouping-chart", inputs: { groups: "groups", numberProperties: "numberProperties" }, ngImport: i0, template: "<section style=\"background: var(--mat-sys-surface-container);\">\r\n\r\n\r\n <div class=\"row\">\r\n @for (chart of totalDistributionPerGroup||[]; track chart.id) {\r\n <div *ngIf=\"!!chart\" class=\"chart-container\" [class]=\"'col-md-'+getChartSize(chart.id)\">\r\n <mat-card (mouseover)=\"activeChart = chart.id\">\r\n <mat-card-header>\r\n <mat-card-title>\r\n <h4>\r\n {{chart.title}}\r\n <button [matMenuTriggerFor]=\"menu\" style=\"position: absolute;right: 24px;top:24px\"\r\n mat-icon-button>\r\n <mat-icon>\r\n more_vert\r\n </mat-icon>\r\n </button>\r\n </h4>\r\n\r\n </mat-card-title>\r\n\r\n </mat-card-header>\r\n <mat-card-content>\r\n <app-e-charts [options]=\"chart.chart\">\r\n\r\n </app-e-charts>\r\n </mat-card-content>\r\n\r\n </mat-card>\r\n </div>\r\n }\r\n\r\n </div>\r\n</section>\r\n\r\n<mat-menu #menu=\"matMenu\">\r\n <h5> Chart settings </h5>\r\n <button (click)=\"toggleExpansion()\" mat-menu-item>\r\n <mat-icon>{{isExpanded()?'close_fullscreen':'open_in_full'}} </mat-icon>\r\n <span> {{isExpanded()?'Close full view':'View in full size' }}</span>\r\n </button>\r\n <h5> Chart type </h5>\r\n <button (click)=\"setChartType(chatTypesEnum.Donut)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.Donut|| !chartType()?'primary':''\">donut_large</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Donut|| !chartType()?'bold':''\">Donut chart</span>\r\n </button>\r\n <button (click)=\"setChartType(chatTypesEnum.Bar)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.Bar?'primary':''\">bar_chart</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Bar?'bold':''\">bar</span>\r\n </button>\r\n <button (click)=\"setChartType(chatTypesEnum.Polar)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.Polar?'primary':''\">radar</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Bar?'bold':''\">Polar </span>\r\n </button>\r\n <button (click)=\"setChartType(chatTypesEnum.RoseType)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.RoseType?'primary':''\">filter_vintage</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Bar?'bold':''\">Rose </span>\r\n </button>\r\n\r\n</mat-menu>", styles: ["mat-card{margin:5px}h5{margin-left:16px;margin-bottom:10px}.chart-container{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: EChartsComponent, selector: "app-e-charts", inputs: ["loading", "options"], outputs: ["chartClick"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i1$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i1$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i1$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i1$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i8.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: GroupingChartComponent, decorators: [{
type: Component,
args: [{ selector: 'app-grouping-chart', standalone: true, imports: [CommonModule, EChartsComponent, MatCardModule, MatButtonModule, MatIconModule, MatMenuModule], template: "<section style=\"background: var(--mat-sys-surface-container);\">\r\n\r\n\r\n <div class=\"row\">\r\n @for (chart of totalDistributionPerGroup||[]; track chart.id) {\r\n <div *ngIf=\"!!chart\" class=\"chart-container\" [class]=\"'col-md-'+getChartSize(chart.id)\">\r\n <mat-card (mouseover)=\"activeChart = chart.id\">\r\n <mat-card-header>\r\n <mat-card-title>\r\n <h4>\r\n {{chart.title}}\r\n <button [matMenuTriggerFor]=\"menu\" style=\"position: absolute;right: 24px;top:24px\"\r\n mat-icon-button>\r\n <mat-icon>\r\n more_vert\r\n </mat-icon>\r\n </button>\r\n </h4>\r\n\r\n </mat-card-title>\r\n\r\n </mat-card-header>\r\n <mat-card-content>\r\n <app-e-charts [options]=\"chart.chart\">\r\n\r\n </app-e-charts>\r\n </mat-card-content>\r\n\r\n </mat-card>\r\n </div>\r\n }\r\n\r\n </div>\r\n</section>\r\n\r\n<mat-menu #menu=\"matMenu\">\r\n <h5> Chart settings </h5>\r\n <button (click)=\"toggleExpansion()\" mat-menu-item>\r\n <mat-icon>{{isExpanded()?'close_fullscreen':'open_in_full'}} </mat-icon>\r\n <span> {{isExpanded()?'Close full view':'View in full size' }}</span>\r\n </button>\r\n <h5> Chart type </h5>\r\n <button (click)=\"setChartType(chatTypesEnum.Donut)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.Donut|| !chartType()?'primary':''\">donut_large</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Donut|| !chartType()?'bold':''\">Donut chart</span>\r\n </button>\r\n <button (click)=\"setChartType(chatTypesEnum.Bar)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.Bar?'primary':''\">bar_chart</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Bar?'bold':''\">bar</span>\r\n </button>\r\n <button (click)=\"setChartType(chatTypesEnum.Polar)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.Polar?'primary':''\">radar</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Bar?'bold':''\">Polar </span>\r\n </button>\r\n <button (click)=\"setChartType(chatTypesEnum.RoseType)\" mat-menu-item>\r\n <mat-icon [color]=\"chartType() ===chatTypesEnum.RoseType?'primary':''\">filter_vintage</mat-icon>\r\n <span [style.font-weight]=\"chartType() ===chatTypesEnum.Bar?'bold':''\">Rose </span>\r\n </button>\r\n\r\n</mat-menu>", styles: ["mat-card{margin:5px}h5{margin-left:16px;margin-bottom:10px}.chart-container{padding:0}\n"] }]
}], ctorParameters: () => [], propDecorators: { groups: [{
type: Input
}], numberProperties: [{
type: Input
}] } });
var ChartTypes;
(function (ChartTypes) {
ChartTypes["Donut"] = "donutChart";
ChartTypes["Bar"] = "bar";
ChartTypes["Polar"] = "polar";
ChartTypes["RoseType"] = "roseType";
})(ChartTypes || (ChartTypes = {}));
const COLOR_CODES = {
secondary: {
color1: '#FFF5EB', // Very light orange tint (5% of base)
color2: '#FFEBD6', // Light orange tint (10% of base)
color3: '#FFE1C2', // Light-medium orange tint (15% of base)
color4: '#FFD7AD', // Medium orange tint (20% of base)
color5: '#FFCD99', // Medium-deep orange tint (25% of base)
color6: '#FFC285', // Deeper orange tint (30% of base)
color7: '#FFB870', // Even deeper orange tint (35% of base)
color8: '#F68C1E' // Base color
},
blue: {
color1: '#DAE5FC', // Very light tint (5% of base)
color2: '#C0D2F9', // Light tint (10% of base)
color3: '#A6C0F7', // Light-medium tint (15% of base)
color4: '#8DADF5', // Medium tint (20% of base)
color5: '#739BF2', // Medium-deep tint (25% of base)
color6: '#5988F0', // Deeper tint (30% of base)
color7: '#3F76ED', // Even deeper tint (35% of base)
color8: '#2563EB', // Base color
},
green: {
color1: '#ECF2DB', // Very light tint (5% of base)
color2: '#DFE9C2', // Light tint (10% of base)
color3: '#D2E0A9', // Light-medium tint (15% of base)
color4: '#C5D790', // Medium tint (20% of base)
color5: '#B8CE77', // Medium-deep tint (25% of base)
color6: '#ABC55E', // Deeper tint (30% of base)
color7: '#9EBC45', // Even deeper tint (35% of base)
color8: '#91B32C', // Base color
},
blueviolet: {
color1: '#EBDBFA', // Very light tint (5% of base)
color2: '#DDC2F7', // Light tint (10% of base)
color3: '#CFA9F3', // Light-medium tint (15% of base)
color4: '#C290F0', // Medium tint (20% of base)
color5: '#B477EC', // Medium-deep tint (25% of base)
color6: '#A65DE9', // Deeper tint (30% of base)
color7: '#9844E5', // Even deeper tint (35% of base)
color8: '#8A2BE2', // Base color
},
red: {
color1: '#FDE5E1', // Very light tint (5% of base)
color2: '#FBD3CB', // Light tint (10% of base)
color3: '#FAC1B6', // Light-medium tint (15% of base)
color4: '#F8AFA1', // Medium tint (20% of base)
color5: '#F79D8B', // Medium-deep tint (25% of base)
color6: '#F58A76', // Deeper tint (30% of base)
color7: '#F47860', // Even deeper tint (35% of base)
color8: '#F2664B', // Base color
},
purple: {
color1: '#E5DDEE', // Very light tint (5% of base)
color2: '#D3C4E2', // Light tint (10% of base)
color3: '#C1ACD6', // Light-medium tint (15% of base)
color4: '#AF94C9', // Medium tint (20% of base)
color5: '#9D7CBD', // Medium-deep tint (25% of base)
color6: '#8A63B1', // Deeper tint (30% of base)
color7: '#784BA5', // Even deeper tint (35% of base)
color8: '#663399', // Base color
},
// Spectrum Harmony - Best for business applications and financial data
spectrumHarmony: {
color1: '#E3F2FD', // Light blue - first level
color2: '#BBDEFB', // Slightly deeper blue
color3: '#DCEDC8', // Light green - second level
color4: '#C5E1A5', // Slightly deeper green
color5: '#FFF9C4', // Light yellow - third level
color6: '#FFECB3', // Light amber - fourth level
color7: '#FFE082', // Medium amber
color8: '#FFCCBC' // Light red/orange - fifth level
},
// Vibrant Distinction - Best for complex hierarchical data requiring quick differentiation
vibrantDistinction: {
color1: '#E8F5E9', // Mint green - first level
color2: '#C8E6C9', // Medium mint
color3: '#E1F5FE', // Light blue - second level
color4: '#B3E5FC', // Medium light blue
color5: '#F3E5F5', // Lavender - third level
color6: '#FFF8E1', // Light amber - fourth level
color7: '#FFECB3', // Medium amber
color8: '#FFEBEE' // Light pink - fifth level
},
// Natural Progression - Best for environmental data and science applications
naturalProgression: {
color1: '#E0F7FA', // Light cyan - first level
color2: '#B2EBF2', // Medium cyan
color3: '#C8E6C9', // Light green - second level
color4: '#A5D6A7', // Medium green
color5: '#F0F4C3', // Light lime - third level
color6: '#FFE0B2', // Light orange - fourth level
color7: '#FFCC80', // Medium orange
color8: '#FFCDD2' // Light red - fifth level
},
// Colorblind-Friendly - Best for accessibility-focused and public-facing applications
colorblindFriendly: {
color1: '#F5F5F5', // Light gray - first level
color2: '#E0E0E0', // Medium light gray
color3: '#BBDEFB', // Light blue - second level
color4: '#90CAF9', // Medium light blue
color5: '#FFECB3', // Light amber - third level
color6: '#C8E6C9', // Light green - fourth level
color7: '#A5D6A7', // Medium green
color8: '#E1BEE7' // Light purple - fifth level
},
// Corporate Structure - Best for formal reports and corporate environments
corporateStructure: {
color1: '#ECEFF1', // Very light blue-gray - first level
color2: '#CFD8DC', // Light blue-gray - second level
color3: '#B0BEC5', // Medium blue-gray - third level
color4: '#90A4AE', // Medium-dark blue-gray - fourth level
color5: '#78909C', // Dark blue-gray - fifth level
color6: '#607D8B', // Deeper blue-gray
color7: '#546E7A', // Very dark blue-gray
color8: '#455A64' // Extremely dark blue-gray
}
};
// You can implement a function to get random color codes similar to the original example
let colorDeck = [];
const randomColorCode = () => {
// If the deck is empty, refill and shuffle it
if (colorDeck.length === 0) {
colorDeck = [...Object.values(COLOR_CODES)];
// Fisher-Yates shuffle algorithm
for (let i = colorDeck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[colorDeck[i], colorDeck[j]] = [colorDeck[j], colorDeck[i]];
}
}
// Take the top color from our shuffled deck
return colorDeck.pop();
};
var SectionTypeGroups;
(function (SectionTypeGroups) {
SectionTypeGroups["Transactions"] = "transactions";
SectionTypeGroups["Processes"] = "processes";
SectionTypeGroups["System"] = "system";
SectionTypeGroups["FormPreview"] = "formPreview";
SectionTypeGroups["Home"] = "home";
SectionTypeGroups["Transaction"] = "transaction";
SectionTypeGroups["Report"] = "Report";
})(SectionTypeGroups || (SectionTypeGroups = {}));
var DocumentLitsLabelConfigInterfaceValueType;
(function (DocumentLitsLabelConfigInterfaceValueType) {
DocumentLitsLabelConfigInterfaceValueType["currency"] = "currency";
DocumentLitsLabelConfigInterfaceValueType["number"] = "number";
DocumentLitsLabelConfigInterfaceValueType["string"] = "string";
DocumentLitsLabelConfigInterfaceValueType["date"] = "date";
DocumentLitsLabelConfigInterfaceValueType["daysAgo"] = "daysAgo";
DocumentLitsLabelConfigInterfaceValueType["systemReference"] = "systemReference";
})(DocumentLitsLabelConfigInterfaceValueType || (DocumentLitsLabelConfigInterfaceValueType = {}));
/**
* Compares previous and current objects, returning only properties that changed.
* - Properties in prev but not in current: set to undefined
* - Properties in current but not in prev: included with current value
* - Properties in both with different values: included with current value
*
* @example
* // prev = {a: 8, b: 0}, current = {b: 5}
* // result = {b: 5, a: undefined}
*/
function getChangedProperties(prevPassed, currentPassed) {
// Handle edge cases
// remove undefined in prev
const prev = prevPassed ? Object.keys(prevPassed).reduce((acc, key) => {
if (prevPassed[key] !== undefined) {
acc[key] = prevPassed[key];
}
return acc;
}, {}) : undefined;
// remove undefined in current
const current = currentPassed ? Object.keys(currentPassed).reduce((acc, key) => {
if (currentPassed[key] !== undefined) {
acc[key] = currentPassed[key];
}
return acc;
}, {}) : undefined;
// if both are undefined return empty object
if (!prev && !current)
return {};
if (!prev)
return { ...current };
if (!current) {
return Object.keys(prev).reduce((acc, key) => {
acc[key] = undefined;
return acc;
}, {});
}
const changedProperties = {};
// Check properties from prev
for (const key in prev) {
if (!(key in current)) {
// Property removed in current
changedProperties[key] = undefined;
}
else if (!_isEqual(prev[key], current[key])) {
// Property value changed
changedProperties[key] = current[key];
}
}
// Check for new properties in current
for (const key in current) {
if (!(key in prev)) {
// New property added
changedProperties[key] = current[key];
}
}
return changedProperties;
}
function _isEqual(prev, next) {
if (prev === next)
return true;
const prevType = typeof prev;
const nextType = typeof next;
if (prevType !== nextType)
return false;
if (prevType === 'number') {
return (prev === next) || (Number.isNaN(prev) && Number.isNaN(next));
}
if (prevType === 'string' || prevType === 'boolean') {
return prev === next;
}
if (Array.isArray(prev) && Array.isArray(next)) {
if (prev.length !== next.length)
return false;
for (let i = 0; i < prev.length; i++) {
if (!_isEqual(prev[i], next[i]))
return false;
}
return true;
}
if (prevType === 'object' && prev !== null && next !== null) {
const prevConstructor = prev.constructor;
const nextConstructor = next.constructor;
if (prevConstructor !== nextConstructor)
return false;
if (prevConstructor === Date) {
return prev.getTime() === next.getTime();
}
if (prevConstructor === RegExp) {
return prev.source === next.source && prev.flags === next.flags;
}
const prevKeys = Object.keys(prev);
const nextKeys = Object.keys(next);
if (prevKeys.length !== nextKeys.length)
return false;
for (const key of prevKeys) {
if (!Object.prototype.hasOwnProperty.call(next, key))
return false;
if (!_isEqual(prev[key], next[key]))
return false;
}
return true;
}
return prev === null && next === null;
}
function groupData(items, keys, level = 0) {
if (keys.length === 0)
return new Map([[level, items]]); // No more keys, return items wrapped in a Map
const key = keys[0];
if (!key)
return new Map([[level, items]]);
const restKeys = keys.slice(1);
const grouped = new Map();
for (const item of items) {
const keyValue = item[key];
if (!grouped.has(keyValue)) {
grouped.set(keyValue, []);
}
grouped.get(keyValue).push(item);
}
if (restKeys.length > 0) {
grouped.forEach((value, key) => {
grouped.set(key, groupData(value, restKeys, level + 1));
});
}
return grouped;
}
function matrixTableGroupedData(array, propertyName, closedGroups, listConfig) {
// Use a Map to handle grouping which can be more efficient for lookups and updates
const numberProperties = listConfig.filter((config) => config.valueType === 'number'
|| config.valueType === 'currency').map((config) => config.formControlName);
const noneEmptyPropertyNames = propertyName.filter(n => !!n && (n.length > 0));
const noneEmptyArray = array.filter(a => !!a && Object.keys(a).length > 0);
const groupedData = groupData(noneEmptyArray, noneEmptyPropertyNames);
const result = flattenGroupedData(groupedData, propertyName);
return result;
function getSums(data) {
if (!Array.isArray(data)) {
return {};
}
const sums = {
IS_SUM_ROW: true
};
numberProperties.forEach((property) => {
sums[property] = `${data.reduce((acc, item) => acc + Number(item[property] || 0), 0)}`;
});
return sums;
}
function flattenGroupedData(grouped, keys, currentLevel = 0) {
let result = [];
if (keys.length === currentLevel) {
return Array.from(grouped.values()).flat();
}
grouped.forEach((value, key) => {
const isClosed = closedGroups.includes(key);
let _data = [];
_data = flattenGroupedData(value, keys, currentLevel + 1);
const objectProperty = propertyName[currentLevel];
const propertyConfig = listConfig.find(l => l.formControlName === objectProperty);
const groupConfig = {
name: key,
propertyLabel: propertyConfig?.label || objectProperty || '',
IS_GROUP_CONFIG: true,
LEVELS_BEFORE: Array.from({ length: currentLevel }, (_, i) => i + 1),
level: currentLevel,
totals: getSums(_data),
IS_CLOSED: isClosed
};
result.push(groupConfig);
result = result.concat(isClosed ? [] : _data);
});
return result;
}
}
function NgxTMatrixTableStoreSelectors(store) {
const state$ = store.select(state => state);
const reportName$ = store.select(state => state.reportName);
const list$ = store.select(state => state.list);
const listConfig$ = store.select(state => state.listConfig);
const aggregates$ = store.select(state => state.aggregates);
const groupOptions$ = store.select(state => state.groupOptions);
const closedGroups$ = store.select(state => state.closedGroups);
const isOpen$ = store.select(state => state.isOpen);
const groupChart$ = store.select(state => state.groupChart);
const draggedGroup$ = store.select(state => state.draggedGroup);
const scrollIndex$ = store.select(state => state.scrollIndex);
const tableColumns$ = listConfig$;
let itemsToDisplay = 0;
combineLatest([
groupOptions$,
listConfig$
])
.pipe(map(([groupOptions, listConfig]) => [{ formControlName: 'tree', label: '' }, ...listConfig || []]
.filter((col) => !col.isHidden).map((col) => ({
...col,
INCLUDED_IN_GROUP: Boolean(groupOptions?.columns?.includes(col.formControlName))
}))));
const displayedColumns$ = tableColumns$.pipe(map(tableColumns => tableColumns?.map((config) => config.formControlName) || []));
const groupLabelConfig$ = groupOptions$.pipe(map(groupOptions => groupOptions?.columns?.map((col, index) => ({ level: index, formControlName: index.toString(), label: '' })) || []));
const getGroupedData$ = combineLatest([list$,
listConfig$,
groupOptions$,
closedGroups$,
scrollIndex$]).pipe(map(([list, listConfig, groupOptions, closedGroups, scrollIndex]) => {
const groupBy = groupOptions?.columns || [];
const data = matrixTableGroupedData(list || [], groupBy, closedGroups, listConfig || []);
const filteredData = data.filter(d => !(d.name === undefined && d.IS_GROUP_CONFIG));
const numberOfFilteredItems = filteredData.length;
const pageSize = 100; // Define your desired multiple
if (numberOfFilteredItems === 0) {
return filteredData.slice(0, 0);
}
if (scrollIndex <= pageSize) {
return filteredData.slice(0, pageSize);
}
itemsToDisplay = Math.ceil((scrollIndex + 1) / pageSize) * pageSize;
return filteredData.slice(0, itemsToDisplay);
}));
const groupedColumns$ = combineLatest([
groupOptions$,
listConfig$
]).pipe(map(([groupOptions, listConfig]) => (groupOptions?.columns?.filter((col) => !groupOptions.hiddenColumns?.includes(col)) || []).map(groupCol => ({
formControlName: groupCol,
label: listConfig?.find((config) => config.formControlName === groupCol)?.label || ''
}))));
return {
state$,
reportName$,
list$,
listConfig$,
closedGroups$,
isOpen$,
aggregates$,
groupOptions$,
tableColumns$,
displayedColumns$,
groupLabelConfig$,
getGroupedData$,
groupedColumns$,
groupChart$,
draggedGroup$,
scrollIndex$
};
}
function NgxTMatrixTableStoreActions(store) {
return {
setReportName: store.updater((state, reportName) => ({
...state,
reportName
})),
setList: store.updater((state, list) => ({
...state,
list
})),
setListConfig: store.updater((state, listConfig) => ({
...state,
listConfig
})),
setAggregates: store.updater((state, aggregates) => ({
...state,
aggregates
})),
setGroupOptions: store.updater((state, groupOptions) => ({
...state,
groupOptions
})),
toggleMenu: store.updater((state, row) => ({
...state,
isOpen: _isEqual(state.isOpen, row) ? undefined : row
})),
setGroupChart: store.updater((state, groupChart) => ({
...state,
groupChart
})),
setDraggedGroup: store.updater((state, draggedGroup) => ({
...state,
draggedGroup
})),
setScrollIndex: store.updater((state, scrollIndex) => ({
...state,
scrollIndex
})),
toggleCloseGroup: store.updater((state, name) => {
const closedGroups = state.closedGroups || [];
const isClosed = closedGroups.includes(name);
return {
...state,
closedGroups: isClosed ? closedGroups.filter(g => g !== name) : [...closedGroups, name]
};
}),
removeGroupColumn: store.updater((state, columnName) => {
return {
...state,
groupOptions: state.groupOptions ? {
...state.groupOptions,
columns: state.groupOptions.columns?.filter((col) => col !== columnName)
} : null
};
}),
dropGroupColumn: store.updater((state) => {
const newGroup = state.draggedGroup;
const doesNotInclude = !state.groupOptions?.columns?.includes(newGroup || '');
if (doesNotInclude && newGroup !== undefined && newGroup !== '') {
return {
...state,
groupOptions: {
...state.groupOptions,
columns: [...(state.groupOptions?.columns || []), newGroup]
}
};
}
return state;
}),
dropGroupingToSortGroups: store.updater((state, event) => {
const newColumns = [...(state.groupOptions?.columns || [])];
moveItemInArray(newColumns, event.previousIndex, event.currentIndex);
return {
...state,
groupOptions: {
...state.groupOptions,
columns: newColumns
}
};
}),
addToGrouping: store.updater((state, formControlName) => {
let columns = state.groupOptions?.columns || [];
if (!columns.includes(formControlName)) {
columns = [...columns, formControlName];
}
else {
columns = columns.filter((col) => col !== formControlName);
}
return {
...state,
groupOptions: {
...state.groupOptions,
columns
}
};
})
};
}
function toCamelCase(arrayOfObjects) {
return arrayOfObjects.map((obj) => {
const newObj = {};
Object.keys(obj).forEach((key) => {
const newKey = ''.concat(key);
const camelCaseKey = camelize(newKey);
newObj[camelCaseKey] = obj[key];
});
return newObj;
});
}
function camelize(str) {
if (!!str) {
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
})
.replace(/\s+/g, '');
}
else {
return;
}
}
const getFileName = (name) => {
let timeSpan = new Date().toISOString();
let sheetName = name || 'ExportResult';
let fileName = `${sheetName}-${timeSpan}`;
return {
sheetName,
fileName,
};
};
class TableUtil {
static exportTableToExcel(tableId, name) {
let { sheetName, fileName } = getFileName(name);
let targetTableElm = document.getElementById(tableId);
let wb = XLSX.utils.table_to_book(targetTableElm, {
sheet: sheetName,
});
XLSX.writeFile(wb, `${fileName}.xlsx`);
}
static exportArrayToExcel(arr, name) {
const { sheetName, fileName } = getFileName(name);
const wb = XLSX.utils.book_new();
// Process the main array
const mainArray = arr.map(item => this.processSimpleObject(item));
const mainSheet = XLSX.utils.json_to_sheet(mainArray);
XLSX.utils.book_append_sheet(wb, mainSheet, this.sanitizeSheetName(sheetName));
// Process nested arrays
arr.forEach((item, index) => {
const arrayProperties = this.findArrayProperties(item);
arrayProperties.forEach((arrayData, propertyName) => {
// Only process if the array contains data
if (arrayData.length > 0) {
// Create a sheet name combining property name and parent index
const nestedSheetName = this.sanitizeSheetName(`${propertyName}_${index + 1}`);
// Process the nested array items
const processedArray = arrayData.map(nestedItem => {
if (typeof nestedItem === 'object' && nestedItem !== null) {
return this.processSimpleObject(nestedItem);
}
// If it's a primitive value, wrap it in an object
return { value: nestedItem };
});
// Create and append the sheet if it has data
if (processedArray.length > 0) {
const nestedSheet = XLSX.utils.json_to_sheet(processedArray);
try {
XLSX.utils.book_append_sheet(wb, nestedSheet, nestedSheetName);
}
catch (e) {
const msg = e instanceof Error ? e.message : 'Unknown error';
console.warn(`Could not create sheet for ${nestedSheetName}: ${msg}`);
}
}
}
});
});
// Write the workbook to file
XLSX.writeFile(wb, `${fileName}.xlsx`);
}
static onFileChange(event) {
return new Promise((resolve, reject) => {
try {
const target = event.target;
if (target.files.length !== 1)
throw new Error('Cannot use multiple files');
const reader = new FileReader();
reader.onload = (e) => {
const bstr = e.target.result;
const workbook = XLSX.read(bstr, { type: 'binary' });
const firstSheetName = workbook.SheetNames[0];
if (!firstSheetName)
throw new Error('No sheet found');
const worksheet = workbook.Sheets[firstSheetName];
if (!worksheet)
throw new Error('No worksheet found');
const arrayData = XLSX.utils.sheet_to_json(worksheet, { raw: false });
// Iterate through each row and format date cells
arrayData.forEach((row) => {
for (const key in row) {
if (row.hasOwnProperty(key) &&
typeof row[key] === 'number' &&
XLSX.SSF.isValidDate(row[key])) {
row[key] = XLSX.SSF.format_date(row[key], 'yyyy-mm-dd');
}
}
});
// Do something with arrayData
resolve(toCamelCase(arrayData));
};
reader.onerror = (e) => reject(e);
const file = target.files[0];
if (!fi