Pull request #350: RED-3331
Merge in RED/ui from RED-3331 to master * commit 'b7fce7525d0c1e924f1a92ab867c4f70f1a2ebd8': refactoired statusPlaceholder getter disabled states select if template has no states small improvements improvements in dossier-states-listing back to data fixed rebase stuff and chart pluralization added delete only functionality added separator between doughnut charts fixed doughnut charts fixed chart legend labels added states chart fixed loadstates when loading dossiers bugs load all states when loading dossiers and templates wip edit dossier status finished states screen, update dossier table wip states screen added dossier status model and service
This commit is contained in:
commit
3505dba7f3
@ -21,6 +21,7 @@ import { DossierTemplatesGuard } from '../../guards/dossier-templates.guard';
|
||||
import { DICTIONARY_TYPE, DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import { DossierTemplateExistsGuard } from '../../guards/dossier-template-exists.guard';
|
||||
import { DictionaryExistsGuard } from '../../guards/dictionary-exists.guard';
|
||||
import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
|
||||
@ -114,6 +115,14 @@ const routes: Routes = [
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'dossier-states',
|
||||
component: DossierStatesListingScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'default-colors',
|
||||
component: DefaultColorsScreenComponent,
|
||||
|
||||
@ -62,6 +62,7 @@ export class AdminSideNavComponent implements OnInit {
|
||||
{ screen: 'watermark', label: _('watermark') },
|
||||
{ screen: 'file-attributes', label: _('file-attributes') },
|
||||
{ screen: 'dossier-attributes', label: _('dossier-attributes') },
|
||||
{ screen: 'dossier-states', label: _('dossier-states') },
|
||||
{ screen: 'reports', label: _('reports') },
|
||||
{ screen: 'justifications', label: _('justifications') },
|
||||
],
|
||||
|
||||
@ -46,6 +46,10 @@ import { SmtpFormComponent } from './screens/general-config/smtp-form/smtp-form.
|
||||
import { FileAttributesConfigurationsDialogComponent } from './dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component';
|
||||
import { SharedAdminModule } from './shared/shared-admin.module';
|
||||
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||
import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component';
|
||||
import { AddEditDossierStateDialogComponent } from './dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { ConfirmDeleteDossierStateDialogComponent } from './dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component';
|
||||
|
||||
const dialogs = [
|
||||
AddEditDossierTemplateDialogComponent,
|
||||
@ -95,8 +99,22 @@ const components = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components],
|
||||
declarations: [
|
||||
...components,
|
||||
DossierStatesListingScreenComponent,
|
||||
AddEditDossierStateDialogComponent,
|
||||
ConfirmDeleteDossierStateDialogComponent,
|
||||
],
|
||||
providers: [AdminDialogService, AuditService, DigitalSignatureService, LicenseReportService, RulesService, SmtpConfigService],
|
||||
imports: [CommonModule, SharedModule, AdminRoutingModule, SharedAdminModule, NgxChartsModule, ColorPickerModule, MonacoEditorModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
AdminRoutingModule,
|
||||
SharedAdminModule,
|
||||
NgxChartsModule,
|
||||
ColorPickerModule,
|
||||
MonacoEditorModule,
|
||||
A11yModule,
|
||||
],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
<section class="dialog">
|
||||
<div
|
||||
[translateParams]="{
|
||||
type: data.dossierState ? 'edit' : 'create',
|
||||
name: data.dossierState?.name
|
||||
}"
|
||||
[translate]="'add-edit-dossier-state.title'"
|
||||
class="dialog-header heading-l"
|
||||
></div>
|
||||
|
||||
<form [formGroup]="form">
|
||||
<div class="dialog-content flex">
|
||||
<div class="iqser-input-group required w-300">
|
||||
<label translate="add-edit-dossier-state.form.name"></label>
|
||||
<input
|
||||
[placeholder]="'add-edit-dossier-state.form.name-placeholder' | translate"
|
||||
formControlName="name"
|
||||
name="name"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group required">
|
||||
<label translate="add-edit-dossier-state.form.color"></label>
|
||||
<input
|
||||
[placeholder]="'add-edit-dossier-state.form.color-placeholder' | translate"
|
||||
class="hex-color-input"
|
||||
formControlName="color"
|
||||
name="color"
|
||||
type="text"
|
||||
/>
|
||||
<div
|
||||
(colorPickerChange)="form.get('color').setValue($event)"
|
||||
[colorPicker]="form.get('color').value"
|
||||
[cpOutputFormat]="'hex'"
|
||||
[style.background]="form.get('color').value"
|
||||
class="input-icon"
|
||||
>
|
||||
<mat-icon
|
||||
*ngIf="!form.get('color').value || form.get('color').value?.length === 0"
|
||||
svgIcon="red:color-picker"
|
||||
></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-dossier-state.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
@ -0,0 +1,5 @@
|
||||
.iqser-input-group:nth-child(2) {
|
||||
width: fit-content;
|
||||
margin-top: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
|
||||
import { BaseDialogComponent } from '../../../../../../../../libs/common-ui/src';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { IDossierState } from '@red/domain';
|
||||
|
||||
interface DialogData {
|
||||
readonly dossierState: IDossierState;
|
||||
readonly dossierTemplateId: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-dossier-state-dialog',
|
||||
templateUrl: './add-edit-dossier-state-dialog.component.html',
|
||||
styleUrls: ['./add-edit-dossier-state-dialog.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddEditDossierStateDialogComponent extends BaseDialogComponent {
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierStateDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this.#getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const dossierState: IDossierState = {
|
||||
dossierStatusId: this.data.dossierState?.dossierStatusId,
|
||||
dossierTemplateId: this.data.dossierTemplateId,
|
||||
...this.form.getRawValue(),
|
||||
};
|
||||
this._dialogRef.close(dossierState);
|
||||
}
|
||||
|
||||
#getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
name: [this.data.dossierState?.name, Validators.required],
|
||||
color: [this.data.dossierState?.color, Validators.required],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<section class="dialog">
|
||||
<div class="dialog-header heading-l">
|
||||
{{ 'confirm-delete-dossier-state.title' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<div class="heading">{{ 'confirm-delete-dossier-state.warning' | translate: translateArgs }}</div>
|
||||
|
||||
<ng-container *ngIf="data.dossierCount !== 0">
|
||||
<div class="replacement-suggestion">{{ 'confirm-delete-dossier-state.suggestion' | translate }}</div>
|
||||
|
||||
<form [formGroup]="form">
|
||||
<div class="flex">
|
||||
<div class="iqser-input-group w-300">
|
||||
<label translate="confirm-delete-dossier-state.form.status"></label>
|
||||
<mat-select
|
||||
[placeholder]="'confirm-delete-dossier-state.form.status-placeholder' | translate"
|
||||
formControlName="replaceDossierStatusId"
|
||||
>
|
||||
<mat-option>{{ 'confirm-delete-dossier-state.form.status-placeholder' | translate }}</mat-option>
|
||||
<mat-option *ngFor="let state of data.otherStates" [value]="state.dossierStatusId">
|
||||
{{ state.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="dialogRef.close(afterCloseValue)" color="primary" mat-flat-button>
|
||||
{{ label | translate }}
|
||||
</button>
|
||||
<div (click)="dialogRef.close()" [translate]="'confirm-delete-dossier-state.cancel'" class="all-caps-label cancel"></div>
|
||||
</div>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
</section>
|
||||
@ -0,0 +1,16 @@
|
||||
@use 'variables';
|
||||
|
||||
.replacement-suggestion {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: variables.$grey-1;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
color: variables.$primary;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { IDossierState } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
|
||||
interface DialogData {
|
||||
readonly toBeDeletedState: IDossierState;
|
||||
readonly otherStates: IDossierState[];
|
||||
readonly dossierCount: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-confirm-delete-dossier-state-dialog',
|
||||
templateUrl: './confirm-delete-dossier-state-dialog.component.html',
|
||||
styleUrls: ['./confirm-delete-dossier-state-dialog.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ConfirmDeleteDossierStateDialogComponent {
|
||||
readonly form: FormGroup;
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
readonly dialogRef: MatDialogRef<ConfirmDeleteDossierStateDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
|
||||
) {
|
||||
this.form = this.#getForm();
|
||||
}
|
||||
|
||||
get translateArgs() {
|
||||
return {
|
||||
name: this.data.toBeDeletedState.name,
|
||||
count: this.data.dossierCount,
|
||||
};
|
||||
}
|
||||
|
||||
get replaceDossierStatusId(): string {
|
||||
return this.form.get('replaceDossierStatusId').value;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this.replaceDossierStatusId ? 'confirm-delete-dossier-state.delete-replace' : 'confirm-delete-dossier-state.delete';
|
||||
}
|
||||
|
||||
get afterCloseValue(): string | true {
|
||||
return this.replaceDossierStatusId ?? true;
|
||||
}
|
||||
|
||||
#getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
replaceDossierStatusId: [null],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
<ng-container *ngIf="dossierStateService.all">
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="actions flex-1">
|
||||
<redaction-dossier-template-actions></redaction-dossier-template-actions>
|
||||
|
||||
<iqser-circle-button
|
||||
[routerLink]="['../..']"
|
||||
[tooltip]="'common.close' | translate"
|
||||
icon="iqser:close"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container">
|
||||
<iqser-table
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="80"
|
||||
[noDataText]="'dossier-states-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-states-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
emptyColumnWidth="1fr"
|
||||
noDataIcon="red:attribute"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
<div class="right-container">
|
||||
<redaction-simple-doughnut-chart
|
||||
*ngIf="chartData"
|
||||
[config]="chartData"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'dossier-states-listing.chart.dossier-states' | translate: { count: chartData.length }"
|
||||
[totalType]="'simpleLabel'"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'dossier-states-listing.search' | translate"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="openAddEditStateDialog($event)"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[label]="'dossier-states-listing.add-new' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tableItemTemplate let-entity="entity">
|
||||
<div *ngIf="cast(entity) as state">
|
||||
<div class="cell">
|
||||
<div class="flex-align-items-center">
|
||||
<div [style.background-color]="state.color" class="dossier-state-square"></div>
|
||||
<div class="state-name">{{ state.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell small-label">
|
||||
<span>{{ state.dossierCount }}</span>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditStateDialog($event, state)"
|
||||
[tooltip]="'dossier-states-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="openConfirmDeleteStateDialog($event, state)"
|
||||
[tooltip]="'dossier-states-listing.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
@ -0,0 +1,18 @@
|
||||
@use 'variables';
|
||||
|
||||
.dossier-state-square {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.state-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: variables.$grey-1;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
padding: 50px 26px 0;
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
DefaultListingServices,
|
||||
IconButtonTypes,
|
||||
ListingComponent,
|
||||
LoadingService,
|
||||
TableColumnConfig,
|
||||
Toaster,
|
||||
} from '../../../../../../../../libs/common-ui/src';
|
||||
import { DossierState, IDossierState } from '@red/domain';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossiersService } from '../../../../services/entity-services/dossiers.service';
|
||||
import { DossierStateService } from '../../../../services/entity-services/dossier-state.service';
|
||||
import { firstValueFrom } 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';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-states-listing-screen.component.html',
|
||||
styleUrls: ['./dossier-states-listing-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
...DefaultListingServices,
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => DossierStatesListingScreenComponent) },
|
||||
],
|
||||
})
|
||||
export class DossierStatesListingScreenComponent extends ListingComponent<DossierState> implements OnInit, OnDestroy {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly #dossierTemplateId: string;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly tableHeaderLabel = _('dossier-states-listing.table-header.title');
|
||||
readonly tableColumnConfigs: TableColumnConfig<DossierState>[] = [
|
||||
{ label: _('dossier-states-listing.table-col-names.name'), sortByKey: 'searchKey' },
|
||||
{ label: _('dossier-states-listing.table-col-names.dossiers-count'), sortByKey: 'dossierCount' },
|
||||
];
|
||||
chartData: DoughnutChartConfig[];
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
readonly dossierStateService: DossierStateService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _route: ActivatedRoute,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
) {
|
||||
super(_injector);
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get('dossierTemplateId');
|
||||
}
|
||||
|
||||
ngOnInit(): Promise<void> {
|
||||
return this.#loadData();
|
||||
}
|
||||
|
||||
openAddEditStateDialog($event: MouseEvent, dossierState?: IDossierState) {
|
||||
const data = {
|
||||
dossierState,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
};
|
||||
this._dialogService.openDialog('addEditDossierState', $event, data, async (newValue: IDossierState) => {
|
||||
await this.#createNewDossierStateAndRefreshView(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
openConfirmDeleteStateDialog($event: MouseEvent, dossierState: IDossierState) {
|
||||
const templateId = this.#dossierTemplateId;
|
||||
const data = {
|
||||
toBeDeletedState: dossierState,
|
||||
otherStates: this.entitiesService.all.filter(state => state.dossierStatusId !== dossierState.dossierStatusId),
|
||||
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 this._dossierTemplatesService.refreshDossierTemplate(templateId);
|
||||
await this.#loadData();
|
||||
});
|
||||
}
|
||||
|
||||
async #createNewDossierStateAndRefreshView(newValue: IDossierState): Promise<void> {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this.dossierStateService.setDossierState(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 this._dossierTemplatesService.refreshDossierTemplate(this.#dossierTemplateId);
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
async #loadData(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._dossiersService.loadAll());
|
||||
|
||||
try {
|
||||
const dossierStates = this.dossierStateService.all.filter(d => d.dossierTemplateId === this.#dossierTemplateId);
|
||||
this.#setStatesCount(dossierStates);
|
||||
this.chartData = this.dossierStateService.all.map(state => {
|
||||
return { 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._dossiersService.getCountWithState(state.dossierStatusId)));
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,8 @@ import { AddEditDossierAttributeDialogComponent } from '../dialogs/add-edit-doss
|
||||
import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui';
|
||||
import { UploadDictionaryDialogComponent } from '../dialogs/upload-dictionary-dialog/upload-dictionary-dialog.component';
|
||||
import { FileAttributesConfigurationsDialogComponent } from '../dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component';
|
||||
import { AddEditDossierStateDialogComponent } from '../dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component';
|
||||
import { ConfirmDeleteDossierStateDialogComponent } from '../dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component';
|
||||
|
||||
type DialogType =
|
||||
| 'confirm'
|
||||
@ -27,7 +29,9 @@ type DialogType =
|
||||
| 'smtpAuthConfig'
|
||||
| 'addEditDossierTemplate'
|
||||
| 'addEditDossierAttribute'
|
||||
| 'uploadDictionary';
|
||||
| 'uploadDictionary'
|
||||
| 'addEditDossierState'
|
||||
| 'deleteDossierState';
|
||||
|
||||
@Injectable()
|
||||
export class AdminDialogService extends DialogService<DialogType> {
|
||||
@ -82,6 +86,12 @@ export class AdminDialogService extends DialogService<DialogType> {
|
||||
uploadDictionary: {
|
||||
component: UploadDictionaryDialogComponent,
|
||||
},
|
||||
addEditDossierState: {
|
||||
component: AddEditDossierStateDialogComponent,
|
||||
},
|
||||
deleteDossierState: {
|
||||
component: ConfirmDeleteDossierStateDialogComponent,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(protected readonly _dialog: MatDialog) {
|
||||
|
||||
@ -1,71 +1,89 @@
|
||||
<form [formGroup]="form">
|
||||
<div class="iqser-input-group required w-300">
|
||||
<label translate="edit-dossier-dialog.general-info.form.name.label"></label>
|
||||
<input
|
||||
[placeholder]="'edit-dossier-dialog.general-info.form.name.placeholder' | translate"
|
||||
formControlName="dossierName"
|
||||
name="dossierName"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="flex fields-container">
|
||||
<div class="iqser-input-group required w-300">
|
||||
<label translate="edit-dossier-dialog.general-info.form.name.label"></label>
|
||||
<input
|
||||
[placeholder]="'edit-dossier-dialog.general-info.form.name.placeholder' | translate"
|
||||
formControlName="dossierName"
|
||||
name="dossierName"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group required w-400">
|
||||
<mat-form-field floatLabel="always">
|
||||
<mat-label>{{ 'edit-dossier-dialog.general-info.form.template' | translate }}</mat-label>
|
||||
<mat-select formControlName="dossierTemplateId" style="width: 100%">
|
||||
<mat-option
|
||||
*ngFor="let dossierTemplate of dossierTemplates"
|
||||
[matTooltip]="dossierTemplate.description ? dossierTemplate.description : dossierTemplate.name"
|
||||
[value]="dossierTemplate.dossierTemplateId"
|
||||
matTooltipPosition="after"
|
||||
<div class="iqser-input-group required w-400">
|
||||
<mat-form-field floatLabel="always">
|
||||
<mat-label>{{ 'edit-dossier-dialog.general-info.form.template' | translate }}</mat-label>
|
||||
<mat-select formControlName="dossierTemplateId" style="width: 100%">
|
||||
<mat-option
|
||||
*ngFor="let dossierTemplate of dossierTemplates"
|
||||
[matTooltip]="dossierTemplate.description ? dossierTemplate.description : dossierTemplate.name"
|
||||
[value]="dossierTemplate.dossierTemplateId"
|
||||
matTooltipPosition="after"
|
||||
>
|
||||
{{ dossierTemplate.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group w-400">
|
||||
<label translate="edit-dossier-dialog.general-info.form.description.label"></label>
|
||||
<textarea
|
||||
[placeholder]="'edit-dossier-dialog.general-info.form.description.placeholder' | translate"
|
||||
formControlName="description"
|
||||
iqserHasScrollbar
|
||||
name="description"
|
||||
rows="5"
|
||||
type="text"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-checkbox class="watermark" color="primary" formControlName="watermarkEnabled">
|
||||
{{ 'edit-dossier-dialog.general-info.form.watermark' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-checkbox class="watermark-preview" color="primary" formControlName="watermarkPreviewEnabled">
|
||||
{{ 'edit-dossier-dialog.general-info.form.watermark-preview' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex fields-container">
|
||||
<div class="iqser-input-group w-300">
|
||||
<label translate="edit-dossier-dialog.general-info.form.dossier-status.label"></label>
|
||||
<mat-select [placeholder]="statusPlaceholder" [disabled]="states.length === 0" formControlName="dossierStatusId">
|
||||
<mat-option *ngFor="let state of states" [value]="state.dossierStatusId">
|
||||
<div class="flex-align-items-center">
|
||||
<redaction-small-chip [color]="state.color"></redaction-small-chip>
|
||||
<div>{{ state.name }}</div>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
|
||||
<div class="due-date">
|
||||
<mat-checkbox
|
||||
(change)="hasDueDate = !hasDueDate"
|
||||
[checked]="hasDueDate"
|
||||
[disabled]="!permissionsService.canEditDossier(dossier)"
|
||||
class="filter-menu-checkbox"
|
||||
color="primary"
|
||||
>
|
||||
{{ dossierTemplate.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
{{ 'edit-dossier-dialog.general-info.form.due-date' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<div class="iqser-input-group w-400">
|
||||
<label translate="edit-dossier-dialog.general-info.form.description.label"></label>
|
||||
<textarea
|
||||
[placeholder]="'edit-dossier-dialog.general-info.form.description.placeholder' | translate"
|
||||
formControlName="description"
|
||||
iqserHasScrollbar
|
||||
name="description"
|
||||
rows="5"
|
||||
type="text"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-checkbox class="watermark" color="primary" formControlName="watermarkEnabled">
|
||||
{{ 'edit-dossier-dialog.general-info.form.watermark' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-checkbox class="watermark-preview" color="primary" formControlName="watermarkPreviewEnabled">
|
||||
{{ 'edit-dossier-dialog.general-info.form.watermark-preview' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="due-date">
|
||||
<mat-checkbox
|
||||
(change)="hasDueDate = !hasDueDate"
|
||||
[checked]="hasDueDate"
|
||||
[disabled]="!permissionsService.canEditDossier(dossier)"
|
||||
class="filter-menu-checkbox"
|
||||
color="primary"
|
||||
>
|
||||
{{ 'edit-dossier-dialog.general-info.form.due-date' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<div *ngIf="hasDueDate" class="iqser-input-group datepicker-wrapper">
|
||||
<input [matDatepicker]="picker" formControlName="dueDate" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="picker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
<div *ngIf="hasDueDate" class="iqser-input-group datepicker-wrapper">
|
||||
<input [matDatepicker]="picker" formControlName="dueDate" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="picker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -23,3 +23,15 @@
|
||||
border-top: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fields-container {
|
||||
flex-direction: column;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
redaction-small-chip {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import * as moment from 'moment';
|
||||
import { Dossier, IDossierRequest, IDossierTemplate } from '@red/domain';
|
||||
import { Dossier, DossierState, 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';
|
||||
@ -14,7 +14,9 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierStatsService } from '@services/entity-services/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';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-edit-dossier-general-info',
|
||||
@ -29,9 +31,12 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
form: FormGroup;
|
||||
hasDueDate: boolean;
|
||||
dossierTemplates: IDossierTemplate[];
|
||||
states: DossierState[];
|
||||
currentStatus: DossierState;
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _dossierStateService: DossierStateService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _dossierStatsService: DossierStatsService,
|
||||
@ -40,6 +45,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
private readonly _router: Router,
|
||||
private readonly _editDossierDialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _translateService: TranslateService,
|
||||
) {}
|
||||
|
||||
get changed(): boolean {
|
||||
@ -67,12 +73,26 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
return this.hasDueDate && this.form.get('dueDate').value === null;
|
||||
}
|
||||
|
||||
get statusPlaceholder(): string {
|
||||
if (this.states.length === 0) {
|
||||
return this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-status.no-status-placeholder');
|
||||
}
|
||||
|
||||
return (
|
||||
this.currentStatus?.name ?? this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-status.placeholder')
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._filterInvalidDossierTemplates();
|
||||
this.form = this._getForm();
|
||||
this.#filterInvalidDossierTemplates();
|
||||
this.form = this.#getForm();
|
||||
if (!this.permissionsService.canEditDossier(this.dossier)) {
|
||||
this.form.disable();
|
||||
}
|
||||
this.states = this._dossierStateService.all.filter(s => s.dossierTemplateId === this.dossier.dossierTemplateId);
|
||||
if (this.dossier.dossierStatusId) {
|
||||
this.currentStatus = this._dossierStateService.all.find(s => s.dossierStatusId === this.dossier.dossierStatusId);
|
||||
}
|
||||
this.hasDueDate = !!this.dossier.dueDate;
|
||||
}
|
||||
|
||||
@ -80,6 +100,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
this.form.reset({
|
||||
dossierName: this.dossier.dossierName,
|
||||
dossierTemplateId: this.dossier.dossierTemplateId,
|
||||
dossierStatusId: this.dossier.dossierStatusId,
|
||||
description: this.dossier.description,
|
||||
watermarkEnabled: this.dossier.watermarkEnabled,
|
||||
watermarkPreviewEnabled: this.dossier.watermarkPreviewEnabled,
|
||||
@ -96,6 +117,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
watermarkPreviewEnabled: this.form.get('watermarkPreviewEnabled').value,
|
||||
dueDate: this.hasDueDate ? this.form.get('dueDate').value : undefined,
|
||||
dossierTemplateId: this.form.get(DOSSIER_TEMPLATE_ID).value,
|
||||
dossierStatusId: this.form.get('dossierStatusId').value,
|
||||
} as IDossierRequest;
|
||||
try {
|
||||
await firstValueFrom(this._dossiersService.createOrUpdate(dossier));
|
||||
@ -122,11 +144,11 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
this._dialogService.openDialog('confirm', null, data, async () => {
|
||||
await firstValueFrom(this._dossiersService.delete(this.dossier));
|
||||
this._editDossierDialogRef.close();
|
||||
this._router.navigate(['main', 'dossiers']).then(() => this._notifyDossierDeleted());
|
||||
this._router.navigate(['main', 'dossiers']).then(() => this.#notifyDossierDeleted());
|
||||
});
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
#getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
dossierName: [this.dossier.dossierName, Validators.required],
|
||||
dossierTemplateId: [
|
||||
@ -136,6 +158,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
},
|
||||
Validators.required,
|
||||
],
|
||||
dossierStatusId: [this.dossier.dossierStatusId],
|
||||
description: [this.dossier.description],
|
||||
dueDate: [this.dossier.dueDate],
|
||||
watermarkEnabled: [this.dossier.watermarkEnabled],
|
||||
@ -143,11 +166,11 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
});
|
||||
}
|
||||
|
||||
private _notifyDossierDeleted() {
|
||||
#notifyDossierDeleted() {
|
||||
this._toaster.success(_('edit-dossier-dialog.delete-successful'), { params: { dossierName: this.dossier.dossierName } });
|
||||
}
|
||||
|
||||
private _filterInvalidDossierTemplates() {
|
||||
#filterInvalidDossierTemplates() {
|
||||
this.dossierTemplates = this._dossierTemplatesService.all.filter(r => {
|
||||
if (this.dossier?.dossierTemplateId === r.dossierTemplateId) {
|
||||
return true;
|
||||
|
||||
@ -0,0 +1 @@
|
||||
<iqser-status-bar *ngIf="stats" [configs]="statusBarConfig"></iqser-status-bar>
|
||||
@ -0,0 +1,24 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||
import { DossierStats, StatusSorter } from '../../../../../../../../../../libs/red-domain/src';
|
||||
import { List, StatusBarConfig } from '../../../../../../../../../../libs/common-ui/src';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-documents-status',
|
||||
templateUrl: './dossier-documents-status.component.html',
|
||||
styleUrls: ['./dossier-documents-status.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossierDocumentsStatusComponent implements OnChanges {
|
||||
@Input() stats: DossierStats;
|
||||
statusBarConfig: List<StatusBarConfig<string>>;
|
||||
|
||||
private 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,5 +1,3 @@
|
||||
<iqser-status-bar *ngIf="stats" [configs]="statusBarConfig"></iqser-status-bar>
|
||||
|
||||
<div (longPress)="forceReanalysisAction($event)" class="action-buttons" redactionLongPress>
|
||||
<iqser-circle-button
|
||||
(action)="openEditDossierDialog($event, dossier.dossierId)"
|
||||
|
||||
@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/c
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { CircleButtonTypes, List, StatusBarConfig } from '@iqser/common-ui';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { Dossier, DossierStats, File, StatusSorter } from '@red/domain';
|
||||
import { Dossier, DossierStats, File } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||
import { LongPressEvent } from '@shared/directives/long-press.directive';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
@ -39,14 +39,7 @@ export class DossiersListingActionsComponent implements OnChanges {
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
) {}
|
||||
|
||||
private 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() {
|
||||
this.statusBarConfig = this._statusConfig;
|
||||
this.files = this.filesMapService.get(this.dossier.dossierId);
|
||||
this.displayReanalyseBtn = this.permissionsService.displayReanalyseBtn(this.dossier) && this.analysisForced;
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="right-chart">
|
||||
<redaction-simple-doughnut-chart
|
||||
*ngIf="documentsChartData$ | async as config"
|
||||
[config]="config"
|
||||
|
||||
@ -29,3 +29,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-chart {
|
||||
border-left: 1px solid rgba(226, 228, 233, 0.9);
|
||||
}
|
||||
|
||||
@ -7,8 +7,9 @@ import { Dossier, DossierStats, FileCountPerWorkflowStatus, StatusSorter } from
|
||||
import { workflowFileStatusTranslations } from '../../../../translations/file-status-translations';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { filter, map, switchMap } from 'rxjs/operators';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
|
||||
import { DossierStateService } from '../../../../../../services/entity-services/dossier-state.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossiers-listing-details',
|
||||
@ -25,6 +26,8 @@ export class DossiersListingDetailsComponent {
|
||||
readonly dossiersService: DossiersService,
|
||||
private readonly _dossierStatsMap: DossierStatsService,
|
||||
private readonly _translateChartService: TranslateChartService,
|
||||
private readonly _dossierStateService: DossierStateService,
|
||||
private readonly _translateService: TranslateService,
|
||||
) {
|
||||
this.documentsChartData$ = this.dossiersService.all$.pipe(
|
||||
mapEach(dossier => _dossierStatsMap.watch$(dossier.dossierId)),
|
||||
@ -37,12 +40,19 @@ export class DossiersListingDetailsComponent {
|
||||
}
|
||||
|
||||
private async _toDossierChartData(dossiers: Dossier[]): Promise<DoughnutChartConfig[]> {
|
||||
const config: DoughnutChartConfig[] = [];
|
||||
this._dossierStateService.all.forEach(state => {
|
||||
state.dossierCount = this.dossiersService.getCountWithState(state.dossierStatusId);
|
||||
config.push({ value: state.dossierCount, label: state.name, color: state.color });
|
||||
});
|
||||
const notAssignedLength = this.dossiersService.all.length - config.map(v => v.value).reduce((acc, val) => acc + val, 0);
|
||||
config.push({
|
||||
value: notAssignedLength,
|
||||
label: this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-status.placeholder'),
|
||||
color: '#E2E4E9',
|
||||
});
|
||||
// TODO: deleted dossiers count should come with stats
|
||||
// const deletedDossiers = await this.dossiersService.getDeleted();
|
||||
return [
|
||||
{ value: dossiers.length, color: 'ACTIVE', label: _('active') },
|
||||
// { value: deletedDossiers.length, color: 'DELETED', label: _('archived') },
|
||||
];
|
||||
return config;
|
||||
}
|
||||
|
||||
private _toChartData(stats: DossierStats[]) {
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
<ng-container *ngIf="dossier.dossierStatusId">
|
||||
<div class="flex-align-items-center dossier-status-container">
|
||||
<div class="dossier-status-text">{{ currentState.name }}</div>
|
||||
<redaction-small-chip [color]="currentState.color"></redaction-small-chip>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!dossier.dossierStatusId">
|
||||
<div class="flex-align-items-center dossier-status-container">
|
||||
<div class="dossier-status-text">{{ 'edit-dossier-dialog.general-info.form.dossier-status.placeholder' | translate }}</div>
|
||||
<redaction-small-chip [color]="'#E2E4E9'"></redaction-small-chip>
|
||||
</div>
|
||||
</ng-container>
|
||||
@ -0,0 +1,16 @@
|
||||
@use 'variables';
|
||||
|
||||
.dossier-status-container {
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
redaction-small-chip {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.dossier-status-text {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
color: variables.$grey-1;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { Dossier } from '../../../../../../../../../../libs/red-domain/src';
|
||||
import { DossierStateService } from '../../../../../../services/entity-services/dossier-state.service';
|
||||
import { DossierState } from '@red/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossiers-listing-status',
|
||||
templateUrl: './dossiers-listing-status.component.html',
|
||||
styleUrls: ['./dossiers-listing-status.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossiersListingStatusComponent implements OnInit, OnChanges {
|
||||
@Input() dossier: Dossier;
|
||||
currentState: DossierState;
|
||||
|
||||
constructor(private readonly _dossierStateService: DossierStateService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.#setState();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.#setState();
|
||||
}
|
||||
|
||||
#setState(): void {
|
||||
if (this.dossier.dossierStatusId) {
|
||||
this.currentState = this._dossierStateService.all.find(s => s.dossierStatusId === this.dossier.dossierStatusId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,12 @@
|
||||
</div>
|
||||
|
||||
<div class="cell status-container">
|
||||
<redaction-dossier-documents-status [stats]="stats"></redaction-dossier-documents-status>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<redaction-dossiers-listing-status [dossier]="dossier"></redaction-dossiers-listing-status>
|
||||
|
||||
<redaction-dossiers-listing-actions [dossier]="dossier" [stats]="stats"></redaction-dossiers-listing-actions>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@ -14,15 +14,15 @@ export class TableItemComponent implements OnChanges {
|
||||
@Input() dossier!: Dossier;
|
||||
|
||||
readonly stats$: Observable<DossierStats>;
|
||||
private readonly _ngOnChanges$ = new BehaviorSubject<string>(undefined);
|
||||
readonly #ngOnChanges$ = new BehaviorSubject<string>(undefined);
|
||||
|
||||
constructor(readonly dossierStatsService: DossierStatsService) {
|
||||
this.stats$ = this._ngOnChanges$.pipe(switchMap(dossierId => this.dossierStatsService.watch$(dossierId)));
|
||||
this.stats$ = this.#ngOnChanges$.pipe(switchMap(dossierId => this.dossierStatsService.watch$(dossierId)));
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.dossier) {
|
||||
this._ngOnChanges$.next(this.dossier.dossierId);
|
||||
this.#ngOnChanges$.next(this.dossier.dossierId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,8 @@ export class ConfigService {
|
||||
{ label: _('dossier-listing.table-col-names.name'), sortByKey: 'searchKey', width: '2fr' },
|
||||
{ label: _('dossier-listing.table-col-names.needs-work') },
|
||||
{ label: _('dossier-listing.table-col-names.owner'), class: 'user-column' },
|
||||
{ label: _('dossier-listing.table-col-names.status'), class: 'flex-end', width: 'auto' },
|
||||
{ label: _('dossier-listing.table-col-names.documents-status'), class: 'flex-end', width: 'auto' },
|
||||
{ label: _('dossier-listing.table-col-names.dossier-status'), class: 'flex-end' },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@ import { ConfigService } from './config.service';
|
||||
import { TableItemComponent } from './components/table-item/table-item.component';
|
||||
import { SharedDossiersModule } from '../../shared/shared-dossiers.module';
|
||||
import { DossierWorkloadColumnComponent } from './components/dossier-workload-column/dossier-workload-column.component';
|
||||
import { DossiersListingStatusComponent } from './components/dossiers-listing-status/dossiers-listing-status.component';
|
||||
import { DossierDocumentsStatusComponent } from './components/dossier-documents-status/dossier-documents-status.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -30,6 +32,8 @@ const routes: Routes = [
|
||||
DossiersListingDossierNameComponent,
|
||||
DossierWorkloadColumnComponent,
|
||||
TableItemComponent,
|
||||
DossiersListingStatusComponent,
|
||||
DossierDocumentsStatusComponent,
|
||||
],
|
||||
providers: [ConfigService],
|
||||
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedDossiersModule, IqserIconsModule, TranslateModule],
|
||||
|
||||
@ -23,7 +23,7 @@ export class SimpleDoughnutChartComponent implements OnChanges, OnInit {
|
||||
@Input() radius = 85;
|
||||
@Input() strokeWidth = 20;
|
||||
@Input() direction: 'row' | 'column' = 'column';
|
||||
@Input() totalType: 'sum' | 'count' = 'sum';
|
||||
@Input() totalType: 'sum' | 'count' | 'simpleLabel' = 'sum';
|
||||
@Input() counterText: string;
|
||||
@Input() filterKey = 'statusFilters';
|
||||
filtersEnabled: boolean;
|
||||
@ -96,7 +96,11 @@ export class SimpleDoughnutChartComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
getLabel({ label, value }: DoughnutChartConfig): string {
|
||||
return this.totalType === 'sum' ? `${value} ${label}` : `${label} (${value} ${this.counterText})`;
|
||||
return this.totalType === 'simpleLabel'
|
||||
? `${label}`
|
||||
: this.totalType === 'sum'
|
||||
? `${value} ${label}`
|
||||
: `${label} (${value} ${this.counterText})`;
|
||||
}
|
||||
|
||||
selectValue(key: string): void {
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
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 { map, tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DossierStateService extends EntitiesService<DossierState, IDossierState> {
|
||||
constructor(protected readonly _injector: Injector, private readonly _dossierTemplatesService: DossierTemplatesService) {
|
||||
super(_injector, DossierState, 'dossier-status');
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setDossierState(@RequiredParam() body: IDossierState) {
|
||||
return this._post<unknown>(body, this._defaultModelPath);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
loadAllForTemplate(@RequiredParam() templateId: string) {
|
||||
return this.loadAll(`${this._defaultModelPath}/dossier-template/${templateId}`);
|
||||
}
|
||||
|
||||
loadAllForAllTemplates(): Observable<DossierState[]> {
|
||||
return this._dossierTemplatesService.all$.pipe(
|
||||
mapEach(template => template.dossierTemplateId),
|
||||
mapEach(id => this.loadAllForTemplate(id)),
|
||||
switchMap(all => forkJoin(all)),
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
import { DossierStateService } from '@services/entity-services/dossier-state.service';
|
||||
|
||||
export interface IDossiersStats {
|
||||
totalPeople: number;
|
||||
@ -37,6 +38,7 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
private readonly _toaster: Toaster,
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _dossierStatsService: DossierStatsService,
|
||||
private readonly _dossierStateService: DossierStateService,
|
||||
) {
|
||||
super(_injector, Dossier, 'dossier');
|
||||
|
||||
@ -54,6 +56,7 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
mapEach(entity => new Dossier(entity)),
|
||||
/* Load stats before updating entities */
|
||||
switchMap(dossiers => this._dossierStatsService.getFor(dossierIds(dossiers)).pipe(mapTo(dossiers))),
|
||||
switchMap(dossiers => this._dossierStateService.loadAllForAllTemplates().pipe(mapTo(dossiers))),
|
||||
tap(dossiers => this.setEntities(dossiers)),
|
||||
);
|
||||
}
|
||||
@ -108,6 +111,10 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
return firstValueFrom(super.delete(body, 'deleted-dossiers/hard-delete', body));
|
||||
}
|
||||
|
||||
getCountWithState(dossierStatusId: string): number {
|
||||
return this.all.filter(dossier => dossier.dossierStatusId === dossierStatusId).length;
|
||||
}
|
||||
|
||||
private _emitFileChanges(changes: ChangesDetails): void {
|
||||
changes.dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId));
|
||||
}
|
||||
|
||||
@ -605,6 +605,7 @@
|
||||
"title": "Datei-Attribute anlegen"
|
||||
},
|
||||
"dossier": "Dossier",
|
||||
"dossier-states": "",
|
||||
"dossier-attribute-types": {
|
||||
"date": "Datum",
|
||||
"image": "Bild",
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
"all": "All",
|
||||
"none": "None"
|
||||
},
|
||||
"active": "Active",
|
||||
"add-dossier-dialog": {
|
||||
"actions": {
|
||||
"save": "Save",
|
||||
@ -72,6 +71,16 @@
|
||||
"save": "Save Attribute",
|
||||
"title": "{type, select, edit{Edit {name}} create{Add New} other{}} Dossier Attribute"
|
||||
},
|
||||
"add-edit-dossier-state": {
|
||||
"form": {
|
||||
"color": "Hex Color",
|
||||
"color-placeholder": "#",
|
||||
"name": "Status Name",
|
||||
"name-placeholder": "Enter Name"
|
||||
},
|
||||
"save": "Save Status",
|
||||
"title": "{type, select, edit{Edit {name}} create{Create} other{}} Dossier Status"
|
||||
},
|
||||
"add-edit-dossier-template": {
|
||||
"error": {
|
||||
"conflict": "Failed to create dossier template: a dossier template with the same name already exists.",
|
||||
@ -399,6 +408,18 @@
|
||||
}
|
||||
},
|
||||
"configurations": "Configurations",
|
||||
"confirm-delete-dossier-state": {
|
||||
"cancel": "Cancel",
|
||||
"delete-replace": "Delete and Replace",
|
||||
"delete": "Delete only",
|
||||
"form": {
|
||||
"status": "Replace Status",
|
||||
"status-placeholder": "Choose another status"
|
||||
},
|
||||
"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}}."
|
||||
},
|
||||
"confirm-delete-file-attribute": {
|
||||
"cancel": "Keep {type, select, single{Attribute} bulk{Attributes} other{}}",
|
||||
"delete": "Delete {type, select, single{Attribute} bulk{Attributes} other{}}",
|
||||
@ -696,10 +717,11 @@
|
||||
"total-people": "Total users"
|
||||
},
|
||||
"table-col-names": {
|
||||
"documents-status": "Documents Status",
|
||||
"dossier-status": "Dossier Status",
|
||||
"name": "Name",
|
||||
"needs-work": "Workload",
|
||||
"owner": "Owner",
|
||||
"status": "Status"
|
||||
"owner": "Owner"
|
||||
},
|
||||
"table-header": {
|
||||
"title": "{length} active {length, plural, one{Dossier} other{Dossiers}}"
|
||||
@ -803,6 +825,35 @@
|
||||
"under-review": "Under Review",
|
||||
"upload-files": "Drag & drop files anywhere..."
|
||||
},
|
||||
"dossier-states": "Dossier States",
|
||||
"dossier-states-listing": {
|
||||
"action": {
|
||||
"delete": "Delete Status",
|
||||
"edit": "Edit Status"
|
||||
},
|
||||
"add-new": "New Status",
|
||||
"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"
|
||||
},
|
||||
"no-data": {
|
||||
"title": "There are no dossier states."
|
||||
},
|
||||
"no-match": {
|
||||
"title": "No dossier states match your current filters."
|
||||
},
|
||||
"search": "Search...",
|
||||
"table-col-names": {
|
||||
"dossiers-count": "Dossiers Count",
|
||||
"name": "Name"
|
||||
},
|
||||
"table-header": {
|
||||
"title": "{length} dossier {length, plural, one{state} other{states}}"
|
||||
}
|
||||
},
|
||||
"dossier-template-info": "Info",
|
||||
"dossier-template-info-screen": {
|
||||
"created-by": "Created by",
|
||||
@ -957,6 +1008,11 @@
|
||||
"label": "Description",
|
||||
"placeholder": "Enter Description"
|
||||
},
|
||||
"dossier-status": {
|
||||
"label": "Dossier Status",
|
||||
"no-status-placeholder": "This dossier template has no states",
|
||||
"placeholder": "Undefined"
|
||||
},
|
||||
"due-date": "Due Date",
|
||||
"name": {
|
||||
"label": "Dossier Name",
|
||||
@ -1633,8 +1689,8 @@
|
||||
"no-time-left": "Time to restore already passed"
|
||||
},
|
||||
"toggle-auto-analysis-message": {
|
||||
"success": "{toggleOperation} automatic processing.",
|
||||
"error": "Something went wrong."
|
||||
"error": "Something went wrong.",
|
||||
"success": "{toggleOperation} automatic processing."
|
||||
},
|
||||
"top-bar": {
|
||||
"navigation-items": {
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 77e22758239cf564965e82579e662416a6349010
|
||||
Subproject commit f54374859ec75ea73921b9e6a934bd3747f9721d
|
||||
@ -19,3 +19,4 @@ export * from './lib/configuration';
|
||||
export * from './lib/signature';
|
||||
export * from './lib/legal-basis';
|
||||
export * from './lib/dossier-stats';
|
||||
export * from './lib/dossier-state';
|
||||
|
||||
28
libs/red-domain/src/lib/dossier-state/dossier-state.model.ts
Normal file
28
libs/red-domain/src/lib/dossier-state/dossier-state.model.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { IListable } from '@iqser/common-ui';
|
||||
import { IDossierState } from './dossier-state';
|
||||
|
||||
export class DossierState implements IDossierState, IListable {
|
||||
readonly description: string;
|
||||
readonly dossierStatusId: string;
|
||||
readonly dossierTemplateId: string;
|
||||
readonly name: string;
|
||||
readonly color: string;
|
||||
dossierCount?: number;
|
||||
|
||||
constructor(dossierState: IDossierState) {
|
||||
this.description = dossierState.description;
|
||||
this.dossierStatusId = dossierState.dossierStatusId;
|
||||
this.dossierTemplateId = dossierState.dossierTemplateId;
|
||||
this.name = dossierState.name;
|
||||
this.color = dossierState.color;
|
||||
this.dossierCount = dossierState.dossierCount;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this.dossierStatusId;
|
||||
}
|
||||
|
||||
get searchKey(): string {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
8
libs/red-domain/src/lib/dossier-state/dossier-state.ts
Normal file
8
libs/red-domain/src/lib/dossier-state/dossier-state.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface IDossierState {
|
||||
description: string;
|
||||
dossierStatusId: string;
|
||||
dossierTemplateId: string;
|
||||
name: string;
|
||||
color: string;
|
||||
dossierCount?: number;
|
||||
}
|
||||
2
libs/red-domain/src/lib/dossier-state/index.ts
Normal file
2
libs/red-domain/src/lib/dossier-state/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dossier-state';
|
||||
export * from './dossier-state.model';
|
||||
@ -11,6 +11,7 @@ export class Dossier implements IDossier, IListable {
|
||||
readonly approverIds: List;
|
||||
readonly reportTemplateIds: List;
|
||||
readonly dossierName: string;
|
||||
readonly dossierStatusId: string;
|
||||
readonly date: string;
|
||||
readonly dueDate?: string;
|
||||
readonly description?: string;
|
||||
@ -29,6 +30,7 @@ export class Dossier implements IDossier, IListable {
|
||||
this.date = dossier.date;
|
||||
this.description = dossier.description;
|
||||
this.dossierName = dossier.dossierName;
|
||||
this.dossierStatusId = dossier.dossierStatusId;
|
||||
this.dossierTemplateId = dossier.dossierTemplateId;
|
||||
this.downloadFileTypes = dossier.downloadFileTypes;
|
||||
this.dueDate = dossier.dueDate;
|
||||
|
||||
@ -7,6 +7,7 @@ export interface IDossier {
|
||||
readonly date: string;
|
||||
readonly description?: string;
|
||||
readonly dossierId: string;
|
||||
readonly dossierStatusId: string;
|
||||
readonly dossierName: string;
|
||||
readonly dossierTemplateId: string;
|
||||
readonly downloadFileTypes?: List<DownloadFileType>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user