RED-10139: refactoring dossier listing.
This commit is contained in:
parent
b7ff80ecac
commit
a2870531c9
@ -1,82 +1,84 @@
|
||||
<a
|
||||
*ngIf="stats as dossierTemplate"
|
||||
[attr.help-mode-key]="!dossierTemplate.isEmpty ? 'open_dossier_template' : null"
|
||||
[class.empty]="dossierTemplate.isEmpty"
|
||||
[routerLink]="dossierTemplate.isEmpty ? null : ['..', dossierTemplate.dossierTemplateId, 'dossiers']"
|
||||
class="dialog"
|
||||
>
|
||||
<ng-container *ngIf="!dossierTemplate.isEmpty; else empty">
|
||||
<div class="flex-2">
|
||||
<div class="heading mb-6">{{ dossierTemplate.name }}</div>
|
||||
<div class="stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:archive"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="
|
||||
'dossier-template-stats.archived-dossiers' | translate: { count: dossierTemplate.numberOfArchivedDossiers }
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:trash"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="
|
||||
'dossier-template-stats.deleted-dossiers' | translate: { count: dossierTemplate.numberOfDeletedDossiers }
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<span [innerHTML]="'dossier-template-stats.total-people' | translate: { count: dossierTemplate.numberOfPeople }"></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:pages"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="'dossier-template-stats.analyzed-pages' | translate: { count: dossierTemplate.numberOfPages }"
|
||||
></span>
|
||||
@if (stats(); as dossierTemplate) {
|
||||
<a
|
||||
[attr.help-mode-key]="!isTemplateEmpty() ? 'open_dossier_template' : null"
|
||||
[class.empty]="isTemplateEmpty()"
|
||||
[routerLink]="isTemplateEmpty() ? null : ['..', dossierTemplate.dossierTemplateId, 'dossiers']"
|
||||
class="dialog"
|
||||
>
|
||||
@if (!isTemplateEmpty()) {
|
||||
<div class="flex-2">
|
||||
<div class="heading mb-6">{{ dossierTemplate.name }}</div>
|
||||
<div class="stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:archive"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="
|
||||
'dossier-template-stats.archived-dossiers' | translate: { count: dossierTemplate.numberOfArchivedDossiers }
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:trash"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="
|
||||
'dossier-template-stats.deleted-dossiers' | translate: { count: dossierTemplate.numberOfDeletedDossiers }
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="'dossier-template-stats.total-people' | translate: { count: dossierTemplate.numberOfPeople }"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:pages"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="'dossier-template-stats.analyzed-pages' | translate: { count: dossierTemplate.numberOfPages }"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-3">
|
||||
<redaction-donut-chart
|
||||
[config]="dossierStates()"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: dossierTemplate.numberOfActiveDossiers }]"
|
||||
direction="row"
|
||||
totalType="sum"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
<div class="flex-3">
|
||||
<redaction-donut-chart
|
||||
[config]="workflowStatuses()"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.total-documents' | translate]"
|
||||
direction="row"
|
||||
totalType="sum"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="text-muted">
|
||||
<div class="heading mb-8">
|
||||
{{ dossierTemplate.name }}
|
||||
</div>
|
||||
<div>
|
||||
{{ 'dashboard.empty-template.description' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-3">
|
||||
<redaction-donut-chart
|
||||
[config]="translateChartService.translateAndSortDossierStates(dossierTemplate.dossiersChartConfig, dossierTemplate.id)"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: dossierTemplate.numberOfActiveDossiers }]"
|
||||
direction="row"
|
||||
totalType="sum"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
<div class="flex-3">
|
||||
<redaction-donut-chart
|
||||
[config]="translateChartService.translateWorkflowStatus(dossierTemplate.documentsChartConfig)"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.total-documents' | translate]"
|
||||
direction="row"
|
||||
totalType="sum"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #empty>
|
||||
<div class="text-muted">
|
||||
<div class="heading mb-8">
|
||||
{{ dossierTemplate.name }}
|
||||
</div>
|
||||
<div>
|
||||
{{ 'dashboard.empty-template.description' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="newDossier()"
|
||||
*ngIf="permissionsService.canCreateDossier(dossierTemplate)"
|
||||
[attr.help-mode-key]="'new_dossier'"
|
||||
[label]="'dashboard.empty-template.new-dossier' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
[buttonId]="(dossierTemplate.name | snakeCase) + '-icon-button'"
|
||||
icon="iqser:plus"
|
||||
></iqser-icon-button>
|
||||
</ng-template>
|
||||
</a>
|
||||
@if (canCreateDossier()) {
|
||||
<iqser-icon-button
|
||||
(action)="newDossier()"
|
||||
[attr.help-mode-key]="'new_dossier'"
|
||||
[label]="'dashboard.empty-template.new-dossier' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
[buttonId]="(dossierTemplate.name | snakeCase) + '-icon-button'"
|
||||
icon="iqser:plus"
|
||||
></iqser-icon-button>
|
||||
}
|
||||
}
|
||||
</a>
|
||||
}
|
||||
|
||||
@ -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<DashboardStats>();
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,3 @@
|
||||
<iqser-status-bar *ngIf="stats" [configs]="statusBarConfig"></iqser-status-bar>
|
||||
@if (stats()) {
|
||||
<iqser-status-bar [configs]="statusBarConfig()"></iqser-status-bar>
|
||||
}
|
||||
|
||||
@ -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<StatusBarConfig<string>>;
|
||||
export class DossierDocumentsStatusComponent {
|
||||
readonly stats = input<DossierStats>();
|
||||
readonly statusBarConfig = computed(() => this.#statusConfig);
|
||||
|
||||
private get _statusConfig(): List<StatusBarConfig<string>> {
|
||||
const { fileCountPerWorkflowStatus } = this.stats;
|
||||
get #statusConfig(): List<StatusBarConfig<string>> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
<div class="needs-work">
|
||||
<redaction-annotation-icon
|
||||
*ngIf="dossierStats.hasRedactionsFilePresent"
|
||||
[color]="redactionColor$ | async"
|
||||
[label]="'redaction-abbreviation' | translate"
|
||||
type="square"
|
||||
></redaction-annotation-icon>
|
||||
@if (dossierStats().hasRedactionsFilePresent) {
|
||||
<redaction-annotation-icon
|
||||
[color]="redactionColor$ | async"
|
||||
[label]="'redaction-abbreviation' | translate"
|
||||
type="square"
|
||||
></redaction-annotation-icon>
|
||||
}
|
||||
|
||||
<redaction-annotation-icon
|
||||
*ngIf="dossierStats.hasHintsNoRedactionsFilePresent"
|
||||
[color]="hintColor$ | async"
|
||||
label="H"
|
||||
type="circle"
|
||||
></redaction-annotation-icon>
|
||||
@if (dossierStats().hasHintsNoRedactionsFilePresent) {
|
||||
<redaction-annotation-icon [color]="hintColor$ | async" label="H" type="circle"></redaction-annotation-icon>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -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<string>(null);
|
||||
@Input() dossier: Dossier;
|
||||
@Input() dossierStats: DossierStats;
|
||||
readonly dossier = input<Dossier>();
|
||||
readonly dossierStats = input<DossierStats>();
|
||||
readonly hintColor$: Observable<string>;
|
||||
readonly redactionColor$: Observable<string>;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,30 +1,32 @@
|
||||
<div *ngIf="stats$ | async as stats">
|
||||
<redaction-donut-chart
|
||||
[config]="dossiersChartConfig$ | async"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.active-dossiers' | translate : { count: stats.numberOfActiveDossiers }]"
|
||||
filterKey="dossierStatesFilters"
|
||||
></redaction-donut-chart>
|
||||
@if (stats$ | async; as stats) {
|
||||
<div>
|
||||
<redaction-donut-chart
|
||||
[config]="dossiersChartConfig$ | async"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: stats.numberOfActiveDossiers }]"
|
||||
filterKey="dossierStatesFilters"
|
||||
></redaction-donut-chart>
|
||||
|
||||
<div class="dossier-stats-container">
|
||||
<div class="dossier-stats-item">
|
||||
<mat-icon svgIcon="red:needs-work"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">{{ stats.numberOfPages | number }}</div>
|
||||
<div [translateParams]="{ count: stats.numberOfPages }" [translate]="'dossier-listing.stats.analyzed-pages'"></div>
|
||||
<div class="dossier-stats-container">
|
||||
<div class="dossier-stats-item">
|
||||
<mat-icon svgIcon="red:needs-work"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">{{ stats.numberOfPages | number }}</div>
|
||||
<div [translateParams]="{ count: stats.numberOfPages }" [translate]="'dossier-listing.stats.analyzed-pages'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dossier-stats-item">
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">{{ stats.numberOfPeople }}</div>
|
||||
<div translate="dossier-listing.stats.total-people"></div>
|
||||
<div class="dossier-stats-item">
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">{{ stats.numberOfPeople }}</div>
|
||||
<div translate="dossier-listing.stats.total-people"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="right-chart">
|
||||
<redaction-donut-chart
|
||||
|
||||
@ -6,7 +6,7 @@ import { map } from 'rxjs/operators';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
import { getParam } from '@iqser/common-ui/lib/utils';
|
||||
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
|
||||
import { AsyncPipe, DecimalPipe, NgIf } from '@angular/common';
|
||||
import { AsyncPipe, DecimalPipe } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
|
||||
@ -16,7 +16,7 @@ import { MatIcon } from '@angular/material/icon';
|
||||
styleUrls: ['./dossiers-listing-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [DonutChartComponent, AsyncPipe, NgIf, TranslateModule, MatIcon, DecimalPipe],
|
||||
imports: [DonutChartComponent, AsyncPipe, TranslateModule, MatIcon, DecimalPipe],
|
||||
})
|
||||
export class DossiersListingDetailsComponent {
|
||||
readonly stats$: Observable<DashboardStats>;
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
<ng-container *ngIf="stats$ | async as stats">
|
||||
@if (stats$ | async; as stats) {
|
||||
<div class="cell">
|
||||
<redaction-dossier-name-column [dossierStats]="stats" [dossier]="dossier"></redaction-dossier-name-column>
|
||||
<redaction-dossier-name-column [dossierStats]="stats" [dossier]="dossier()"></redaction-dossier-name-column>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<redaction-date-column [date]="stats.fileManipulationDate"></redaction-date-column>
|
||||
</div>
|
||||
|
||||
<div class="cell" *ngIf="!isDocumine">
|
||||
<redaction-dossier-workload-column [dossierStats]="stats" [dossier]="dossier"></redaction-dossier-workload-column>
|
||||
</div>
|
||||
@if (!isDocumine) {
|
||||
<div class="cell">
|
||||
<redaction-dossier-workload-column [dossierStats]="stats" [dossier]="dossier()"></redaction-dossier-workload-column>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="cell user-column">
|
||||
<iqser-initials-avatar [user]="dossier.ownerId" [withName]="true"></iqser-initials-avatar>
|
||||
<iqser-initials-avatar [user]="dossier().ownerId" [withName]="true"></iqser-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div class="cell status-container">
|
||||
@ -20,8 +22,8 @@
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<redaction-dossier-state [dossier]="dossier"></redaction-dossier-state>
|
||||
<redaction-dossier-state [dossier]="dossier()"></redaction-dossier-state>
|
||||
|
||||
<redaction-dossiers-listing-actions [dossier]="dossier"></redaction-dossiers-listing-actions>
|
||||
<redaction-dossiers-listing-actions [dossier]="dossier()"></redaction-dossiers-listing-actions>
|
||||
</div>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
@ -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<Dossier>();
|
||||
|
||||
readonly stats$: Observable<DossierStats>;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 @@
|
||||
</div>
|
||||
|
||||
<div class="right-container" iqserHasScrollbar>
|
||||
<redaction-dossiers-listing-details *ngIf="(entitiesService.noData$ | async) === false"></redaction-dossiers-listing-details>
|
||||
@if ((entitiesService.noData$ | async) === false) {
|
||||
<redaction-dossiers-listing-details></redaction-dossiers-listing-details>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -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<Dossier> 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<Dossier> im
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private _computeAllFilters() {
|
||||
#computeAllFilters() {
|
||||
const filterGroups = this._configService.filterGroups(
|
||||
this.entitiesService.all,
|
||||
this._needsWorkFilterTemplate,
|
||||
|
||||
@ -1,33 +1,30 @@
|
||||
<div (longPress)="forceReanalysisAction($event)" class="action-buttons" redactionLongPress>
|
||||
<iqser-circle-button
|
||||
(action)="openEditDossierDialog(dossier.id)"
|
||||
(action)="openEditDossierDialog(dossier().id)"
|
||||
*allow="roles.dossiers.read; if: currentUser.isUser"
|
||||
[attr.help-mode-key]="'edit_dossier'"
|
||||
[icon]="
|
||||
((iqserPermissionsService.has$(roles.dossiers.edit) | async) && currentUser.isManager) || canEditDossierDictionary
|
||||
? 'iqser:edit'
|
||||
: 'red:info'
|
||||
"
|
||||
[icon]="(hasEditDossierRole() && currentUser.isManager) || canEditDossierDictionary() ? 'iqser:edit' : 'red:info'"
|
||||
[tooltip]="
|
||||
(((iqserPermissionsService.has$(roles.dossiers.edit) | async) && currentUser.isManager) || canEditDossierDictionary
|
||||
((hasEditDossierRole() && currentUser.isManager) || canEditDossierDictionary()
|
||||
? 'dossier-listing.edit.action'
|
||||
: 'dossier-listing.dossier-info.action'
|
||||
) | translate
|
||||
"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="reanalyseDossier(dossier)"
|
||||
*ngIf="displayReanalyseBtn"
|
||||
[tooltip]="'dossier-listing.reanalyse.action' | translate"
|
||||
icon="iqser:refresh"
|
||||
></iqser-circle-button>
|
||||
@if (displayReanalyseBtn) {
|
||||
<iqser-circle-button
|
||||
(action)="reanalyseDossier(dossier())"
|
||||
[tooltip]="'dossier-listing.reanalyse.action' | translate"
|
||||
icon="iqser:refresh"
|
||||
></iqser-circle-button>
|
||||
}
|
||||
|
||||
<redaction-file-download-btn
|
||||
[attr.help-mode-key]="isDocumine ? 'template_download_dossier' : 'download_dossier'"
|
||||
[buttonId]="'download-dossier-files-' + dossier.id"
|
||||
[buttonId]="'download-dossier-files-' + dossier().id"
|
||||
[disabled]="downloadBtnDisabled"
|
||||
[dossier]="dossier"
|
||||
[dossier]="dossier()"
|
||||
[files]="files"
|
||||
dossierDownload
|
||||
></redaction-file-download-btn>
|
||||
|
||||
@ -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<Dossier>();
|
||||
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) {
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
<svg:circle
|
||||
[attr.cx]="cx()"
|
||||
[attr.cy]="cy()"
|
||||
[attr.r]="radius()"
|
||||
[attr.stroke-dasharray]="circumference()"
|
||||
[attr.stroke-dashoffset]="strokeDashOffset()"
|
||||
[attr.stroke-width]="strokeWidth()"
|
||||
[attr.stroke]="stroke()"
|
||||
[attr.transform]="circleTransformValue()"
|
||||
[class]="config().color"
|
||||
fill="transparent"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
|
After Width: | Height: | Size: 387 B |
@ -0,0 +1,4 @@
|
||||
:host {
|
||||
display: contents;
|
||||
height: fit-content;
|
||||
}
|
||||
@ -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<number>();
|
||||
readonly config = input<DonutChartConfig>();
|
||||
readonly cx = input<number>();
|
||||
readonly cy = input<number>();
|
||||
readonly radius = input<number>();
|
||||
readonly circumference = input<number>();
|
||||
readonly strokeWidth = input<number>();
|
||||
readonly chartData = input<{ degrees: number }>();
|
||||
readonly dataTotal = input<number>();
|
||||
|
||||
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()})`;
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<div
|
||||
(click)="config().key && selectValue(config().key)"
|
||||
[class.active]="filterChecked()"
|
||||
[class.filter-disabled]="!config().key || !filters().length"
|
||||
[id]="config().id"
|
||||
>
|
||||
<iqser-status-bar [configs]="statusBarConfigs()" [small]="true"></iqser-status-bar>
|
||||
</div>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<DonutChartConfig>();
|
||||
readonly totalType = input<'sum' | 'count' | 'simpleLabel'>('sum');
|
||||
readonly counterText = input<string>();
|
||||
readonly filterKey = input<string>();
|
||||
readonly valueFormatter = input<(value: number) => string>();
|
||||
readonly filters = input<INestedFilter[]>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,59 +1,63 @@
|
||||
<div [class]="'container flex ' + direction">
|
||||
<svg [attr.height]="size" [attr.width]="size" [style.min-width]="size" attr.viewBox="0 0 {{ size }} {{ size }}" class="donut-chart">
|
||||
<g *ngFor="let value of config; let i = index">
|
||||
<circle
|
||||
*ngIf="!!chartData[i]"
|
||||
[attr.cx]="cx"
|
||||
[attr.cy]="cy"
|
||||
[attr.r]="radius"
|
||||
[attr.stroke-dasharray]="circumference"
|
||||
[attr.stroke-dashoffset]="calculateStrokeDashOffset(value.value)"
|
||||
[attr.stroke-width]="strokeWidth"
|
||||
[attr.stroke]="value.color.includes('#') ? value.color : ''"
|
||||
[attr.transform]="returnCircleTransformValue(i)"
|
||||
[class]="value.color"
|
||||
fill="transparent"
|
||||
/>
|
||||
</g>
|
||||
<div [class]="'container flex ' + direction()">
|
||||
<svg
|
||||
[attr.height]="size()"
|
||||
[attr.width]="size()"
|
||||
[style.min-width]="size()"
|
||||
attr.viewBox="0 0 {{ size() }} {{ size() }}"
|
||||
class="donut-chart"
|
||||
>
|
||||
@for (value of config(); track value.label) {
|
||||
@if (!!chartData()[$index]) {
|
||||
<g
|
||||
redaction-chart-circle-config
|
||||
[config]="value"
|
||||
[index]="$index"
|
||||
[cx]="cx()"
|
||||
[cy]="cy()"
|
||||
[circumference]="circumference()"
|
||||
[radius]="radius()"
|
||||
[strokeWidth]="strokeWidth()"
|
||||
[chartData]="chartData()[$index]"
|
||||
[dataTotal]="dataTotal()"
|
||||
></g>
|
||||
}
|
||||
}
|
||||
</svg>
|
||||
|
||||
<div [style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + (strokeWidth + 5) + 'px;'" class="text-container">
|
||||
<div class="heading-xl">{{ getFormattedValue(displayedDataTotal) }}</div>
|
||||
<div *ngIf="subtitles.length === 1" class="mt-5">{{ subtitles[0] }}</div>
|
||||
<div [style]="'height: ' + size() + 'px; width: ' + size() + 'px; padding: ' + (strokeWidth() + 5) + 'px;'" class="text-container">
|
||||
<div class="heading-xl">{{ formatedValue() }}</div>
|
||||
@if (subtitles().length === 1) {
|
||||
<div class="mt-5">{{ subtitles()[0] }}</div>
|
||||
}
|
||||
|
||||
<div *ngIf="subtitleTemplate as t" class="mt-5">
|
||||
<ng-container *ngTemplateOutlet="t"></ng-container>
|
||||
</div>
|
||||
@if (subtitleTemplate(); as t) {
|
||||
<div class="mt-5">
|
||||
<ng-container *ngTemplateOutlet="t"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-select
|
||||
(selectionChange)="subtitleChanged.emit(subtitles.indexOf($event.value))"
|
||||
*ngIf="subtitles.length > 1"
|
||||
[value]="subtitles[0]"
|
||||
class="mt-5 ml-10"
|
||||
>
|
||||
<mat-option *ngFor="let subtitle of subtitles" [value]="subtitle"> {{ subtitle }} </mat-option>
|
||||
</mat-select>
|
||||
@if (subtitles().length > 1) {
|
||||
<mat-select
|
||||
(selectionChange)="subtitleChanged.emit(subtitles().indexOf($event.value))"
|
||||
[value]="subtitles()[0]"
|
||||
class="mt-5 ml-10"
|
||||
>
|
||||
@for (subtitle of subtitles(); track subtitle) {
|
||||
<mat-option [value]="subtitle"> {{ subtitle }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div [attr.help-mode-key]="helpModeKey" class="breakdown-container">
|
||||
<div
|
||||
(click)="val.key && selectValue(val.key)"
|
||||
*ngFor="let val of config"
|
||||
[class.active]="filterChecked$(val.key) | async"
|
||||
[class.filter-disabled]="!val.key || !(filters$ | async).length"
|
||||
[id]="val.id"
|
||||
>
|
||||
<iqser-status-bar
|
||||
[configs]="[
|
||||
{
|
||||
length: val.value,
|
||||
color: val.color,
|
||||
label: getLabel(val),
|
||||
cssClass: val.color === 'PROCESSING' || val.color === 'OCR_PROCESSING' ? 'loading' : ''
|
||||
}
|
||||
]"
|
||||
[small]="true"
|
||||
></iqser-status-bar>
|
||||
</div>
|
||||
<div [attr.help-mode-key]="helpModeKey()" class="breakdown-container">
|
||||
@for (value of config(); track value.label) {
|
||||
<redaction-chart-filters
|
||||
[config]="value"
|
||||
[filterKey]="filterKey()"
|
||||
[totalType]="totalType()"
|
||||
[counterText]="counterText()"
|
||||
[filters]="filters()"
|
||||
></redaction-chart-filters>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<any>;
|
||||
export class DonutChartComponent {
|
||||
readonly subtitles = input<string[]>([]);
|
||||
readonly config = input<DonutChartConfig[]>([]);
|
||||
readonly radius = input(85);
|
||||
readonly strokeWidth = input(20);
|
||||
readonly direction = input<'row' | 'column'>('column');
|
||||
readonly totalType = input<'sum' | 'count' | 'simpleLabel'>('sum');
|
||||
readonly counterText = input<string>();
|
||||
readonly filterKey = input<string>();
|
||||
readonly helpModeKey = input();
|
||||
readonly valueFormatter = input<(value: number) => string>();
|
||||
readonly subtitleTemplate = input<TemplateRef<any>>();
|
||||
|
||||
@Output() readonly subtitleChanged = new EventEmitter<number>();
|
||||
readonly subtitleChanged = output<number>();
|
||||
|
||||
chartData: any[] = [];
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
size = 0;
|
||||
filters$: Observable<INestedFilter[]>;
|
||||
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<boolean> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,37 +1,43 @@
|
||||
<div [attr.help-mode-key]="'dossier'" [matTooltip]="dossier.dossierName" class="table-item-title heading mb-6" matTooltipPosition="above">
|
||||
{{ dossier.dossierName }}
|
||||
<div [attr.help-mode-key]="'dossier'" [matTooltip]="dossier().dossierName" class="table-item-title heading mb-6" matTooltipPosition="above">
|
||||
{{ dossier().dossierName }}
|
||||
</div>
|
||||
|
||||
<div class="small-label stats-subtitle mb-6">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:template"></mat-icon>
|
||||
{{ getDossierTemplateNameFor(dossier.dossierTemplateId) }}
|
||||
{{ dossierTemplateName() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossierStats" class="stats-subtitle">
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="iqser:document"></mat-icon>
|
||||
{{ isSoftDeleted ? dossierStats.numberOfSoftDeletedFiles : dossierStats.numberOfFiles }}
|
||||
</div>
|
||||
@if (dossierStats()) {
|
||||
<div class="stats-subtitle">
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="iqser:document"></mat-icon>
|
||||
{{ isSoftDeleted() ? dossierStats().numberOfSoftDeletedFiles : dossierStats().numberOfFiles }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isSoftDeleted" class="small-label">
|
||||
<mat-icon svgIcon="iqser:pages"></mat-icon>
|
||||
{{ dossierStats.numberOfPages }}
|
||||
</div>
|
||||
@if (!isSoftDeleted()) {
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="iqser:pages"></mat-icon>
|
||||
{{ dossierStats().numberOfPages }}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
{{ dossier.memberIds.length }}
|
||||
</div>
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
{{ dossier().memberIds.length }}
|
||||
</div>
|
||||
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
{{ dossier.date | date: 'mediumDate' }}
|
||||
</div>
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
{{ dossier().date | date: 'mediumDate' }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossier.dueDate" [class.error]="passedDueDate" [class.warn]="approachingDueDate" class="small-label">
|
||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||
{{ dossier.dueDate | date: 'mediumDate' }}
|
||||
@if (dossier().dueDate) {
|
||||
<div [class.error]="passedDueDate()" [class.warn]="approachingDueDate()" class="small-label">
|
||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||
{{ dossier().dueDate | date: 'mediumDate' }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -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<IDossier> {
|
||||
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<PartialDossier>();
|
||||
readonly dossierStats = input<DossierStats>();
|
||||
|
||||
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 || '-';
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Dossier>();
|
||||
dossierState$: Observable<DossierState>;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user