RED-7324: Backport license screen updates
This commit is contained in:
parent
953d9253eb
commit
7edfec222b
@ -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,
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
<ngx-charts-chart
|
||||
(legendLabelActivate)="onActivate($event)"
|
||||
(legendLabelClick)="onClick($event)"
|
||||
(legendLabelDeactivate)="onDeactivate($event)"
|
||||
[activeEntries]="activeEntries"
|
||||
[animations]="animations"
|
||||
[legendOptions]="legendOptions"
|
||||
[showLegend]="legend"
|
||||
[view]="[width + legendSpacing, height]"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg:g [attr.transform]="transform" class="bar-chart chart">
|
||||
<svg:g
|
||||
(dimensionsChanged)="updateXAxisHeight($event)"
|
||||
*ngIf="xAxis"
|
||||
[dims]="dims"
|
||||
[labelText]="xAxisLabel"
|
||||
[rotateTicks]="true"
|
||||
[showLabel]="showXAxisLabel"
|
||||
[tickFormatting]="xAxisTickFormatting"
|
||||
[xScale]="xScale"
|
||||
ngx-charts-x-axis
|
||||
></svg:g>
|
||||
<svg:g
|
||||
(dimensionsChanged)="updateYAxisWidth($event)"
|
||||
*ngIf="yAxis"
|
||||
[dims]="dims"
|
||||
[labelText]="yAxisLabel"
|
||||
[showGridLines]="showGridLines"
|
||||
[showLabel]="showYAxisLabel"
|
||||
[tickFormatting]="yAxisTickFormatting"
|
||||
[yOrient]="yOrientLeft"
|
||||
[yScale]="yScale"
|
||||
[valuesMaxLength]="getValuesMaxLength(1)"
|
||||
red-ngx-charts-y-axis
|
||||
></svg:g>
|
||||
<svg:g
|
||||
(dimensionsChanged)="updateYAxisWidth($event)"
|
||||
*ngIf="yAxis"
|
||||
[dims]="dims"
|
||||
[labelText]="yAxisLabelRight"
|
||||
[showGridLines]="showGridLines"
|
||||
[showLabel]="showRightYAxisLabel"
|
||||
[tickFormatting]="yRightAxisTickFormatting"
|
||||
[yOrient]="yOrientRight"
|
||||
[yScale]="yScaleLine"
|
||||
[valuesMaxLength]="getValuesMaxLength(0)"
|
||||
red-ngx-charts-y-axis
|
||||
></svg:g>
|
||||
<svg:g
|
||||
(activate)="onActivate($event)"
|
||||
(bandwidth)="updateLineWidth($event)"
|
||||
(deactivate)="onDeactivate($event)"
|
||||
[activeEntries]="activeEntries"
|
||||
[animations]="animations"
|
||||
[colors]="colors"
|
||||
[dims]="dims"
|
||||
[gradient]="gradient"
|
||||
[noBarWhenZero]="noBarWhenZero"
|
||||
[seriesLine]="lineChart"
|
||||
[series]="results"
|
||||
[tooltipDisabled]="true"
|
||||
[xScale]="xScale"
|
||||
[yScale]="yScale"
|
||||
ngx-combo-charts-series-vertical
|
||||
></svg:g>
|
||||
</svg:g>
|
||||
|
||||
<svg:g [attr.transform]="transform" class="line-chart chart">
|
||||
<svg:g>
|
||||
<svg:g *ngFor="let series of lineChart; trackBy: trackBy">
|
||||
<svg:g
|
||||
[activeEntries]="activeEntries"
|
||||
[animations]="animations"
|
||||
[colors]="colorsLine"
|
||||
[curve]="curve"
|
||||
[data]="series"
|
||||
[rangeFillOpacity]="rangeFillOpacity"
|
||||
[scaleType]="scaleType"
|
||||
[xScale]="xScaleLine"
|
||||
[yScale]="yScaleLine"
|
||||
ngx-charts-line-series
|
||||
></svg:g>
|
||||
</svg:g>
|
||||
|
||||
<svg:g
|
||||
(hover)="updateHoveredVertical($event)"
|
||||
*ngIf="!tooltipDisabled"
|
||||
[colors]="colorsLine"
|
||||
[dims]="dims"
|
||||
[results]="combinedSeries"
|
||||
[tooltipDisabled]="tooltipDisabled"
|
||||
[xScale]="xScaleLine"
|
||||
[xSet]="xSet"
|
||||
[yScale]="yScaleLine"
|
||||
ngx-charts-tooltip-area
|
||||
></svg:g>
|
||||
|
||||
<svg:g *ngFor="let series of lineChart">
|
||||
<svg:g
|
||||
(activate)="onActivate($event)"
|
||||
(deactivate)="onDeactivate($event)"
|
||||
[activeEntries]="activeEntries"
|
||||
[colors]="colorsLine"
|
||||
[data]="series"
|
||||
[scaleType]="scaleType"
|
||||
[tooltipDisabled]="tooltipDisabled"
|
||||
[visibleValue]="hoveredVertical"
|
||||
[xScale]="xScaleLine"
|
||||
[yScale]="yScaleLine"
|
||||
ngx-charts-circle-series
|
||||
></svg:g>
|
||||
</svg:g>
|
||||
</svg:g>
|
||||
</svg:g>
|
||||
</ngx-charts-chart>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
@ -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: `
|
||||
<svg:g
|
||||
ngx-charts-bar
|
||||
*ngFor="let bar of bars; trackBy: trackBy"
|
||||
[@animationState]="'active'"
|
||||
[width]="bar.width"
|
||||
[height]="bar.height"
|
||||
[x]="bar.x"
|
||||
[y]="bar.y"
|
||||
[fill]="bar.color"
|
||||
[stops]="bar.gradientStops"
|
||||
[data]="bar.data"
|
||||
[orientation]="orientations.Vertical"
|
||||
[roundEdges]="bar.roundEdges"
|
||||
[gradient]="gradient"
|
||||
[isActive]="isActive(bar.data)"
|
||||
[animations]="animations"
|
||||
[noBarWhenZero]="noBarWhenZero"
|
||||
(activate)="activate.emit($event)"
|
||||
(deactivate)="deactivate.emit($event)"
|
||||
ngx-tooltip
|
||||
[tooltipDisabled]="tooltipDisabled"
|
||||
[tooltipPlacement]="tooltipPlacements.Top"
|
||||
[tooltipType]="tooltipTypes.tooltip"
|
||||
[tooltipTitle]="bar.tooltipText"
|
||||
></svg:g>
|
||||
`,
|
||||
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 = `
|
||||
<span class="tooltip-label">${tooltipLabel}</span>
|
||||
<span class="tooltip-val">
|
||||
Y1 - ${value.toLocaleString()} • Y2 - ${lineValue.toLocaleString()}%
|
||||
</span>
|
||||
`;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
export * from './combo-chart.component';
|
||||
export * from './combo-series-vertical.component';
|
||||
export * from './y-axis.component';
|
||||
@ -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: `
|
||||
<svg:g [attr.class]="yAxisClassName" [attr.transform]="transform">
|
||||
<svg:g
|
||||
ngx-charts-y-axis-ticks
|
||||
*ngIf="yScale"
|
||||
[trimTicks]="trimTicks"
|
||||
[maxTickLength]="maxTickLength"
|
||||
[tickFormatting]="tickFormatting"
|
||||
[tickArguments]="tickArguments"
|
||||
[tickValues]="ticks"
|
||||
[tickStroke]="tickStroke"
|
||||
[scale]="yScale"
|
||||
[orient]="yOrient"
|
||||
[showGridLines]="showGridLines"
|
||||
[gridLineWidth]="dims.width"
|
||||
[referenceLines]="referenceLines"
|
||||
[showRefLines]="showRefLines"
|
||||
[showRefLabels]="showRefLabels"
|
||||
[height]="dims.height"
|
||||
(dimensionsChanged)="emitTicksWidth($event)"
|
||||
/>
|
||||
<svg:g
|
||||
ngx-charts-axis-label
|
||||
*ngIf="showLabel"
|
||||
[label]="labelText"
|
||||
[offset]="labelOffset"
|
||||
[orient]="yOrient"
|
||||
[height]="dims.height"
|
||||
[width]="dims.width"
|
||||
></svg:g>
|
||||
</svg:g>
|
||||
`,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
<canvas height="400px" id="{{ chartId }}" width="1000px">
|
||||
{{ chart }}
|
||||
</canvas>
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<div class="grid-container">
|
||||
<div class="section-title all-caps-label" translate="license-info-screen.analysis-capacity-usage.section-title"></div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.analysis-capacity-usage.used-in-period"></div>
|
||||
<div>
|
||||
{{ licenseService.selectedLicenseReport.analysedFilesBytes | size }}
|
||||
<ng-container *ngIf="licenseService.analysisCapacityBytesForSelectedLicensePercentage >= 0">
|
||||
({{ licenseService.analysisCapacityBytesForSelectedLicensePercentage | number : '1.0-2' }}%)
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.analysis-capacity-usage.used-in-total"></div>
|
||||
<div>
|
||||
{{ licenseService.allLicensesReport.analysedFilesBytes | size }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row chart-row">
|
||||
<div>
|
||||
<redaction-chart
|
||||
*ngIf="data$ | async as data"
|
||||
[datasets]="data.datasets"
|
||||
[labels]="data.labels"
|
||||
[secondaryAxis]="true"
|
||||
[valueFormatter]="data.valueFormatter"
|
||||
[yAxisLabelRight]="data.yAxisLabelRight"
|
||||
[yAxisLabel]="data.yAxisLabel"
|
||||
chartId="analysis"
|
||||
></redaction-chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<div class="grid-container">
|
||||
<div class="section-title all-caps-label" translate="license-info-screen.page-usage.section-title"></div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.page-usage.current-analyzed-pages"></div>
|
||||
<div>
|
||||
{{ licenseService.selectedLicenseReport.numberOfAnalyzedPages }}
|
||||
<ng-container *ngIf="licenseService.analyzedPagesPercentageForSelectedLicensePercentage > 0">
|
||||
({{ licenseService.analyzedPagesPercentageForSelectedLicensePercentage | number : '1.0-2' }}%)
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.page-usage.ocr-analyzed-pages"></div>
|
||||
<div>{{ licenseService.selectedLicenseReport.numberOfOcrPages }}</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="licenseService.unlicensedPages" class="row">
|
||||
<div translate="license-info-screen.page-usage.unlicensed-analyzed"></div>
|
||||
<div>{{ licenseService.unlicensedPages }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div [innerHTML]="'license-info-screen.page-usage.total-analyzed' | translate"></div>
|
||||
<div>{{ licenseService.allLicensesReport.numberOfAnalyzedPages }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div [innerHTML]="'license-info-screen.page-usage.total-ocr-analyzed' | translate"></div>
|
||||
<div>{{ licenseService.allLicensesReport.numberOfOcrPages }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row chart-row">
|
||||
<div>
|
||||
<redaction-chart
|
||||
*ngIf="data$ | async as data"
|
||||
[datasets]="data.datasets"
|
||||
[labels]="data.labels"
|
||||
[secondaryAxis]="true"
|
||||
[yAxisLabelRight]="data.yAxisLabelRight"
|
||||
[yAxisLabel]="data.yAxisLabel"
|
||||
chartId="pages"
|
||||
></redaction-chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
<div class="grid-container">
|
||||
<div class="section-title all-caps-label" translate="license-info-screen.retention-capacity-usage.section-title"></div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.retention-capacity-usage.active-documents"></div>
|
||||
<div>{{ licenseService.selectedLicenseReport.activeFilesUploadedBytes | size }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.retention-capacity-usage.archived-documents"></div>
|
||||
<div>{{ licenseService.selectedLicenseReport.archivedFilesUploadedBytes | size }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.retention-capacity-usage.trash-documents"></div>
|
||||
<div>{{ licenseService.selectedLicenseReport.trashFilesUploadedBytes | size }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.retention-capacity-usage.used-capacity"></div>
|
||||
<div>
|
||||
{{ licenseService.selectedLicenseReport.totalFilesUploadedBytes | size }}
|
||||
<ng-container *ngIf="licenseService.retentionCapacityBytesForSelectedLicensePercentage >= 0">
|
||||
({{ licenseService.retentionCapacityBytesForSelectedLicensePercentage | number : '1.0-2' }}%)
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="donut-chart-wrapper pl-20">
|
||||
<redaction-donut-chart
|
||||
*ngIf="licenseService.retentionCapacityBytes > 0"
|
||||
[config]="donutChartConfig$ | async"
|
||||
[direction]="'row'"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitleTemplate]="capacitySubtitles"
|
||||
[totalType]="'sum'"
|
||||
[valueFormatter]="size"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
|
||||
<div class="row chart-row">
|
||||
<div>
|
||||
<redaction-chart
|
||||
*ngIf="data$ | async as data"
|
||||
[datasets]="data.datasets"
|
||||
[labels]="data.labels"
|
||||
[reverseLegend]="true"
|
||||
[valueFormatter]="formatSize"
|
||||
chartId="retention"
|
||||
></redaction-chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #capacitySubtitles>
|
||||
<span *ngIf="licenseService.retentionCapacityBytesForSelectedLicensePercentage <= 100; else exceeded">
|
||||
{{ 'license-info-screen.retention-capacity-usage.storage-capacity' | translate }}
|
||||
</span>
|
||||
|
||||
<ng-template #exceeded>
|
||||
<span class="exceeded-capacity">
|
||||
{{ 'license-info-screen.retention-capacity-usage.exceeded-capacity' | translate }}
|
||||
</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
@ -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);
|
||||
}
|
||||
@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
<div *ngIf="licenses$ | async as licenses" class="iqser-input-group w-400">
|
||||
<mat-form-field>
|
||||
<mat-select (valueChange)="licenseChanged($event)" *ngIf="value" [(ngModel)]="value" [iqserHelpMode]="'license_information'">
|
||||
<mat-select (valueChange)="licenseChanged($event)" *ngIf="value" [(ngModel)]="value" [attr.help-mode-key]="'license_information'">
|
||||
<mat-select-trigger>
|
||||
<ng-container *ngTemplateOutlet="licenseInfo; context: { license: value }"></ng-container>
|
||||
</mat-select-trigger>
|
||||
@ -14,9 +14,9 @@
|
||||
|
||||
<ng-template #licenseInfo let-license="license">
|
||||
<div class="space-between flex-align-items-center">
|
||||
<span>{{ license.name }}</span>
|
||||
<div>{{ license.name }}</div>
|
||||
<div class="mr-10 flex-align-items-center">
|
||||
<div [class.green]="license.id === licenseService.activeLicenseId" class="dot mr-4"></div>
|
||||
<div [class.green]="license.id === licenseService.activeLicense.id" class="dot mr-4"></div>
|
||||
<span class="small-label">{{ getStatus(license.id) | translate | uppercase }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -29,7 +29,7 @@ export class LicenseSelectComponent {
|
||||
constructor(readonly licenseService: LicenseService, private readonly _permissionsService: IqserPermissionsService) {}
|
||||
|
||||
getStatus(id) {
|
||||
return id === this.licenseService.activeLicenseId ? translations.active : translations.inactive;
|
||||
return id === this.licenseService.activeLicense.id ? translations.active : translations.inactive;
|
||||
}
|
||||
|
||||
async licenseChanged(license: ILicense) {
|
||||
@ -1,18 +0,0 @@
|
||||
<combo-chart-component
|
||||
*ngIf="lineChartSeries$ | async as lineChartSeries"
|
||||
[animations]="true"
|
||||
[colorSchemeLine]="lineChartScheme"
|
||||
[legendTitle]="'license-info-screen.chart.legend' | translate"
|
||||
[legend]="true"
|
||||
[lineChart]="lineChartSeries"
|
||||
[results]="barChart"
|
||||
[scheme]="comboBarScheme"
|
||||
[showGridLines]="true"
|
||||
[showRightYAxisLabel]="true"
|
||||
[showYAxisLabel]="true"
|
||||
[view]="[1000, 300]"
|
||||
[xAxis]="true"
|
||||
[yAxisLabelRight]="'license-info-screen.chart.total-pages' | translate"
|
||||
[yAxisLabel]="'license-info-screen.chart.pages-per-month' | translate"
|
||||
[yAxis]="true"
|
||||
></combo-chart-component>
|
||||
@ -1,142 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ComboBarScheme, LICENSE_STORAGE_KEY, LineChartScheme } from '../utils/constants';
|
||||
import dayjs from 'dayjs';
|
||||
import { IDateRange, ILicense, ILicenseReport } from '@red/domain';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { List, LoadingService } from '@iqser/common-ui';
|
||||
import { isCurrentMonth, toDate, verboseDate } from '../utils/functions';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Series } from '@swimlane/ngx-charts';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-license-chart',
|
||||
templateUrl: './license-chart.component.html',
|
||||
})
|
||||
export class LicenseChartComponent {
|
||||
readonly lineChartScheme = LineChartScheme;
|
||||
readonly comboBarScheme = ComboBarScheme;
|
||||
|
||||
readonly lineChartSeries$ = this.#licenseChartSeries$;
|
||||
barChart = [];
|
||||
|
||||
constructor(
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _licenseService: LicenseService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
) {}
|
||||
|
||||
get #licenseChartSeries$(): Observable<Series[]> {
|
||||
return this._licenseService.selectedLicense$.pipe(
|
||||
tap(() => this._loadingService.start()),
|
||||
switchMap(license => this.#getLicenseData(license)),
|
||||
tap(() => this._loadingService.stop()),
|
||||
);
|
||||
}
|
||||
|
||||
async #getLicenseData(license: ILicense): Promise<Series[]> {
|
||||
const startDate = dayjs(license.validFrom);
|
||||
const endDate = dayjs(license.validUntil);
|
||||
const startDay: number = startDate.date();
|
||||
const startMonth: number = startDate.month();
|
||||
const startYear: number = startDate.year();
|
||||
|
||||
const dateRanges = [];
|
||||
|
||||
for (let dt = startDate; dt <= endDate; dt = dt.add(1, 'month')) {
|
||||
const end = dt.add(1, 'month');
|
||||
dateRanges.push({ startMonth: dt.month(), startYear: dt.year(), endMonth: end.month(), endYear: end.year() });
|
||||
}
|
||||
|
||||
if (dateRanges.length > 0) {
|
||||
dateRanges[0].startDay = startDay;
|
||||
}
|
||||
const reports = await this.#getReports(dateRanges, license.id);
|
||||
|
||||
return this.#mapRangesToReports(startMonth, startYear, dateRanges, reports);
|
||||
}
|
||||
|
||||
#mapRangesToReports(month: number, year: number, dateRanges: List<IDateRange>, reports: List<ILicenseReport>): Series[] {
|
||||
return [
|
||||
{
|
||||
name: this._translateService.instant('license-info-screen.chart.total-pages'),
|
||||
series: this.#totalLicensedPagesSeries(dateRanges),
|
||||
},
|
||||
{
|
||||
name: this._translateService.instant('license-info-screen.chart.cumulative'),
|
||||
series: this.#setBar(month, year, reports),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
#setBar(month: number, year: number, reports: List<ILicenseReport>) {
|
||||
let cumulativePages = 0;
|
||||
const cumulativePagesSeries = [];
|
||||
this.barChart = [];
|
||||
const monthNames = dayjs.monthsShort();
|
||||
|
||||
for (const report of reports) {
|
||||
cumulativePages += report.numberOfAnalyzedPages;
|
||||
|
||||
const name = `${monthNames[month]} ${year}`;
|
||||
this.barChart.push({
|
||||
name,
|
||||
value: report.numberOfAnalyzedPages,
|
||||
});
|
||||
|
||||
cumulativePagesSeries.push({
|
||||
name,
|
||||
value: cumulativePages,
|
||||
});
|
||||
|
||||
month++;
|
||||
if (month === 12) {
|
||||
month = 0;
|
||||
year++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cumulativePages !== this._licenseService.currentLicenseInfo.numberOfAnalyzedPages) {
|
||||
this._licenseService.wipeStoredReportsAndReloadSelectedLicenseData();
|
||||
}
|
||||
|
||||
return cumulativePagesSeries;
|
||||
}
|
||||
|
||||
#getReports(dateRanges: List<IDateRange>, id: string) {
|
||||
const reports = dateRanges.map(range => {
|
||||
const startMonth = range.startMonth + 1;
|
||||
const endMonth = range.endMonth + 1;
|
||||
|
||||
const key = `${id}-${startMonth}.${range.startYear}-${endMonth}.${range.endYear}`;
|
||||
const existingReport = this._licenseService.storedReports[key];
|
||||
if (existingReport) {
|
||||
return existingReport;
|
||||
}
|
||||
|
||||
const startDate = toDate(startMonth, range.startYear, range.startDay);
|
||||
const endDate = toDate(endMonth, range.endYear);
|
||||
const requestedReport = this._licenseService.getReport({ startDate, endDate });
|
||||
return requestedReport.then(report => this.#storeReportIfNotCurrentMonth(range, report, key));
|
||||
});
|
||||
|
||||
return Promise.all(reports);
|
||||
}
|
||||
|
||||
#storeReportIfNotCurrentMonth(dateRange: IDateRange, report: ILicenseReport, key: string) {
|
||||
if (!isCurrentMonth(dateRange.startMonth + 1, dateRange.startYear)) {
|
||||
this._licenseService.storedReports[key] = report;
|
||||
localStorage.setItem(LICENSE_STORAGE_KEY, JSON.stringify(this._licenseService.storedReports));
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
#totalLicensedPagesSeries(dateRanges: List<IDateRange>) {
|
||||
return dateRanges.map(dateRange => ({
|
||||
name: verboseDate(dateRange),
|
||||
value: this._licenseService.totalLicensedNumberOfPages,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -1,100 +1,77 @@
|
||||
<section class="settings">
|
||||
<div>
|
||||
<iqser-page-header
|
||||
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
|
||||
[buttonConfigs]="buttonConfigs"
|
||||
[pageLabel]="'license-information' | translate"
|
||||
[showCloseButton]="currentUser.isUser && permissionsService.has$(roles.dossiers.read) | async"
|
||||
></iqser-page-header>
|
||||
<iqser-page-header
|
||||
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
|
||||
[buttonConfigs]="buttonConfigs"
|
||||
[pageLabel]="'license-information' | translate"
|
||||
[showCloseButton]="currentUser.isUser && permissionsService.has$(roles.dossiers.read) | async"
|
||||
></iqser-page-header>
|
||||
|
||||
<div class="content-inner">
|
||||
<div class="content-container">
|
||||
<div *ngIf="licenseService.licenseData$ | async" class="grid-container">
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.backend-version"></div>
|
||||
<div>{{ configService.values.BACKEND_APP_VERSION || '-' }}</div>
|
||||
</div>
|
||||
<div class="content-inner">
|
||||
<div class="content-container">
|
||||
<div *ngIf="licenseService.licenseData$ | async" class="grid-container">
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.backend-version"></div>
|
||||
<div>{{ configService.values.BACKEND_APP_VERSION || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.custom-app-title"></div>
|
||||
<div>{{ configService.values.APP_NAME || '-' }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.custom-app-title"></div>
|
||||
<div>{{ configService.values.APP_NAME || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.copyright-claim-title"></div>
|
||||
<div>
|
||||
{{ 'license-info-screen.copyright-claim-text' | translate : { currentYear: currentYear } }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.copyright-claim-title"></div>
|
||||
<div>
|
||||
{{ 'license-info-screen.copyright-claim-text' | translate : { currentYear: currentYear } }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.end-user-license-title"></div>
|
||||
<div translate="license-info-screen.end-user-license-text"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.end-user-license-title"></div>
|
||||
<div translate="license-info-screen.end-user-license-text"></div>
|
||||
</div>
|
||||
|
||||
<div class="section-title all-caps-label" translate="license-info-screen.licensing-details"></div>
|
||||
<div class="section-title all-caps-label" translate="license-info-screen.licensing-details.section-title"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="flex-align-items-center" translate="license-info-screen.license-title"></div>
|
||||
<div>
|
||||
<redaction-license-select></redaction-license-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="flex-align-items-center" translate="license-info-screen.licensing-details.license-title"></div>
|
||||
<div>
|
||||
<redaction-license-select></redaction-license-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="licenseService.selectedLicense$ | async as selectedLicense">
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.licensed-to"></div>
|
||||
<div>{{ selectedLicense.licensedTo || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.licensing-period"></div>
|
||||
<div>
|
||||
{{ (selectedLicense.validFrom | date : 'dd-MM-yyyy') || '-' }} /
|
||||
{{ (selectedLicense.validUntil | date : 'dd-MM-yyyy') || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="row">
|
||||
<div>{{ 'license-info-screen.licensed-page-count' | translate }}</div>
|
||||
<div>{{ licenseService.totalLicensedNumberOfPages }}</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title all-caps-label" translate="license-info-screen.usage-details"></div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.current-analyzed"></div>
|
||||
<div>
|
||||
{{ licenseService.analyzedPagesInCurrentLicensingPeriod }}
|
||||
({{ analysisPercentageOfLicense$ | async | number : '1.0-2' }}%)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.ocr-analyzed-pages"></div>
|
||||
<div>{{ licenseService.currentLicenseInfo.numberOfOcrPages }}</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!!licenseService.unlicensedPages" class="row">
|
||||
<div translate="license-info-screen.unlicensed-analyzed"></div>
|
||||
<div>{{ licenseService.unlicensedPages }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div [innerHTML]="'license-info-screen.total-analyzed' | translate"></div>
|
||||
|
||||
<div>{{ licenseService.allLicensesInfo.numberOfAnalyzedPages }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div [innerHTML]="'license-info-screen.total-ocr-analyzed' | translate"></div>
|
||||
<div>{{ licenseService.allLicensesInfo.numberOfOcrPages }}</div>
|
||||
</div>
|
||||
<ng-container *ngIf="licenseService.selectedLicense$ | async as selectedLicense">
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.licensing-details.licensed-to"></div>
|
||||
<div>{{ selectedLicense.licensedTo || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<redaction-license-chart></redaction-license-chart>
|
||||
<div class="row">
|
||||
<div translate="license-info-screen.licensing-details.licensing-period"></div>
|
||||
<div>
|
||||
{{ (selectedLicense.validFrom | date : 'dd-MM-yyyy') || '-' }} /
|
||||
{{ (selectedLicense.validUntil | date : 'dd-MM-yyyy') || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="licenseService.isPageBased" class="row">
|
||||
<div>{{ 'license-info-screen.licensing-details.licensed-page-count' | translate }}</div>
|
||||
<div>{{ licenseService.totalLicensedNumberOfPages }}</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="licenseService.isCapacityBased" class="row">
|
||||
<div>{{ 'license-info-screen.licensing-details.licensed-analysis-capacity' | translate }}</div>
|
||||
<div>{{ licenseService.analysisCapacityBytes | size }}</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="licenseService.isCapacityBased" class="row">
|
||||
<div>{{ 'license-info-screen.licensing-details.licensed-retention-capacity' | translate }}</div>
|
||||
<div>{{ licenseService.retentionCapacityBytes | size }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<red-license-page-usage *ngIf="licenseService.isPageBased"></red-license-page-usage>
|
||||
<red-license-analysis-capacity-usage *ngIf="licenseService.isCapacityBased"></red-license-analysis-capacity-usage>
|
||||
<red-license-retention-capacity *ngIf="licenseService.isCapacityBased"></red-license-retention-capacity>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -4,15 +4,10 @@
|
||||
overflow: auto;
|
||||
@include common-mixins.scroll-bar;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.grid-container {
|
||||
width: calc(100% - 40px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
margin: 20px 20px 50px 20px;
|
||||
grid-template-columns: 250px 300px 1fr;
|
||||
margin: 20px;
|
||||
|
||||
.row {
|
||||
display: contents;
|
||||
@ -23,6 +18,10 @@
|
||||
&:first-of-type {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@ -33,14 +32,14 @@
|
||||
}
|
||||
|
||||
.section-title {
|
||||
grid-column: span 2;
|
||||
grid-column: span 3;
|
||||
padding: 20px 20px 8px;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
}
|
||||
}
|
||||
|
||||
redaction-license-chart {
|
||||
margin-bottom: 50px;
|
||||
redaction-chart {
|
||||
grid-column: span 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
ButtonConfig,
|
||||
getCurrentUser,
|
||||
IconButtonTypes,
|
||||
IqserPermissionsService,
|
||||
LoadingService,
|
||||
OverlappingElements,
|
||||
} from '@iqser/common-ui';
|
||||
import { ButtonConfig, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { getCurrentUser } from '@users/user.service';
|
||||
import { ROLES } from '@users/roles';
|
||||
import type { User } from '@red/domain';
|
||||
|
||||
@Component({
|
||||
templateUrl: './license-screen.component.html',
|
||||
@ -22,7 +14,7 @@ import type { User } from '@red/domain';
|
||||
})
|
||||
export class LicenseScreenComponent {
|
||||
readonly roles = ROLES;
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
readonly currentUser = getCurrentUser();
|
||||
readonly currentYear = new Date().getFullYear();
|
||||
readonly buttonConfigs: readonly ButtonConfig[] = [
|
||||
{
|
||||
@ -31,28 +23,16 @@ export class LicenseScreenComponent {
|
||||
type: IconButtonTypes.primary,
|
||||
helpModeKey: 'license_information',
|
||||
hide: !this.permissionsService.has(ROLES.license.readReport),
|
||||
overlappingElements: [OverlappingElements.USER_MENU],
|
||||
},
|
||||
];
|
||||
|
||||
readonly analysisPercentageOfLicense$ = this.licenseService.selectedLicense$.pipe(map(() => this.getAnalysisPercentageOfLicense()));
|
||||
|
||||
constructor(
|
||||
readonly configService: ConfigService,
|
||||
readonly licenseService: LicenseService,
|
||||
readonly permissionsService: IqserPermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
readonly routerHistoryService: RouterHistoryService,
|
||||
private readonly _translateService: TranslateService,
|
||||
) {
|
||||
_loadingService.start();
|
||||
}
|
||||
|
||||
getAnalysisPercentageOfLicense() {
|
||||
const totalLicensedNumberOfPages = this.licenseService.totalLicensedNumberOfPages;
|
||||
const numberOfAnalyzedPages = this.licenseService.analyzedPagesInCurrentLicensingPeriod;
|
||||
return totalLicensedNumberOfPages > 0 ? (numberOfAnalyzedPages / totalLicensedNumberOfPages) * 100 : 100;
|
||||
}
|
||||
) {}
|
||||
|
||||
sendMail(): void {
|
||||
const licenseCustomer = this.licenseService.selectedLicense.licensedTo;
|
||||
@ -62,7 +42,7 @@ export class LicenseScreenComponent {
|
||||
const lineBreak = '%0D%0A';
|
||||
const body = [
|
||||
this._translateService.instant('license-info-screen.email.body.analyzed', {
|
||||
pages: this.licenseService.currentLicenseInfo.numberOfAnalyzedPages,
|
||||
pages: this.licenseService.selectedLicenseReport.numberOfAnalyzedPages,
|
||||
}),
|
||||
this._translateService.instant('license-info-screen.email.body.licensed', {
|
||||
pages: this.licenseService.totalLicensedNumberOfPages,
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import { inject, NgModule } from '@angular/core';
|
||||
import { LicenseScreenComponent } from './license-screen/license-screen.component';
|
||||
import { LicenseSelectComponent } from './license-select/license-select.component';
|
||||
import { LicenseChartComponent } from './license-chart/license-chart.component';
|
||||
import { LicenseSelectComponent } from './components/license-select/license-select.component';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { IqserHelpModeModule, IqserListingModule } from '@iqser/common-ui';
|
||||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
import { ComboChartComponent, ComboSeriesVerticalComponent, YAxisComponent } from './combo-chart';
|
||||
import { IqserHelpModeModule, IqserListingModule, SizePipe } from '@iqser/common-ui';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { ChartComponent } from './components/chart/chart.component';
|
||||
import { NgChartsModule } from 'ng2-charts';
|
||||
import { LicenseRetentionCapacityComponent } from './components/license-retention-capacity-usage/license-retention-capacity.component';
|
||||
import { LicensePageUsageComponent } from './components/license-page-usage/license-page-usage.component';
|
||||
import { LicenseAnalysisCapacityUsageComponent } from './components/license-analysis-capacity-usage/license-analysis-capacity-usage.component';
|
||||
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -26,10 +29,10 @@ const routes: Routes = [
|
||||
declarations: [
|
||||
LicenseScreenComponent,
|
||||
LicenseSelectComponent,
|
||||
LicenseChartComponent,
|
||||
ComboChartComponent,
|
||||
ComboSeriesVerticalComponent,
|
||||
YAxisComponent,
|
||||
ChartComponent,
|
||||
LicenseRetentionCapacityComponent,
|
||||
LicensePageUsageComponent,
|
||||
LicenseAnalysisCapacityUsageComponent,
|
||||
],
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
@ -37,9 +40,11 @@ const routes: Routes = [
|
||||
TranslateModule,
|
||||
MatSelectModule,
|
||||
FormsModule,
|
||||
NgxChartsModule,
|
||||
IqserListingModule,
|
||||
IqserHelpModeModule,
|
||||
SizePipe,
|
||||
DonutChartComponent,
|
||||
NgChartsModule,
|
||||
],
|
||||
})
|
||||
export class LicenseModule {}
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
import { Color, ScaleType } from '@swimlane/ngx-charts';
|
||||
export const ChartRed = '#dd4d50';
|
||||
export const ChartGreen = '#5ce594';
|
||||
export const ChartBlue = '#0389ec';
|
||||
export const ChartGrey = '#ccced3'; // grey-5
|
||||
export const ChartBlack = '#283241'; // grey-1
|
||||
|
||||
export const ComboBarScheme: Color = {
|
||||
name: 'Combo bar scheme',
|
||||
selectable: true,
|
||||
group: ScaleType.Ordinal,
|
||||
domain: ['#0389ec'],
|
||||
};
|
||||
|
||||
export const LineChartScheme: Color = {
|
||||
name: 'Line chart scheme',
|
||||
selectable: true,
|
||||
group: ScaleType.Ordinal,
|
||||
domain: ['#dd4d50', '#5ce594', '#0389ec'],
|
||||
};
|
||||
|
||||
export const LICENSE_STORAGE_KEY = 'redaction-license-reports';
|
||||
export enum LicenseFeatures {
|
||||
PROCESSING_PAGES = 'processingPages',
|
||||
PDFTRON = 'pdftron',
|
||||
ANALYSIS_CAPACITY_BYTES = 'analysisCapacityBytes',
|
||||
RETENTION_CAPACITY_BYTES = 'retentionCapacityBytes',
|
||||
}
|
||||
|
||||
@ -1,18 +1,46 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { IDateRange } from '@red/domain';
|
||||
|
||||
export function toDate(month: number, year: number, day: number = 1) {
|
||||
return dayjs(`${day}-${month}-${year}`, 'D-M-YYYY').toDate();
|
||||
}
|
||||
|
||||
export function isCurrentMonth(month: number, year: number) {
|
||||
const now = dayjs();
|
||||
const currentMonth = now.month() + 1;
|
||||
const currentYear = now.year();
|
||||
|
||||
return month === currentMonth && year === currentYear;
|
||||
}
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { FillTarget } from 'chart.js';
|
||||
import { hexToRgba } from '@utils/functions';
|
||||
import { ILicenseReport } from '@red/domain';
|
||||
import { ComplexFillTarget } from 'chart.js/dist/types';
|
||||
|
||||
const monthNames = dayjs.monthsShort();
|
||||
|
||||
export const verboseDate = (range: IDateRange) => `${monthNames[range.startMonth]} ${range.startYear}`;
|
||||
export const verboseDate = (date: Dayjs) => `${monthNames[date.month()]} ${date.year()}`;
|
||||
|
||||
export const getLineConfig: (
|
||||
color: string,
|
||||
displayLine: boolean,
|
||||
target: FillTarget,
|
||||
) => {
|
||||
type: 'line';
|
||||
borderColor: string;
|
||||
backgroundColor: string;
|
||||
pointBackgroundColor: string;
|
||||
fill: ComplexFillTarget;
|
||||
} = (color, displayLine, target) => ({
|
||||
type: 'line',
|
||||
borderColor: hexToRgba(color, displayLine ? 1 : 0),
|
||||
backgroundColor: hexToRgba(color, 1),
|
||||
pointBackgroundColor: hexToRgba(color, 1),
|
||||
fill: {
|
||||
target: target ?? '-1',
|
||||
above: hexToRgba(color, 0.3),
|
||||
below: hexToRgba(color, 0.3),
|
||||
},
|
||||
pointStyle: false,
|
||||
});
|
||||
|
||||
export const getLabelsFromLicense = (license: ILicenseReport) => {
|
||||
let startMonth = dayjs(license.startDate).startOf('month');
|
||||
const endMonth = dayjs(license.endDate).endOf('month');
|
||||
|
||||
const result = [];
|
||||
|
||||
while (startMonth.isBefore(endMonth)) {
|
||||
result.push(verboseDate(startMonth));
|
||||
startMonth = startMonth.add(1, 'month');
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import { TemplateStatsComponent } from './components/template-stats/template-sta
|
||||
import { BreadcrumbTypes } from '@red/domain';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { IqserButtonsModule, IqserHelpModeModule, IqserPermissionsModule } from '@iqser/common-ui';
|
||||
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@ -28,6 +29,7 @@ const routes = [
|
||||
IqserButtonsModule,
|
||||
IqserHelpModeModule,
|
||||
IqserPermissionsModule,
|
||||
DonutChartComponent,
|
||||
],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
IqserScrollbarModule,
|
||||
IqserSharedModule,
|
||||
IqserUsersModule,
|
||||
StatusBarComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DossierOverviewScreenComponent } from './screen/dossier-overview-screen.component';
|
||||
@ -26,6 +27,7 @@ import { WorkflowItemComponent } from './components/workflow-item/workflow-item.
|
||||
import { DossierOverviewScreenHeaderComponent } from './components/screen-header/dossier-overview-screen-header.component';
|
||||
import { ViewModeSelectionComponent } from './components/view-mode-selection/view-mode-selection.component';
|
||||
import { FileAttributeComponent } from './components/file-attribute/file-attribute.component';
|
||||
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -67,6 +69,8 @@ const routes: Routes = [
|
||||
IqserScrollbarModule,
|
||||
IqserPermissionsModule,
|
||||
IqserInputsModule,
|
||||
StatusBarComponent,
|
||||
DonutChartComponent,
|
||||
],
|
||||
})
|
||||
export class DossierOverviewModule {}
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
IqserSharedModule,
|
||||
IqserUsersModule,
|
||||
LogPipe,
|
||||
StatusBarComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DossiersListingScreenComponent } from './screen/dossiers-listing-screen.component';
|
||||
@ -24,6 +25,7 @@ import { DossierWorkloadColumnComponent } from './components/dossier-workload-co
|
||||
import { DossierDocumentsStatusComponent } from './components/dossier-documents-status/dossier-documents-status.component';
|
||||
import { DossierFilesGuard } from '@guards/dossier-files-guard';
|
||||
import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens';
|
||||
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -59,6 +61,8 @@ const routes: Routes = [
|
||||
IqserSharedModule,
|
||||
IqserPermissionsModule,
|
||||
LogPipe,
|
||||
StatusBarComponent,
|
||||
DonutChartComponent,
|
||||
],
|
||||
})
|
||||
export class DossiersListingModule {}
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
IqserSharedModule,
|
||||
IqserUploadFileModule,
|
||||
IqserUsersModule,
|
||||
StatusBarComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
@ -125,6 +126,7 @@ const components = [
|
||||
IqserFiltersModule,
|
||||
IqserScrollbarModule,
|
||||
IqserPermissionsModule,
|
||||
StatusBarComponent,
|
||||
],
|
||||
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, SuggestionsService],
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SearchScreenComponent } from './search-screen/search-screen.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { IqserListingModule, IqserSharedModule, IqserUsersModule } from '@iqser/common-ui';
|
||||
import { IqserListingModule, IqserSharedModule, IqserUsersModule, StatusBarComponent } from '@iqser/common-ui';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchItemTemplateComponent } from './search-item-template/search-item-template.component';
|
||||
@ -19,6 +19,7 @@ const routes = [{ path: '', component: SearchScreenComponent }];
|
||||
TranslateModule,
|
||||
IqserListingModule,
|
||||
IqserSharedModule,
|
||||
StatusBarComponent,
|
||||
],
|
||||
})
|
||||
export class SearchModule {}
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
IqserScrollbarModule,
|
||||
IqserSharedModule,
|
||||
IqserUsersModule,
|
||||
StatusBarComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DossiersListingActionsComponent } from './components/dossiers-listing-actions/dossiers-listing-actions.component';
|
||||
@ -55,6 +56,7 @@ const services = [FileAssignService];
|
||||
IqserInputsModule,
|
||||
IqserScrollbarModule,
|
||||
IqserPermissionsModule,
|
||||
StatusBarComponent,
|
||||
],
|
||||
})
|
||||
export class SharedDossiersModule {}
|
||||
|
||||
@ -18,8 +18,13 @@
|
||||
</svg>
|
||||
|
||||
<div [style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + (strokeWidth + 5) + 'px;'" class="text-container">
|
||||
<div class="heading-xl">{{ displayedDataTotal }}</div>
|
||||
<div class="heading-xl">{{ getFormattedValue(displayedDataTotal) }}</div>
|
||||
<div *ngIf="subtitles.length === 1" class="mt-5">{{ subtitles[0] }}</div>
|
||||
|
||||
<div *ngIf="subtitleTemplate as t" class="mt-5">
|
||||
<ng-container *ngTemplateOutlet="t"></ng-container>
|
||||
</div>
|
||||
|
||||
<mat-select
|
||||
(selectionChange)="subtitleChanged.emit(subtitles.indexOf($event.value))"
|
||||
*ngIf="subtitles.length > 1"
|
||||
@ -30,7 +35,7 @@
|
||||
</mat-select>
|
||||
</div>
|
||||
|
||||
<div [iqserHelpMode]="helpModeKey" class="breakdown-container">
|
||||
<div [attr.help-mode-key]="helpModeKey" class="breakdown-container">
|
||||
<div
|
||||
(click)="val.key && selectValue(val.key)"
|
||||
*ngFor="let val of config"
|
||||
@ -48,8 +53,7 @@
|
||||
}
|
||||
]"
|
||||
[small]="true"
|
||||
>
|
||||
</iqser-status-bar>
|
||||
></iqser-status-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Optional, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Optional, Output, TemplateRef } from '@angular/core';
|
||||
import { DonutChartConfig } from '@red/domain';
|
||||
import { FilterService, get, INestedFilter, shareLast } from '@iqser/common-ui';
|
||||
import { FilterService, get, INestedFilter, IqserHelpModeModule, shareLast, StatusBarComponent } from '@iqser/common-ui';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-donut-chart',
|
||||
templateUrl: './donut-chart.component.html',
|
||||
styleUrls: ['./donut-chart.component.scss'],
|
||||
standalone: true,
|
||||
imports: [NgForOf, NgIf, MatSelectModule, IqserHelpModeModule, StatusBarComponent, AsyncPipe, NgTemplateOutlet],
|
||||
})
|
||||
export class DonutChartComponent implements OnChanges, OnInit {
|
||||
@Input() subtitles: string[];
|
||||
@Input() subtitles: string[] = [];
|
||||
@Input() config: DonutChartConfig[] = [];
|
||||
@Input() radius = 85;
|
||||
@Input() strokeWidth = 20;
|
||||
@ -19,6 +23,8 @@ export class DonutChartComponent implements OnChanges, OnInit {
|
||||
@Input() counterText: string;
|
||||
@Input() filterKey;
|
||||
@Input() helpModeKey;
|
||||
@Input() valueFormatter?: (value: number) => string;
|
||||
@Input() subtitleTemplate?: TemplateRef<any>;
|
||||
|
||||
@Output() readonly subtitleChanged = new EventEmitter<number>();
|
||||
|
||||
@ -28,10 +34,6 @@ export class DonutChartComponent implements OnChanges, OnInit {
|
||||
size = 0;
|
||||
filters$: Observable<INestedFilter[]>;
|
||||
|
||||
constructor(@Optional() readonly filterService: FilterService) {
|
||||
// TODO: move this component to a separate module, split into smaller components, improve filters
|
||||
}
|
||||
|
||||
get circumference(): number {
|
||||
return 2 * Math.PI * this.radius;
|
||||
}
|
||||
@ -44,12 +46,17 @@ export class DonutChartComponent implements OnChanges, OnInit {
|
||||
return this.totalType === 'sum' ? this.dataTotal : this.config.length;
|
||||
}
|
||||
|
||||
constructor(@Optional() readonly filterService: FilterService) {
|
||||
// TODO: move this component to a separate module, split into smaller components, improve filters
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filters$ =
|
||||
this.filterService?.getFilterModels$(this.filterKey).pipe(
|
||||
map(filters => filters ?? []),
|
||||
shareLast(),
|
||||
) ?? of([]);
|
||||
const filterModels$ = this.filterService?.getFilterModels$(this.filterKey).pipe(
|
||||
map(filters => filters ?? []),
|
||||
shareLast(),
|
||||
);
|
||||
|
||||
this.filters$ = filterModels$ ?? of([]);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
@ -66,6 +73,10 @@ export class DonutChartComponent implements OnChanges, OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
getFormattedValue(value: number): string {
|
||||
return this.valueFormatter ? this.valueFormatter(value) : value.toString();
|
||||
}
|
||||
|
||||
calculateChartData() {
|
||||
let angleOffset = -90;
|
||||
this.chartData = this.config.map(dataVal => {
|
||||
@ -96,8 +107,8 @@ export class DonutChartComponent implements OnChanges, OnInit {
|
||||
return this.totalType === 'simpleLabel'
|
||||
? `${label}`
|
||||
: this.totalType === 'sum'
|
||||
? `${value} ${label}`
|
||||
: `${label} (${value} ${this.counterText})`;
|
||||
? `${this.getFormattedValue(value)} ${label}`
|
||||
: `${label} (${this.getFormattedValue(value)} ${this.counterText})`;
|
||||
}
|
||||
|
||||
selectValue(key: string): void {
|
||||
|
||||
@ -7,7 +7,6 @@ import { MatConfigModule } from '../mat-config/mat-config.module';
|
||||
import { IconsModule } from '../icons/icons.module';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { AnnotationIconComponent } from './components/annotation-icon/annotation-icon.component';
|
||||
import { DonutChartComponent } from './components/donut-chart/donut-chart.component';
|
||||
import {
|
||||
IqserButtonsModule,
|
||||
IqserHelpModeModule,
|
||||
@ -50,7 +49,6 @@ const buttons = [FileDownloadBtnComponent];
|
||||
const components = [
|
||||
PaginationComponent,
|
||||
AnnotationIconComponent,
|
||||
DonutChartComponent,
|
||||
SelectComponent,
|
||||
DictionaryManagerComponent,
|
||||
AssignUserDropdownComponent,
|
||||
|
||||
@ -1,18 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, QueryParam } from '@iqser/common-ui';
|
||||
import { ILicense, ILicenseReport, ILicenseReportRequest, ILicenses } from '@red/domain';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { GenericService, Toaster } from '@iqser/common-ui';
|
||||
import { ILicense, ILicenseFeature, ILicenseReport, ILicenseReportRequest, ILicenses } from '@red/domain';
|
||||
import { BehaviorSubject, firstValueFrom, Observable, of } from 'rxjs';
|
||||
import { catchError, filter, tap } from 'rxjs/operators';
|
||||
import { LICENSE_STORAGE_KEY } from '../modules/admin/screens/license/utils/constants';
|
||||
import { catchError, filter } from 'rxjs/operators';
|
||||
import dayjs from 'dayjs';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { UI } from '@pdftron/webviewer';
|
||||
import off = UI.Hotkeys.off;
|
||||
|
||||
export function getStoredReports() {
|
||||
const rawStoredReports = localStorage.getItem(LICENSE_STORAGE_KEY);
|
||||
return JSON.parse(rawStoredReports ?? '{}') as Record<string, ILicenseReport>;
|
||||
}
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { LicenseFeatures } from '../modules/admin/screens/license/utils/constants';
|
||||
|
||||
const defaultOnError: ILicenses = {
|
||||
activeLicense: 'err',
|
||||
@ -23,16 +17,16 @@ const defaultOnError: ILicenses = {
|
||||
product: 'Error',
|
||||
licensedTo: 'Error',
|
||||
licensedToEmail: 'Error',
|
||||
validFrom: '01-01-2022',
|
||||
validUntil: '01-01-2023',
|
||||
validFrom: '01-01-2023',
|
||||
validUntil: '01-01-2024',
|
||||
features: [
|
||||
{
|
||||
name: 'processingPages',
|
||||
name: LicenseFeatures.PROCESSING_PAGES,
|
||||
type: 'NUMBER',
|
||||
value: '2000000',
|
||||
},
|
||||
{
|
||||
name: 'pdftron',
|
||||
name: LicenseFeatures.PDFTRON,
|
||||
type: 'STRING',
|
||||
value:
|
||||
'S25lY29uIEFHKGVuLmtuZWNvbi5zd2lzcyk6T0VNOkREQS1SOjpCKzpBTVMoMj' +
|
||||
@ -47,27 +41,15 @@ const defaultOnError: ILicenses = {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LicenseService extends GenericService<ILicenseReport> {
|
||||
storedReports = getStoredReports();
|
||||
readonly licenseData$: Observable<ILicenses>;
|
||||
readonly selectedLicense$: Observable<ILicense>;
|
||||
activeLicenseId: string;
|
||||
totalLicensedNumberOfPages = 0;
|
||||
currentLicenseInfo: ILicenseReport = {};
|
||||
allLicensesInfo: ILicenseReport = {};
|
||||
unlicensedPages = 0;
|
||||
analyzedPagesInCurrentLicensingPeriod = 0;
|
||||
selectedLicenseReport: ILicenseReport = {};
|
||||
allLicensesReport: ILicenseReport = {};
|
||||
protected readonly _defaultModelPath = 'report';
|
||||
readonly #licenseData$ = new BehaviorSubject<ILicenses | undefined>(undefined);
|
||||
readonly #selectedLicense$ = new BehaviorSubject<ILicense | undefined>(undefined);
|
||||
|
||||
constructor(private readonly _logger: NGXLogger) {
|
||||
super();
|
||||
this.selectedLicense$ = this.#selectedLicense$.pipe(filter(license => !!license));
|
||||
this.licenseData$ = this.#licenseData$.pipe(
|
||||
filter(licenses => !!licenses),
|
||||
tap(data => (this.activeLicenseId = data.activeLicense)),
|
||||
);
|
||||
}
|
||||
readonly #logger = inject(NGXLogger);
|
||||
readonly #toaster = inject(Toaster);
|
||||
|
||||
get selectedLicense() {
|
||||
return this.#selectedLicense$.value;
|
||||
@ -83,64 +65,88 @@ export class LicenseService extends GenericService<ILicenseReport> {
|
||||
}
|
||||
|
||||
get activeLicenseKey(): string {
|
||||
return this.activeLicense.features.find(f => f.name === 'pdftron').value;
|
||||
const activeLicense = this.activeLicense;
|
||||
if (!activeLicense) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return activeLicense.features.find(f => f.name === LicenseFeatures.PDFTRON).value as string;
|
||||
}
|
||||
|
||||
wipeStoredReportsAndReloadSelectedLicenseData() {
|
||||
this._logger.info('[LICENSE] Wiping stored reports and reloading license data');
|
||||
this.storedReports = {};
|
||||
this.setSelectedLicense(this.selectedLicense);
|
||||
get totalLicensedNumberOfPages(): number {
|
||||
const processingPagesFeature = this.getFeature(LicenseFeatures.PROCESSING_PAGES);
|
||||
return Number(processingPagesFeature?.value ?? '-1');
|
||||
}
|
||||
|
||||
get analyzedPagesPercentageForSelectedLicensePercentage(): number {
|
||||
return this.totalLicensedNumberOfPages > 0
|
||||
? (this.selectedLicenseReport.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100
|
||||
: -1;
|
||||
}
|
||||
|
||||
get analysisCapacityBytes(): number {
|
||||
const capacityFeature = this.getFeature(LicenseFeatures.ANALYSIS_CAPACITY_BYTES);
|
||||
return (Number(capacityFeature?.value ?? '-1') / 10) * 10;
|
||||
}
|
||||
|
||||
get retentionCapacityBytes(): number {
|
||||
const capacityFeature = this.getFeature(LicenseFeatures.RETENTION_CAPACITY_BYTES);
|
||||
return Number(capacityFeature?.value ?? '-1');
|
||||
}
|
||||
|
||||
get analysisCapacityBytesForSelectedLicensePercentage(): number {
|
||||
return this.analysisCapacityBytes > 0 ? (this.selectedLicenseReport.analysedFilesBytes / this.analysisCapacityBytes) * 100 : -1;
|
||||
}
|
||||
|
||||
get retentionCapacityBytesForSelectedLicensePercentage(): number {
|
||||
return this.retentionCapacityBytes > 0
|
||||
? (this.selectedLicenseReport.totalFilesUploadedBytes / this.retentionCapacityBytes) * 100
|
||||
: -1;
|
||||
}
|
||||
|
||||
get unlicensedPages(): number {
|
||||
return Math.max(0, this.selectedLicenseReport.numberOfAnalyzedPages - this.totalLicensedNumberOfPages);
|
||||
}
|
||||
|
||||
get isPageBased(): boolean {
|
||||
return this.totalLicensedNumberOfPages >= 0;
|
||||
}
|
||||
|
||||
get isCapacityBased(): boolean {
|
||||
return this.analysisCapacityBytes >= 0 && this.retentionCapacityBytes >= 0;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selectedLicense$ = this.#selectedLicense$.pipe(filter(license => !!license));
|
||||
this.licenseData$ = this.#licenseData$.pipe(filter(licenses => !!licenses));
|
||||
}
|
||||
|
||||
getFeature(name: string): ILicenseFeature | undefined {
|
||||
return this.selectedLicense.features?.find(f => f.name === name);
|
||||
}
|
||||
|
||||
async loadLicenseData(license: ILicense = this.selectedLicense) {
|
||||
this.totalLicensedNumberOfPages = this.getTotalLicensedNumberOfPages(license);
|
||||
|
||||
const startDate = dayjs(license.validFrom);
|
||||
const endDate = dayjs(license.validUntil);
|
||||
|
||||
const currentLicenseConfig = {
|
||||
this.selectedLicenseReport = await this.getReport({
|
||||
startDate: startDate.toDate(),
|
||||
endDate: endDate.toDate(),
|
||||
};
|
||||
});
|
||||
|
||||
const allLicensesConfig = {
|
||||
this.allLicensesReport = await this.getReport({
|
||||
startDate: '2020-01-01T00:00:00.000Z',
|
||||
endDate: '2023-12-31T00:00:00.000Z',
|
||||
};
|
||||
|
||||
const configs = [currentLicenseConfig, allLicensesConfig];
|
||||
const reports = configs.map(config => this.getReport(config));
|
||||
|
||||
[this.currentLicenseInfo, this.allLicensesInfo] = await Promise.all(reports);
|
||||
|
||||
if (this.currentLicenseInfo.numberOfAnalyzedPages > this.totalLicensedNumberOfPages) {
|
||||
this.unlicensedPages = this.currentLicenseInfo.numberOfAnalyzedPages - this.totalLicensedNumberOfPages;
|
||||
} else {
|
||||
this.unlicensedPages = 0;
|
||||
}
|
||||
this.analyzedPagesInCurrentLicensingPeriod = this.currentLicenseInfo.numberOfAnalyzedPages;
|
||||
}
|
||||
|
||||
getTotalLicensedNumberOfPages(license: ILicense) {
|
||||
const processingPagesFeature = license.features?.find(f => f.name === 'processingPages');
|
||||
return Number(processingPagesFeature?.value ?? '0');
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultSelectedLicense() {
|
||||
this.setSelectedLicense(this.activeLicense);
|
||||
}
|
||||
|
||||
getReport(body: ILicenseReportRequest, limit?: number, offset?: number) {
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (limit) {
|
||||
queryParams.push({ key: 'limit', value: limit });
|
||||
}
|
||||
|
||||
if (offset) {
|
||||
queryParams.push({ key: 'offset', value: offset });
|
||||
}
|
||||
|
||||
return firstValueFrom(this._post(body, `${this._defaultModelPath}/license`, queryParams));
|
||||
getReport(body: ILicenseReportRequest) {
|
||||
return firstValueFrom(this._post(body, `${this._defaultModelPath}/license`));
|
||||
}
|
||||
|
||||
async loadLicenses() {
|
||||
@ -148,6 +154,11 @@ export class LicenseService extends GenericService<ILicenseReport> {
|
||||
const licenses = await firstValueFrom(licenses$);
|
||||
this.#licenseData$.next(licenses);
|
||||
this.setDefaultSelectedLicense();
|
||||
const activeLicense = this.activeLicense;
|
||||
if (!activeLicense) {
|
||||
this.#logger.error('[LICENSE] No active license found!');
|
||||
this.#toaster.warning(_('no-active-license'));
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedLicense($event: ILicense) {
|
||||
|
||||
@ -14,6 +14,11 @@ export function hexToRgb(hex: string) {
|
||||
: null;
|
||||
}
|
||||
|
||||
export function hexToRgba(hex: string, alpha: number) {
|
||||
const rgb = hexToRgb(hex);
|
||||
return rgb ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})` : null;
|
||||
}
|
||||
|
||||
export function getFirstRelevantTextPart(text: string, direction: 'FORWARD' | 'BACKWARD') {
|
||||
let spaceCount = 0;
|
||||
let accumulator = '';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-05.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://frontend1.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
@ -11,7 +11,7 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-05.iqser.cloud/auth/realms/redaction",
|
||||
"OAUTH_URL": "https://frontend1.iqser.cloud/auth/realms/redaction",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural",
|
||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
||||
|
||||
@ -1633,16 +1633,18 @@
|
||||
"table-header": "{length} {length, plural, one{justification} other{justifications}}"
|
||||
},
|
||||
"license-info-screen": {
|
||||
"backend-version": "Backend Application Version",
|
||||
"chart": {
|
||||
"cumulative": "Cumulative Pages",
|
||||
"legend": "Legend",
|
||||
"pages-per-month": "Pages per Month",
|
||||
"total-pages": "Total Pages"
|
||||
"analysis-capacity-usage": {
|
||||
"analyzed-cumulative": "Cumulative Analyzed Data Volume",
|
||||
"analyzed-per-month": "Analyzed Data Volume per Month",
|
||||
"licensed": "Licensed Capacity",
|
||||
"section-title": "Analysis Capacity Details",
|
||||
"total-analyzed-data": "Total Analyzed Data",
|
||||
"used-in-period": "Analysis Capacity Used in Licensing Period",
|
||||
"used-in-total": "Total Analysis Capacity Used"
|
||||
},
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||
"backend-version": "Backend Application Version",
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon",
|
||||
"copyright-claim-title": "Copyright Claim",
|
||||
"current-analyzed": "Analyzed Pages in Licensing Period",
|
||||
"custom-app-title": "Custom Application Title",
|
||||
"email-report": "Email Report",
|
||||
"email": {
|
||||
@ -1652,22 +1654,42 @@
|
||||
},
|
||||
"title": "License Report {licenseCustomer}"
|
||||
},
|
||||
"end-user-license-text": "The use of this product is subject to the terms of the Redaction End User Agreement, unless otherwise specified therein.",
|
||||
"end-user-license-text": "The use of this product is subject to the terms of the DocuMine End User License Agreement, unless otherwise specified therein.",
|
||||
"end-user-license-title": "End User License Agreement",
|
||||
"license-title": "License Title",
|
||||
"licensed-page-count": "Licensed Pages",
|
||||
"licensed-to": "Licensed to",
|
||||
"licensing-details": "Licensing Details",
|
||||
"licensing-period": "Licensing Period",
|
||||
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||
"licensing-details": {
|
||||
"license-title": "License Title",
|
||||
"licensed-analysis-capacity": "Licensed Analysis Capacity",
|
||||
"licensed-page-count": "Licensed Pages",
|
||||
"licensed-retention-capacity": "Licensed Retention Capacity",
|
||||
"licensed-to": "Licensed to",
|
||||
"section-title": "Licensing Details",
|
||||
"licensing-period": "Licensing Period"
|
||||
},
|
||||
"page-usage": {
|
||||
"cumulative-pages": "Cumulative Pages",
|
||||
"current-analyzed-pages": "Analyzed Pages in Licensing Period",
|
||||
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||
"pages-per-month": "Pages per Month",
|
||||
"section-title": "Page Usage Details",
|
||||
"total-analyzed": "Total Analyzed Pages",
|
||||
"total-ocr-analyzed": "Total OCR Processed Pages",
|
||||
"total-pages": "Total Pages",
|
||||
"unlicensed-analyzed": "Unlicensed Analyzed Pages"
|
||||
},
|
||||
"retention-capacity-usage": {
|
||||
"active-documents": "Active Documents",
|
||||
"archived-documents": "Archived Documents",
|
||||
"exceeded-capacity": "Exceeded Capacity",
|
||||
"section-title": "Retention Capacity Details",
|
||||
"storage-capacity": "Capacity",
|
||||
"trash-documents": "Documents in Trash",
|
||||
"unused": "Unused Retention Capacity",
|
||||
"used-capacity": "Retention Capacity Used"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive"
|
||||
},
|
||||
"total-analyzed": "Total Analyzed Pages",
|
||||
"total-ocr-analyzed": "Total OCR Processed Pages",
|
||||
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
|
||||
"usage-details": "Usage Details"
|
||||
}
|
||||
},
|
||||
"license-information": "License Information",
|
||||
"load-all-annotations-success": "All annotations were loaded and are now visible in the document thumbnails",
|
||||
|
||||
@ -1633,16 +1633,18 @@
|
||||
"table-header": "{length} {length, plural, one{justification} other{justifications}}"
|
||||
},
|
||||
"license-info-screen": {
|
||||
"backend-version": "Backend Application Version",
|
||||
"chart": {
|
||||
"cumulative": "Cumulative Pages",
|
||||
"legend": "Legend",
|
||||
"pages-per-month": "Pages per Month",
|
||||
"total-pages": "Total Pages"
|
||||
"analysis-capacity-usage": {
|
||||
"analyzed-cumulative": "Cumulative Analyzed Data Volume",
|
||||
"analyzed-per-month": "Analyzed Data Volume per Month",
|
||||
"licensed": "Licensed Capacity",
|
||||
"section-title": "Analysis Capacity Details",
|
||||
"total-analyzed-data": "Total Analyzed Data",
|
||||
"used-in-period": "Analysis Capacity Used in Licensing Period",
|
||||
"used-in-total": "Total Analysis Capacity Used"
|
||||
},
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||
"backend-version": "Backend Application Version",
|
||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon",
|
||||
"copyright-claim-title": "Copyright Claim",
|
||||
"current-analyzed": "Analyzed Pages in Licensing Period",
|
||||
"custom-app-title": "Custom Application Title",
|
||||
"email-report": "Email Report",
|
||||
"email": {
|
||||
@ -1652,22 +1654,42 @@
|
||||
},
|
||||
"title": "License Report {licenseCustomer}"
|
||||
},
|
||||
"end-user-license-text": "The use of this product is subject to the terms of the Component End User Agreement, unless otherwise specified therein.",
|
||||
"end-user-license-text": "The use of this product is subject to the terms of the DocuMine End User License Agreement, unless otherwise specified therein.",
|
||||
"end-user-license-title": "End User License Agreement",
|
||||
"license-title": "License Title",
|
||||
"licensed-page-count": "Licensed pages",
|
||||
"licensed-to": "Licensed to",
|
||||
"licensing-details": "Licensing Details",
|
||||
"licensing-period": "Licensing Period",
|
||||
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||
"licensing-details": {
|
||||
"license-title": "License Title",
|
||||
"licensed-analysis-capacity": "Licensed Analysis Capacity",
|
||||
"licensed-page-count": "Licensed Pages",
|
||||
"licensed-retention-capacity": "Licensed Retention Capacity",
|
||||
"licensed-to": "Licensed to",
|
||||
"section-title": "Licensing Details",
|
||||
"licensing-period": "Licensing Period"
|
||||
},
|
||||
"page-usage": {
|
||||
"cumulative-pages": "Cumulative Pages",
|
||||
"current-analyzed-pages": "Analyzed Pages in Licensing Period",
|
||||
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||
"pages-per-month": "Pages per Month",
|
||||
"section-title": "Page Usage Details",
|
||||
"total-analyzed": "Total Analyzed Pages",
|
||||
"total-ocr-analyzed": "Total OCR Processed Pages",
|
||||
"total-pages": "Total Pages",
|
||||
"unlicensed-analyzed": "Unlicensed Analyzed Pages"
|
||||
},
|
||||
"retention-capacity-usage": {
|
||||
"active-documents": "Active Documents",
|
||||
"archived-documents": "Archived Documents",
|
||||
"exceeded-capacity": "Exceeded Capacity",
|
||||
"section-title": "Retention Capacity Details",
|
||||
"storage-capacity": "Capacity",
|
||||
"trash-documents": "Documents in Trash",
|
||||
"unused": "Unused Retention Capacity",
|
||||
"used-capacity": "Retention Capacity Used"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive"
|
||||
},
|
||||
"total-analyzed": "Total Analyzed Pages Since {date}",
|
||||
"total-ocr-analyzed": "Total OCR Processed Pages Since {date}",
|
||||
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
|
||||
"usage-details": "Usage Details"
|
||||
}
|
||||
},
|
||||
"license-information": "License Information",
|
||||
"load-all-annotations-success": "All annotations were loaded and are now visible in the document thumbnails",
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit ac3ccb1d7dda79fa196269d3f0565bbcf5d9067d
|
||||
Subproject commit 7312caa8d0119c5c153f02ddc22075ec9e945451
|
||||
@ -1,8 +1,4 @@
|
||||
import { List } from '@iqser/common-ui';
|
||||
|
||||
export interface ILicenseReportRequest {
|
||||
dossierIds?: List;
|
||||
endDate?: Date | string;
|
||||
requestId?: string;
|
||||
startDate?: Date | string;
|
||||
}
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
import { IReportData } from './report-data';
|
||||
|
||||
export interface ILicenseReport {
|
||||
data?: IReportData[];
|
||||
export interface ILicenseData {
|
||||
activeFilesUploadedBytes?: number;
|
||||
archivedFilesUploadedBytes?: number;
|
||||
totalFilesUploadedBytes?: number;
|
||||
trashFilesUploadedBytes?: number;
|
||||
numberOfAnalyzedPages?: number;
|
||||
numberOfOcrPages?: number;
|
||||
analysedFilesBytes?: number;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}
|
||||
|
||||
export interface ILicenseReport extends ILicenseData {
|
||||
limit?: number;
|
||||
numberOfAnalyses?: number;
|
||||
numberOfAnalyzedFiles?: number;
|
||||
numberOfAnalyzedPages?: number;
|
||||
numberOfDossiers?: number;
|
||||
numberOfOcrFiles?: number;
|
||||
numberOfOcrPages?: number;
|
||||
offset?: number;
|
||||
requestId?: string;
|
||||
startDate?: Date;
|
||||
monthlyData?: ILicenseData[];
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"@ngx-translate/http-loader": "^7.0.0",
|
||||
"@nrwl/angular": "15.6.3",
|
||||
"@pdftron/webviewer": "10.1.1",
|
||||
"@swimlane/ngx-charts": "^20.0.1",
|
||||
"chart.js": "4.4.0",
|
||||
"dayjs": "^1.11.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"jwt-decode": "^3.1.2",
|
||||
@ -48,6 +48,7 @@
|
||||
"keycloak-js": "20.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"ng2-charts": "5.0.3",
|
||||
"ngx-color-picker": "^13.0.0",
|
||||
"ngx-logger": "^5.0.11",
|
||||
"ngx-toastr": "^16.0.2",
|
||||
|
||||
189
yarn.lock
189
yarn.lock
@ -2556,6 +2556,11 @@
|
||||
"@jridgewell/resolve-uri" "3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "1.4.14"
|
||||
|
||||
"@kurkle/color@^0.3.0":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
|
||||
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
|
||||
|
||||
"@leichtgewicht/ip-codec@^2.0.1":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
||||
@ -3692,26 +3697,6 @@
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@swimlane/ngx-charts@^20.0.1":
|
||||
version "20.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@swimlane/ngx-charts/-/ngx-charts-20.4.1.tgz#42f3d63c1326cfe347d62d1f626840d6c1511276"
|
||||
integrity sha512-DyTQe0fcqLDoLEZca45gkdjxP8iLH7kh4pCkr+TCFIkmgEdfQ5DpavNBOOVO0qd5J5uV/tbtSnkYWSx8JkbFpg==
|
||||
dependencies:
|
||||
d3-array "^3.1.1"
|
||||
d3-brush "^3.0.0"
|
||||
d3-color "^3.1.0"
|
||||
d3-ease "^3.0.1"
|
||||
d3-format "^3.1.0"
|
||||
d3-hierarchy "^3.1.0"
|
||||
d3-interpolate "^3.0.1"
|
||||
d3-scale "^4.0.2"
|
||||
d3-selection "^3.0.0"
|
||||
d3-shape "^3.2.0"
|
||||
d3-time-format "^3.0.0"
|
||||
d3-transition "^3.0.1"
|
||||
rfdc "^1.3.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
@ -5317,6 +5302,13 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
chart.js@4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.0.tgz#df843fdd9ec6bd88d7f07e2b95348d221bd2698c"
|
||||
integrity sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==
|
||||
dependencies:
|
||||
"@kurkle/color" "^0.3.0"
|
||||
|
||||
chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.1, chokidar@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||
@ -5873,143 +5865,6 @@ cuint@^0.2.2:
|
||||
resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
|
||||
integrity sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==
|
||||
|
||||
d3-array@2:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
|
||||
integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
|
||||
dependencies:
|
||||
internmap "^1.0.0"
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.1:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
|
||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
d3-brush@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c"
|
||||
integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-drag "2 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-selection "3"
|
||||
d3-transition "3"
|
||||
|
||||
"d3-color@1 - 3", d3-color@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||
|
||||
"d3-dispatch@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
||||
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
|
||||
|
||||
"d3-drag@2 - 3":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
|
||||
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-selection "3"
|
||||
|
||||
"d3-ease@1 - 3", d3-ease@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
||||
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
||||
|
||||
"d3-format@1 - 3", d3-format@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||
|
||||
d3-hierarchy@^3.1.0:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
|
||||
integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
|
||||
|
||||
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
d3-path@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
|
||||
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||
|
||||
d3-scale@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||
dependencies:
|
||||
d3-array "2.10.0 - 3"
|
||||
d3-format "1 - 3"
|
||||
d3-interpolate "1.2.0 - 3"
|
||||
d3-time "2.1.1 - 3"
|
||||
d3-time-format "2 - 4"
|
||||
|
||||
d3-selection@3, d3-selection@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
||||
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
|
||||
|
||||
d3-shape@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
|
||||
integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
|
||||
dependencies:
|
||||
d3-path "^3.1.0"
|
||||
|
||||
"d3-time-format@2 - 4":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||
dependencies:
|
||||
d3-time "1 - 3"
|
||||
|
||||
d3-time-format@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6"
|
||||
integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==
|
||||
dependencies:
|
||||
d3-time "1 - 2"
|
||||
|
||||
"d3-time@1 - 2":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682"
|
||||
integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==
|
||||
dependencies:
|
||||
d3-array "2"
|
||||
|
||||
"d3-time@1 - 3", "d3-time@2.1.1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
|
||||
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
||||
dependencies:
|
||||
d3-array "2 - 3"
|
||||
|
||||
"d3-timer@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
||||
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
||||
|
||||
d3-transition@3, d3-transition@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
|
||||
integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
d3-dispatch "1 - 3"
|
||||
d3-ease "1 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-timer "1 - 3"
|
||||
|
||||
data-urls@^3.0.1, data-urls@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
|
||||
@ -7728,16 +7583,6 @@ internal-slot@^1.0.4:
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
"internmap@1 - 2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||
|
||||
internmap@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
|
||||
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
|
||||
|
||||
ip@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
|
||||
@ -8962,7 +8807,7 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash-es@^4.17.21:
|
||||
lodash-es@^4.17.15, lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
@ -9420,6 +9265,14 @@ neo-async@^2.6.2:
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
ng2-charts@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ng2-charts/-/ng2-charts-5.0.3.tgz#ce48e85ba375864928ca5c2788e11cbf1023cea2"
|
||||
integrity sha512-/lTY64tiCN/pJPx+oIWRWOhtCk+ZbAU9yAUDNnRJwhe+a8ajcO5yS0tVOm5k7pj3doVp9+UdBRahyt6woJ95Rw==
|
||||
dependencies:
|
||||
lodash-es "^4.17.15"
|
||||
tslib "^2.3.0"
|
||||
|
||||
ngx-color-picker@^13.0.0:
|
||||
version "13.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ngx-color-picker/-/ngx-color-picker-13.0.0.tgz#fe6d3b2def721ebc4f2a1a4ed83e552e0cfe3601"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user