diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.html index 4bcd21d0b..d12946f42 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component.html @@ -1,7 +1,7 @@
, @Inject(MAT_DIALOG_DATA) readonly data: DialogData, @@ -27,13 +33,23 @@ export class AddEditDossierStateDialogComponent extends BaseDialogComponent { this.initialFormValue = this.form.getRawValue(); } - save(): void { + get type(): 'edit' | 'create' { + return this.data.dossierState ? 'edit' : 'create'; + } + + async save(): Promise { const dossierState: IDossierState = { dossierStatusId: this.data.dossierState?.dossierStatusId, dossierTemplateId: this.data.dossierTemplateId, ...this.form.getRawValue(), }; - this._dialogRef.close(dossierState); + this._loadingService.start(); + try { + await firstValueFrom(this._dossierStateService.createOrUpdate(dossierState)); + this._toaster.success(_('add-edit-dossier-state.success'), { params: { type: this.type } }); + this._dialogRef.close(); + } catch (e) {} + this._loadingService.stop(); } #getForm(): FormGroup { diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.html index 332c07a5e..5e26d6211 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.html @@ -12,12 +12,12 @@
- + - {{ 'confirm-delete-dossier-state.form.status-placeholder' | translate }} + {{ 'confirm-delete-dossier-state.form.state-placeholder' | translate }} {{ state.name }} @@ -29,10 +29,10 @@
- -
+
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.ts index 387160092..910b6e977 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component.ts @@ -1,12 +1,18 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { IDossierState } from '@red/domain'; +import { DossierState } from '@red/domain'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { FormBuilder, FormGroup } from '@angular/forms'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { firstValueFrom, forkJoin } from 'rxjs'; +import { DossierStatesService } from '@services/entity-services/dossier-states.service'; +import { LoadingService, Toaster } from '@iqser/common-ui'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service'; +import { take } from 'rxjs/operators'; interface DialogData { - readonly toBeDeletedState: IDossierState; - readonly otherStates: IDossierState[]; + readonly toBeDeletedState: DossierState; + readonly otherStates: DossierState[]; readonly dossierCount: number; } @@ -21,7 +27,12 @@ export class ConfirmDeleteDossierStateDialogComponent { constructor( private readonly _formBuilder: FormBuilder, - readonly dialogRef: MatDialogRef, + private readonly _loadingService: LoadingService, + private readonly _toaster: Toaster, + private readonly _dossierStateService: DossierStatesService, + private readonly _dialogRef: MatDialogRef, + private readonly _activeDossiersService: ActiveDossiersService, + private readonly _archivedDossiersService: ArchivedDossiersService, @Inject(MAT_DIALOG_DATA) readonly data: DialogData, ) { this.form = this.#getForm(); @@ -42,8 +53,15 @@ export class ConfirmDeleteDossierStateDialogComponent { return this.replaceDossierStatusId ? _('confirm-delete-dossier-state.delete-replace') : _('confirm-delete-dossier-state.delete'); } - get afterCloseValue(): string | true { - return this.replaceDossierStatusId ?? true; + async save(): Promise { + this._loadingService.start(); + await firstValueFrom(this._dossierStateService.deleteState(this.data.toBeDeletedState, this.replaceDossierStatusId)); + await firstValueFrom( + forkJoin([this._activeDossiersService.loadAll().pipe(take(1)), this._archivedDossiersService.loadAll().pipe(take(1))]), + ); + this._toaster.success(_('confirm-delete-dossier-state.success')); + this._dialogRef.close(); + this._loadingService.stop(); } #getForm(): FormGroup { diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.html index b8cbb26bd..1b55ea69a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.html @@ -1,101 +1,98 @@ - -
-
- - -
- - - -
-
- - -
-
-
-
-
{{ state.name }}
-
-
- -
- {{ state.rank }} -
- -
- {{ state.dossierCount }} -
- -
-
- - -
-
-
-
-
+
+ diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.scss index ef7a2530e..1492972d9 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.scss @@ -3,6 +3,7 @@ .dossier-state-square { height: 16px; width: 16px; + min-width: 16px; margin-right: 16px; } diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts index f704bc10f..8411daf27 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts @@ -4,22 +4,19 @@ import { DefaultListingServices, IconButtonTypes, ListingComponent, - LoadingService, SortingOrders, TableColumnConfig, - Toaster, } from '@iqser/common-ui'; import { DossierState, IDossierState } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; -import { firstValueFrom } from 'rxjs'; +import { firstValueFrom, map, Observable } from 'rxjs'; import { AdminDialogService } from '../../services/admin-dialog.service'; -import { UserService } from '@services/user.service'; -import { HttpStatusCode } from '@angular/common/http'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { ActivatedRoute } from '@angular/router'; -import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; +import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; +import { tap } from 'rxjs/operators'; +import { PermissionsService } from '@services/permissions.service'; +import { DossierStatesService } from '@services/entity-services/dossier-states.service'; @Component({ templateUrl: './dossier-states-listing-screen.component.html', @@ -33,29 +30,29 @@ import { DossierTemplatesService } from '@services/entity-services/dossier-templ export class DossierStatesListingScreenComponent extends ListingComponent implements OnInit, OnDestroy { readonly iconButtonTypes = IconButtonTypes; readonly circleButtonTypes = CircleButtonTypes; - readonly currentUser = this._userService.currentUser; readonly tableHeaderLabel = _('dossier-states-listing.table-header.title'); readonly tableColumnConfigs: TableColumnConfig[] = [ { label: _('dossier-states-listing.table-col-names.name'), sortByKey: 'name' }, { label: _('dossier-states-listing.table-col-names.rank'), sortByKey: 'rank' }, { label: _('dossier-states-listing.table-col-names.dossiers-count') }, ]; - chartData: DoughnutChartConfig[]; + chartData$: Observable; readonly #dossierTemplateId: string; constructor( protected readonly _injector: Injector, - private readonly _loadingService: LoadingService, - private readonly _activeDossiersService: ActiveDossiersService, - readonly dossierStateService: DossierStateService, private readonly _dialogService: AdminDialogService, - private readonly _userService: UserService, - private readonly _toaster: Toaster, private readonly _route: ActivatedRoute, - private readonly _dossierTemplatesService: DossierTemplatesService, + private readonly _dossierStatesMapService: DossierStatesMapService, + private readonly _dossierStatesService: DossierStatesService, + readonly permissionsService: PermissionsService, ) { super(_injector); this.#dossierTemplateId = _route.snapshot.paramMap.get('dossierTemplateId'); + this.chartData$ = this._dossierStatesMapService.get$(this.#dossierTemplateId).pipe( + tap(states => this.entitiesService.setEntities(states)), + map(states => this.#chartData(states)), + ); } async ngOnInit(): Promise { @@ -63,7 +60,7 @@ export class DossierStatesListingScreenComponent extends ListingComponent { - await this.#createNewDossierStateAndRefreshView(newValue); - }); + this._dialogService.openDialog('addEditDossierState', $event, data); } - openConfirmDeleteStateDialog($event: MouseEvent, dossierState: IDossierState) { - const templateId = this.#dossierTemplateId; + openConfirmDeleteStateDialog($event: MouseEvent, dossierState: DossierState) { const data = { toBeDeletedState: dossierState, - otherStates: this.entitiesService.all.filter(state => state.dossierStatusId !== dossierState.dossierStatusId), + otherStates: this.entitiesService.all.filter(state => state.id !== dossierState.id), dossierCount: dossierState.dossierCount, }; - this._dialogService.openDialog('deleteDossierState', $event, data, async (value: string | true) => { - if (value) { - if (typeof value === 'string') { - await firstValueFrom(this.dossierStateService.deleteAndReplace(dossierState.dossierStatusId, value)); - } else { - await firstValueFrom(this.dossierStateService.delete(dossierState.dossierStatusId)); - } - } - - await firstValueFrom(this._dossierTemplatesService.refreshDossierTemplate(templateId)); - await this.#loadData(); - }); + this._dialogService.openDialog('deleteDossierState', $event, data); } - async #createNewDossierStateAndRefreshView(newValue: IDossierState): Promise { - this._loadingService.start(); - await firstValueFrom(this.dossierStateService.updateDossierState(newValue)).catch(error => { - if (error.status === HttpStatusCode.Conflict) { - this._toaster.error(_('dossier-states-listing.error.conflict')); - } else { - this._toaster.error(_('dossier-states-listing.error.generic')); - } - }); - await firstValueFrom(this._dossierTemplatesService.refreshDossierTemplate(this.#dossierTemplateId)); - await this.#loadData(); - } - - async #loadData(): Promise { - this._loadingService.start(); - // TODO: Move this in service; dossiers states service should be a mapping service - await firstValueFrom(this._activeDossiersService.loadAll()); - - try { - const dossierStates = this.dossierStateService.all.filter(d => d.dossierTemplateId === this.#dossierTemplateId); - this.#setStatesCount(dossierStates); - this.chartData = dossierStates.map(state => ({ - value: state.dossierCount, - label: state.name, - key: state.name, - color: state.color, - })); - - this.entitiesService.setEntities(dossierStates || []); - } catch (e) {} - this._loadingService.stop(); - } - - #setStatesCount(dossierStates: DossierState[]): void { - dossierStates.forEach(state => (state.dossierCount = this._activeDossiersService.getCountWithState(state.dossierStatusId))); + #chartData(states: DossierState[]): DoughnutChartConfig[] { + return states.map(state => ({ + value: state.dossierCount, + label: state.name, + key: state.name, + color: state.color, + })); } } diff --git a/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html b/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html index 38bd957cb..aabfd8229 100644 --- a/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html +++ b/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html @@ -9,5 +9,5 @@
- +
diff --git a/apps/red-ui/src/app/modules/archive/services/config.service.ts b/apps/red-ui/src/app/modules/archive/services/config.service.ts index 49c876ccb..dc503650e 100644 --- a/apps/red-ui/src/app/modules/archive/services/config.service.ts +++ b/apps/red-ui/src/app/modules/archive/services/config.service.ts @@ -10,7 +10,7 @@ export class ConfigService { { label: _('archived-dossiers-listing.table-col-names.name'), sortByKey: 'searchKey', width: '2fr' }, { label: _('archived-dossiers-listing.table-col-names.last-modified'), sortByKey: 'archivedTime' }, { label: _('archived-dossiers-listing.table-col-names.owner'), class: 'user-column' }, - { label: _('archived-dossiers-listing.table-col-names.dossier-status'), class: 'flex-end', width: '2fr' }, + { label: _('archived-dossiers-listing.table-col-names.dossier-state'), class: 'flex-end', width: '2fr' }, ]; } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.html index d774a15de..381897a8c 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.html @@ -42,7 +42,7 @@
- +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts index 86ea692bc..4f8180d37 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import * as moment from 'moment'; -import { Dossier, DossierState, IDossierRequest, IDossierTemplate } from '@red/domain'; +import { Dossier, IDossierRequest, IDossierTemplate } from '@red/domain'; import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface'; import { DossiersDialogService } from '../../../services/dossiers-dialog.service'; import { PermissionsService } from '@services/permissions.service'; @@ -13,12 +13,12 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; import { firstValueFrom } from 'rxjs'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; import { TranslateService } from '@ngx-translate/core'; import { DossiersService } from '@services/dossiers/dossiers.service'; import { TrashDossiersService } from '@services/entity-services/trash-dossiers.service'; import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service'; +import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; @Component({ selector: 'redaction-edit-dossier-general-info', @@ -36,11 +36,10 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti hasDueDate: boolean; dossierTemplates: IDossierTemplate[]; states: string[]; - currentStatus: DossierState; constructor( readonly permissionsService: PermissionsService, - private readonly _dossierStateService: DossierStateService, + private readonly _dossierStatesMapService: DossierStatesMapService, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossiersService: DossiersService, private readonly _trashDossiersService: TrashDossiersService, @@ -80,20 +79,22 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti return this.hasDueDate && this.form.get('dueDate').value === null; } + get #statusPlaceholder(): string { + return this._translateService.instant( + this.states.length === 1 + ? 'edit-dossier-dialog.general-info.form.dossier-state.no-state-placeholder' + : 'edit-dossier-dialog.general-info.form.dossier-state.placeholder', + ); + } + ngOnInit() { - this.states = [ - null, - ...this._dossierStateService.all.filter(s => s.dossierTemplateId === this.dossier.dossierTemplateId).map(s => s.id), - ]; + this.states = [null, ...this._dossierStatesMapService.get(this.dossier.dossierTemplateId).map(s => s.id)]; this.statusPlaceholder = this.#statusPlaceholder; this.#filterInvalidDossierTemplates(); this.form = this.#getForm(); if (!this.permissionsService.canEditDossier(this.dossier)) { this.form.disable(); } - if (this.dossier.dossierStatusId) { - this.currentStatus = this._dossierStateService.find(this.dossier.dossierStatusId); - } this.hasDueDate = !!this.dossier.dueDate; } @@ -168,6 +169,17 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti }); } + getStateName(stateId: string): string { + return ( + this._dossierStatesMapService.get(this.dossier.dossierTemplateId, stateId)?.name || + this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-state.placeholder') + ); + } + + getStateColor(stateId: string): string { + return this._dossierStatesMapService.get(this.dossier.dossierTemplateId, stateId).color; + } + #getForm(): FormGroup { const formFieldWithArchivedCheck = value => ({ value, disabled: !this.dossier.isActive }); return this._formBuilder.group({ @@ -185,25 +197,6 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti }); } - get #statusPlaceholder(): string { - return this._translateService.instant( - this.states.length === 1 - ? 'edit-dossier-dialog.general-info.form.dossier-status.no-status-placeholder' - : 'edit-dossier-dialog.general-info.form.dossier-status.placeholder', - ); - } - - getStateName(stateId: string): string { - return ( - this._dossierStateService.find(stateId)?.name || - this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-status.placeholder') - ); - } - - getStateColor(stateId: string): string { - return this._dossierStateService.find(stateId).color; - } - #filterInvalidDossierTemplates() { this.dossierTemplates = this._dossierTemplatesService.all.filter(r => { if (this.dossier?.dossierTemplateId === r.dossierTemplateId) { diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts index 72d2d8988..84aff054e 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts @@ -8,8 +8,8 @@ import { workflowFileStatusTranslations } from '../../../../../../translations/f import { TranslateChartService } from '@services/translate-chart.service'; import { filter, map, switchMap } from 'rxjs/operators'; import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; import { TranslateService } from '@ngx-translate/core'; +import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; @Component({ selector: 'redaction-dossiers-listing-details', @@ -26,7 +26,7 @@ export class DossiersListingDetailsComponent { readonly activeDossiersService: ActiveDossiersService, private readonly _dossierStatsMap: DossierStatsService, private readonly _translateChartService: TranslateChartService, - private readonly _dossierStateService: DossierStateService, + private readonly _dossierStatesMapService: DossierStatesMapService, private readonly _translateService: TranslateService, ) { this.documentsChartData$ = this.activeDossiersService.all$.pipe( @@ -40,24 +40,12 @@ export class DossiersListingDetailsComponent { } private _toDossierChartData(): DoughnutChartConfig[] { - this._dossierStateService.all.forEach( - state => (state.dossierCount = this.activeDossiersService.getCountWithState(state.dossierStatusId)), - ); - const configArray: DoughnutChartConfig[] = [ - ...this._dossierStateService.all - .reduce((acc, { color, dossierCount, name }) => { - const key = name + '-' + color; - const item = acc.get(key) ?? Object.assign({}, { value: 0, label: name, color: color }); - - return acc.set(key, { ...item, value: item.value + dossierCount }); - }, new Map()) - .values(), - ]; - - const notAssignedLength = this.activeDossiersService.all.length - configArray.map(v => v.value).reduce((acc, val) => acc + val, 0); + const configArray: DoughnutChartConfig[] = this._dossierStatesMapService.stats; + const undefinedStateLength = + this.activeDossiersService.all.length - configArray.map(v => v.value).reduce((acc, val) => acc + val, 0); configArray.push({ - value: notAssignedLength, - label: this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-status.placeholder'), + value: undefinedStateLength, + label: this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-state.placeholder'), color: '#E2E4E9', }); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/table-item/table-item.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/table-item/table-item.component.html index 1adaefc28..31b1fbe22 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/table-item/table-item.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/table-item/table-item.component.html @@ -21,7 +21,7 @@
- +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts index d4d510284..f9419fc53 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts @@ -1,6 +1,6 @@ import { Injectable, TemplateRef } from '@angular/core'; import { ButtonConfig, IFilterGroup, INestedFilter, keyChecker, NestedFilter, TableColumnConfig } from '@iqser/common-ui'; -import { Dossier, StatusSorter, User } from '@red/domain'; +import { Dossier, StatusSorter, User, WorkflowFileStatus } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { TranslateService } from '@ngx-translate/core'; import { UserPreferenceService } from '@services/user-preference.service'; @@ -10,7 +10,7 @@ import { dossierMemberChecker, dossierStateChecker, dossierTemplateChecker, Reda import { workloadTranslations } from '../../translations/workload-translations'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; +import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; @Injectable() export class ConfigService { @@ -20,7 +20,7 @@ export class ConfigService { private readonly _userService: UserService, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierStatsService: DossierStatsService, - private readonly _dossierStateService: DossierStateService, + private readonly _dossierStatesMapService: DossierStatesMapService, ) {} get tableConfig(): TableColumnConfig[] { @@ -30,7 +30,7 @@ export class ConfigService { { label: _('dossier-listing.table-col-names.needs-work') }, { label: _('dossier-listing.table-col-names.owner'), class: 'user-column' }, { label: _('dossier-listing.table-col-names.documents-status'), class: 'flex-end', width: 'auto' }, - { label: _('dossier-listing.table-col-names.dossier-status'), class: 'flex-end' }, + { label: _('dossier-listing.table-col-names.dossier-state'), class: 'flex-end' }, ]; } @@ -66,6 +66,8 @@ export class ConfigService { const allDistinctDossierTemplates = new Set(); const allDistinctDossierStates = new Set(); + const stateToTemplateMap = new Map(); + const filterGroups: IFilterGroup[] = []; entities?.forEach(entry => { @@ -73,6 +75,7 @@ export class ConfigService { allDistinctDossierTemplates.add(entry.dossierTemplateId); if (entry.dossierStatusId) { allDistinctDossierStates.add(entry.dossierStatusId); + stateToTemplateMap.set(entry.dossierStatusId, entry.dossierTemplateId); } const stats = this._dossierStatsService.get(entry.dossierId); @@ -100,13 +103,13 @@ export class ConfigService { id => new NestedFilter({ id: id, - label: this._dossierStateService.find(id).name, + label: this._dossierStatesMapService.get(stateToTemplateMap.get(id), id).name, }), ); filterGroups.push({ slug: 'dossierStatesFilters', - label: this._translateService.instant('filters.dossier-status'), + label: this._translateService.instant('filters.dossier-state'), icon: 'red:status', hide: dossierStatesFilters.length <= 1, filters: dossierStatesFilters, @@ -114,7 +117,7 @@ export class ConfigService { }); const statusFilters = [...allDistinctFileStatus].map( - status => + (status: WorkflowFileStatus) => new NestedFilter({ id: status, label: this._translateService.instant(workflowFileStatusTranslations[status]), diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.html b/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.html new file mode 100644 index 000000000..30fd6acca --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.html @@ -0,0 +1,6 @@ +
+
+ {{ (dossierState$ | async)?.name || ('edit-dossier-dialog.general-info.form.dossier-state.placeholder' | translate) }} +
+ +
diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.scss b/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.scss similarity index 79% rename from apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.scss rename to apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.scss index 7d5a74298..c6f74191a 100644 --- a/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.scss +++ b/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.scss @@ -1,6 +1,6 @@ @use 'variables'; -.dossier-status-container { +.dossier-state-container { justify-content: flex-end; width: 100%; } @@ -9,7 +9,7 @@ redaction-small-chip { margin-left: 8px; } -.dossier-status-text { +.dossier-state-text { font-size: 13px; line-height: 16px; color: variables.$grey-1; 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 new file mode 100644 index 000000000..f22a673b6 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/dossier-state/dossier-state.component.ts @@ -0,0 +1,21 @@ +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'; + +@Component({ + selector: 'redaction-dossier-state [dossier]', + templateUrl: './dossier-state.component.html', + styleUrls: ['./dossier-state.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DossierStateComponent implements OnChanges { + @Input() dossier: Dossier; + dossierState$: Observable; + + constructor(private readonly _dossierStatesMapService: DossierStatesMapService) {} + + ngOnChanges(): void { + this.dossierState$ = this._dossierStatesMapService.watch$(this.dossier.dossierTemplateId, this.dossier.dossierStatusId); + } +} diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.html b/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.html deleted file mode 100644 index f4c5f96bf..000000000 --- a/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
- {{ currentState?.name || ('edit-dossier-dialog.general-info.form.dossier-status.placeholder' | translate) }} -
- -
diff --git a/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.ts b/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.ts deleted file mode 100644 index 357b098d3..000000000 --- a/apps/red-ui/src/app/modules/shared/components/dossier-status/dossier-status.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core'; -import { Dossier, DossierState } from '@red/domain'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; - -@Component({ - selector: 'redaction-dossier-status [dossier]', - templateUrl: './dossier-status.component.html', - styleUrls: ['./dossier-status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class DossierStatusComponent implements OnInit, OnChanges { - @Input() dossier: Dossier; - currentState: DossierState; - - constructor(private readonly _dossierStateService: DossierStateService) {} - - ngOnInit(): void { - this.#setState(); - } - - ngOnChanges(): void { - this.#setState(); - } - - #setState(): void { - this.currentState = this._dossierStateService.find(this.dossier.dossierStatusId); - } -} diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index a185ccf62..4305bfbd8 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -27,7 +27,7 @@ import { TeamMembersComponent } from './components/team-members/team-members.com import { EditorComponent } from './components/editor/editor.component'; import { ExpandableFileActionsComponent } from './components/expandable-file-actions/expandable-file-actions.component'; import { ProcessingIndicatorComponent } from '@shared/components/processing-indicator/processing-indicator.component'; -import { DossierStatusComponent } from '@shared/components/dossier-status/dossier-status.component'; +import { DossierStateComponent } from '@shared/components/dossier-state/dossier-state.component'; import { DossiersListingDossierNameComponent } from '@shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component'; const buttons = [FileDownloadBtnComponent, UserButtonComponent]; @@ -45,7 +45,7 @@ const components = [ TeamMembersComponent, ExpandableFileActionsComponent, ProcessingIndicatorComponent, - DossierStatusComponent, + DossierStateComponent, DossiersListingDossierNameComponent, ...buttons, diff --git a/apps/red-ui/src/app/services/dossiers/active-dossiers.service.ts b/apps/red-ui/src/app/services/dossiers/active-dossiers.service.ts index af72b2eb5..0112b0ac6 100644 --- a/apps/red-ui/src/app/services/dossiers/active-dossiers.service.ts +++ b/apps/red-ui/src/app/services/dossiers/active-dossiers.service.ts @@ -1,7 +1,7 @@ import { Injectable, Injector } from '@angular/core'; import { switchMap, tap } from 'rxjs/operators'; import { timer } from 'rxjs'; -import { CHANGED_CHECK_INTERVAL } from '../../utils/constants'; +import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; import { DossiersService } from './dossiers.service'; export interface IDossiersStats { diff --git a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts index ad2c166b4..bac220e55 100644 --- a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts +++ b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts @@ -3,20 +3,20 @@ import { Dossier, DossierStats, IChangesDetails, IDossier, IDossierChanges, IDos import { combineLatest, EMPTY, forkJoin, Observable, of, Subject, throwError } from 'rxjs'; import { catchError, filter, map, mapTo, pluck, switchMap, tap } from 'rxjs/operators'; import { Injector } from '@angular/core'; -import { DossierStateService } from '../entity-services/dossier-state.service'; +import { DossierStatesService } from '../entity-services/dossier-states.service'; import { DossierStatsService } from './dossier-stats.service'; import { IDossiersStats } from './active-dossiers.service'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists'); +const CONFLICT_MSG = _('add-dossier-dialog.errors.dossier-already-exists'); const GENERIC_MSG = _('add-dossier-dialog.errors.generic'); export abstract class DossiersService extends EntitiesService { readonly dossierFileChanges$ = new Subject(); readonly generalStats$ = this.all$.pipe(switchMap(entities => this.#generalStats$(entities))); protected readonly _dossierStatsService = this._injector.get(DossierStatsService); - protected readonly _dossierStateService = this._injector.get(DossierStateService); + protected readonly _dossierStateService = this._injector.get(DossierStatesService); protected readonly _toaster = this._injector.get(Toaster); protected constructor(protected readonly _injector: Injector, protected readonly _path: string, readonly routerPath: string) { @@ -26,7 +26,7 @@ export abstract class DossiersService extends EntitiesService @Validate() createOrUpdate(@RequiredParam() dossier: IDossierRequest): Observable { const showToast = (error: HttpErrorResponse) => { - this._toaster.error(error.status === HttpStatusCode.Conflict ? DOSSIER_EXISTS_MSG : GENERIC_MSG); + this._toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG); return EMPTY; }; diff --git a/apps/red-ui/src/app/services/entity-services/dossier-state.service.ts b/apps/red-ui/src/app/services/entity-services/dossier-state.service.ts deleted file mode 100644 index cfe8f536a..000000000 --- a/apps/red-ui/src/app/services/entity-services/dossier-state.service.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Injectable, Injector } from '@angular/core'; -import { EntitiesService, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; -import { DossierState, IDossierState } from '@red/domain'; -import { forkJoin, Observable, switchMap } from 'rxjs'; -import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { defaultIfEmpty, map, tap } from 'rxjs/operators'; - -@Injectable({ - providedIn: 'root', -}) -export class DossierStateService extends EntitiesService { - constructor(protected readonly _injector: Injector, private readonly _dossierTemplatesService: DossierTemplatesService) { - super(_injector, DossierState, 'dossier-status'); - } - - @Validate() - updateDossierState(@RequiredParam() body: IDossierState) { - return this._post(body, this._defaultModelPath); - } - - @Validate() - loadAllForTemplate(@RequiredParam() templateId: string) { - return this.loadAll(`${this._defaultModelPath}/dossier-template/${templateId}`); - } - - loadAllForAllTemplates(): Observable { - return this._dossierTemplatesService.all$.pipe( - mapEach(template => template.dossierTemplateId), - mapEach(id => this.loadAllForTemplate(id)), - switchMap(all => forkJoin(all).pipe(defaultIfEmpty([] as DossierState[][]))), - map(value => value.flatMap(item => item)), - tap(value => this.setEntities(value)), - ); - } - - @Validate() - deleteAndReplace(@RequiredParam() dossierStatusId: string, @RequiredParam() replaceDossierStatusId: string) { - const url = `${this._defaultModelPath}/${dossierStatusId}?replaceDossierStatusId=${replaceDossierStatusId}`; - return this.delete({}, url); - } -} diff --git a/apps/red-ui/src/app/services/entity-services/dossier-states-map.service.ts b/apps/red-ui/src/app/services/entity-services/dossier-states-map.service.ts new file mode 100644 index 000000000..2f870d2eb --- /dev/null +++ b/apps/red-ui/src/app/services/entity-services/dossier-states-map.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { DossierState, IDossierState } from '@red/domain'; +import { EntitiesMapService } from '@iqser/common-ui'; +import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; +import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; +import { flatMap } from 'lodash'; + +@Injectable({ providedIn: 'root' }) +export class DossierStatesMapService extends EntitiesMapService { + constructor() { + super(DOSSIER_TEMPLATE_ID); + } + + get stats(): DoughnutChartConfig[] { + const allStates = flatMap(Array.from(this._map.values()).map(obs => obs.value)); + return Array.from( + allStates + .reduce((acc, { color, name, dossierCount }) => { + const key = name + '-' + color; + const item = acc.get(key) ?? Object.assign({}, { value: 0, label: name, color: color }); + return acc.set(key, { ...item, value: item.value + dossierCount }); + }, new Map()) + .values(), + ); + } +} diff --git a/apps/red-ui/src/app/services/entity-services/dossier-states.service.ts b/apps/red-ui/src/app/services/entity-services/dossier-states.service.ts new file mode 100644 index 000000000..7e2659262 --- /dev/null +++ b/apps/red-ui/src/app/services/entity-services/dossier-states.service.ts @@ -0,0 +1,60 @@ +import { Injectable, Injector } from '@angular/core'; +import { EntitiesService, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; +import { DossierState, IDossierState } from '@red/domain'; +import { EMPTY, forkJoin, Observable, switchMap } from 'rxjs'; +import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; +import { catchError, defaultIfEmpty, tap } from 'rxjs/operators'; +import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; +import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +const CONFLICT_MSG = _('dossier-states-listing.error.conflict'); +const GENERIC_MSG = _('dossier-states-listing.error.generic'); + +@Injectable({ + providedIn: 'root', +}) +export class DossierStatesService extends EntitiesService { + constructor( + protected readonly _injector: Injector, + private readonly _toaster: Toaster, + private readonly _dossierTemplatesService: DossierTemplatesService, + private readonly _dossierStatesMapService: DossierStatesMapService, + ) { + super(_injector, DossierState, 'dossier-status'); + } + + @Validate() + createOrUpdate(@RequiredParam() state: IDossierState): Observable { + const showToast = (error: HttpErrorResponse) => { + this._toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG); + return EMPTY; + }; + return this._post(state, this._defaultModelPath).pipe( + catchError(showToast), + switchMap(() => this.loadAllForTemplate(state.dossierTemplateId)), + ); + } + + @Validate() + loadAllForTemplate(@RequiredParam() templateId: string) { + return this.loadAll(`${this._defaultModelPath}/dossier-template/${templateId}`).pipe( + tap(states => this._dossierStatesMapService.set(templateId, states)), + ); + } + + loadAllForAllTemplates(): Observable { + return this._dossierTemplatesService.all$.pipe( + mapEach(template => template.dossierTemplateId), + mapEach(id => this.loadAllForTemplate(id)), + switchMap(all => forkJoin(all).pipe(defaultIfEmpty([] as DossierState[][]))), + ); + } + + deleteState(dossierState: IDossierState, replaceDossierStatusId?: string): Observable { + const queryParams = replaceDossierStatusId ? [{ key: 'replaceDossierStatusId', value: replaceDossierStatusId }] : null; + return super + .delete(dossierState.dossierStatusId, this._defaultModelPath, queryParams) + .pipe(switchMap(() => this.loadAllForTemplate(dossierState.dossierTemplateId))); + } +} diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index f5419c584..19dcbd1fc 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -19,6 +19,10 @@ export class PermissionsService { return dossiersServiceResolver(this._injector); } + canPerformDossierStatesActions(user = this._userService.currentUser): boolean { + return user.isAdmin; + } + isReviewerOrApprover(file: File): boolean { const dossier = this._getDossier(file); return this.isFileAssignee(file) || this.isApprover(dossier); diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index 9637c6408..23b90efed 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -84,6 +84,7 @@ "rank": "" }, "save": "", + "success": "", "title": "" }, "add-edit-dossier-template": { @@ -327,7 +328,7 @@ "title": "" }, "table-col-names": { - "dossier-status": "", + "dossier-state": "", "last-modified": "", "name": "", "owner": "" @@ -460,9 +461,10 @@ "delete": "", "delete-replace": "", "form": { - "status": "", - "status-placeholder": "" + "state": "", + "state-placeholder": "" }, + "success": "", "suggestion": "", "title": "", "warning": "" @@ -769,7 +771,7 @@ }, "table-col-names": { "documents-status": "", - "dossier-status": "", + "dossier-state": "", "name": "Name", "needs-work": "Arbeitsvorrat", "owner": "Besitzer" @@ -1065,9 +1067,9 @@ "label": "Beschreibung", "placeholder": "Beschreibung eingeben" }, - "dossier-status": { + "dossier-state": { "label": "", - "no-status-placeholder": "", + "no-state-placeholder": "", "placeholder": "" }, "due-date": "Termin", @@ -1359,7 +1361,7 @@ "filters": { "assigned-people": "Beauftragt", "documents-status": "", - "dossier-status": "", + "dossier-state": "", "dossier-templates": "Regelsätze", "empty": "Leer", "filter-by": "Filter:", diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 00c703c74..eeee471e4 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -83,8 +83,9 @@ "name-placeholder": "Enter Name", "rank": "Rank" }, - "save": "Save Status", - "title": "{type, select, edit{Edit {name}} create{Create} other{}} Dossier Status" + "save": "Save State", + "success": "Successfully {type, select, edit{updated} create{created} other{}} the dossier state!", + "title": "{type, select, edit{Edit {name}} create{Create} other{}} Dossier State" }, "add-edit-dossier-template": { "error": { @@ -327,7 +328,7 @@ "title": "No archived dossiers match your current filters." }, "table-col-names": { - "dossier-status": "Dossier Status", + "dossier-state": "Dossier State", "last-modified": "Archived Time", "name": "Name", "owner": "Owner" @@ -460,12 +461,13 @@ "delete": "Delete", "delete-replace": "Delete and Replace", "form": { - "status": "Replace Status", - "status-placeholder": "Choose another status" + "state": "Replace State", + "state-placeholder": "Choose another state" }, - "suggestion": "Would you like to replace the states of the Dossiers with another status?", - "title": "Delete Dossier Status", - "warning": "The {name} status is assigned to {count} {count, plural, one{Dossier} other{Dossiers}}." + "success": "Successfully deleted state!", + "suggestion": "Would you like to replace the dossiers' states with another state?", + "title": "Delete Dossier State", + "warning": "The {name} state is assigned to {count} {count, plural, one{dossier} other{dossiers}}." }, "confirm-delete-users": { "cancel": "Keep {usersCount, plural, one{User} other{Users}}", @@ -769,7 +771,7 @@ }, "table-col-names": { "documents-status": "Documents Status", - "dossier-status": "Dossier Status", + "dossier-state": "Dossier State", "name": "Name", "needs-work": "Workload", "owner": "Owner" @@ -881,16 +883,16 @@ "dossier-states": "Dossier States", "dossier-states-listing": { "action": { - "delete": "Delete Status", - "edit": "Edit Status" + "delete": "Delete State", + "edit": "Edit State" }, - "add-new": "New Status", + "add-new": "New State", "chart": { "dossier-states": "{count, plural, one{Dossier State} other{Dossier States}}" }, "error": { - "conflict": "Dossier State with this name already exists!", - "generic": "Failed to add Dossier State" + "conflict": "Dossier state with this name already exists!", + "generic": "Failed to save dossier state!" }, "no-data": { "title": "There are no dossier states." @@ -1065,9 +1067,9 @@ "label": "Description", "placeholder": "Enter Description" }, - "dossier-status": { - "label": "Dossier Status", - "no-status-placeholder": "This dossier template has no states", + "dossier-state": { + "label": "Dossier State", + "no-state-placeholder": "This dossier template has no states", "placeholder": "Undefined" }, "due-date": "Due Date", @@ -1359,7 +1361,7 @@ "filters": { "assigned-people": "Assignee(s)", "documents-status": "Documents Status", - "dossier-status": "Dossier Status", + "dossier-state": "Dossier State", "dossier-templates": "Dossier Templates", "empty": "Empty", "filter-by": "Filter:", diff --git a/libs/red-domain/src/lib/dossier-state/dossier-state.model.ts b/libs/red-domain/src/lib/dossier-state/dossier-state.model.ts index 394193fd0..585935ee6 100644 --- a/libs/red-domain/src/lib/dossier-state/dossier-state.model.ts +++ b/libs/red-domain/src/lib/dossier-state/dossier-state.model.ts @@ -1,23 +1,25 @@ -import { IListable } from '@iqser/common-ui'; +import { Entity } from '@iqser/common-ui'; import { IDossierState } from './dossier-state'; -export class DossierState implements IDossierState, IListable { +export class DossierState extends Entity { readonly description: string; readonly dossierStatusId: string; readonly dossierTemplateId: string; readonly name: string; readonly color: string; readonly rank?: number; - dossierCount?: number; + readonly routerLink = undefined; + readonly dossierCount: number; constructor(dossierState: IDossierState) { + super(dossierState); this.description = dossierState.description; this.dossierStatusId = dossierState.dossierStatusId; this.dossierTemplateId = dossierState.dossierTemplateId; this.name = dossierState.name; this.color = dossierState.color; - this.dossierCount = dossierState.dossierCount; this.rank = dossierState.rank; + this.dossierCount = dossierState.dossierCount || 0; } get id(): string {