From c8745f048d82dc099340b8c6d33566f69ad34e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 29 Jun 2023 16:23:50 +0300 Subject: [PATCH] RED-6830: License information with graphjs --- apps/red-ui/project.json | 2 +- .../combo-chart/combo-chart.component.html | 116 ------ .../combo-chart/combo-chart.component.scss | 103 ----- .../combo-chart/combo-chart.component.ts | 384 ------------------ .../combo-series-vertical.component.ts | 200 --------- .../screens/license/combo-chart/index.ts | 3 - .../license/combo-chart/y-axis.component.ts | 103 ----- .../components/chart/chart.component.html | 3 + .../components/chart/chart.component.scss | 8 + .../components/chart/chart.component.ts | 72 ++++ .../license-select.component.html | 0 .../license-select.component.scss | 0 .../license-select.component.ts | 0 .../license-storage.component.html | 47 +++ .../license-storage.component.scss | 38 ++ .../license-storage.component.ts | 91 +++++ .../license-usage.component.html | 41 ++ .../license-usage.component.scss | 36 ++ .../license-usage/license-usage.component.ts | 63 +++ .../license-chart.component.html | 18 - .../license-chart/license-chart.component.ts | 142 ------- .../license-screen.component.html | 61 +-- .../license-screen.component.scss | 15 +- .../license-screen.component.ts | 18 +- .../admin/screens/license/license.module.ts | 22 +- .../admin/screens/license/utils/constants.ts | 21 +- .../admin/screens/license/utils/functions.ts | 44 +- .../donut-chart/donut-chart.component.html | 2 +- .../donut-chart/donut-chart.component.ts | 17 +- .../src/app/services/license.service.ts | 45 +- apps/red-ui/src/app/utils/functions.ts | 5 + apps/red-ui/src/assets/i18n/redact/de.json | 4 +- apps/red-ui/src/assets/i18n/redact/en.json | 6 +- apps/red-ui/src/assets/i18n/scm/de.json | 4 +- apps/red-ui/src/assets/i18n/scm/en.json | 6 +- libs/common-ui | 2 +- .../src/lib/reports/license-report.request.ts | 4 - .../src/lib/reports/license-report.ts | 3 +- package.json | 7 +- yarn.lock | 189 +-------- 40 files changed, 528 insertions(+), 1417 deletions(-) delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/y-axis.component.ts create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.html create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.scss create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts rename apps/red-ui/src/app/modules/admin/screens/license/{ => components}/license-select/license-select.component.html (100%) rename apps/red-ui/src/app/modules/admin/screens/license/{ => components}/license-select/license-select.component.scss (100%) rename apps/red-ui/src/app/modules/admin/screens/license/{ => components}/license-select/license-select.component.ts (100%) create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.scss create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.scss create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts diff --git a/apps/red-ui/project.json b/apps/red-ui/project.json index 213866f34..c0f01719a 100644 --- a/apps/red-ui/project.json +++ b/apps/red-ui/project.json @@ -63,7 +63,7 @@ "stylePreprocessorOptions": { "includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"] }, - "scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"], + "scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js", "node_modules/chart.js/dist/chart.js"], "vendorChunk": true, "extractLicenses": false, "buildOptimizer": false, diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html deleted file mode 100644 index b1f569bc3..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss deleted file mode 100644 index c2ef9aba1..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss +++ /dev/null @@ -1,103 +0,0 @@ -.ngx-charts { - float: left; - overflow: visible; - - .circle, - .bar, - .arc { - cursor: pointer; - } - - .bar, - .cell, - .arc, - .card { - &.active, - &:hover { - opacity: 0.8; - transition: opacity 100ms ease-in-out; - } - - &:focus { - outline: none; - } - - &.hidden { - display: none; - } - } - - g { - &:focus { - outline: none; - } - } - - .line-series, - .line-series-range, - .area-series { - &.inactive { - transition: opacity 100ms ease-in-out; - opacity: 0.2; - } - } - - .line-highlight { - display: none; - - &.active { - display: block; - } - } - - .area { - opacity: 0.6; - } - - .circle { - &:hover { - cursor: pointer; - } - } - - .label { - font-size: 12px; - font-weight: normal; - } - - .tooltip-anchor { - fill: var(--iqser-text); - } - - .gridline-path { - stroke: #ddd; - stroke-width: 1; - fill: none; - } - - .grid-panel { - rect { - fill: none; - } - - &.odd { - rect { - fill: rgba(0, 0, 0, 0.05); - } - } - } - - fill: var(--iqser-text); -} - -.chart-legend .legend-labels { - background: var(--iqser-alt-background) !important; - - .legend-label .legend-label-text { - color: var(--iqser-text) !important; - - &:hover { - color: rgba(var(--iqser-text-rgb), 0.8) !important; - } - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts deleted file mode 100644 index 58db4f019..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts +++ /dev/null @@ -1,384 +0,0 @@ -import { Component, EventEmitter, HostListener, Input, Output, ViewEncapsulation } from '@angular/core'; - -import { curveLinear } from 'd3-shape'; -import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale'; -import { - BaseChartComponent, - calculateViewDimensions, - Color, - ColorHelper, - LegendPosition, - Orientation, - ScaleType, - Series, - StringOrNumberOrDate, - ViewDimensions, -} from '@swimlane/ngx-charts'; - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'combo-chart-component', - templateUrl: './combo-chart.component.html', - styleUrls: ['./combo-chart.component.scss'], - encapsulation: ViewEncapsulation.None, -}) -export class ComboChartComponent extends BaseChartComponent { - @Input() curve: any = curveLinear; - @Input() legend = false; - @Input() legendTitle = 'Legend'; - @Input() legendPosition: LegendPosition = LegendPosition.Right; - @Input() xAxis; - @Input() yAxis; - @Input() showXAxisLabel; - @Input() showYAxisLabel; - @Input() showRightYAxisLabel; - @Input() xAxisLabel; - @Input() yAxisLabel; - @Input() yAxisLabelRight; - @Input() tooltipDisabled = false; - @Input() gradient: boolean; - @Input() showGridLines = true; - @Input() activeEntries: any[] = []; - @Input() schemeType: ScaleType; - @Input() yAxisTickFormatting: any; - @Input() yRightAxisTickFormatting: any; - @Input() roundDomains = false; - @Input() colorSchemeLine: Color; - @Input() autoScale; - @Input() lineChart: Series[]; - @Input() yLeftAxisScaleFactor: any; - @Input() yRightAxisScaleFactor: any; - @Input() rangeFillOpacity: number; - @Input() animations = true; - @Input() noBarWhenZero = true; - @Output() activate = new EventEmitter<{ value; entries: unknown[] }>(); - @Output() deactivate = new EventEmitter<{ value; entries: unknown[] }>(); - dims: ViewDimensions; - xScale: any; - yScale: any; - xDomain: string[] | number[]; - yDomain: string[] | number[]; - transform: string; - colors: ColorHelper; - colorsLine: ColorHelper; - margin = [10, 20, 10, 20]; - xAxisHeight = 0; - yAxisWidth = 0; - legendOptions: any; - scaleType: ScaleType = ScaleType.Linear; - xScaleLine; - yScaleLine; - xDomainLine; - yDomainLine; - seriesDomain; - combinedSeries: Series[]; - xSet; - filteredDomain; - hoveredVertical; - yOrientLeft: Orientation = Orientation.Left; - yOrientRight: Orientation = Orientation.Right; - legendSpacing = 0; - bandwidth: number; - barPadding = 8; - - @Input() xAxisTickFormatting: any; - - trackBy(_index, item): string { - return item.name; - } - - update(): void { - super.update(); - this.dims = calculateViewDimensions({ - width: this.width, - height: this.height, - margins: this.margin, - showXAxis: this.xAxis, - showYAxis: this.yAxis, - xAxisHeight: this.xAxisHeight, - yAxisWidth: this.yAxisWidth, - showXLabel: this.showXAxisLabel, - showYLabel: this.showYAxisLabel, - showLegend: this.legend, - legendType: this.schemeType, - legendPosition: this.legendPosition, - }); - - if (!this.yAxis) { - this.legendSpacing = 0; - } else if (this.showYAxisLabel && this.yAxis) { - this.legendSpacing = 150; - } else { - this.legendSpacing = 40; - } - this.xScale = this.getXScale(); - this.yScale = this.getYScale(); - - // line chart - this.xDomainLine = this.getXDomainLine(); - if (this.filteredDomain) { - this.xDomainLine = this.filteredDomain; - } - - this.yDomainLine = this.getYDomainLine(); - this.seriesDomain = this.getSeriesDomain(); - - this.scaleLines(); - - this.setColors(); - this.legendOptions = this.getLegendOptions(); - - this.transform = `translate(${this.dims.xOffset} , ${this.margin[0]})`; - } - - deactivateAll() { - this.activeEntries = [...this.activeEntries]; - for (const entry of this.activeEntries) { - this.deactivate.emit({ value: entry, entries: [] }); - } - this.activeEntries = []; - } - - @HostListener('mouseleave') - hideCircles(): void { - this.hoveredVertical = null; - this.deactivateAll(); - } - - updateHoveredVertical(item): void { - this.hoveredVertical = item.value; - this.deactivateAll(); - } - - scaleLines() { - this.xScaleLine = this.getXScaleLine(this.xDomainLine, this.dims.width); - this.yScaleLine = this.getYScaleLine(this.yDomainLine, this.dims.height); - } - - getSeriesDomain(): any[] { - this.combinedSeries = this.lineChart.slice(0); - this.combinedSeries.push({ - name: this.yAxisLabel, - series: this.results, - }); - return this.combinedSeries.map(d => d.name); - } - - isDate(value): value is Date { - return value instanceof Date; - } - - getScaleType(values): ScaleType { - let date = true; - let num = true; - - for (const value of values) { - if (!this.isDate(value)) { - date = false; - } - - if (typeof value !== 'number') { - num = false; - } - } - - if (date) { - return ScaleType.Time; - } - if (num) { - return ScaleType.Linear; - } - return ScaleType.Ordinal; - } - - getXDomainLine(): any[] { - let values: StringOrNumberOrDate[] = []; - - for (const results of this.lineChart) { - for (const d of results.series) { - if (!values.includes(d.name)) { - values.push(d.name); - } - } - } - - this.scaleType = this.getScaleType(values); - let domain = []; - - if (this.scaleType === 'time') { - const min = Math.min(...(values as number[])); - const max = Math.max(...(values as number[])); - domain = [min, max]; - } else if (this.scaleType === 'linear') { - values = values.map(v => Number(v)); - const min = Math.min(...(values as number[])); - const max = Math.max(...(values as number[])); - domain = [min, max]; - } else { - domain = values; - } - - this.xSet = values; - return domain; - } - - getYDomainLine(): any[] { - const domain: number[] = []; - - for (const results of this.lineChart) { - for (const d of results.series) { - if (domain.indexOf(d.value) < 0) { - domain.push(d.value); - } - if (d.min !== undefined) { - if (domain.indexOf(d.min) < 0) { - domain.push(d.min); - } - } - if (d.max !== undefined) { - if (domain.indexOf(d.max) < 0) { - domain.push(d.max); - } - } - } - } - - let min = Math.min(...domain); - const max = Math.max(...domain); - if (this.yRightAxisScaleFactor) { - const minMax = this.yRightAxisScaleFactor(min, max); - return [Math.min(0, minMax.min as number), minMax.max]; - } - min = Math.min(0, min); - return [min, max]; - } - - getXScaleLine(domain, width: number): any { - let scale; - if (this.bandwidth === undefined) { - this.bandwidth = width - this.barPadding; - } - const offset = Math.floor((width + this.barPadding - (this.bandwidth + this.barPadding) * domain.length) / 2); - - if (this.scaleType === 'time') { - scale = scaleTime().range([0, width]).domain(domain); - } else if (this.scaleType === 'linear') { - scale = scaleLinear().range([0, width]).domain(domain); - - if (this.roundDomains) { - scale = scale.nice(); - } - } else if (this.scaleType === 'ordinal') { - scale = scalePoint() - .range([offset + this.bandwidth / 2, width - offset - this.bandwidth / 2]) - .domain(domain); - } - - return scale; - } - - getYScaleLine(domain, height): any { - const scale = scaleLinear().range([height, 0]).domain(domain); - return this.roundDomains ? scale.nice() : scale; - } - - getXScale(): any { - this.xDomain = this.getXDomain(); - const spacing = this.xDomain.length / (this.dims.width / this.barPadding + 1); - return scaleBand().range([0, this.dims.width]).paddingInner(spacing).domain(this.xDomain); - } - - getYScale(): any { - this.yDomain = this.getYDomain(); - const scale = scaleLinear().range([this.dims.height, 0]).domain(this.yDomain); - return this.roundDomains ? scale.nice() : scale; - } - - getXDomain(): any[] { - return this.results.map(d => d.name); - } - - getYDomain() { - const values: number[] = this.results.map(d => d.value); - const min = Math.min(0, ...values); - const max = Math.max(...values); - if (this.yLeftAxisScaleFactor) { - const minMax = this.yLeftAxisScaleFactor(min, max); - return [Math.min(0, minMax.min as number), minMax.max]; - } - return [min, max]; - } - - onClick(data) { - this.select.emit(data); - } - - setColors(): void { - let domain: number[] | string[]; - if (this.schemeType === 'ordinal') { - domain = this.xDomain; - } else { - domain = this.yDomain; - } - this.colors = new ColorHelper(this.scheme, this.schemeType, domain, this.customColors); - this.colorsLine = new ColorHelper(this.colorSchemeLine, this.schemeType, domain, this.customColors); - } - - getLegendOptions() { - const opts = { - scaleType: this.schemeType, - colors: undefined, - domain: [], - title: undefined, - position: this.legendPosition, - }; - if (opts.scaleType === 'ordinal') { - opts.domain = this.seriesDomain; - opts.colors = this.colorsLine; - opts.title = this.legendTitle; - } else { - opts.domain = this.seriesDomain; - opts.colors = this.colors.scale; - } - return opts; - } - - getValuesMaxLength(index: number): number { - const values = this.lineChart[index].series.map(s => s.value); - return Math.max(...values).toString().length; - } - - updateLineWidth(width): void { - this.bandwidth = width; - this.scaleLines(); - } - - updateYAxisWidth({ width }: { width: number }): void { - this.yAxisWidth = width + 20; - this.update(); - } - - updateXAxisHeight({ height }): void { - this.xAxisHeight = height; - this.update(); - } - - onActivate(item) { - const idx = this.activeEntries.findIndex(d => d.name === item.name && d.value === item.value && d.series === item.series); - if (idx > -1) { - return; - } - - this.activeEntries = [item, ...this.activeEntries]; - this.activate.emit({ value: item, entries: this.activeEntries }); - } - - onDeactivate(item) { - const idx = this.activeEntries.findIndex(d => d.name === item.name && d.value === item.value && d.series === item.series); - - this.activeEntries.splice(idx, 1); - this.activeEntries = [...this.activeEntries]; - - this.deactivate.emit({ value: item, entries: this.activeEntries }); - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts deleted file mode 100644 index ecc1f2f17..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; -import { animate, style, transition, trigger } from '@angular/animations'; -import { Bar, BarOrientation, formatLabel, PlacementTypes, StyleTypes } from '@swimlane/ngx-charts'; - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'g[ngx-combo-charts-series-vertical]', - template: ` - - `, - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [ - trigger('animationState', [ - transition('* => void', [ - style({ - opacity: 1, - transform: '*', - }), - animate(500, style({ opacity: 0, transform: 'scale(0)' })), - ]), - ]), - ], -}) -export class ComboSeriesVerticalComponent implements OnChanges { - @Input() dims; - @Input() type = 'standard'; - @Input() series; - @Input() seriesLine; - @Input() xScale; - @Input() yScale; - @Input() colors; - @Input() tooltipDisabled = false; - @Input() gradient: boolean; - @Input() activeEntries: any[]; - @Input() seriesName: string; - @Input() animations = true; - @Input() noBarWhenZero = true; - - @Output() activate = new EventEmitter(); - @Output() deactivate = new EventEmitter(); - @Output() bandwidth = new EventEmitter(); - - bars: Bar[]; - x: any; - y: any; - readonly tooltipTypes = StyleTypes; - readonly tooltipPlacements = PlacementTypes; - readonly orientations = BarOrientation; - - ngOnChanges(): void { - this.update(); - } - - update(): void { - let width; - if (this.series.length) { - width = this.xScale.bandwidth(); - this.bandwidth.emit(width); - } - - let d0 = 0; - let total; - if (this.type === 'normalized') { - total = this.series.map(d => d.value).reduce((sum: number, d: number) => sum + d, 0); - } - - this.bars = this.series.map((d, index) => { - let value: number = d.value; - const label = d.name; - const formattedLabel = formatLabel(label); - const roundEdges = this.type === 'standard'; - - const bar: Bar = { - value, - label, - roundEdges, - data: d, - width, - formattedLabel, - height: 0, - x: 0, - y: 0, - ariaLabel: label, - tooltipText: label, - color: undefined, - gradientStops: undefined, - }; - - let offset0 = d0; - let offset1 = offset0 + value; - - if (this.type === 'standard') { - bar.height = Math.abs(this.yScale(value) - this.yScale(0)); - bar.x = this.xScale(label); - - if (value < 0) { - bar.y = this.yScale(0); - } else { - bar.y = this.yScale(value); - } - } else if (this.type === 'stacked') { - d0 += value; - - bar.height = this.yScale(offset0) - this.yScale(offset1); - bar.x = 0; - bar.y = this.yScale(offset1); - // bar.offset0 = offset0; - // bar.offset1 = offset1; - } else if (this.type === 'normalized') { - d0 += value; - - if (total > 0) { - offset0 = (offset0 * 100) / total; - offset1 = (offset1 * 100) / total; - } else { - offset0 = 0; - offset1 = 0; - } - - bar.height = this.yScale(offset0) - this.yScale(offset1); - bar.x = 0; - bar.y = this.yScale(offset1); - // bar.offset0 = offset0; - // bar.offset1 = offset1; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - value = (offset1 - offset0).toFixed(2) + '%'; - } - - if (this.colors.scaleType === 'ordinal') { - bar.color = this.colors.getColor(label); - } else { - if (this.type === 'standard') { - bar.color = this.colors.getColor(value); - bar.gradientStops = this.colors.getLinearGradientStops(value); - } else { - bar.color = this.colors.getColor(offset1); - bar.gradientStops = this.colors.getLinearGradientStops(offset1, offset0); - } - } - - let tooltipLabel = formattedLabel; - if (this.seriesName) { - tooltipLabel = `${this.seriesName} • ${formattedLabel}`; - } - - this.getSeriesTooltips(this.seriesLine, index); - const lineValue: string = this.seriesLine[0].series[index].value; - bar.tooltipText = ` - ${tooltipLabel} - - Y1 - ${value.toLocaleString()} • Y2 - ${lineValue.toLocaleString()}% - - `; - - return bar; - }); - } - - getSeriesTooltips(seriesLine, index) { - return seriesLine.map(d => d.series[index]); - } - - isActive(entry): boolean { - if (!this.activeEntries) { - return false; - } - const item = this.activeEntries.find(d => entry.name === d.name && entry.series === d.series); - return item !== undefined; - } - - trackBy(_index: number, bar: Bar): string { - return bar.label; - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts deleted file mode 100644 index 07ef678f5..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './combo-chart.component'; -export * from './combo-series-vertical.component'; -export * from './y-axis.component'; diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/y-axis.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/y-axis.component.ts deleted file mode 100644 index 3509f511f..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/y-axis.component.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; -import { Orientation, ViewDimensions, YAxisTicksComponent } from '@swimlane/ngx-charts'; - -@Component({ - selector: 'g[red-ngx-charts-y-axis]', - template: ` - - - - - `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class YAxisComponent implements OnChanges { - @Input() yScale; - @Input() dims: ViewDimensions; - @Input() trimTicks: boolean; - @Input() maxTickLength: number; - @Input() tickFormatting; - @Input() ticks: any[]; - @Input() showGridLines = false; - @Input() showLabel: boolean; - @Input() labelText: string; - @Input() yAxisTickCount: any; - @Input() yOrient: Orientation = Orientation.Left; - @Input() referenceLines; - @Input() showRefLines: boolean; - @Input() showRefLabels: boolean; - @Input() yAxisOffset = 0; - @Input() valuesMaxLength = 0; - @Output() dimensionsChanged = new EventEmitter(); - - yAxisClassName = 'y axis'; - tickArguments: number[]; - offset: number; - transform: string; - labelOffset = 15; - fill = 'none'; - stroke = '#CCC'; - tickStroke = '#CCC'; - strokeWidth = 1; - padding = 5; - - @ViewChild(YAxisTicksComponent) ticksComponent: YAxisTicksComponent; - - ngOnChanges(changes: SimpleChanges): void { - this.update(); - } - - update(): void { - this.offset = -(this.yAxisOffset + this.padding); - if (this.yOrient === Orientation.Right) { - this.labelOffset = 65 + (this.valuesMaxLength + this.valuesMaxLength / 4) * 5; - this.transform = `translate(${this.offset + this.dims.width} , 0)`; - } else { - this.transform = `translate(${this.offset} , 0)`; - } - - if (this.yAxisTickCount !== undefined) { - this.tickArguments = [this.yAxisTickCount]; - } - } - - emitTicksWidth({ width }): void { - if (width !== this.labelOffset && this.yOrient === Orientation.Right) { - this.labelOffset = width + this.labelOffset + 300; - setTimeout(() => { - this.dimensionsChanged.emit({ width }); - }, 0); - } else if (width !== this.labelOffset) { - this.labelOffset = width + (this.valuesMaxLength + this.valuesMaxLength / 4) * 5; - setTimeout(() => { - this.dimensionsChanged.emit({ width }); - }, 0); - } - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.html new file mode 100644 index 000000000..0e417290e --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.scss new file mode 100644 index 000000000..b59b59cec --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.scss @@ -0,0 +1,8 @@ +:host { + margin: 0 auto; +} + +.canvas-container { + position: relative; + width: 1000px; +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts new file mode 100644 index 000000000..0faf36246 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts @@ -0,0 +1,72 @@ +import { Component, Input, OnChanges } from '@angular/core'; +import { ChartConfiguration, ChartDataset } from 'chart.js'; + +@Component({ + selector: 'redaction-chart', + templateUrl: './chart.component.html', + styleUrls: ['./chart.component.scss'], +}) +export class ChartComponent implements OnChanges { + @Input({ required: true }) datasets: ChartDataset[]; + @Input({ required: true }) labels: string[]; + @Input() ticksCallback?: (value: number) => string; + @Input() secondaryAxis = false; + @Input() yAxisLabel?: string; + @Input() yAxisLabelRight?: string; + + chartData: ChartConfiguration['data']; + chartOptions: ChartConfiguration<'line'>['options'] = {}; + + constructor() { + this.#setChartOptions(); + } + + ngOnChanges() { + this.chartData = { + labels: this.labels, + datasets: this.datasets, + }; + this.#setChartOptions(); + } + + #setChartOptions(): void { + this.chartOptions = { + scales: { + y: { + stacked: true, + ticks: { callback: this.ticksCallback ? (value: number) => this.ticksCallback(value) : undefined }, + title: { + display: !!this.yAxisLabel, + text: this.yAxisLabel, + padding: { bottom: 20 }, + }, + }, + y1: { + display: this.secondaryAxis, + position: 'right', + title: { + display: !!this.yAxisLabelRight, + text: this.yAxisLabelRight, + padding: { bottom: 20 }, + }, + }, + }, + plugins: { + legend: { position: 'right' }, + tooltip: { + callbacks: { + label: this.ticksCallback ? item => `${item.dataset.label}: ${this.ticksCallback(item.parsed.y)}` : undefined, + }, + }, + }, + layout: { + padding: { + top: 50, + bottom: 50, + }, + }, + datasets: { bar: { barPercentage: 0.7 } }, + aspectRatio: 2.5, + }; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.html similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.html rename to apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.html diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.scss similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.scss rename to apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.scss diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.ts similarity index 100% rename from apps/red-ui/src/app/modules/admin/screens/license/license-select/license-select.component.ts rename to apps/red-ui/src/app/modules/admin/screens/license/components/license-select/license-select.component.ts diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html new file mode 100644 index 000000000..87bd0b86b --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html @@ -0,0 +1,47 @@ +
+ +
+
+
{{ licenseService.currentLicenseReport.activeFilesUploadedBytes | size }}
+
+ +
+
+
{{ licenseService.currentLicenseReport.archivedFilesUploadedBytes | size }}
+
+
+
+
{{ licenseService.currentLicenseReport.trashFilesUploadedBytes | size }}
+
+ +
+
+
+ {{ licenseService.currentLicenseReport.totalFilesUploadedBytes | size }} + + ({{ uploadedBytesCapacityPercentage | number : '1.0-2' }}%) + +
+
+ +
+ +
+ +
+ +
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.scss new file mode 100644 index 000000000..c0a071ff5 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.scss @@ -0,0 +1,38 @@ +:host { + display: contents; +} + +.donut-chart-wrapper { + grid-row: 11 / span 4; + grid-column: 3; + width: fit-content; +} + +.row { + display: contents; + + > div { + padding: 8px 20px; + + &:first-of-type { + font-weight: 600; + } + } + + &: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); +} + +redaction-chart { + grid-column: span 3; +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts new file mode 100644 index 000000000..cce4f1eef --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts @@ -0,0 +1,91 @@ +import { Component } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { size } from '@iqser/common-ui'; +import { LicenseService } from '@services/license.service'; +import { map, tap } from 'rxjs/operators'; +import type { DonutChartConfig, ILicenseReport } from '@red/domain'; +import { ChartDataset } from 'chart.js'; +import { ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants'; +import { getLabelsFromMonthlyData, getLineConfig } from '../../utils/functions'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +@Component({ + selector: 'red-license-storage', + templateUrl: './license-storage.component.html', + styleUrls: ['./license-storage.component.scss'], +}) +export class LicenseStorageComponent { + readonly formatSize = size; + uploadedBytesCapacityPercentage = -1; + donutChartConfig: DonutChartConfig[] = []; + readonly data$ = this.licenseService.licenseData$.pipe( + map(() => this.licenseService.currentLicenseReport), + tap(license => { + this.uploadedBytesCapacityPercentage = this.#getUploadedBytesCapacityPercentage(license); + this.donutChartConfig = this.#getDonutChartConfig(license); + }), + map(license => ({ + datasets: this.#getDatasets(license), + labels: getLabelsFromMonthlyData(license.monthlyData), + })), + ); + readonly size = size; + + constructor(readonly licenseService: LicenseService, private readonly _translateService: TranslateService) {} + + #getUploadedBytesCapacityPercentage(license: ILicenseReport): number { + return this.licenseService.uploadedBytesCapacity + ? (license.totalFilesUploadedBytes / this.licenseService.uploadedBytesCapacity) * 100 + : -1; + } + + #getDonutChartConfig(license: ILicenseReport): DonutChartConfig[] { + return [ + { + value: license.activeFilesUploadedBytes, + color: ChartGreen, + label: this._translateService.instant(_('license-info-screen.storage.active-documents')), + }, + { + value: license.archivedFilesUploadedBytes, + color: ChartBlue, + label: this._translateService.instant(_('license-info-screen.storage.archived-documents')), + }, + { + value: license.trashFilesUploadedBytes, + color: ChartRed, + label: this._translateService.instant(_('license-info-screen.storage.trash-documents')), + }, + { + value: this.licenseService.uploadedBytesCapacity - license.totalFilesUploadedBytes, + color: ChartGrey, + label: this._translateService.instant(_('license-info-screen.storage.unused')), + }, + ]; + } + + #getDatasets(license: ILicenseReport): ChartDataset[] { + const monthlyData = license.monthlyData; + + return [ + { + data: monthlyData.flatMap(d => d.activeFilesUploadedBytes), + label: this._translateService.instant('license-info-screen.storage.active-documents'), + ...getLineConfig(ChartGreen, 'origin'), + stack: 'storage', + }, + { + data: monthlyData.flatMap(d => d.archivedFilesUploadedBytes), + label: this._translateService.instant('license-info-screen.storage.archived-documents'), + ...getLineConfig(ChartBlue, '-1'), + stack: 'storage', + }, + { + data: monthlyData.flatMap(d => d.trashFilesUploadedBytes), + label: this._translateService.instant('license-info-screen.storage.trash-documents'), + ...getLineConfig(ChartRed, '-1'), + stack: 'storage', + }, + ]; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html new file mode 100644 index 000000000..dec058ea6 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html @@ -0,0 +1,41 @@ +
+ +
+
+
+ {{ licenseService.analyzedPagesInCurrentLicensingPeriod }} + ({{ analysisPercentageOfLicense$ | async | number : '1.0-2' }}%) +
+
+ +
+
+
{{ licenseService.currentLicenseReport.numberOfOcrPages }}
+
+ +
+
+
{{ licenseService.unlicensedPages }}
+
+ +
+
+ +
{{ licenseService.allLicensesReport.numberOfAnalyzedPages }}
+
+ +
+
+
{{ licenseService.allLicensesReport.numberOfOcrPages }}
+
+ +
+ +
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.scss new file mode 100644 index 000000000..2a35a4526 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.scss @@ -0,0 +1,36 @@ +:host { + display: contents; +} + +.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); +} + +redaction-chart { + grid-column: span 3; +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts new file mode 100644 index 000000000..8f1524d3c --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { LicenseService } from '@services/license.service'; +import { map } from 'rxjs/operators'; +import type { ILicenseReport } from '@red/domain'; +import { ChartDataset } from 'chart.js'; +import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants'; +import { getLabelsFromMonthlyData, getLineConfig } from '../../utils/functions'; + +@Component({ + selector: 'red-license-usage', + templateUrl: './license-usage.component.html', + styleUrls: ['./license-usage.component.scss'], +}) +export class LicenseUsageComponent { + readonly analysisPercentageOfLicense$ = this.licenseService.selectedLicense$.pipe(map(() => this.getAnalysisPercentageOfLicense())); + readonly data$ = this.licenseService.selectedLicense$.pipe( + map(() => this.licenseService.currentLicenseReport), + map(license => ({ + datasets: this.#getDatasets(license), + labels: getLabelsFromMonthlyData(license.monthlyData), + })), + ); + + constructor(readonly licenseService: LicenseService, private readonly _translateService: TranslateService) {} + + getAnalysisPercentageOfLicense() { + const totalLicensedNumberOfPages = this.licenseService.totalLicensedNumberOfPages; + const numberOfAnalyzedPages = this.licenseService.analyzedPagesInCurrentLicensingPeriod; + return totalLicensedNumberOfPages > 0 ? (numberOfAnalyzedPages / totalLicensedNumberOfPages) * 100 : 100; + } + + #getDatasets(license: ILicenseReport): ChartDataset[] { + const monthlyData = license.monthlyData; + return [ + { + data: monthlyData.flatMap(d => d.numberOfAnalyzedPages), + label: 'Pages per Month', + type: 'bar', + backgroundColor: ChartBlue, + order: 2, + }, + { + data: monthlyData.flatMap(() => 200000), + label: 'Total Pages', + ...getLineConfig(ChartRed, false), + yAxisID: 'y1', + order: 1, + }, + { + data: monthlyData.map( + (month, monthIndex) => + month.numberOfAnalyzedPages + + monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + curr.numberOfAnalyzedPages, 0), + ), + label: 'Cumulative Pages', + yAxisID: 'y1', + order: 1, + ...getLineConfig(ChartGreen, false), + }, + ]; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html b/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html deleted file mode 100644 index 8da1b9488..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts deleted file mode 100644 index 23d20b594..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts +++ /dev/null @@ -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 = []; - - get #licenseChartSeries$(): Observable { - return this._licenseService.selectedLicense$.pipe( - tap(() => this._loadingService.start()), - switchMap(license => this.#getLicenseData(license)), - tap(() => this._loadingService.stop()), - ); - } - - constructor( - private readonly _translateService: TranslateService, - private readonly _licenseService: LicenseService, - private readonly _loadingService: LoadingService, - ) {} - - async #getLicenseData(license: ILicense): Promise { - 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, reports: List): 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) { - 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, 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) { - return dateRanges.map(dateRange => ({ - name: verboseDate(dateRange), - value: this._licenseService.totalLicensedNumberOfPages, - })); - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.html index 386d0f278..7a642fc4b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.html @@ -61,65 +61,8 @@
{{ licenseService.totalLicensedNumberOfPages }}
- - -
- -
-
-
{{ licenseService.currentLicenseInfo.activeFilesUploadedBytes | size }}
-
- -
-
-
{{ licenseService.currentLicenseInfo.archivedFilesUploadedBytes | size }}
-
-
-
-
{{ licenseService.currentLicenseInfo.trashFilesUploadedBytes | size }}
-
- -
-
-
{{ licenseService.currentLicenseInfo.totalFilesUploadedBytes | size }}
-
- - - -
- -
-
-
- {{ licenseService.analyzedPagesInCurrentLicensingPeriod }} - ({{ analysisPercentageOfLicense$ | async | number : '1.0-2' }}%) -
-
- -
-
-
{{ licenseService.currentLicenseInfo.numberOfOcrPages }}
-
- -
-
-
{{ licenseService.unlicensedPages }}
-
- -
-
- -
{{ licenseService.allLicensesInfo.numberOfAnalyzedPages }}
-
- -
-
-
{{ licenseService.allLicensesInfo.numberOfOcrPages }}
-
- -
- -
+ + diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.scss index a48358949..0320d1c0b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.scss @@ -11,7 +11,7 @@ .grid-container { width: calc(100% - 40px); display: grid; - grid-template-columns: 1fr 2fr; + grid-template-columns: 1fr 2fr 2fr; margin: 20px; .row { @@ -23,6 +23,10 @@ &:first-of-type { font-weight: 600; } + + &:nth-child(2) { + grid-column: span 2; + } } &:hover { @@ -33,15 +37,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: 50px 0; - grid-column: span 2; + redaction-chart { + grid-column: span 3; + } } } diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts index c4609aac6..c09292794 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts @@ -1,11 +1,10 @@ import { Component } from '@angular/core'; import { ConfigService } from '@services/config.service'; import { TranslateService } from '@ngx-translate/core'; -import { ButtonConfig, getCurrentUser, IconButtonTypes, IqserPermissionsService, LoadingService } from '@iqser/common-ui'; +import { ButtonConfig, getCurrentUser, 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 { Roles } from '@users/roles'; import type { User } from '@red/domain'; @@ -27,24 +26,13 @@ export class LicenseScreenComponent { }, ]; - 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; @@ -54,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.currentLicenseReport.numberOfAnalyzedPages, }), this._translateService.instant('license-info-screen.email.body.licensed', { pages: this.licenseService.totalLicensedNumberOfPages, diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts b/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts index 5b9bfc13c..6ac357a00 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts @@ -1,16 +1,18 @@ 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, SizePipe } from '@iqser/common-ui'; -import { NgxChartsModule } from '@swimlane/ngx-charts'; -import { ComboChartComponent, ComboSeriesVerticalComponent, YAxisComponent } from './combo-chart'; 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 { LicenseStorageComponent } from './components/license-storage/license-storage.component'; +import { LicenseUsageComponent } from './components/license-usage/license-usage.component'; +import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component'; const routes: Routes = [ { @@ -23,24 +25,18 @@ const routes: Routes = [ ]; @NgModule({ - declarations: [ - LicenseScreenComponent, - LicenseSelectComponent, - LicenseChartComponent, - ComboChartComponent, - ComboSeriesVerticalComponent, - YAxisComponent, - ], + declarations: [LicenseScreenComponent, LicenseSelectComponent, ChartComponent, LicenseStorageComponent, LicenseUsageComponent], imports: [ RouterModule.forChild(routes), CommonModule, TranslateModule, MatSelectModule, FormsModule, - NgxChartsModule, IqserListingModule, IqserHelpModeModule, SizePipe, + NgChartsModule, + DonutChartComponent, ], }) export class LicenseModule {} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts b/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts index a139c9732..24c1bde59 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts @@ -1,17 +1,4 @@ -import { Color, ScaleType } from '@swimlane/ngx-charts'; - -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 const ChartRed = '#dd4d50'; +export const ChartGreen = '#5ce594'; +export const ChartBlue = '#0389ec'; +export const ChartGrey = '#ccced3'; // grey-5 diff --git a/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts b/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts index 0dd196237..efbbea5d3 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts @@ -1,18 +1,32 @@ -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 { ILicenseData } 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, + target: FillTarget, +) => { + type: 'line'; + borderColor: string; + backgroundColor: string; + pointBackgroundColor: string; + fill: ComplexFillTarget; +} = (color, target) => ({ + type: 'line', + borderColor: hexToRgba(color, 1), + backgroundColor: hexToRgba(color, 1), + pointBackgroundColor: hexToRgba(color, 1), + fill: { + target: target ?? '-1', + above: hexToRgba(color, 0.3), + below: hexToRgba(color, 0.3), + }, +}); + +export const getLabelsFromMonthlyData = (monthlyData: ILicenseData[]) => monthlyData.map(data => verboseDate(dayjs(data.startDate))); diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.html b/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.html index 5d08fd33b..2de1a0ff6 100644 --- a/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.html +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.html @@ -18,7 +18,7 @@
-
{{ displayedDataTotal }}
+
{{ getFormattedValue(displayedDataTotal) }}
{{ subtitles[0] }}
string; @Output() readonly subtitleChanged = new EventEmitter(); @@ -32,10 +33,6 @@ export class DonutChartComponent implements OnChanges, OnInit { size = 0; filters$: Observable; - 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; } @@ -48,6 +45,10 @@ 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() { const filterModels$ = this.filterService?.getFilterModels$(this.filterKey).pipe( map(filters => filters ?? []), @@ -71,6 +72,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 => { @@ -101,8 +106,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 { diff --git a/apps/red-ui/src/app/services/license.service.ts b/apps/red-ui/src/app/services/license.service.ts index f56db7792..7c1dd0d14 100644 --- a/apps/red-ui/src/app/services/license.service.ts +++ b/apps/red-ui/src/app/services/license.service.ts @@ -1,17 +1,11 @@ import { Injectable } from '@angular/core'; -import { GenericService, QueryParam } from '@iqser/common-ui'; +import { GenericService } from '@iqser/common-ui'; import { ILicense, 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 dayjs from 'dayjs'; import { NGXLogger } from 'ngx-logger'; -export function getStoredReports() { - const rawStoredReports = localStorage.getItem(LICENSE_STORAGE_KEY); - return JSON.parse(rawStoredReports ?? '{}') as Record; -} - const defaultOnError: ILicenses = { activeLicense: 'err', licenses: [ @@ -45,15 +39,15 @@ const defaultOnError: ILicenses = { providedIn: 'root', }) export class LicenseService extends GenericService { - storedReports = getStoredReports(); readonly licenseData$: Observable; readonly selectedLicense$: Observable; activeLicenseId: string; totalLicensedNumberOfPages = 0; - currentLicenseInfo: ILicenseReport = {}; - allLicensesInfo: ILicenseReport = {}; + currentLicenseReport: ILicenseReport = {}; + allLicensesReport: ILicenseReport = {}; unlicensedPages = 0; analyzedPagesInCurrentLicensingPeriod = 0; + uploadedBytesCapacity = 0; protected readonly _defaultModelPath = 'report'; readonly #licenseData$ = new BehaviorSubject(undefined); readonly #selectedLicense$ = new BehaviorSubject(undefined); @@ -81,15 +75,13 @@ export class LicenseService extends GenericService { this.licenseData$ = this.#licenseData$.pipe( filter(licenses => !!licenses), tap(data => (this.activeLicenseId = data.activeLicense)), + tap(() => { + const uploadedBytesCapacity = this.activeLicense.features.find(f => f.name === 'uploadedBytesCapacity')?.value; + this.uploadedBytesCapacity = uploadedBytesCapacity ? parseInt(uploadedBytesCapacity, 10) : 0; + }), ); } - wipeStoredReportsAndReloadSelectedLicenseData() { - this._logger.info('[LICENSE] Wiping stored reports and reloading license data'); - this.storedReports = {}; - this.setSelectedLicense(this.selectedLicense); - } - async loadLicenseData(license: ILicense = this.selectedLicense) { this.totalLicensedNumberOfPages = this.getTotalLicensedNumberOfPages(license); @@ -109,14 +101,14 @@ export class LicenseService extends GenericService { const configs = [currentLicenseConfig, allLicensesConfig]; const reports = configs.map(config => this.getReport(config)); - [this.currentLicenseInfo, this.allLicensesInfo] = await Promise.all(reports); + [this.currentLicenseReport, this.allLicensesReport] = await Promise.all(reports); - if (this.currentLicenseInfo.numberOfAnalyzedPages > this.totalLicensedNumberOfPages) { - this.unlicensedPages = this.currentLicenseInfo.numberOfAnalyzedPages - this.totalLicensedNumberOfPages; + if (this.currentLicenseReport.numberOfAnalyzedPages > this.totalLicensedNumberOfPages) { + this.unlicensedPages = this.currentLicenseReport.numberOfAnalyzedPages - this.totalLicensedNumberOfPages; } else { this.unlicensedPages = 0; } - this.analyzedPagesInCurrentLicensingPeriod = this.currentLicenseInfo.numberOfAnalyzedPages; + this.analyzedPagesInCurrentLicensingPeriod = this.currentLicenseReport.numberOfAnalyzedPages; } getTotalLicensedNumberOfPages(license: ILicense) { @@ -128,17 +120,8 @@ export class LicenseService extends GenericService { 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() { diff --git a/apps/red-ui/src/app/utils/functions.ts b/apps/red-ui/src/app/utils/functions.ts index 02e0dffa1..ed720c96f 100644 --- a/apps/red-ui/src/app/utils/functions.ts +++ b/apps/red-ui/src/app/utils/functions.ts @@ -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 = ''; diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index dfea3dcef..294073cf1 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -1681,7 +1681,9 @@ "active-documents": "", "all-documents": "", "archived-documents": "", - "trash-documents": "" + "storage-capacity": "", + "trash-documents": "", + "unused": "" }, "total-analyzed": "Seit {date} insgesamt analysierte Seiten", "total-ocr-analyzed": "", diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 6f0d29933..0c2d1092d 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -1679,9 +1679,11 @@ "storage-details": "Storage Details", "storage": { "active-documents": "Active Documents", - "all-documents": "All Documents", + "all-documents": "Total Storage Used", "archived-documents": "Archived Documents", - "trash-documents": "Documents in Trash" + "storage-capacity": "Storage Capacity", + "trash-documents": "Documents in Trash", + "unused": "Unused Storage" }, "total-analyzed": "Total Analyzed Pages", "total-ocr-analyzed": "Total OCR Processed Pages", diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index ba2394f36..33d99d99a 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -1681,7 +1681,9 @@ "active-documents": "", "all-documents": "", "archived-documents": "", - "trash-documents": "" + "storage-capacity": "", + "trash-documents": "", + "unused": "" }, "total-analyzed": "Seit {date} insgesamt analysierte Seiten", "total-ocr-analyzed": "", diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 5d5dd741c..2fe18f605 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -1679,9 +1679,11 @@ "storage-details": "Storage Details", "storage": { "active-documents": "Active Documents", - "all-documents": "All Documents", + "all-documents": "Total Storage Used", "archived-documents": "Archived Documents", - "trash-documents": "Documents in Trash" + "storage-capacity": "Storage Capacity", + "trash-documents": "Documents in Trash", + "unused": "Unused Storage" }, "total-analyzed": "Total Analyzed Pages Since {date}", "total-ocr-analyzed": "Total OCR Processed Pages Since {date}", diff --git a/libs/common-ui b/libs/common-ui index aa4516286..2d6ee6655 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit aa4516286eeeaab7e69adadb4f58193e8f2b64ed +Subproject commit 2d6ee6655c26debca43f3bf248f8521e793117f4 diff --git a/libs/red-domain/src/lib/reports/license-report.request.ts b/libs/red-domain/src/lib/reports/license-report.request.ts index 796fb114a..99330d5bd 100644 --- a/libs/red-domain/src/lib/reports/license-report.request.ts +++ b/libs/red-domain/src/lib/reports/license-report.request.ts @@ -1,8 +1,4 @@ -import { List } from '@iqser/common-ui'; - export interface ILicenseReportRequest { - dossierIds?: List; endDate?: Date | string; - requestId?: string; startDate?: Date | string; } diff --git a/libs/red-domain/src/lib/reports/license-report.ts b/libs/red-domain/src/lib/reports/license-report.ts index ab145d2c0..969ff4dcb 100644 --- a/libs/red-domain/src/lib/reports/license-report.ts +++ b/libs/red-domain/src/lib/reports/license-report.ts @@ -1,4 +1,4 @@ -interface ILicenseData { +export interface ILicenseData { activeFilesUploadedBytes?: number; archivedFilesUploadedBytes?: number; totalFilesUploadedBytes?: number; @@ -16,6 +16,5 @@ export interface ILicenseReport extends ILicenseData { numberOfDossiers?: number; numberOfOcrFiles?: number; offset?: number; - requestId?: string; monthlyData?: ILicenseData[]; } diff --git a/package.json b/package.json index 8fd5f5910..e084fdb84 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,9 @@ "@messageformat/core": "^3.1.0", "@ngx-translate/core": "15.0.0", "@ngx-translate/http-loader": "8.0.0", + "@nx/angular": "16.3.2", "@pdftron/webviewer": "10.1.1", - "@swimlane/ngx-charts": "20.4.1", + "chart.js": "^4.3.0", "dayjs": "^1.11.5", "file-saver": "^2.0.5", "jwt-decode": "^3.1.2", @@ -46,6 +47,7 @@ "keycloak-js": "21.1.1", "lodash-es": "^4.17.21", "monaco-editor": "0.39.0", + "ng2-charts": "^4.1.1", "ngx-color-picker": "^14.0.0", "ngx-logger": "^5.0.11", "ngx-toastr": "17.0.2", @@ -57,8 +59,7 @@ "scroll-into-view-if-needed": "^3.0.6", "streamsaver": "^2.0.5", "tslib": "2.5.3", - "zone.js": "0.13.1", - "@nx/angular": "16.3.2" + "zone.js": "0.13.1" }, "devDependencies": { "@angular-devkit/build-angular": "16.1.0", diff --git a/yarn.lock b/yarn.lock index 32b342a21..5f7b26dfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2233,6 +2233,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" @@ -3568,26 +3573,6 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@swimlane/ngx-charts@20.4.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" @@ -5051,6 +5036,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.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.3.0.tgz#ac363030ab3fec572850d2d872956f32a46326a1" + integrity sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g== + 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" @@ -5625,143 +5617,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.2: version "3.0.2" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" @@ -7516,16 +7371,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" @@ -8571,7 +8416,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== @@ -9052,6 +8897,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@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ng2-charts/-/ng2-charts-4.1.1.tgz#699bee539030b093caf54ab6111c6dea9e8e7ed3" + integrity sha512-iHwXDbmX86lfeH8VRcsaW2tJATsuAZo4kvvC/Yk2l35zOHjevja1qBvO6BAibiDazi9r9aS6ZRJOqWPsz1pP2w== + dependencies: + lodash-es "^4.17.15" + tslib "^2.3.0" + ngx-color-picker@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/ngx-color-picker/-/ngx-color-picker-14.0.0.tgz#4587f517ac5683a705d4e55cd0939afa91faa853"