diff --git a/angular.json b/angular.json
index c05451e7d..2913f8855 100644
--- a/angular.json
+++ b/angular.json
@@ -73,7 +73,7 @@
"stylePreprocessorOptions": {
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
},
- "scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"],
+ "scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js", "node_modules/chart.js/dist/chart.js"],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts
index 92aa673d4..79238e28e 100644
--- a/apps/red-ui/src/app/modules/admin/admin.module.ts
+++ b/apps/red-ui/src/app/modules/admin/admin.module.ts
@@ -60,6 +60,7 @@ import {
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info-dialog.component';
+import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
const dialogs = [
AddEditCloneDossierTemplateDialogComponent,
@@ -131,6 +132,7 @@ const components = [
IqserSharedModule,
IqserHelpModeModule,
IqserPermissionsModule,
+ DonutChartComponent,
],
})
export class AdminModule {}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html
deleted file mode 100644
index b1f569bc3..000000000
--- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss
deleted file mode 100644
index c2ef9aba1..000000000
--- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss
+++ /dev/null
@@ -1,103 +0,0 @@
-.ngx-charts {
- float: left;
- overflow: visible;
-
- .circle,
- .bar,
- .arc {
- cursor: pointer;
- }
-
- .bar,
- .cell,
- .arc,
- .card {
- &.active,
- &:hover {
- opacity: 0.8;
- transition: opacity 100ms ease-in-out;
- }
-
- &:focus {
- outline: none;
- }
-
- &.hidden {
- display: none;
- }
- }
-
- g {
- &:focus {
- outline: none;
- }
- }
-
- .line-series,
- .line-series-range,
- .area-series {
- &.inactive {
- transition: opacity 100ms ease-in-out;
- opacity: 0.2;
- }
- }
-
- .line-highlight {
- display: none;
-
- &.active {
- display: block;
- }
- }
-
- .area {
- opacity: 0.6;
- }
-
- .circle {
- &:hover {
- cursor: pointer;
- }
- }
-
- .label {
- font-size: 12px;
- font-weight: normal;
- }
-
- .tooltip-anchor {
- fill: var(--iqser-text);
- }
-
- .gridline-path {
- stroke: #ddd;
- stroke-width: 1;
- fill: none;
- }
-
- .grid-panel {
- rect {
- fill: none;
- }
-
- &.odd {
- rect {
- fill: rgba(0, 0, 0, 0.05);
- }
- }
- }
-
- fill: var(--iqser-text);
-}
-
-.chart-legend .legend-labels {
- background: var(--iqser-alt-background) !important;
-
- .legend-label .legend-label-text {
- color: var(--iqser-text) !important;
-
- &:hover {
- color: rgba(var(--iqser-text-rgb), 0.8) !important;
- }
- }
-}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts
deleted file mode 100644
index 58db4f019..000000000
--- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts
+++ /dev/null
@@ -1,384 +0,0 @@
-import { Component, EventEmitter, HostListener, Input, Output, ViewEncapsulation } from '@angular/core';
-
-import { curveLinear } from 'd3-shape';
-import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale';
-import {
- BaseChartComponent,
- calculateViewDimensions,
- Color,
- ColorHelper,
- LegendPosition,
- Orientation,
- ScaleType,
- Series,
- StringOrNumberOrDate,
- ViewDimensions,
-} from '@swimlane/ngx-charts';
-
-@Component({
- // eslint-disable-next-line @angular-eslint/component-selector
- selector: 'combo-chart-component',
- templateUrl: './combo-chart.component.html',
- styleUrls: ['./combo-chart.component.scss'],
- encapsulation: ViewEncapsulation.None,
-})
-export class ComboChartComponent extends BaseChartComponent {
- @Input() curve: any = curveLinear;
- @Input() legend = false;
- @Input() legendTitle = 'Legend';
- @Input() legendPosition: LegendPosition = LegendPosition.Right;
- @Input() xAxis;
- @Input() yAxis;
- @Input() showXAxisLabel;
- @Input() showYAxisLabel;
- @Input() showRightYAxisLabel;
- @Input() xAxisLabel;
- @Input() yAxisLabel;
- @Input() yAxisLabelRight;
- @Input() tooltipDisabled = false;
- @Input() gradient: boolean;
- @Input() showGridLines = true;
- @Input() activeEntries: any[] = [];
- @Input() schemeType: ScaleType;
- @Input() yAxisTickFormatting: any;
- @Input() yRightAxisTickFormatting: any;
- @Input() roundDomains = false;
- @Input() colorSchemeLine: Color;
- @Input() autoScale;
- @Input() lineChart: Series[];
- @Input() yLeftAxisScaleFactor: any;
- @Input() yRightAxisScaleFactor: any;
- @Input() rangeFillOpacity: number;
- @Input() animations = true;
- @Input() noBarWhenZero = true;
- @Output() activate = new EventEmitter<{ value; entries: unknown[] }>();
- @Output() deactivate = new EventEmitter<{ value; entries: unknown[] }>();
- dims: ViewDimensions;
- xScale: any;
- yScale: any;
- xDomain: string[] | number[];
- yDomain: string[] | number[];
- transform: string;
- colors: ColorHelper;
- colorsLine: ColorHelper;
- margin = [10, 20, 10, 20];
- xAxisHeight = 0;
- yAxisWidth = 0;
- legendOptions: any;
- scaleType: ScaleType = ScaleType.Linear;
- xScaleLine;
- yScaleLine;
- xDomainLine;
- yDomainLine;
- seriesDomain;
- combinedSeries: Series[];
- xSet;
- filteredDomain;
- hoveredVertical;
- yOrientLeft: Orientation = Orientation.Left;
- yOrientRight: Orientation = Orientation.Right;
- legendSpacing = 0;
- bandwidth: number;
- barPadding = 8;
-
- @Input() xAxisTickFormatting: any;
-
- trackBy(_index, item): string {
- return item.name;
- }
-
- update(): void {
- super.update();
- this.dims = calculateViewDimensions({
- width: this.width,
- height: this.height,
- margins: this.margin,
- showXAxis: this.xAxis,
- showYAxis: this.yAxis,
- xAxisHeight: this.xAxisHeight,
- yAxisWidth: this.yAxisWidth,
- showXLabel: this.showXAxisLabel,
- showYLabel: this.showYAxisLabel,
- showLegend: this.legend,
- legendType: this.schemeType,
- legendPosition: this.legendPosition,
- });
-
- if (!this.yAxis) {
- this.legendSpacing = 0;
- } else if (this.showYAxisLabel && this.yAxis) {
- this.legendSpacing = 150;
- } else {
- this.legendSpacing = 40;
- }
- this.xScale = this.getXScale();
- this.yScale = this.getYScale();
-
- // line chart
- this.xDomainLine = this.getXDomainLine();
- if (this.filteredDomain) {
- this.xDomainLine = this.filteredDomain;
- }
-
- this.yDomainLine = this.getYDomainLine();
- this.seriesDomain = this.getSeriesDomain();
-
- this.scaleLines();
-
- this.setColors();
- this.legendOptions = this.getLegendOptions();
-
- this.transform = `translate(${this.dims.xOffset} , ${this.margin[0]})`;
- }
-
- deactivateAll() {
- this.activeEntries = [...this.activeEntries];
- for (const entry of this.activeEntries) {
- this.deactivate.emit({ value: entry, entries: [] });
- }
- this.activeEntries = [];
- }
-
- @HostListener('mouseleave')
- hideCircles(): void {
- this.hoveredVertical = null;
- this.deactivateAll();
- }
-
- updateHoveredVertical(item): void {
- this.hoveredVertical = item.value;
- this.deactivateAll();
- }
-
- scaleLines() {
- this.xScaleLine = this.getXScaleLine(this.xDomainLine, this.dims.width);
- this.yScaleLine = this.getYScaleLine(this.yDomainLine, this.dims.height);
- }
-
- getSeriesDomain(): any[] {
- this.combinedSeries = this.lineChart.slice(0);
- this.combinedSeries.push({
- name: this.yAxisLabel,
- series: this.results,
- });
- return this.combinedSeries.map(d => d.name);
- }
-
- isDate(value): value is Date {
- return value instanceof Date;
- }
-
- getScaleType(values): ScaleType {
- let date = true;
- let num = true;
-
- for (const value of values) {
- if (!this.isDate(value)) {
- date = false;
- }
-
- if (typeof value !== 'number') {
- num = false;
- }
- }
-
- if (date) {
- return ScaleType.Time;
- }
- if (num) {
- return ScaleType.Linear;
- }
- return ScaleType.Ordinal;
- }
-
- getXDomainLine(): any[] {
- let values: StringOrNumberOrDate[] = [];
-
- for (const results of this.lineChart) {
- for (const d of results.series) {
- if (!values.includes(d.name)) {
- values.push(d.name);
- }
- }
- }
-
- this.scaleType = this.getScaleType(values);
- let domain = [];
-
- if (this.scaleType === 'time') {
- const min = Math.min(...(values as number[]));
- const max = Math.max(...(values as number[]));
- domain = [min, max];
- } else if (this.scaleType === 'linear') {
- values = values.map(v => Number(v));
- const min = Math.min(...(values as number[]));
- const max = Math.max(...(values as number[]));
- domain = [min, max];
- } else {
- domain = values;
- }
-
- this.xSet = values;
- return domain;
- }
-
- getYDomainLine(): any[] {
- const domain: number[] = [];
-
- for (const results of this.lineChart) {
- for (const d of results.series) {
- if (domain.indexOf(d.value) < 0) {
- domain.push(d.value);
- }
- if (d.min !== undefined) {
- if (domain.indexOf(d.min) < 0) {
- domain.push(d.min);
- }
- }
- if (d.max !== undefined) {
- if (domain.indexOf(d.max) < 0) {
- domain.push(d.max);
- }
- }
- }
- }
-
- let min = Math.min(...domain);
- const max = Math.max(...domain);
- if (this.yRightAxisScaleFactor) {
- const minMax = this.yRightAxisScaleFactor(min, max);
- return [Math.min(0, minMax.min as number), minMax.max];
- }
- min = Math.min(0, min);
- return [min, max];
- }
-
- getXScaleLine(domain, width: number): any {
- let scale;
- if (this.bandwidth === undefined) {
- this.bandwidth = width - this.barPadding;
- }
- const offset = Math.floor((width + this.barPadding - (this.bandwidth + this.barPadding) * domain.length) / 2);
-
- if (this.scaleType === 'time') {
- scale = scaleTime().range([0, width]).domain(domain);
- } else if (this.scaleType === 'linear') {
- scale = scaleLinear().range([0, width]).domain(domain);
-
- if (this.roundDomains) {
- scale = scale.nice();
- }
- } else if (this.scaleType === 'ordinal') {
- scale = scalePoint()
- .range([offset + this.bandwidth / 2, width - offset - this.bandwidth / 2])
- .domain(domain);
- }
-
- return scale;
- }
-
- getYScaleLine(domain, height): any {
- const scale = scaleLinear().range([height, 0]).domain(domain);
- return this.roundDomains ? scale.nice() : scale;
- }
-
- getXScale(): any {
- this.xDomain = this.getXDomain();
- const spacing = this.xDomain.length / (this.dims.width / this.barPadding + 1);
- return scaleBand().range([0, this.dims.width]).paddingInner(spacing).domain(this.xDomain);
- }
-
- getYScale(): any {
- this.yDomain = this.getYDomain();
- const scale = scaleLinear().range([this.dims.height, 0]).domain(this.yDomain);
- return this.roundDomains ? scale.nice() : scale;
- }
-
- getXDomain(): any[] {
- return this.results.map(d => d.name);
- }
-
- getYDomain() {
- const values: number[] = this.results.map(d => d.value);
- const min = Math.min(0, ...values);
- const max = Math.max(...values);
- if (this.yLeftAxisScaleFactor) {
- const minMax = this.yLeftAxisScaleFactor(min, max);
- return [Math.min(0, minMax.min as number), minMax.max];
- }
- return [min, max];
- }
-
- onClick(data) {
- this.select.emit(data);
- }
-
- setColors(): void {
- let domain: number[] | string[];
- if (this.schemeType === 'ordinal') {
- domain = this.xDomain;
- } else {
- domain = this.yDomain;
- }
- this.colors = new ColorHelper(this.scheme, this.schemeType, domain, this.customColors);
- this.colorsLine = new ColorHelper(this.colorSchemeLine, this.schemeType, domain, this.customColors);
- }
-
- getLegendOptions() {
- const opts = {
- scaleType: this.schemeType,
- colors: undefined,
- domain: [],
- title: undefined,
- position: this.legendPosition,
- };
- if (opts.scaleType === 'ordinal') {
- opts.domain = this.seriesDomain;
- opts.colors = this.colorsLine;
- opts.title = this.legendTitle;
- } else {
- opts.domain = this.seriesDomain;
- opts.colors = this.colors.scale;
- }
- return opts;
- }
-
- getValuesMaxLength(index: number): number {
- const values = this.lineChart[index].series.map(s => s.value);
- return Math.max(...values).toString().length;
- }
-
- updateLineWidth(width): void {
- this.bandwidth = width;
- this.scaleLines();
- }
-
- updateYAxisWidth({ width }: { width: number }): void {
- this.yAxisWidth = width + 20;
- this.update();
- }
-
- updateXAxisHeight({ height }): void {
- this.xAxisHeight = height;
- this.update();
- }
-
- onActivate(item) {
- const idx = this.activeEntries.findIndex(d => d.name === item.name && d.value === item.value && d.series === item.series);
- if (idx > -1) {
- return;
- }
-
- this.activeEntries = [item, ...this.activeEntries];
- this.activate.emit({ value: item, entries: this.activeEntries });
- }
-
- onDeactivate(item) {
- const idx = this.activeEntries.findIndex(d => d.name === item.name && d.value === item.value && d.series === item.series);
-
- this.activeEntries.splice(idx, 1);
- this.activeEntries = [...this.activeEntries];
-
- this.deactivate.emit({ value: item, entries: this.activeEntries });
- }
-}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts
deleted file mode 100644
index b8d9fe052..000000000
--- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
-import { animate, style, transition, trigger } from '@angular/animations';
-import { Bar, BarOrientation, formatLabel, PlacementTypes, StyleTypes } from '@swimlane/ngx-charts';
-
-@Component({
- // eslint-disable-next-line @angular-eslint/component-selector
- selector: 'g[ngx-combo-charts-series-vertical]',
- template: `
-
- `,
- changeDetection: ChangeDetectionStrategy.OnPush,
- animations: [
- trigger('animationState', [
- transition('* => void', [
- style({
- opacity: 1,
- transform: '*',
- }),
- animate(500, style({ opacity: 0, transform: 'scale(0)' })),
- ]),
- ]),
- ],
-})
-export class ComboSeriesVerticalComponent implements OnChanges {
- @Input() dims;
- @Input() type = 'standard';
- @Input() series;
- @Input() seriesLine;
- @Input() xScale;
- @Input() yScale;
- @Input() colors;
- @Input() tooltipDisabled = false;
- @Input() gradient: boolean;
- @Input() activeEntries: any[];
- @Input() seriesName: string;
- @Input() animations = true;
- @Input() noBarWhenZero = true;
-
- @Output() activate = new EventEmitter();
- @Output() deactivate = new EventEmitter();
- @Output() bandwidth = new EventEmitter();
-
- bars: any;
- x: any;
- y: any;
- readonly tooltipTypes = StyleTypes;
- readonly tooltipPlacements = PlacementTypes;
- readonly orientations = BarOrientation;
-
- ngOnChanges(): void {
- this.update();
- }
-
- update(): void {
- let width;
- if (this.series.length) {
- width = this.xScale.bandwidth();
- this.bandwidth.emit(width);
- }
-
- let d0 = 0;
- let total;
- if (this.type === 'normalized') {
- total = this.series.map(d => d.value).reduce((sum: number, d: number) => sum + d, 0);
- }
-
- this.bars = this.series.map((d, index) => {
- let value: number = d.value;
- const label = d.name;
- const formattedLabel = formatLabel(label);
- const roundEdges = this.type === 'standard';
-
- const bar: Bar = {
- value,
- label,
- roundEdges,
- data: d,
- width,
- formattedLabel,
- height: 0,
- x: 0,
- y: 0,
- ariaLabel: label,
- tooltipText: label,
- color: undefined,
- gradientStops: undefined,
- };
-
- let offset0 = d0;
- let offset1 = offset0 + value;
-
- if (this.type === 'standard') {
- bar.height = Math.abs(this.yScale(value) - this.yScale(0));
- bar.x = this.xScale(label);
-
- if (value < 0) {
- bar.y = this.yScale(0);
- } else {
- bar.y = this.yScale(value);
- }
- } else if (this.type === 'stacked') {
- d0 += value;
-
- bar.height = this.yScale(offset0) - this.yScale(offset1);
- bar.x = 0;
- bar.y = this.yScale(offset1);
- // bar.offset0 = offset0;
- // bar.offset1 = offset1;
- } else if (this.type === 'normalized') {
- d0 += value;
-
- if (total > 0) {
- offset0 = (offset0 * 100) / total;
- offset1 = (offset1 * 100) / total;
- } else {
- offset0 = 0;
- offset1 = 0;
- }
-
- bar.height = this.yScale(offset0) - this.yScale(offset1);
- bar.x = 0;
- bar.y = this.yScale(offset1);
- // bar.offset0 = offset0;
- // bar.offset1 = offset1;
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- value = (offset1 - offset0).toFixed(2) + '%';
- }
-
- if (this.colors.scaleType === 'ordinal') {
- bar.color = this.colors.getColor(label);
- } else {
- if (this.type === 'standard') {
- bar.color = this.colors.getColor(value);
- bar.gradientStops = this.colors.getLinearGradientStops(value);
- } else {
- bar.color = this.colors.getColor(offset1);
- bar.gradientStops = this.colors.getLinearGradientStops(offset1, offset0);
- }
- }
-
- let tooltipLabel = formattedLabel;
- if (this.seriesName) {
- tooltipLabel = `${this.seriesName} • ${formattedLabel}`;
- }
-
- this.getSeriesTooltips(this.seriesLine, index);
- const lineValue: string = this.seriesLine[0].series[index].value;
- bar.tooltipText = `
- ${tooltipLabel}
-
- Y1 - ${value.toLocaleString()} • Y2 - ${lineValue.toLocaleString()}%
-
- `;
-
- return bar;
- });
- }
-
- getSeriesTooltips(seriesLine, index) {
- return seriesLine.map(d => d.series[index]);
- }
-
- isActive(entry): boolean {
- if (!this.activeEntries) {
- return false;
- }
- const item = this.activeEntries.find(d => entry.name === d.name && entry.series === d.series);
- return item !== undefined;
- }
-
- trackBy(_index, bar): string {
- return bar.label;
- }
-}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts
deleted file mode 100644
index 07ef678f5..000000000
--- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './combo-chart.component';
-export * from './combo-series-vertical.component';
-export * from './y-axis.component';
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/y-axis.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/y-axis.component.ts
deleted file mode 100644
index 91690fea4..000000000
--- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/y-axis.component.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { Component, Input, Output, EventEmitter, OnChanges, ViewChild, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
-import { Orientation, ViewDimensions, YAxisTicksComponent } from '@swimlane/ngx-charts';
-
-@Component({
- selector: 'g[red-ngx-charts-y-axis]',
- template: `
-
-
-
-
- `,
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class YAxisComponent implements OnChanges {
- @Input() yScale;
- @Input() dims: ViewDimensions;
- @Input() trimTicks: boolean;
- @Input() maxTickLength: number;
- @Input() tickFormatting;
- @Input() ticks: any[];
- @Input() showGridLines = false;
- @Input() showLabel: boolean;
- @Input() labelText: string;
- @Input() yAxisTickCount: any;
- @Input() yOrient: Orientation = Orientation.Left;
- @Input() referenceLines;
- @Input() showRefLines: boolean;
- @Input() showRefLabels: boolean;
- @Input() yAxisOffset = 0;
- @Input() valuesMaxLength = 0;
- @Output() dimensionsChanged = new EventEmitter();
-
- yAxisClassName = 'y axis';
- tickArguments: number[];
- offset: number;
- transform: string;
- labelOffset = 15;
- fill = 'none';
- stroke = '#CCC';
- tickStroke = '#CCC';
- strokeWidth = 1;
- padding = 5;
-
- @ViewChild(YAxisTicksComponent) ticksComponent: YAxisTicksComponent;
-
- ngOnChanges(changes: SimpleChanges): void {
- this.update();
- }
-
- update(): void {
- this.offset = -(this.yAxisOffset + this.padding);
- if (this.yOrient === Orientation.Right) {
- this.labelOffset = 65 + (this.valuesMaxLength + this.valuesMaxLength / 4) * 5;
- this.transform = `translate(${this.offset + this.dims.width} , 0)`;
- } else {
- this.transform = `translate(${this.offset} , 0)`;
- }
-
- if (this.yAxisTickCount !== undefined) {
- this.tickArguments = [this.yAxisTickCount];
- }
- }
-
- emitTicksWidth({ width }): void {
- if (width !== this.labelOffset && this.yOrient === Orientation.Right) {
- this.labelOffset = width + this.labelOffset + 300;
- setTimeout(() => {
- this.dimensionsChanged.emit({ width });
- }, 0);
- } else if (width !== this.labelOffset) {
- this.labelOffset = width + (this.valuesMaxLength + this.valuesMaxLength / 4) * 5;
- setTimeout(() => {
- this.dimensionsChanged.emit({ width });
- }, 0);
- }
- }
-}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.html
new file mode 100644
index 000000000..c879845d0
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.html
@@ -0,0 +1,3 @@
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts
new file mode 100644
index 000000000..ff1dc90f1
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts
@@ -0,0 +1,89 @@
+import { Component, Input, OnChanges } from '@angular/core';
+import { Chart, ChartConfiguration, ChartDataset } from 'chart.js';
+import { Debounce } from '@iqser/common-ui';
+
+@Component({
+ selector: 'redaction-chart [datasets] [labels] [chartId]',
+ templateUrl: './chart.component.html',
+ styleUrls: ['./chart.component.scss'],
+})
+export class ChartComponent implements OnChanges {
+ @Input() datasets: ChartDataset[];
+ @Input() labels: string[];
+ @Input() valueFormatter?: (value: number) => string;
+ @Input() secondaryAxis = false;
+ @Input() yAxisLabel?: string;
+ @Input() yAxisLabelRight?: string;
+ @Input() reverseLegend = false;
+ @Input() chartId: string;
+
+ chartData: ChartConfiguration['data'];
+ chartOptions: ChartConfiguration<'line'>['options'] = {};
+ chart: Chart;
+
+ @Debounce(0)
+ ngOnChanges() {
+ this.chartData = {
+ labels: this.labels,
+ datasets: this.datasets,
+ };
+ this.#setChartOptions();
+ }
+
+ #setChartOptions(): void {
+ this.chartOptions = {
+ responsive: false,
+ scales: {
+ y: {
+ stacked: true,
+ ticks: {
+ callback: this.valueFormatter ? (value: number) => this.valueFormatter(value) : undefined,
+ count: 9,
+ },
+ title: {
+ display: !!this.yAxisLabel,
+ text: this.yAxisLabel,
+ padding: { bottom: 20 },
+ },
+ min: 0,
+ },
+ y1: {
+ display: this.secondaryAxis,
+ position: 'right',
+ ticks: {
+ callback: this.valueFormatter ? (value: number) => this.valueFormatter(value) : undefined,
+ count: 9,
+ },
+ title: {
+ display: !!this.yAxisLabelRight,
+ text: this.yAxisLabelRight,
+ padding: { bottom: 20 },
+ },
+ min: 0,
+ },
+ },
+ plugins: {
+ legend: { position: 'right', reverse: this.reverseLegend, maxWidth: 280 },
+ tooltip: {
+ callbacks: {
+ label: this.valueFormatter ? item => `${item.dataset.label}: ${this.valueFormatter(item.parsed.y)}` : undefined,
+ },
+ },
+ },
+ layout: {
+ padding: {
+ top: 50,
+ bottom: 50,
+ },
+ },
+ datasets: { bar: { barPercentage: 0.7 } },
+ aspectRatio: 2.5,
+ };
+
+ this.chart = new Chart(this.chartId, {
+ type: 'line',
+ data: this.chartData,
+ options: this.chartOptions,
+ });
+ }
+}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.html
new file mode 100644
index 000000000..eb78b00ed
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ {{ licenseService.selectedLicenseReport.analysedFilesBytes | size }}
+ = 0">
+ ({{ licenseService.analysisCapacityBytesForSelectedLicensePercentage | number : '1.0-2' }}%)
+
+
+
+
+
+
+
+ {{ licenseService.allLicensesReport.analysedFilesBytes | size }}
+
+
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.scss
new file mode 100644
index 000000000..e91dc2856
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.scss
@@ -0,0 +1,48 @@
+:host {
+ display: contents;
+}
+
+.grid-container {
+ display: grid;
+ grid-template-columns: 250px 300px 1fr;
+ margin: 20px;
+
+ .row {
+ display: contents;
+
+ > div {
+ padding: 8px 20px;
+
+ &:first-of-type {
+ font-weight: 600;
+ }
+
+ &:nth-child(2) {
+ grid-column: span 2;
+ }
+ }
+
+ &:hover {
+ > div {
+ background-color: var(--iqser-alt-background);
+ }
+ }
+ }
+
+ .section-title {
+ grid-column: span 3;
+ padding: 20px 20px 8px;
+ margin-bottom: 8px;
+ border-bottom: 1px solid var(--iqser-separator);
+ }
+
+ .chart-row {
+ > div {
+ grid-column: span 3;
+ }
+
+ &:hover > div {
+ background-color: var(--iqser-background);
+ }
+ }
+}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.ts
new file mode 100644
index 000000000..d50c7fdd1
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-analysis-capacity-usage/license-analysis-capacity-usage.component.ts
@@ -0,0 +1,71 @@
+import { Component } from '@angular/core';
+import { LicenseService } from '@services/license.service';
+import { map } from 'rxjs/operators';
+import { ChartDataset } from 'chart.js';
+import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
+import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
+import { TranslateService } from '@ngx-translate/core';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import { size } from '@iqser/common-ui';
+
+@Component({
+ selector: 'red-license-analysis-capacity-usage',
+ templateUrl: './license-analysis-capacity-usage.component.html',
+ styleUrls: ['./license-analysis-capacity-usage.component.scss'],
+})
+export class LicenseAnalysisCapacityUsageComponent {
+ readonly data$ = this.licenseService.selectedLicense$.pipe(map(() => this.#getData()));
+
+ constructor(readonly licenseService: LicenseService, private readonly _translateService: TranslateService) {}
+
+ #getData() {
+ const yAxisLabel = _('license-info-screen.analysis-capacity-usage.analyzed-per-month');
+ const yAxisLabelRight = _('license-info-screen.analysis-capacity-usage.total-analyzed-data');
+
+ return {
+ datasets: this.#getCapacityDatasets(),
+ labels: getLabelsFromLicense(this.licenseService.selectedLicenseReport),
+ yAxisLabel: this._translateService.instant(yAxisLabel),
+ yAxisLabelRight: this._translateService.instant(yAxisLabelRight),
+ valueFormatter: (value: number) => size(value),
+ };
+ }
+
+ #getCapacityDatasets(): ChartDataset[] {
+ const monthlyData = this.licenseService.selectedLicenseReport.monthlyData;
+
+ const datasets: ChartDataset[] = [
+ {
+ data: monthlyData.flatMap(d => d.analysedFilesBytes),
+ label: this._translateService.instant('license-info-screen.analysis-capacity-usage.analyzed-per-month'),
+ type: 'bar',
+ backgroundColor: ChartBlue,
+ borderColor: ChartBlue,
+ order: 2,
+ },
+
+ {
+ data: monthlyData.map(
+ (month, monthIndex) =>
+ month.analysedFilesBytes + monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + curr.analysedFilesBytes, 0),
+ ),
+ label: this._translateService.instant('license-info-screen.analysis-capacity-usage.analyzed-cumulative'),
+ yAxisID: 'y1',
+ order: 1,
+ ...getLineConfig(ChartGreen, true, false),
+ },
+ ];
+
+ if (this.licenseService.analysisCapacityBytes > 0) {
+ datasets.push({
+ data: monthlyData.flatMap(() => this.licenseService.analysisCapacityBytes),
+ label: this._translateService.instant('license-info-screen.analysis-capacity-usage.licensed'),
+ ...getLineConfig(ChartRed, true, false),
+ yAxisID: 'y1',
+ order: 1,
+ });
+ }
+
+ return datasets;
+ }
+}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.html
new file mode 100644
index 000000000..bd83c0bb1
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ {{ licenseService.selectedLicenseReport.numberOfAnalyzedPages }}
+ 0">
+ ({{ licenseService.analyzedPagesPercentageForSelectedLicensePercentage | number : '1.0-2' }}%)
+
+
+
+
+
+
+
{{ licenseService.selectedLicenseReport.numberOfOcrPages }}
+
+
+
+
+
{{ licenseService.unlicensedPages }}
+
+
+
+
+
{{ licenseService.allLicensesReport.numberOfAnalyzedPages }}
+
+
+
+
+
{{ licenseService.allLicensesReport.numberOfOcrPages }}
+
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.scss
new file mode 100644
index 000000000..e91dc2856
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.scss
@@ -0,0 +1,48 @@
+:host {
+ display: contents;
+}
+
+.grid-container {
+ display: grid;
+ grid-template-columns: 250px 300px 1fr;
+ margin: 20px;
+
+ .row {
+ display: contents;
+
+ > div {
+ padding: 8px 20px;
+
+ &:first-of-type {
+ font-weight: 600;
+ }
+
+ &:nth-child(2) {
+ grid-column: span 2;
+ }
+ }
+
+ &:hover {
+ > div {
+ background-color: var(--iqser-alt-background);
+ }
+ }
+ }
+
+ .section-title {
+ grid-column: span 3;
+ padding: 20px 20px 8px;
+ margin-bottom: 8px;
+ border-bottom: 1px solid var(--iqser-separator);
+ }
+
+ .chart-row {
+ > div {
+ grid-column: span 3;
+ }
+
+ &:hover > div {
+ background-color: var(--iqser-background);
+ }
+ }
+}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.ts
new file mode 100644
index 000000000..b76d30d21
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-page-usage/license-page-usage.component.ts
@@ -0,0 +1,64 @@
+import { Component } from '@angular/core';
+import { LicenseService } from '@services/license.service';
+import { map } from 'rxjs/operators';
+import { ChartDataset } from 'chart.js';
+import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
+import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
+import { TranslateService } from '@ngx-translate/core';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+
+@Component({
+ selector: 'red-license-page-usage',
+ templateUrl: './license-page-usage.component.html',
+ styleUrls: ['./license-page-usage.component.scss'],
+})
+export class LicensePageUsageComponent {
+ readonly data$ = this.licenseService.selectedLicense$.pipe(map(() => this.#getData()));
+
+ constructor(readonly licenseService: LicenseService, private readonly _translateService: TranslateService) {}
+
+ #getData() {
+ const yAxisLabel = _('license-info-screen.page-usage.pages-per-month');
+ const yAxisLabelRight = _('license-info-screen.page-usage.total-pages');
+
+ return {
+ datasets: this.#getPagesDatasets(),
+ labels: getLabelsFromLicense(this.licenseService.selectedLicenseReport),
+ yAxisLabel: this._translateService.instant(yAxisLabel),
+ yAxisLabelRight: this._translateService.instant(yAxisLabelRight),
+ };
+ }
+
+ #getPagesDatasets(): ChartDataset[] {
+ const monthlyData = this.licenseService.selectedLicenseReport.monthlyData;
+
+ return [
+ {
+ data: monthlyData.flatMap(d => d.numberOfAnalyzedPages),
+ label: this._translateService.instant('license-info-screen.page-usage.pages-per-month'),
+ type: 'bar',
+ backgroundColor: ChartBlue,
+ borderColor: ChartBlue,
+ order: 2,
+ },
+ {
+ data: monthlyData.flatMap(() => this.licenseService.totalLicensedNumberOfPages),
+ label: this._translateService.instant('license-info-screen.page-usage.total-pages'),
+ ...getLineConfig(ChartRed, true, false),
+ yAxisID: 'y1',
+ order: 1,
+ },
+ {
+ data: monthlyData.map(
+ (month, monthIndex) =>
+ month.numberOfAnalyzedPages +
+ monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + curr.numberOfAnalyzedPages, 0),
+ ),
+ label: this._translateService.instant('license-info-screen.page-usage.cumulative-pages'),
+ yAxisID: 'y1',
+ order: 1,
+ ...getLineConfig(ChartGreen, true, false),
+ },
+ ];
+ }
+}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.html
new file mode 100644
index 000000000..4d92e71d8
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+
{{ licenseService.selectedLicenseReport.activeFilesUploadedBytes | size }}
+
+
+
+
+
{{ licenseService.selectedLicenseReport.archivedFilesUploadedBytes | size }}
+
+
+
+
{{ licenseService.selectedLicenseReport.trashFilesUploadedBytes | size }}
+
+
+
+
+
+ {{ licenseService.selectedLicenseReport.totalFilesUploadedBytes | size }}
+ = 0">
+ ({{ licenseService.retentionCapacityBytesForSelectedLicensePercentage | number : '1.0-2' }}%)
+
+
+
+
+
+ 0"
+ [config]="donutChartConfig$ | async"
+ [direction]="'row'"
+ [radius]="80"
+ [strokeWidth]="15"
+ [subtitleTemplate]="capacitySubtitles"
+ [totalType]="'sum'"
+ [valueFormatter]="size"
+ >
+
+
+
+
+
+
+
+ {{ 'license-info-screen.retention-capacity-usage.storage-capacity' | translate }}
+
+
+
+
+ {{ 'license-info-screen.retention-capacity-usage.exceeded-capacity' | translate }}
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.scss
new file mode 100644
index 000000000..3863510aa
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.scss
@@ -0,0 +1,48 @@
+:host {
+ display: contents;
+}
+
+.grid-container {
+ display: grid;
+ grid-template-columns: 250px 300px 1fr;
+ margin: 20px;
+
+ .donut-chart-wrapper {
+ grid-row: 2 / span 5;
+ grid-column: 3;
+ width: fit-content;
+ }
+
+ .row {
+ display: contents;
+
+ > div {
+ padding: 8px 20px;
+
+ &:first-of-type {
+ font-weight: 600;
+ }
+ }
+
+ &:not(.chart-row):hover {
+ > div {
+ background-color: var(--iqser-alt-background);
+ }
+ }
+ }
+
+ .section-title {
+ grid-column: span 3;
+ padding: 20px 20px 8px;
+ margin-bottom: 8px;
+ border-bottom: 1px solid var(--iqser-separator);
+ }
+
+ .chart-row > div {
+ grid-column: span 3;
+ }
+}
+
+.exceeded-capacity {
+ color: var(--iqser-red-1);
+}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.ts
new file mode 100644
index 000000000..f7227cbe2
--- /dev/null
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-retention-capacity-usage/license-retention-capacity.component.ts
@@ -0,0 +1,89 @@
+import { Component } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { LicenseService } from '@services/license.service';
+import { map } from 'rxjs/operators';
+import type { DonutChartConfig, ILicenseReport } from '@red/domain';
+import { ChartDataset } from 'chart.js';
+import { ChartBlack, ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants';
+import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import { size } from '@iqser/common-ui';
+
+@Component({
+ selector: 'red-license-retention-capacity',
+ templateUrl: './license-retention-capacity.component.html',
+ styleUrls: ['./license-retention-capacity.component.scss'],
+})
+export class LicenseRetentionCapacityComponent {
+ readonly formatSize = size;
+ readonly donutChartConfig$ = this.licenseService.selectedLicense$.pipe(
+ map(() => this.licenseService.selectedLicenseReport),
+ map(license => this.#getDonutChartConfig(license)),
+ );
+ readonly data$ = this.licenseService.selectedLicense$.pipe(
+ map(() => this.licenseService.selectedLicenseReport),
+ map(license => ({
+ datasets: this.#getDatasets(license),
+ labels: getLabelsFromLicense(license),
+ })),
+ );
+ readonly size = size;
+
+ constructor(readonly licenseService: LicenseService, private readonly _translateService: TranslateService) {}
+
+ #getDonutChartConfig(license: ILicenseReport): DonutChartConfig[] {
+ return [
+ {
+ value: license.activeFilesUploadedBytes,
+ color: ChartGreen,
+ label: this._translateService.instant(_('license-info-screen.retention-capacity-usage.active-documents')),
+ },
+ {
+ value: license.archivedFilesUploadedBytes,
+ color: ChartBlue,
+ label: this._translateService.instant(_('license-info-screen.retention-capacity-usage.archived-documents')),
+ },
+ {
+ value: license.trashFilesUploadedBytes,
+ color: ChartRed,
+ label: this._translateService.instant(_('license-info-screen.retention-capacity-usage.trash-documents')),
+ },
+ {
+ value: Math.max(this.licenseService.retentionCapacityBytes - license.totalFilesUploadedBytes, 0),
+ color: ChartGrey,
+ label: this._translateService.instant(_('license-info-screen.retention-capacity-usage.unused')),
+ },
+ ];
+ }
+
+ #getDatasets(license: ILicenseReport): ChartDataset[] {
+ const monthlyData = license.monthlyData;
+
+ return [
+ {
+ data: monthlyData.flatMap(d => d.activeFilesUploadedBytes),
+ label: this._translateService.instant('license-info-screen.retention-capacity-usage.active-documents'),
+ ...getLineConfig(ChartGreen, false, 'origin'),
+ stack: 'storage',
+ },
+ {
+ data: monthlyData.flatMap(d => d.archivedFilesUploadedBytes),
+ label: this._translateService.instant('license-info-screen.retention-capacity-usage.archived-documents'),
+ ...getLineConfig(ChartBlue, false, '-1'),
+ stack: 'storage',
+ },
+ {
+ data: monthlyData.flatMap(d => d.trashFilesUploadedBytes),
+ label: this._translateService.instant('license-info-screen.retention-capacity-usage.trash-documents'),
+ ...getLineConfig(ChartRed, false, '-1'),
+ stack: 'storage',
+ },
+ {
+ data: monthlyData.flatMap(d => d.activeFilesUploadedBytes + d.archivedFilesUploadedBytes + d.trashFilesUploadedBytes),
+ label: this._translateService.instant('license-info-screen.retention-capacity-usage.used-capacity'),
+ ...getLineConfig(ChartBlack, true, false),
+ borderWidth: 2,
+ },
+ ];
+ }
+}
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.html
similarity index 86%
rename from apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.html
rename to apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.html
index 06bd9b457..9649c841b 100644
--- a/apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.html
+++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.html
@@ -1,6 +1,6 @@