RED-5289 - Docs/Pages switch in dossier stats

This commit is contained in:
Valentin Mihai 2022-10-15 17:47:17 +03:00
parent 1e741ce67e
commit 764525af7e
13 changed files with 130 additions and 42 deletions

View File

@ -24,7 +24,7 @@
[config]="chartConfig"
[radius]="63"
[strokeWidth]="15"
[subtitle]="'user-stats.chart.users' | translate"
[subtitles]="['user-stats.chart.users' | translate]"
direction="row"
filterKey="roleFilters"
totalType="sum"

View File

@ -37,7 +37,7 @@
[config]="chartConfig"
[radius]="80"
[strokeWidth]="15"
[subtitle]="'dossier-states-listing.chart.dossier-states' | translate: { count: chartConfig.length }"
[subtitles]="['dossier-states-listing.chart.dossier-states' | translate: { count: chartConfig.length }]"
[totalType]="'simpleLabel'"
></redaction-donut-chart>
</div>

View File

@ -42,7 +42,7 @@
[config]="translateChartService.translateDossierStates(dossierTemplate.dossiersChartConfig, dossierTemplate.id)"
[radius]="63"
[strokeWidth]="15"
[subtitle]="'dossier-template-stats.active-dossiers' | translate: { count: dossierTemplate.numberOfActiveDossiers }"
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: dossierTemplate.numberOfActiveDossiers }]"
direction="row"
totalType="sum"
></redaction-donut-chart>
@ -52,7 +52,7 @@
[config]="translateChartService.translateWorkflowStatus(dossierTemplate.documentsChartConfig)"
[radius]="63"
[strokeWidth]="15"
[subtitle]="'dossier-template-stats.total-documents' | translate"
[subtitles]="['dossier-template-stats.total-documents' | translate]"
direction="row"
totalType="sum"
></redaction-donut-chart>

View File

@ -1,11 +1,11 @@
<ng-container *ngIf="dossier$ | async as dossier">
<ng-container *ngIf="componentContext$ | async as ctx">
<div class="collapsed-wrapper">
<ng-container *ngTemplateOutlet="collapsible; context: { action: 'expand', tooltip: (expandTooltip | translate) }"></ng-container>
<div class="all-caps-label" translate="dossier-details.title"></div>
</div>
<div class="header-wrapper mt-8">
<div class="heading-xl flex-1" id="dossierDetailsDossierName">{{ dossier.dossierName }}</div>
<div class="heading-xl flex-1" id="dossierDetailsDossierName">{{ ctx.dossier?.dossierName }}</div>
<ng-container
*ngTemplateOutlet="collapsible; context: { action: 'collapse', tooltip: (collapseTooltip | translate) }"
></ng-container>
@ -15,7 +15,7 @@
<div class="all-caps-label" translate="dossier-details.owner"></div>
<div class="mt-12 d-flex">
<ng-container *ngIf="!editingOwner; else editOwner">
<iqser-initials-avatar [user]="dossier.ownerId" [withName]="true" color="gray" size="large"></iqser-initials-avatar>
<iqser-initials-avatar [user]="ctx.dossier?.ownerId" [withName]="true" color="gray" size="large"></iqser-initials-avatar>
<iqser-circle-button
(action)="editingOwner = true"
@ -32,23 +32,31 @@
<div class="mt-16">
<div class="all-caps-label" translate="dossier-details.members"></div>
<redaction-team-members [dossierId]="dossier.dossierId" [memberIds]="dossier.memberIds" [perLine]="9"></redaction-team-members>
<redaction-team-members
[dossierId]="ctx.dossier?.dossierId"
[memberIds]="ctx.dossier?.memberIds"
[perLine]="9"
></redaction-team-members>
</div>
<ng-container *ngIf="dossierStats$ | async as stats">
<ng-container *ngIf="ctx.dossierStats as stats">
<div *ngIf="stats.hasFiles" class="mt-24">
<redaction-donut-chart
[config]="chartConfig$ | async"
[config]="chartConfig"
[filterKey]="'statusFilters'"
[helpModeKey]="'filter_document_list'"
[radius]="63"
[strokeWidth]="15"
[subtitle]="'dossier-overview.dossier-details.charts.documents-in-dossier' | translate"
[subtitles]="[
'dossier-overview.dossier-details.charts.documents-in-dossier' | translate,
'dossier-overview.dossier-details.charts.pages-in-dossier' | translate
]"
(subtitleChanged)="onSubtitleChanged($event)"
direction="row"
></redaction-donut-chart>
</div>
<div *ngIf="statusConfig$ | async as statusConfig" class="mt-24">
<div *ngIf="ctx.statusConfig as statusConfig" class="mt-24">
<iqser-progress-bar
*ngFor="let config of statusConfig"
[config]="config"
@ -57,26 +65,25 @@
></iqser-progress-bar>
</div>
<div
*ngIf="stats.hasFiles && needsWorkFilters$ | async as filters"
[iqserHelpMode]="'dashboard_in_dossier'"
class="mt-32 legend pb-32"
>
<div *ngIf="stats.hasFiles && ctx.needsWorkFilters as filters" [iqserHelpMode]="'dashboard_in_dossier'" class="mt-32 legend pb-32">
<div
(click)="filterService.toggleFilter('needsWorkFilters', filter.id)"
*ngFor="let filter of filters"
[class.active]="filter.checked"
>
<redaction-type-filter [dossierTemplateId]="dossier.dossierTemplateId" [filter]="filter"></redaction-type-filter>
<redaction-type-filter [dossierTemplateId]="ctx.dossier?.dossierTemplateId" [filter]="filter"></redaction-type-filter>
</div>
</div>
<div [class.mt-24]="!stats.hasFiles" class="pb-32">
<redaction-dossier-details-stats [dossierAttributes]="dossierAttributes" [dossier]="dossier"></redaction-dossier-details-stats>
<redaction-dossier-details-stats
[dossierAttributes]="dossierAttributes"
[dossier]="ctx.dossier"
></redaction-dossier-details-stats>
</div>
</ng-container>
<div *ngIf="dossier.description as description" class="pb-32">
<div *ngIf="ctx.dossier?.description as description" class="pb-32">
<div class="heading" translate="dossier-overview.dossier-details.description"></div>
<div class="mt-8">{{ description }}</div>
</div>
@ -84,9 +91,9 @@
<ng-template #editOwner>
<redaction-assign-user-dropdown
(cancel)="editingOwner = false"
(save)="editingOwner = false; assignOwner($event, dossier)"
(save)="editingOwner = false; assignOwner($event, ctx.dossier)"
[options]="managers"
[value]="dossier.ownerId"
[value]="ctx.dossier?.ownerId"
></redaction-assign-user-dropdown>
</ng-template>
</ng-container>

View File

@ -1,10 +1,11 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
DonutChartConfig,
Dossier,
DOSSIER_ID,
DossierAttributeWithValue,
DossierStats,
File,
IDossierRequest,
ProcessingTypes,
StatusSorter,
@ -12,13 +13,22 @@ import {
} from '@red/domain';
import { TranslateChartService } from '@services/translate-chart.service';
import { UserService } from '@users/user.service';
import { FilterService, getParam, ProgressBarConfigModel, shareLast, Toaster } from '@iqser/common-ui';
import { ContextComponent, FilterService, getParam, INestedFilter, ProgressBarConfigModel, shareLast, Toaster } from '@iqser/common-ui';
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { firstValueFrom, Observable } from 'rxjs';
import { firstValueFrom, Observable, of } from 'rxjs';
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
import { map } from 'rxjs/operators';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { FilesMapService } from '@services/files/files-map.service';
interface DossierDetailsContext {
needsWorkFilters: INestedFilter[] | undefined;
dossier: Dossier;
dossierStats: DossierStats;
chartConfig: void;
statusConfig: ProgressBarConfigModel[];
}
@Component({
selector: 'redaction-dossier-details',
@ -26,36 +36,52 @@ import { DossiersService } from '@services/dossiers/dossiers.service';
styleUrls: ['./dossier-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DossierDetailsComponent {
export class DossierDetailsComponent extends ContextComponent<DossierDetailsContext> implements OnInit {
@Input() dossierAttributes: DossierAttributeWithValue[];
@Output() readonly toggleCollapse = new EventEmitter();
editingOwner = false;
#currentChartSubtitleIndex = 0;
readonly #dossierId: string;
readonly collapseTooltip = _('dossier-details.collapse');
readonly expandTooltip = _('dossier-details.expand');
readonly needsWorkFilters$ = this.filterService.getFilterModels$('needsWorkFilters');
readonly currentUser = this._userService.currentUser;
readonly dossier$: Observable<Dossier>;
readonly dossierStats$: Observable<DossierStats>;
readonly chartConfig$: Observable<DonutChartConfig[]>;
readonly chartConfig$: Observable<void>;
readonly statusConfig$: Observable<ProgressBarConfigModel[]>;
chartConfig: DonutChartConfig[] = [];
constructor(
private readonly _toaster: Toaster,
readonly filterService: FilterService,
dossierStatsService: DossierStatsService,
private readonly _userService: UserService,
private readonly _dossiersService: DossiersService,
private readonly _filesMapService: FilesMapService,
readonly dossierStatsService: DossierStatsService,
readonly filterService: FilterService,
readonly translateChartService: TranslateChartService,
) {
const dossierId = getParam(DOSSIER_ID);
this.dossier$ = _dossiersService.getEntityChanged$(dossierId).pipe(shareLast());
this.dossierStats$ = dossierStatsService.watch$(dossierId).pipe(shareLast());
super();
this.#dossierId = getParam(DOSSIER_ID);
this.dossier$ = _dossiersService.getEntityChanged$(this.#dossierId).pipe(shareLast());
this.dossierStats$ = dossierStatsService.watch$(this.#dossierId).pipe(shareLast());
this.chartConfig$ = this.dossierStats$.pipe(map(stats => this.#calculateChartConfig(stats)));
this.statusConfig$ = this.dossierStats$.pipe(map(stats => this.#calculateStatusConfig(stats)));
}
ngOnInit() {
super._initContext({
needsWorkFilters: this.needsWorkFilters$,
dossier: this.dossier$,
dossierStats: this.dossierStats$,
chartConfig: this.chartConfig$,
statusConfig: this.statusConfig$,
});
}
get managers() {
return this._userService.managerUsers.map(manager => manager.id);
}
@ -70,15 +96,24 @@ export class DossierDetailsComponent {
this._toaster.info(_('assignment.owner'), { params: { ownerName, dossierName } });
}
#calculateChartConfig(stats: DossierStats): DonutChartConfig[] {
#calculateChartConfig(stats: DossierStats): void {
const documentsChartConfig: DonutChartConfig[] = Object.keys(stats.fileCountPerWorkflowStatus).map(status => ({
value: stats.fileCountPerWorkflowStatus[status],
value: !this.#currentChartSubtitleIndex
? stats.fileCountPerWorkflowStatus[status]
: this.#getPageCountPerWorkflowStatus(status),
color: status,
label: workflowFileStatusTranslations[status],
key: status,
}));
documentsChartConfig.sort((a, b) => StatusSorter.byStatus(a.key, b.key));
return this.translateChartService.translateLabels(documentsChartConfig);
this.chartConfig = this.translateChartService.translateLabels(documentsChartConfig);
}
#getPageCountPerWorkflowStatus(status: string) {
const files = this._filesMapService.get(this.#dossierId);
return files
.filter((file: File) => file.workflowStatus === status)
.reduce((sum: number, file: File) => sum + file.numberOfPages, 0);
}
#calculateStatusConfig(stats: DossierStats): ProgressBarConfigModel[] {
@ -113,4 +148,9 @@ export class DossierDetailsComponent {
},
].filter(config => config.count > 0);
}
onSubtitleChanged(subtitleIndex: number) {
this.#currentChartSubtitleIndex = subtitleIndex;
this.#calculateChartConfig(this.dossierStatsService.get(this.#dossierId));
}
}

View File

@ -3,7 +3,7 @@
[config]="dossiersChartConfig$ | async"
[radius]="80"
[strokeWidth]="15"
[subtitle]="'dossier-template-stats.active-dossiers' | translate: { count: stats.numberOfActiveDossiers }"
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: stats.numberOfActiveDossiers }]"
filterKey="dossierStatesFilters"
></redaction-donut-chart>
@ -31,7 +31,7 @@
[config]="documentsChartConfig$ | async"
[radius]="80"
[strokeWidth]="15"
[subtitle]="'dossier-template-stats.total-documents' | translate"
[subtitles]="['dossier-template-stats.total-documents' | translate]"
filterKey="statusFilters"
></redaction-donut-chart>
</div>

View File

@ -15,6 +15,7 @@ export class IconsModule {
'approved',
'archive',
'arrow-up',
'arrow-down',
'assign',
'assign-me',
'attribute',

View File

@ -19,7 +19,14 @@
<div [style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + (strokeWidth + 5) + 'px;'" class="text-container">
<div class="heading-xl">{{ displayedDataTotal }}</div>
<div class="mt-5">{{ subtitle }}</div>
<div *ngIf="subtitles.length === 1" class="mt-5">{{ subtitles[0] }}</div>
<mat-select
*ngIf="subtitles.length > 1"
[value]="subtitles[0]"
(selectionChange)="subtitleChanged.emit(subtitles.indexOf($event.value))"
>
<mat-option *ngFor="let subtitle of subtitles" [value]="subtitle"> {{ subtitle }} </mat-option>
</mat-select>
</div>
<div [iqserHelpMode]="helpModeKey" class="breakdown-container">

View File

@ -70,3 +70,18 @@
}
}
}
mat-select {
width: unset;
margin-top: 5px;
margin-left: 10px;
::ng-deep .mat-select-value,
::ng-deep .mat-select-arrow {
color: var(--iqser-text);
}
::ng-deep .mat-select-arrow {
margin-top: 2px;
}
}

View File

@ -1,4 +1,4 @@
import { Component, Input, OnChanges, OnInit, Optional } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, OnInit, Optional, Output } from '@angular/core';
import { DonutChartConfig } from '@red/domain';
import { FilterService, get, INestedFilter, shareLast } from '@iqser/common-ui';
import { Observable, of } from 'rxjs';
@ -10,7 +10,7 @@ import { map } from 'rxjs/operators';
styleUrls: ['./donut-chart.component.scss'],
})
export class DonutChartComponent implements OnChanges, OnInit {
@Input() subtitle: string;
@Input() subtitles: string[];
@Input() config: DonutChartConfig[] = [];
@Input() radius = 85;
@Input() strokeWidth = 20;
@ -20,6 +20,8 @@ export class DonutChartComponent implements OnChanges, OnInit {
@Input() filterKey;
@Input() helpModeKey;
@Output() readonly subtitleChanged = new EventEmitter<number>();
chartData: any[] = [];
cx = 0;
cy = 0;

View File

@ -832,7 +832,8 @@
"show-less": "weniger anzeigen"
},
"charts": {
"documents-in-dossier": "Dokumente im Dossier"
"documents-in-dossier": "Dokumente",
"pages-in-dossier": ""
},
"description": "Beschreibung",
"dictionary": "Dossier-Wörterbuch",

View File

@ -832,7 +832,8 @@
"show-less": "show less"
},
"charts": {
"documents-in-dossier": "Documents in Dossier"
"documents-in-dossier": "Documents",
"pages-in-dossier": "Pages"
},
"description": "Description",
"dictionary": "Dossier Dictionary",

View File

@ -0,0 +1,14 @@
<?xml version="1.0" standalone="no"?>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1280.000000pt" height="1280.000000pt" viewBox="0 0 1280.000000 1280.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,1280.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3672 8198 c14 -18 631 -842 1373 -1830 741 -989 1351 -1798 1355
-1798 4 0 614 809 1355 1798 742 988 1359 1812 1373 1830 l24 32 -2752 0
-2752 0 24 -32z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 576 B