Merge branch 'master' into VM/RED-3370

This commit is contained in:
Valentin Mihai 2022-02-16 00:41:07 +02:00
commit 04edeb0da4
61 changed files with 1036 additions and 184 deletions

View File

@ -13,6 +13,7 @@
[routerLink]="breadcrumb.routerLink"
class="breadcrumb"
routerLinkActive="active"
[id]="first ? 'navigateToActiveDossiers' : ''"
>
{{ breadcrumb.name$ | async }}
</a>

View File

@ -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,

View File

@ -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') },
],

View File

@ -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 {}

View File

@ -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>

View File

@ -0,0 +1,5 @@
.iqser-input-group:nth-child(2) {
width: fit-content;
margin-top: 0;
margin-left: 16px;
}

View File

@ -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],
});
}
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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],
});
}
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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)));
}
}

View File

@ -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) {

View File

@ -8,6 +8,7 @@
<input
[placeholder]="'add-dossier-dialog.form.name.placeholder' | translate"
formControlName="dossierName"
id="dossierNameInput"
name="dossierName"
type="text"
/>
@ -16,7 +17,12 @@
<div class="iqser-input-group required w-400">
<mat-form-field floatLabel="always">
<mat-label>{{ 'add-dossier-dialog.form.template' | translate }}</mat-label>
<mat-select (valueChange)="dossierTemplateChanged($event)" formControlName="dossierTemplateId" style="width: 100%">
<mat-select
(valueChange)="dossierTemplateChanged($event)"
formControlName="dossierTemplateId"
id="dossierTemplateSelect"
style="width: 100%"
>
<mat-option
*ngFor="let dossierTemplate of dossierTemplates"
[matTooltip]="dossierTemplate.description ? dossierTemplate.description : dossierTemplate.name"
@ -88,7 +94,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
<button [disabled]="disabled" color="primary" id="createDossierSaveButton" mat-flat-button type="submit">
{{ 'add-dossier-dialog.actions.save' | translate }}
</button>
@ -98,11 +104,12 @@
[label]="'add-dossier-dialog.actions.save-and-add-members' | translate"
[type]="iconButtonTypes.dark"
icon="red:assign"
id="createDossierEditTeamButton"
></iqser-icon-button>
</div>
</form>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>
<ng-template #reportTemplateOptionTemplate let-option="option">

View File

@ -10,7 +10,7 @@
</div>
</div>
<form [formGroup]="form" *ngIf="form">
<form *ngIf="form" [formGroup]="form">
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
{{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }}
@ -19,7 +19,7 @@
</form>
</div>
<div *ngIf="canEdit" class="display-name">
<div *ngIf="canEditDisplayName" class="display-name">
<div translate="edit-dossier-dialog.dictionary.display-name.edit"></div>
<iqser-editable-input
(save)="updateDisplayName($event)"

View File

@ -20,10 +20,10 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
form: FormGroup;
canEdit = false;
canEditDisplayName = false;
dossierDictionary: IDictionary;
readonly circleButtonTypes = CircleButtonTypes;
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
constructor(
@ -58,6 +58,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
async ngOnInit() {
this._loadingService.start();
this.canEdit = this._permissionsService.canEditDossier(this.dossier);
this.canEditDisplayName = this._permissionsService.isOwner(this.dossier);
await this._updateDossierDictionary();
this.form = this._getForm();
this._loadingService.stop();

View File

@ -49,7 +49,14 @@
</div>
<div *ngIf="showActionButtons" class="dialog-actions">
<button (click)="save()" [disabled]="disabled || !valid || !changed" color="primary" mat-flat-button type="button">
<button
(click)="save()"
[disabled]="disabled || !valid || !changed"
color="primary"
mat-flat-button
type="button"
id="editDossierSaveButton"
>
{{ 'edit-dossier-dialog.actions.save' | translate }}
</button>
<iqser-icon-button
@ -65,5 +72,5 @@
</div>
</div>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()" id="editDossierCloseButton"></iqser-circle-button>
</section>

View File

@ -2,7 +2,7 @@
<div class="iqser-input-group w-300 required">
<mat-form-field floatLabel="always">
<mat-label>{{ 'assign-dossier-owner.dialog.single-user' | translate }}</mat-label>
<mat-select formControlName="owner">
<mat-select formControlName="owner" id="editDossierOwnerSelect">
<mat-option *ngFor="let userId of ownersSelectOptions" [value]="userId">
{{ userId | name }}
</mat-option>

View File

@ -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>

View File

@ -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;
}

View File

@ -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;

View File

@ -10,6 +10,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { Dossier, ILegalBasisChangeRequest } from '@red/domain';
import { firstValueFrom } from 'rxjs';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
export interface LegalBasisOption {
label?: string;
@ -37,11 +38,11 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
private readonly _permissionsService: PermissionsService,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
@Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dossier: Dossier; readonly hint: boolean },
@Inject(MAT_DIALOG_DATA)
private readonly _data: { readonly dossier: Dossier; readonly hint: boolean; annotations: AnnotationWrapper[] },
) {
super(_injector, _dialogRef);
this.form = this._getForm();
this.initialFormValue = this.form.getRawValue();
}
get isHintDialog() {
@ -59,6 +60,14 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
}));
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
// Set pre-existing reason if it exists
const existingReason = this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis);
if (!this._data.hint && existingReason) {
this.form.patchValue({ reason: existingReason }, { emitEvent: false });
}
this.initialFormValue = this.form.getRawValue();
}
save() {

View File

@ -19,24 +19,26 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
@Input() @Required() selectedFiles: File[];
@Input() buttonType: CircleButtonType = CircleButtonTypes.dark;
@Input() maxWidth: number;
analysisForced: boolean;
canAssignToSelf: boolean;
canAssign: boolean;
canDelete: boolean;
canReanalyse: boolean;
canDisableAutoAnalysis: boolean;
canEnableAutoAnalysis: boolean;
canOcr: boolean;
canSetToUnderReview: boolean;
canSetToUnderApproval: boolean;
isReadyForApproval: boolean;
canApprove: boolean;
canUndoApproval: boolean;
assignTooltip: string;
buttons: Action[];
private _canMoveToSameState: boolean;
#analysisForced: boolean;
#canAssignToSelf: boolean;
#canAssign: boolean;
#canDelete: boolean;
#canReanalyse: boolean;
#canDisableAutoAnalysis: boolean;
#canEnableAutoAnalysis: boolean;
#canOcr: boolean;
#canSetToUnderReview: boolean;
#canSetToUnderApproval: boolean;
#isReadyForApproval: boolean;
#canApprove: boolean;
#canUndoApproval: boolean;
#canToggleAnalysis: boolean;
#assignTooltip: string;
#toggleAnalysisTooltip: string;
#allFilesAreExcluded: boolean;
#canMoveToSameState: boolean;
constructor(
private readonly _permissionsService: PermissionsService,
@ -52,35 +54,35 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
action: () => this._bulkActionsService.delete(this.selectedFiles),
tooltip: _('dossier-overview.bulk.delete'),
icon: 'iqser:trash',
show: this.canDelete,
show: this.#canDelete,
},
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.assign(this.selectedFiles),
tooltip: this.assignTooltip,
tooltip: this.#assignTooltip,
icon: 'red:assign',
show: this.canAssign,
show: this.#canAssign,
},
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.assignToMe(this.selectedFiles),
tooltip: _('dossier-overview.assign-me'),
icon: 'red:assign-me',
show: this.canAssignToSelf,
show: this.#canAssignToSelf,
},
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles),
tooltip: _('dossier-overview.under-approval'),
icon: 'red:ready-for-approval',
show: this.canSetToUnderApproval,
show: this.#canSetToUnderApproval,
},
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.backToUnderReview(this.selectedFiles),
tooltip: _('dossier-overview.under-review'),
icon: 'red:undo',
show: this.canSetToUnderReview,
show: this.#canSetToUnderReview,
},
{
type: ActionTypes.downloadBtn,
@ -90,45 +92,53 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.approve(this.selectedFiles),
disabled: !this.canApprove,
tooltip: this.canApprove ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'),
disabled: !this.#canApprove,
tooltip: this.#canApprove ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'),
icon: 'red:approved',
show: this.isReadyForApproval,
show: this.#isReadyForApproval,
},
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles),
tooltip: _('dossier-overview.under-approval'),
icon: 'red:undo',
show: this.canUndoApproval,
show: this.#canUndoApproval,
},
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.ocr(this.selectedFiles),
tooltip: _('dossier-overview.ocr-file'),
icon: 'iqser:ocr',
show: this.canOcr,
show: this.#canOcr,
},
{
type: ActionTypes.circleBtn,
action: () => this._bulkActionsService.reanalyse(this.selectedFiles),
tooltip: _('dossier-overview.bulk.reanalyse'),
icon: 'iqser:refresh',
show: this.canReanalyse && (this.analysisForced || this.canEnableAutoAnalysis),
show: this.#canReanalyse && (this.#analysisForced || this.#canEnableAutoAnalysis),
},
{
type: ActionTypes.circleBtn,
action: $event => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
tooltip: _('dossier-overview.disable-auto-analysis'),
icon: 'red:stop',
show: this.canDisableAutoAnalysis,
show: this.#canDisableAutoAnalysis,
},
{
type: ActionTypes.circleBtn,
action: $event => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
tooltip: _('dossier-overview.enable-auto-analysis'),
icon: 'red:play',
show: this.canEnableAutoAnalysis,
show: this.#canEnableAutoAnalysis,
},
{
type: ActionTypes.toggle,
action: () => this._bulkActionsService.toggleAnalysis(this.selectedFiles, !this.#allFilesAreExcluded),
tooltip: this.#toggleAnalysisTooltip,
checked: !this.#allFilesAreExcluded,
show: this.#canToggleAnalysis,
},
].filter(btn => btn.show);
}
@ -138,7 +148,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
}
forceReanalysisAction($event: LongPressEvent) {
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
this.#analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
this._setup();
}
@ -151,37 +161,44 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
true,
);
const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
this._canMoveToSameState = allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval;
this.#allFilesAreExcluded = this.selectedFiles.reduce((acc, file) => acc && file.excluded, true);
this.#canMoveToSameState = allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval;
this.canAssign =
this._canMoveToSameState &&
this.#canAssign =
this.#canMoveToSameState &&
this.selectedFiles.reduce(
(acc, file) => (acc && this._permissionsService.canAssignUser(file)) || this._permissionsService.canUnassignUser(file),
true,
);
this.canAssignToSelf = this._canMoveToSameState && this._permissionsService.canAssignToSelf(this.selectedFiles);
this.#canAssignToSelf = this.#canMoveToSameState && this._permissionsService.canAssignToSelf(this.selectedFiles);
this.canDelete = this._permissionsService.canDeleteFile(this.selectedFiles);
this.#canDelete = this._permissionsService.canDeleteFile(this.selectedFiles);
this.canReanalyse = this._permissionsService.canReanalyseFile(this.selectedFiles);
this.#canReanalyse = this._permissionsService.canReanalyseFile(this.selectedFiles);
this.canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis(this.selectedFiles);
this.#canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis(this.selectedFiles);
this.canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis(this.selectedFiles);
this.#canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis(this.selectedFiles);
this.canOcr = this.selectedFiles.reduce((acc, file) => acc && file.canBeOCRed, true);
this.#canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.selectedFiles);
this.canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles) && !isWorkflow;
this.#canOcr = this.selectedFiles.reduce((acc, file) => acc && file.canBeOCRed, true);
this.canSetToUnderApproval = this._permissionsService.canSetUnderApproval(this.selectedFiles) && !isWorkflow;
this.#canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles) && !isWorkflow;
this.isReadyForApproval = this._permissionsService.isReadyForApproval(this.selectedFiles) && !isWorkflow;
this.#canSetToUnderApproval = this._permissionsService.canSetUnderApproval(this.selectedFiles) && !isWorkflow;
this.canApprove = this._permissionsService.canBeApproved(this.selectedFiles) && !isWorkflow;
this.#isReadyForApproval = this._permissionsService.isReadyForApproval(this.selectedFiles) && !isWorkflow;
this.canUndoApproval = this._permissionsService.canUndoApproval(this.selectedFiles) && !isWorkflow;
this.#canApprove = this._permissionsService.canBeApproved(this.selectedFiles) && !isWorkflow;
this.assignTooltip = allFilesAreUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer');
this.#canUndoApproval = this._permissionsService.canUndoApproval(this.selectedFiles) && !isWorkflow;
this.#assignTooltip = allFilesAreUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer');
this.#toggleAnalysisTooltip = this.#allFilesAreExcluded
? _('file-preview.toggle-analysis.enable')
: _('file-preview.toggle-analysis.disable');
this.buttons = this._buttons;
}

View File

@ -5,7 +5,7 @@
</div>
<div class="header-wrapper mt-8">
<div class="heading-xl flex-1">{{ dossier.dossierName }}</div>
<div class="heading-xl flex-1" id="dossierDetailsDossierName">{{ dossier.dossierName }}</div>
<ng-container
*ngTemplateOutlet="collapsible; context: { action: 'collapse', tooltip: (collapseTooltip | translate) }"
></ng-container>

View File

@ -91,6 +91,18 @@ export class BulkActionsService {
this._loadingService.stop();
}
async toggleAnalysis(files: File[], excluded: boolean) {
this._loadingService.start();
await firstValueFrom(
this._reanalysisService.toggleAnalysis(
files[0].dossierId,
files.map(f => f.id),
excluded,
),
);
this._loadingService.stop();
}
async backToUnderReview(files: File[]): Promise<void> {
this._loadingService.start();
await firstValueFrom(

View File

@ -0,0 +1 @@
<iqser-status-bar *ngIf="stats" [configs]="statusBarConfig"></iqser-status-bar>

View File

@ -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;
}
}

View File

@ -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)"

View File

@ -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;
}

View File

@ -26,7 +26,7 @@
</div>
</div>
<div>
<div class="right-chart">
<redaction-simple-doughnut-chart
*ngIf="documentsChartData$ | async as config"
[config]="config"

View File

@ -29,3 +29,7 @@
}
}
}
.right-chart {
border-left: 1px solid rgba(226, 228, 233, 0.9);
}

View File

@ -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[]) {

View File

@ -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>

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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' },
];
}

View File

@ -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],

View File

@ -92,7 +92,7 @@
<mat-icon svgIcon="red:nav-first"></mat-icon>
</div>
<div class="pages" id="pages">
<div *ngIf="state.fileData$ | async as fileData" class="pages" id="pages">
<redaction-page-indicator
(pageSelected)="pageSelectedByClick($event)"
*ngFor="let pageNumber of displayedPages"
@ -100,6 +100,7 @@
[active]="pageNumber === activeViewerPage"
[number]="pageNumber"
[showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)"
[viewedPages]="fileData.viewedPages"
></redaction-page-indicator>
</div>

View File

@ -1,11 +1,9 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { ConfigService } from '@services/config.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
import { IViewedPage } from '@red/domain';
import { AutoUnsubscribe } from '@iqser/common-ui';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { firstValueFrom } from 'rxjs';
@ -20,6 +18,7 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
@Input() showDottedIcon = false;
@Input() number: number;
@Input() activeSelection = false;
@Input() viewedPages: IViewedPage[] = [];
@Output() readonly pageSelected = new EventEmitter<number>();
@ -28,8 +27,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
constructor(
private readonly _viewedPagesService: ViewedPagesService,
private readonly _filesMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
private readonly _configService: ConfigService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _permissionService: PermissionsService,
@ -39,7 +36,7 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
}
get activePage() {
return this._viewedPages.find(p => p.page === this.number);
return this.viewedPages.find(p => p.page === this.number);
}
get dossierId() {
@ -50,10 +47,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
return this._stateService.fileId;
}
private get _viewedPages(): IViewedPage[] {
return this._stateService.fileData?.viewedPages || [];
}
ngOnChanges() {
this._setReadState();
return this.handlePageRead();
@ -104,7 +97,7 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
if (this.activePage) {
this.activePage.showAsUnseen = false;
} else {
this._viewedPages.push({ page: this.number, fileId: this.fileId });
this.viewedPages.push({ page: this.number, fileId: this.fileId });
}
this._setReadState();
}
@ -113,8 +106,8 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
const removePage$ = this._viewedPagesService.removePage(this.dossierId, this.fileId, this.number);
await firstValueFrom(removePage$);
this._viewedPages.splice(
this._viewedPages.findIndex(p => p.page === this.number),
this.viewedPages.splice(
this.viewedPages.findIndex(p => p.page === this.number),
1,
);
this._setReadState();

View File

@ -77,7 +77,7 @@ export class AnnotationActionsService {
hint: boolean = false,
) {
const { dossierId, fileId } = this._screenStateService;
const data = { dossier: this._dossier, hint };
const data = { dossier: this._dossier, annotations, hint };
this._dialogService.openDialog('forceAnnotation', $event, data, (request: ILegalBasisChangeRequest) => {
annotations.forEach(annotation => {
this._processObsAndEmit(

View File

@ -345,7 +345,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
private async _toggleAnalysis() {
this._loadingService.start();
await firstValueFrom(this._reanalysisService.toggleAnalysis(this.file.dossierId, this.file.fileId, !this.file.excluded));
await firstValueFrom(this._reanalysisService.toggleAnalysis(this.file.dossierId, [this.file.fileId], !this.file.excluded));
this._loadingService.stop();
}

View File

@ -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';
@Input() helpModeKey: 'filter_for_status';
@ -97,7 +97,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 {

View File

@ -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);
}
}

View File

@ -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));
}

View File

@ -23,8 +23,10 @@ export class PermissionsService {
return ((file.isUnderReview || file.isNew) && this.isDossierMember(dossier)) || (file.isUnderApproval && this.isApprover(dossier));
}
canToggleAnalysis(file: File): boolean {
return this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval);
canToggleAnalysis(file: File | File[]): boolean {
const files = file instanceof File ? [file] : file;
const sameState = new Set(files.map(f => f.excluded)).size === 1;
return sameState && files.reduce((acc, _file) => this._canToggleAnalysis(_file) && acc, true);
}
canReanalyseFile(file: File | File[]): boolean {
@ -152,6 +154,10 @@ export class PermissionsService {
return (comment.user === this._userService.currentUser.id || this.isApprover(dossier)) && !file.isApproved;
}
private _canToggleAnalysis(file: File): boolean {
return this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval);
}
// https://jira.iqser.com/browse/RED-2787
private _canDeleteFile(file: File, dossier: Dossier): boolean {
return (

View File

@ -49,13 +49,13 @@ export class ReanalysisService extends GenericService<unknown> {
}
@Validate()
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, excluded?: boolean) {
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() fileIds: string[], excluded?: boolean) {
const queryParams: QueryParam[] = [];
if (excluded) {
queryParams.push({ key: 'excluded', value: excluded });
}
return this._post({}, `toggle-analysis/${dossierId}/${fileId}`, queryParams).pipe(
return this._post(fileIds, `toggle-analysis/${dossierId}/bulk`, queryParams).pipe(
switchMap(() => this._filesService.loadAll(dossierId)),
);
}

View File

@ -605,6 +605,7 @@
"title": "Datei-Attribute anlegen"
},
"dossier": "Dossier",
"dossier-states": "",
"dossier-attribute-types": {
"date": "Datum",
"image": "Bild",

View File

@ -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.",
@ -296,8 +305,8 @@
"suggestion-add": "Suggested redaction",
"suggestion-add-dictionary": "Suggested dictionary add",
"suggestion-change-legal-basis": "Suggested change legal basis",
"suggestion-force-hint": "",
"suggestion-force-redaction": "Suggestion force hint",
"suggestion-force-hint": "Suggestion force hint",
"suggestion-force-redaction": "Suggestion force redaction",
"suggestion-recategorize-image": "Suggested recategorize image",
"suggestion-remove": "Suggested local removal",
"suggestion-remove-dictionary": "Suggested dictionary removal",
@ -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": {

View File

@ -48,6 +48,7 @@ public class PlanSpec {
.userPermissions("atlbamboo", PermissionType.EDIT, PermissionType.VIEW, PermissionType.ADMIN, PermissionType.CLONE, PermissionType.BUILD)
.userPermissions("tbejan", PermissionType.ADMIN, PermissionType.EDIT, PermissionType.VIEW, PermissionType.CLONE, PermissionType.BUILD)
.groupPermissions("devplant", PermissionType.EDIT, PermissionType.VIEW, PermissionType.BUILD)
.groupPermissions("Documentation", PermissionType.VIEW)
.loggedInUserPermissions(PermissionType.VIEW).anonymousUserPermissionView();
return new PlanPermissions(planIdentifier.getProjectKey(), planIdentifier.getPlanKey()).permissions(permission);
}

View File

@ -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';

View 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;
}
}

View File

@ -0,0 +1,8 @@
export interface IDossierState {
description: string;
dossierStatusId: string;
dossierTemplateId: string;
name: string;
color: string;
dossierCount?: number;
}

View File

@ -0,0 +1,2 @@
export * from './dossier-state';
export * from './dossier-state.model';

View File

@ -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;

View File

@ -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>;

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "3.229.0",
"version": "3.236.0",
"private": true,
"license": "MIT",
"scripts": {

Binary file not shown.