From a2870531c9f4d07e3890b4242d76a84f008cef62 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Mon, 21 Oct 2024 17:15:53 +0300 Subject: [PATCH] RED-10139: refactoring dossier listing. --- .../template-stats.component.html | 160 +++++++++--------- .../template-stats.component.ts | 16 +- .../dossier-documents-status.component.html | 4 +- .../dossier-documents-status.component.ts | 19 +-- .../dossier-workload-column.component.html | 22 ++- .../dossier-workload-column.component.ts | 12 +- .../dossiers-listing-details.component.html | 44 ++--- .../dossiers-listing-details.component.ts | 4 +- .../table-item/table-item.component.html | 20 ++- .../table-item/table-item.component.ts | 13 +- .../dossiers-listing-screen.component.html | 6 +- .../dossiers-listing-screen.component.ts | 7 +- .../dossiers-listing-actions.component.html | 27 ++- .../dossiers-listing-actions.component.ts | 19 +-- .../chart-circle-config.component.html | 13 ++ .../chart-circle-config.component.scss | 4 + .../chart-circle-config.component.ts | 33 ++++ .../chart-filters.component.html | 8 + .../chart-filters.component.scss | 23 +++ .../chart-filters/chart-filters.component.ts | 47 +++++ .../donut-chart/donut-chart.component.html | 106 ++++++------ .../donut-chart/donut-chart.component.scss | 24 --- .../donut-chart/donut-chart.component.ts | 145 ++++++---------- .../dossier-name-column.component.html | 54 +++--- .../dossier-name-column.component.ts | 36 ++-- .../dossier-state/dossier-state.component.ts | 6 +- 26 files changed, 471 insertions(+), 401 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.html create mode 100644 apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.scss create mode 100644 apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.ts create mode 100644 apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.html create mode 100644 apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.scss create mode 100644 apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.ts diff --git a/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html b/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html index ae1e63523..bd9b8c681 100644 --- a/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html +++ b/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.html @@ -1,82 +1,84 @@ - - -
-
{{ dossierTemplate.name }}
-
-
- - -
-
- - -
-
- - -
-
- - +@if (stats(); as dossierTemplate) { + + @if (!isTemplateEmpty()) { +
+
{{ dossierTemplate.name }}
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ +
+ } @else { +
+
+ {{ dossierTemplate.name }} +
+
+ {{ 'dashboard.empty-template.description' | translate }}
-
-
- -
-
- -
- - -
-
- {{ dossierTemplate.name }} -
-
- {{ 'dashboard.empty-template.description' | translate }} -
-
- - -
-
+ @if (canCreateDossier()) { + + } + } + +} diff --git a/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.ts b/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.ts index d555b664f..a6037561a 100644 --- a/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.ts +++ b/apps/red-ui/src/app/modules/dashboard/components/template-stats/template-stats.component.ts @@ -1,11 +1,10 @@ -import { Component, Input } from '@angular/core'; +import { Component, computed, input } from '@angular/core'; import { DashboardStats } from '@red/domain'; import { IconButtonComponent, IconButtonTypes } from '@iqser/common-ui'; import { TranslateChartService } from '@services/translate-chart.service'; import { SharedDialogService } from '@shared/services/dialog.service'; import { Roles } from '@users/roles'; import { PermissionsService } from '@services/permissions.service'; -import { NgIf } from '@angular/common'; import { RouterLink } from '@angular/router'; import { MatIcon } from '@angular/material/icon'; import { TranslateModule } from '@ngx-translate/core'; @@ -17,13 +16,20 @@ import { SnakeCasePipe } from '@common-ui/pipes/snake-case.pipe'; templateUrl: './template-stats.component.html', styleUrls: ['./template-stats.component.scss'], standalone: true, - imports: [NgIf, RouterLink, MatIcon, TranslateModule, DonutChartComponent, IconButtonComponent, SnakeCasePipe], + imports: [RouterLink, MatIcon, TranslateModule, DonutChartComponent, IconButtonComponent, SnakeCasePipe], }) export class TemplateStatsComponent { readonly iconButtonTypes = IconButtonTypes; readonly roles = Roles; - @Input() stats: DashboardStats; + readonly stats = input(); + + readonly dossierStates = computed(() => + this.translateChartService.translateAndSortDossierStates(this.stats().dossiersChartConfig, this.stats().id), + ); + readonly workflowStatuses = computed(() => this.translateChartService.translateWorkflowStatus(this.stats().documentsChartConfig)); + readonly isTemplateEmpty = computed(() => this.stats().isEmpty); + readonly canCreateDossier = computed(() => this.permissionsService.canCreateDossier(this.stats())); constructor( private readonly _dialogService: SharedDialogService, @@ -32,6 +38,6 @@ export class TemplateStatsComponent { ) {} newDossier(): void { - this._dialogService.openDialog('addDossier', { dossierTemplateId: this.stats.dossierTemplateId }); + this._dialogService.openDialog('addDossier', { dossierTemplateId: this.stats().dossierTemplateId }); } } diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.html b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.html index 3025a99af..c7aaab480 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.html +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.html @@ -1 +1,3 @@ - +@if (stats()) { + +} diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.ts b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.ts index c7693d179..877620048 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.ts +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-documents-status/dossier-documents-status.component.ts @@ -1,27 +1,22 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; import { DossierStats, StatusSorter } from '@red/domain'; import { List } from '@iqser/common-ui/lib/utils'; import { StatusBarComponent, StatusBarConfig } from '@iqser/common-ui/lib/shared'; -import { NgIf } from '@angular/common'; @Component({ selector: 'redaction-dossier-documents-status', templateUrl: './dossier-documents-status.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [StatusBarComponent, NgIf], + imports: [StatusBarComponent], }) -export class DossierDocumentsStatusComponent implements OnChanges { - @Input() stats: DossierStats; - statusBarConfig: List>; +export class DossierDocumentsStatusComponent { + readonly stats = input(); + readonly statusBarConfig = computed(() => this.#statusConfig); - private get _statusConfig(): List> { - const { fileCountPerWorkflowStatus } = this.stats; + get #statusConfig(): List> { + const { fileCountPerWorkflowStatus } = this.stats(); const statuses = Object.keys(fileCountPerWorkflowStatus).sort(StatusSorter.byStatus); return statuses.map(status => ({ length: fileCountPerWorkflowStatus[status], color: status })); } - - ngOnChanges(): void { - this.statusBarConfig = this._statusConfig; - } } diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.html b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.html index 597091def..5e82e4c98 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.html +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.html @@ -1,15 +1,13 @@
- + @if (dossierStats().hasRedactionsFilePresent) { + + } - + @if (dossierStats().hasHintsNoRedactionsFilePresent) { + + }
diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.ts b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.ts index abf8db8ad..6c02b8262 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.ts +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/dossier-workload-column/dossier-workload-column.component.ts @@ -1,9 +1,9 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input, OnChanges, SimpleChanges } from '@angular/core'; import { DefaultColorType, Dossier, DossierStats } from '@red/domain'; import { DefaultColorsService } from '@services/entity-services/default-colors.service'; import { BehaviorSubject, Observable, switchMap } from 'rxjs'; import { AnnotationIconComponent } from '@shared/components/annotation-icon/annotation-icon.component'; -import { AsyncPipe, NgIf } from '@angular/common'; +import { AsyncPipe } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; @Component({ @@ -12,12 +12,12 @@ import { TranslateModule } from '@ngx-translate/core'; styleUrls: ['./dossier-workload-column.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [AnnotationIconComponent, AsyncPipe, TranslateModule, NgIf], + imports: [AnnotationIconComponent, AsyncPipe, TranslateModule], }) export class DossierWorkloadColumnComponent implements OnChanges { readonly #dossierTemplateId$ = new BehaviorSubject(null); - @Input() dossier: Dossier; - @Input() dossierStats: DossierStats; + readonly dossier = input(); + readonly dossierStats = input(); readonly hintColor$: Observable; readonly redactionColor$: Observable; @@ -30,7 +30,7 @@ export class DossierWorkloadColumnComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (changes.dossier) { - this.#dossierTemplateId$.next(this.dossier.dossierTemplateId); + this.#dossierTemplateId$.next(this.dossier().dossierTemplateId); } } diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html b/apps/red-ui/src/app/modules/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html index 66cb7aca3..861fd8964 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html @@ -1,30 +1,32 @@ -
- +@if (stats$ | async; as stats) { +
+ -
-
- -
-
{{ stats.numberOfPages | number }}
-
+
+
+ +
+
{{ stats.numberOfPages | number }}
+
+
-
-
- -
-
{{ stats.numberOfPeople }}
-
+
+ +
+
{{ stats.numberOfPeople }}
+
+
-
+}
; diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html b/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html index f9bb8e9bd..bf5d4ff48 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html @@ -1,18 +1,20 @@ - +@if (stats$ | async; as stats) {
- +
-
- -
+ @if (!isDocumine) { +
+ +
+ }
- +
@@ -20,8 +22,8 @@
- + - +
-
+} diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.ts b/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.ts index f85e3f995..b560bbc89 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.ts +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input, OnChanges } from '@angular/core'; import { Dossier, DossierStats } from '@red/domain'; import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; import { BehaviorSubject, Observable } from 'rxjs'; @@ -6,7 +6,7 @@ import { switchMap, tap } from 'rxjs/operators'; import { getConfig } from '@iqser/common-ui'; import { DossierNameColumnComponent } from '@shared/components/dossier-name-column/dossier-name-column.component'; import { DateColumnComponent } from '../../../shared-dossiers/components/date-column/date-column.component'; -import { AsyncPipe, NgIf } from '@angular/common'; +import { AsyncPipe } from '@angular/common'; import { DossierWorkloadColumnComponent } from '../dossier-workload-column/dossier-workload-column.component'; import { InitialsAvatarComponent } from '@common-ui/users'; import { DossierDocumentsStatusComponent } from '../dossier-documents-status/dossier-documents-status.component'; @@ -23,7 +23,6 @@ import { DossiersListingActionsComponent } from '../../../shared-dossiers/compon DossierNameColumnComponent, DateColumnComponent, AsyncPipe, - NgIf, DossierWorkloadColumnComponent, InitialsAvatarComponent, DossierDocumentsStatusComponent, @@ -32,7 +31,7 @@ import { DossiersListingActionsComponent } from '../../../shared-dossiers/compon ], }) export class TableItemComponent implements OnChanges { - @Input() dossier!: Dossier; + readonly dossier = input.required(); readonly stats$: Observable; readonly isDocumine = getConfig().IS_DOCUMINE; @@ -43,14 +42,14 @@ export class TableItemComponent implements OnChanges { switchMap(dossierId => this.dossierStatsService.watch$(dossierId)), // TODO required for sorting the dossier table - fix me Baby one more time! tap(stats => { - this.dossier.changedDate = stats.fileManipulationDate; + this.dossier().changedDate = stats.fileManipulationDate; }), ); } ngOnChanges() { - if (this.dossier) { - this.#ngOnChanges$.next(this.dossier.id); + if (this.dossier()) { + this.#ngOnChanges$.next(this.dossier().id); } } } diff --git a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html index ec029e356..1e58022e9 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.html @@ -18,7 +18,7 @@ [noDataButtonLabel]="'dossier-listing.no-data.action' | translate" [noDataText]="'dossier-listing.no-data.title' | translate" [noMatchText]="'dossier-listing.no-match.title' | translate" - [showNoDataButton]="permissionsService.canCreateDossier(dossierTemplate)" + [showNoDataButton]="canCreateDossier()" [tableColumnConfigs]="tableColumnConfigs" [rowIdPrefix]="'dossier'" [namePropertyKey]="'dossierName'" @@ -27,7 +27,9 @@
- + @if ((entitiesService.noData$ | async) === false) { + + }
diff --git a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts index c59b47b63..1f18686dc 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossiers-listing/screen/dossiers-listing-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { Dossier, DOSSIER_TEMPLATE_ID, DossierTemplate } from '@red/domain'; import { PermissionsService } from '@services/permissions.service'; import { @@ -49,7 +49,8 @@ export class DossiersListingScreenComponent extends ListingComponent im readonly tableHeaderLabel = _('dossier-listing.table-header.title'); readonly buttonConfigs: ButtonConfig[]; readonly dossierTemplate: DossierTemplate; - readonly computeFilters$ = this._activeDossiersService.all$.pipe(tap(() => this._computeAllFilters())); + readonly canCreateDossier = computed(() => this.permissionsService.canCreateDossier(this.dossierTemplate)); + readonly computeFilters$ = this._activeDossiersService.all$.pipe(tap(() => this.#computeAllFilters())); @ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true, @@ -91,7 +92,7 @@ export class DossiersListingScreenComponent extends ListingComponent im this._loadingService.stop(); } - private _computeAllFilters() { + #computeAllFilters() { const filterGroups = this._configService.filterGroups( this.entitiesService.all, this._needsWorkFilterTemplate, diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.html b/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.html index 483ff6e47..282aa4e26 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.html @@ -1,33 +1,30 @@
- + @if (displayReanalyseBtn) { + + } diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.ts index 8b08b30aa..67a1b76f9 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/dossiers-listing-actions/dossiers-listing-actions.component.ts @@ -1,5 +1,5 @@ -import { AsyncPipe, NgIf } from '@angular/common'; -import { Component, Input, OnChanges } from '@angular/core'; +import { AsyncPipe } from '@angular/common'; +import { Component, computed, input, OnChanges } from '@angular/core'; import { CircleButtonComponent, getConfig, IqserAllowDirective, IqserPermissionsService, largeDialogConfig } from '@iqser/common-ui'; import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { TranslateModule } from '@ngx-translate/core'; @@ -13,12 +13,13 @@ import { Roles } from '@users/roles'; import { UserPreferenceService } from '@users/user-preference.service'; import { EditDossierDialogComponent } from '../../dialogs/edit-dossier-dialog/edit-dossier-dialog.component'; import { DossiersDialogService } from '../../services/dossiers-dialog.service'; +import { toSignal } from '@angular/core/rxjs-interop'; @Component({ selector: 'redaction-dossiers-listing-actions [dossier]', templateUrl: './dossiers-listing-actions.component.html', standalone: true, - imports: [LongPressDirective, CircleButtonComponent, IqserAllowDirective, TranslateModule, NgIf, FileDownloadBtnComponent, AsyncPipe], + imports: [LongPressDirective, CircleButtonComponent, IqserAllowDirective, TranslateModule, FileDownloadBtnComponent, AsyncPipe], }) export class DossiersListingActionsComponent implements OnChanges { readonly roles = Roles; @@ -30,7 +31,9 @@ export class DossiersListingActionsComponent implements OnChanges { displayReanalyseBtn = false; downloadBtnDisabled = false; - @Input() dossier: Dossier; + readonly dossier = input(); + readonly canEditDossierDictionary = computed(() => this.permissionsService.canEditDossierDictionary(this.dossier())); + readonly hasEditDossierRole = toSignal(this.iqserPermissionsService.has$(this.roles.dossiers.edit)); constructor( private readonly _reanalysisService: ReanalysisService, @@ -41,14 +44,10 @@ export class DossiersListingActionsComponent implements OnChanges { private readonly _userPreferenceService: UserPreferenceService, ) {} - get canEditDossierDictionary() { - return this.permissionsService.canEditDossierDictionary(this.dossier); - } - ngOnChanges() { - this.files = this.filesMapService.get(this.dossier.id); + this.files = this.filesMapService.get(this.dossier().id); this.downloadBtnDisabled = this.files.some(file => !file.lastProcessed); - this.displayReanalyseBtn = this.permissionsService.displayReanalyseBtn(this.dossier) && this.analysisForced; + this.displayReanalyseBtn = this.permissionsService.displayReanalyseBtn(this.dossier()) && this.analysisForced; } forceReanalysisAction($event: LongPressEvent) { diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.html b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.html new file mode 100644 index 000000000..72cc81781 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.html @@ -0,0 +1,13 @@ + diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.scss b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.scss new file mode 100644 index 000000000..96b0195cf --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.scss @@ -0,0 +1,4 @@ +:host { + display: contents; + height: fit-content; +} diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.ts b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.ts new file mode 100644 index 000000000..484d48cee --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-circle-config/chart-circle-config.component.ts @@ -0,0 +1,33 @@ +import { Component, computed, input } from '@angular/core'; +import { DonutChartConfig } from '@red/domain'; + +@Component({ + selector: '[redaction-chart-circle-config]', + standalone: true, + imports: [], + templateUrl: './chart-circle-config.component.html', + styleUrl: './chart-circle-config.component.scss', +}) +export class ChartCircleConfigComponent { + readonly index = input(); + readonly config = input(); + readonly cx = input(); + readonly cy = input(); + readonly radius = input(); + readonly circumference = input(); + readonly strokeWidth = input(); + readonly chartData = input<{ degrees: number }>(); + readonly dataTotal = input(); + + readonly stroke = computed(() => (this.config().color.includes('#') ? this.config().color : '')); + readonly percentage = computed(() => this.config().value / this.dataTotal()); + + readonly strokeDashOffset = computed(() => { + const strokeDiff = this.percentage() * this.circumference(); + return this.circumference() - strokeDiff; + }); + + readonly circleTransformValue = computed(() => { + return `rotate(${this.chartData().degrees}, ${this.cx()}, ${this.cy()})`; + }); +} diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.html b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.html new file mode 100644 index 000000000..4121ef883 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.html @@ -0,0 +1,8 @@ +
+ +
diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.scss b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.scss new file mode 100644 index 000000000..d9d8daa6a --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.scss @@ -0,0 +1,23 @@ +@use 'variables'; + +div { + border-radius: 4px; + padding: 3px 8px; + width: 100%; + + &:not(:last-child) { + margin-bottom: 8px; + } + + &:not(.filter-disabled) { + cursor: pointer; + } + + &:hover:not(.active):not(.filter-disabled) { + background-color: var(--iqser-btn-bg); + } + + &.active { + background-color: rgba(variables.$primary, 0.1); + } +} diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.ts b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.ts new file mode 100644 index 000000000..d1d2cca7d --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/chart-filters/chart-filters.component.ts @@ -0,0 +1,47 @@ +import { Component, computed, input, Optional } from '@angular/core'; +import { AsyncPipe } from '@angular/common'; +import { StatusBarComponent } from '@common-ui/shared'; +import { DonutChartConfig } from '@red/domain'; +import { FilterService, INestedFilter } from '@common-ui/filtering'; + +@Component({ + selector: 'redaction-chart-filters', + standalone: true, + imports: [AsyncPipe, StatusBarComponent], + templateUrl: './chart-filters.component.html', + styleUrl: './chart-filters.component.scss', +}) +export class ChartFiltersComponent { + readonly config = input(); + readonly totalType = input<'sum' | 'count' | 'simpleLabel'>('sum'); + readonly counterText = input(); + readonly filterKey = input(); + readonly valueFormatter = input<(value: number) => string>(); + readonly filters = input(); + + readonly formattedValue = computed(() => + this.valueFormatter() ? this.valueFormatter()(this.config().value) : this.config().value.toString(), + ); + readonly filterChecked = computed(() => this.filters().find(item => item.id === this.config().key)?.checked); + readonly label = computed(() => { + return this.totalType() === 'simpleLabel' + ? `${this.config().label}` + : this.totalType() === 'sum' + ? `${this.formattedValue()} ${this.config().label}` + : `${this.config().label} (${this.formattedValue()} ${this.counterText()})`; + }); + readonly statusBarConfigs = computed(() => [ + { + length: this.config().value, + color: this.config().color, + label: this.label(), + cssClass: this.config().color === 'PROCESSING' || this.config().color === 'OCR_PROCESSING' ? 'loading' : '', + }, + ]); + + constructor(@Optional() readonly filterService: FilterService) {} + + selectValue(key: string): void { + this.filterService?.toggleFilter(this.filterKey(), key); + } +} 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 b744b9f11..fb9932064 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 @@ -1,59 +1,63 @@ -
- - - - +
+ + @for (value of config(); track value.label) { + @if (!!chartData()[$index]) { + + } + } -
-
{{ getFormattedValue(displayedDataTotal) }}
-
{{ subtitles[0] }}
+
+
{{ formatedValue() }}
+ @if (subtitles().length === 1) { +
{{ subtitles()[0] }}
+ } -
- -
+ @if (subtitleTemplate(); as t) { +
+ +
+ } - - {{ subtitle }} - + @if (subtitles().length > 1) { + + @for (subtitle of subtitles(); track subtitle) { + {{ subtitle }} + } + + }
-
-
- -
+
+ @for (value of config(); track value.label) { + + }
diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.scss b/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.scss index 61688d0c4..75585d56c 100644 --- a/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.scss +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.scss @@ -1,5 +1,3 @@ -@use 'variables'; - :host { height: fit-content; } @@ -47,28 +45,6 @@ flex-direction: column; align-items: flex-start; margin-left: -8px; - - > div { - border-radius: 4px; - padding: 3px 8px; - width: 100%; - - &:not(:last-child) { - margin-bottom: 8px; - } - - &:not(.filter-disabled) { - cursor: pointer; - } - - &:hover:not(.active):not(.filter-disabled) { - background-color: var(--iqser-btn-bg); - } - - &.active { - background-color: rgba(variables.$primary, 0.1); - } - } } mat-select { diff --git a/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.ts b/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.ts index 00435a9e5..75f25cd7f 100644 --- a/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/donut-chart/donut-chart.component.ts @@ -1,87 +1,49 @@ -import { Component, EventEmitter, Input, OnChanges, OnInit, Optional, Output, TemplateRef } from '@angular/core'; +import { Component, computed, input, Optional, output, TemplateRef } from '@angular/core'; import { DonutChartConfig } from '@red/domain'; -import { Observable, of } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { filter, map, switchMap } from 'rxjs/operators'; +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { MatSelectModule } from '@angular/material/select'; -import { FilterService, INestedFilter } from '@iqser/common-ui/lib/filtering'; -import { get, shareLast } from '@iqser/common-ui/lib/utils'; +import { FilterService } from '@iqser/common-ui/lib/filtering'; +import { shareLast } from '@iqser/common-ui/lib/utils'; import { StatusBarComponent } from '@iqser/common-ui/lib/shared'; +import { ChartCircleConfigComponent } from '@shared/components/donut-chart/chart-circle-config/chart-circle-config.component'; +import { toObservable, toSignal } from '@angular/core/rxjs-interop'; +import { ChartFiltersComponent } from '@shared/components/donut-chart/chart-filters/chart-filters.component'; @Component({ selector: 'redaction-donut-chart', templateUrl: './donut-chart.component.html', styleUrls: ['./donut-chart.component.scss'], standalone: true, - imports: [NgForOf, NgIf, MatSelectModule, StatusBarComponent, AsyncPipe, NgTemplateOutlet], + imports: [MatSelectModule, StatusBarComponent, AsyncPipe, NgTemplateOutlet, ChartCircleConfigComponent, ChartFiltersComponent], }) -export class DonutChartComponent implements OnChanges, OnInit { - @Input() subtitles: string[] = []; - @Input() config: DonutChartConfig[] = []; - @Input() radius = 85; - @Input() strokeWidth = 20; - @Input() direction: 'row' | 'column' = 'column'; - @Input() totalType: 'sum' | 'count' | 'simpleLabel' = 'sum'; - @Input() counterText: string; - @Input() filterKey; - @Input() helpModeKey; - @Input() valueFormatter?: (value: number) => string; - @Input() subtitleTemplate?: TemplateRef; +export class DonutChartComponent { + readonly subtitles = input([]); + readonly config = input([]); + readonly radius = input(85); + readonly strokeWidth = input(20); + readonly direction = input<'row' | 'column'>('column'); + readonly totalType = input<'sum' | 'count' | 'simpleLabel'>('sum'); + readonly counterText = input(); + readonly filterKey = input(); + readonly helpModeKey = input(); + readonly valueFormatter = input<(value: number) => string>(); + readonly subtitleTemplate = input>(); - @Output() readonly subtitleChanged = new EventEmitter(); + readonly subtitleChanged = output(); - chartData: any[] = []; - cx = 0; - cy = 0; - size = 0; - filters$: Observable; + readonly dataTotal = computed(() => { + return this.config() + .map(v => v.value) + .reduce((acc, val) => acc + val, 0); + }); + readonly displayedDataTotal = computed(() => (this.totalType() === 'sum' ? this.dataTotal() : this.config().length)); + readonly formatedValue = computed(() => this.getFormattedValue(this.displayedDataTotal())); + readonly circumference = computed(() => 2 * Math.PI * this.radius()); - get circumference(): number { - return 2 * Math.PI * this.radius; - } - - get dataTotal(): number { - return this.config.map(v => v.value).reduce((acc, val) => acc + val, 0); - } - - get displayedDataTotal() { - 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 ?? []), - shareLast(), - ); - - this.filters$ = filterModels$ ?? of([]); - } - - ngOnChanges(): void { - this.calculateChartData(); - this.cx = this.radius + this.strokeWidth / 2; - this.cy = this.radius + this.strokeWidth / 2; - this.size = this.strokeWidth + this.radius * 2; - } - - filterChecked$(key: string): Observable { - return this.filters$.pipe( - get(filter => filter.id === key), - map(filter => !!filter?.checked), - ); - } - - getFormattedValue(value: number): string { - return this.valueFormatter ? this.valueFormatter(value) : value.toString(); - } - - calculateChartData() { + readonly chartData = computed(() => { let angleOffset = -90; - this.chartData = this.config.map(dataVal => { + return this.config().map(dataVal => { if (dataVal.value === 0) { return null; } @@ -90,30 +52,33 @@ export class DonutChartComponent implements OnChanges, OnInit { angleOffset = this.dataPercentage(dataVal.value) * 360 + angleOffset; return res; }); + }); + readonly cx = computed(() => this.radius() + this.strokeWidth() / 2); + readonly cy = computed(() => this.radius() + this.strokeWidth() / 2); + readonly size = computed(() => this.strokeWidth() + this.radius() * 2); + + readonly filterKey$ = toObservable(this.filterKey); + readonly filters = toSignal( + this.filterKey$.pipe( + filter(Boolean), + switchMap(filterKey => { + return this.filterService?.getFilterModels$(filterKey).pipe( + map(filters => [...filters] ?? []), + shareLast(), + ); + }), + ), + { initialValue: [] }, + ); + constructor(@Optional() readonly filterService: FilterService) { + // TODO: move this component to a separate module, split into smaller components, improve filters } - calculateStrokeDashOffset(dataVal: number): number { - const strokeDiff = this.dataPercentage(dataVal) * this.circumference; - return this.circumference - strokeDiff; + getFormattedValue(value: number): string { + return this.valueFormatter() ? this.valueFormatter()(value) : value.toString(); } dataPercentage(dataVal: number): number { - return dataVal / this.dataTotal; - } - - returnCircleTransformValue(index: number) { - return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`; - } - - getLabel({ label, value }: DonutChartConfig): string { - return this.totalType === 'simpleLabel' - ? `${label}` - : this.totalType === 'sum' - ? `${this.getFormattedValue(value)} ${label}` - : `${label} (${this.getFormattedValue(value)} ${this.counterText})`; - } - - selectValue(key: string): void { - this.filterService?.toggleFilter(this.filterKey, key); + return dataVal / this.dataTotal(); } } diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.html b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.html index 513b08b53..de7710a61 100644 --- a/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.html +++ b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.html @@ -1,37 +1,43 @@ -
- {{ dossier.dossierName }} +
+ {{ dossier().dossierName }}
- {{ getDossierTemplateNameFor(dossier.dossierTemplateId) }} + {{ dossierTemplateName() }}
-
-
- - {{ isSoftDeleted ? dossierStats.numberOfSoftDeletedFiles : dossierStats.numberOfFiles }} -
+@if (dossierStats()) { +
+
+ + {{ isSoftDeleted() ? dossierStats().numberOfSoftDeletedFiles : dossierStats().numberOfFiles }} +
-
- - {{ dossierStats.numberOfPages }} -
+ @if (!isSoftDeleted()) { +
+ + {{ dossierStats().numberOfPages }} +
+ } -
- - {{ dossier.memberIds.length }} -
+
+ + {{ dossier().memberIds.length }} +
-
- - {{ dossier.date | date: 'mediumDate' }} -
+
+ + {{ dossier().date | date: 'mediumDate' }} +
-
- - {{ dossier.dueDate | date: 'mediumDate' }} + @if (dossier().dueDate) { +
+ + {{ dossier().dueDate | date: 'mediumDate' }} +
+ }
-
+} diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts index 5e70b9258..c0b74147d 100644 --- a/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; import { List } from '@iqser/common-ui/lib/utils'; import { DossierStats, IDossier } from '@red/domain'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; @@ -6,7 +6,7 @@ import { dateWithoutTime } from '@utils/functions'; import dayjs from 'dayjs'; import { MatTooltip } from '@angular/material/tooltip'; import { MatIcon } from '@angular/material/icon'; -import { DatePipe, NgIf } from '@angular/common'; +import { DatePipe } from '@angular/common'; const DUE_DATE_WARN_DAYS = 14; @@ -24,31 +24,17 @@ export interface PartialDossier extends Partial { templateUrl: './dossier-name-column.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [MatTooltip, MatIcon, NgIf, DatePipe], + imports: [MatTooltip, MatIcon, DatePipe], }) export class DossierNameColumnComponent { - @Input() dossier: PartialDossier; - @Input() dossierStats: DossierStats; + readonly dossier = input(); + readonly dossierStats = input(); + + readonly dossierTemplateName = computed(() => this._dossierTemplatesService.find(this.dossier().dossierTemplateId)?.name); + readonly approachingDueDate = computed(() => this.#dueDateDaysDiff() >= 0 && this.#dueDateDaysDiff() <= DUE_DATE_WARN_DAYS); + readonly passedDueDate = computed(() => this.#dueDateDaysDiff() < 0); + readonly isSoftDeleted = computed(() => !!this.dossier().softDeletedTime); + readonly #dueDateDaysDiff = computed(() => dateWithoutTime(dayjs(this.dossier().dueDate)).diff(dateWithoutTime(dayjs()), 'day')); constructor(private readonly _dossierTemplatesService: DossierTemplatesService) {} - - get approachingDueDate(): boolean { - return this.#dueDateDaysDiff >= 0 && this.#dueDateDaysDiff <= DUE_DATE_WARN_DAYS; - } - - get passedDueDate(): boolean { - return this.#dueDateDaysDiff < 0; - } - - get isSoftDeleted(): boolean { - return !!this.dossier.softDeletedTime; - } - - get #dueDateDaysDiff(): number { - return dateWithoutTime(dayjs(this.dossier.dueDate)).diff(dateWithoutTime(dayjs()), 'day'); - } - - getDossierTemplateNameFor(dossierTemplateId: string): string { - return this._dossierTemplatesService.find(dossierTemplateId)?.name || '-'; - } } diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.ts b/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.ts index f29a69db1..68d74d7f4 100644 --- a/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input, OnChanges } from '@angular/core'; import { Dossier, DossierState } from '@red/domain'; import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; import { Observable } from 'rxjs'; @@ -15,12 +15,12 @@ import { TranslateModule } from '@ngx-translate/core'; imports: [SmallChipComponent, AsyncPipe, TranslateModule], }) export class DossierStateComponent implements OnChanges { - @Input() dossier: Dossier; + readonly dossier = input(); dossierState$: Observable; constructor(private readonly _dossierStatesMapService: DossierStatesMapService) {} ngOnChanges(): void { - this.dossierState$ = this._dossierStatesMapService.watch$(this.dossier.dossierTemplateId, this.dossier.dossierStatusId); + this.dossierState$ = this._dossierStatesMapService.watch$(this.dossier().dossierTemplateId, this.dossier().dossierStatusId); } }