RED-3800: update angular to v16

This commit is contained in:
Dan Percic 2023-05-17 11:10:09 +03:00
parent bfedd0e607
commit ec20dc714c
51 changed files with 2384 additions and 2764 deletions

View File

@ -79,7 +79,7 @@
"accessibility": "no-public" "accessibility": "no-public"
} }
], ],
"@typescript-eslint/member-ordering": "error", "@typescript-eslint/member-ordering": "warn",
"@typescript-eslint/naming-convention": [ "@typescript-eslint/naming-convention": [
"error", "error",
{ {

View File

@ -1,4 +1,4 @@
import { Component, Inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core'; import { Component, inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
import { RouterHistoryService } from '@services/router-history.service'; import { RouterHistoryService } from '@services/router-history.service';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
@ -35,12 +35,11 @@ export class AppComponent implements OnDestroy {
/** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */ /** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */
private readonly _routerHistoryService: RouterHistoryService, private readonly _routerHistoryService: RouterHistoryService,
userPreferenceService: UserPreferenceService, userPreferenceService: UserPreferenceService,
@Inject(DOCUMENT) document: Document,
renderer: Renderer2, renderer: Renderer2,
private readonly _router: Router, private readonly _router: Router,
route: ActivatedRoute, route: ActivatedRoute,
) { ) {
renderer.addClass(document.body, userPreferenceService.getTheme()); renderer.addClass(inject(DOCUMENT).body, userPreferenceService.getTheme());
loadCustomTheme(); loadCustomTheme();
const sub = route.queryParamMap.subscribe(queryParams => this.#navigate(queryParams)); const sub = route.queryParamMap.subscribe(queryParams => this.#navigate(queryParams));
this.#subscription.add(sub); this.#subscription.add(sub);

View File

@ -2,17 +2,17 @@
<p *ngIf="!adminName && !adminUrl" class="heading-xl" translate="auth-error.heading"></p> <p *ngIf="!adminName && !adminUrl" class="heading-xl" translate="auth-error.heading"></p>
<p <p
*ngIf="adminName && adminUrl" *ngIf="adminName && adminUrl"
[innerHTML]="'auth-error.heading-with-name-and-link' | translate: { adminName: adminName, adminUrl: adminUrl }" [innerHTML]="'auth-error.heading-with-name-and-link' | translate : { adminName: adminName, adminUrl: adminUrl }"
class="heading-xl" class="heading-xl"
></p> ></p>
<p <p
*ngIf="adminName && !adminUrl" *ngIf="adminName && !adminUrl"
[innerHTML]="'auth-error.heading-with-name' | translate: { adminName: adminName }" [innerHTML]="'auth-error.heading-with-name' | translate : { adminName: adminName }"
class="heading-xl" class="heading-xl"
></p> ></p>
<p <p
*ngIf="!adminName && adminUrl" *ngIf="!adminName && adminUrl"
[innerHTML]="'auth-error.heading-with-link' | translate: { adminName: adminName }" [innerHTML]="'auth-error.heading-with-link' | translate : { adminName: adminName }"
class="heading-xl" class="heading-xl"
></p> ></p>
<a (click)="userService.logout()" translate="auth-error.logout"></a> <a (click)="userService.logout()" translate="auth-error.logout"></a>

View File

@ -6,7 +6,7 @@
<div class="dialog-content"> <div class="dialog-content">
<redaction-add-edit-entity <redaction-add-edit-entity
#redactionAddEditEntity #redactionAddEditEntity
[dossierTemplateId]="dossierTemplateId" [dossierTemplateId]="data.dossierTemplateId"
[entity]="null" [entity]="null"
[readOnly]="false" [readOnly]="false"
></redaction-add-edit-entity> ></redaction-add-edit-entity>

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Inject, ViewChild } from '@angular/core'; import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AddEditEntityComponent } from '@shared/components/add-edit-entity/add-edit-entity.component'; import { AddEditEntityComponent } from '@shared/components/add-edit-entity/add-edit-entity.component';
import { BaseDialogComponent, IconButtonTypes } from '@iqser/common-ui'; import { BaseDialogComponent, IconButtonTypes } from '@iqser/common-ui';
@ -16,14 +16,10 @@ interface DialogData {
export class AddEntityDialogComponent extends BaseDialogComponent { export class AddEntityDialogComponent extends BaseDialogComponent {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly roles = ROLES; readonly roles = ROLES;
readonly dossierTemplateId = this._data.dossierTemplateId; readonly data = inject<DialogData>(MAT_DIALOG_DATA);
@ViewChild(AddEditEntityComponent, { static: true }) private readonly _addEditEntityComponent: AddEditEntityComponent; @ViewChild(AddEditEntityComponent, { static: true }) private readonly _addEditEntityComponent: AddEditEntityComponent;
constructor( constructor(protected readonly _dialogRef: MatDialogRef<AddEntityDialogComponent>) {
protected readonly _dialogRef: MatDialogRef<AddEntityDialogComponent>,
@Inject(MAT_DIALOG_DATA) private readonly _data: DialogData,
) {
super(_dialogRef, false); super(_dialogRef, false);
} }

View File

@ -6,7 +6,7 @@
<div class="table-header">Key</div> <div class="table-header">Key</div>
<div class="table-header">Value</div> <div class="table-header">Value</div>
<ng-container *ngFor="let entry of data.auditEntry.details | keyvalue: originalOrder"> <ng-container *ngFor="let entry of data.auditEntry.details | keyvalue : originalOrder">
<div class="bold">{{ entry.key | humanize }}</div> <div class="bold">{{ entry.key | humanize }}</div>
<div>{{ entry.value }}</div> <div>{{ entry.value }}</div>
</ng-container> </ng-container>

View File

@ -6,6 +6,11 @@ import { JustificationsService } from '@services/entity-services/justifications.
import { BaseDialogComponent } from '@iqser/common-ui'; import { BaseDialogComponent } from '@iqser/common-ui';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
interface DialogData {
justification?: Justification;
dossierTemplateId: string;
}
@Component({ @Component({
templateUrl: './add-edit-justification-dialog.component.html', templateUrl: './add-edit-justification-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -14,7 +19,7 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
constructor( constructor(
private readonly _justificationService: JustificationsService, private readonly _justificationService: JustificationsService,
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>, protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly data: { justification?: Justification; dossierTemplateId: string }, @Inject(MAT_DIALOG_DATA) readonly data: DialogData,
) { ) {
super(_dialogRef, !!data.justification); super(_dialogRef, !!data.justification);

View File

@ -30,12 +30,12 @@ const placeholderTypes: PlaceholderType[] = ['generalPlaceholders', 'fileAttribu
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ReportsScreenComponent implements OnInit { export class ReportsScreenComponent implements OnInit {
placeholders$ = new BehaviorSubject<Placeholder[]>([]); readonly placeholders$ = new BehaviorSubject<Placeholder[]>([]);
availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]); readonly availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
readonly currentUser = getCurrentUser<User>(); readonly currentUser = getCurrentUser<User>();
readonly roles = ROLES; readonly roles = ROLES;
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
@ViewChild('fileInput') private _fileInput: ElementRef;
constructor( constructor(
private readonly _reportTemplateService: ReportTemplateService, private readonly _reportTemplateService: ReportTemplateService,
@ -91,7 +91,7 @@ export class ReportsScreenComponent implements OnInit {
}, },
}; };
this._dialogService.openDialog('confirm', data, null, async result => { this._dialogService.openDialog('confirm', data, null, async (result: number) => {
if (result) { if (result) {
const multiFileReport = result > 1; const multiFileReport = result > 1;
if ( if (

View File

@ -1,11 +1,10 @@
import { ChangeDetectorRef, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer'; import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { import {
AsControl, AsControl,
BASE_HREF_FN, BASE_HREF_FN,
BaseHrefFn,
Debounce, Debounce,
getCurrentUser, getCurrentUser,
getParam, getParam,
@ -64,9 +63,10 @@ export class WatermarkScreenComponent implements OnInit {
]; ];
readonly orientationOptions = ['DIAGONAL', 'HORIZONTAL', 'VERTICAL']; readonly orientationOptions = ['DIAGONAL', 'HORIZONTAL', 'VERTICAL'];
instance: WebViewerInstance; instance: WebViewerInstance;
readonly #loaded$ = new BehaviorSubject(false); readonly loaded$: Observable<boolean>;
readonly loaded$ = this.#loaded$.asObservable().pipe(tap(() => setTimeout(() => this._changeDetectorRef.detectChanges())));
@ViewChild('viewer', { static: true }) private readonly _viewer: ElementRef<HTMLDivElement>; @ViewChild('viewer', { static: true }) private readonly _viewer: ElementRef<HTMLDivElement>;
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #loaded$ = new BehaviorSubject(false);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #watermarkId = Number(getParam(WATERMARK_ID)); readonly #watermarkId = Number(getParam(WATERMARK_ID));
#watermark: Partial<IWatermark> = {}; #watermark: Partial<IWatermark> = {};
@ -79,13 +79,13 @@ export class WatermarkScreenComponent implements OnInit {
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _tenantContextHolder: TenantContextHolder, private readonly _tenantContextHolder: TenantContextHolder,
private readonly _licenseService: LicenseService, private readonly _licenseService: LicenseService,
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _watermarkService: WatermarkService, private readonly _watermarkService: WatermarkService,
private readonly _userPreferenceService: UserPreferenceService, private readonly _userPreferenceService: UserPreferenceService,
private readonly _router: Router, private readonly _router: Router,
private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _changeDetectorRef: ChangeDetectorRef,
watermarksMapService: WatermarksMapService, watermarksMapService: WatermarksMapService,
) { ) {
this.loaded$ = this.#loaded$.asObservable().pipe(tap(() => setTimeout(() => this._changeDetectorRef.detectChanges())));
const watermark$ = watermarksMapService.watch$(this.#dossierTemplateId, this.#watermarkId); const watermark$ = watermarksMapService.watch$(this.#dossierTemplateId, this.#watermarkId);
const obs$: Observable<Partial<IWatermark>> = this.#watermarkId ? watermark$ : of(DEFAULT_WATERMARK); const obs$: Observable<Partial<IWatermark>> = this.#watermarkId ? watermark$ : of(DEFAULT_WATERMARK);
this.watermark$ = obs$.pipe(tap(watermark => this.#initForm(watermark))); this.watermark$ = obs$.pipe(tap(watermark => this.#initForm(watermark)));
@ -125,15 +125,15 @@ export class WatermarkScreenComponent implements OnInit {
return form; return form;
} }
async ngOnInit() {
await this.#loadViewer();
}
@Debounce() @Debounce()
async configChanged() { async configChanged() {
await this.#drawWatermark(); await this.#drawWatermark();
} }
async ngOnInit() {
await this.#loadViewer();
}
async save(): Promise<void> { async save(): Promise<void> {
const watermark: IWatermark = { const watermark: IWatermark = {
id: this.#watermark.id, id: this.#watermark.id,

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService } from '@iqser/common-ui';
import { IAudit, IAuditResponse, IAuditSearchRequest, ICategory } from '@red/domain'; import { IAudit, IAuditResponse, IAuditSearchRequest, ICategory } from '@red/domain';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -11,8 +11,7 @@ export class AuditService extends GenericService<IAudit> {
return super.getAll<ICategory[]>(`${this._defaultModelPath}/categories`); return super.getAll<ICategory[]>(`${this._defaultModelPath}/categories`);
} }
@Validate() searchAuditLog(body: IAuditSearchRequest): Observable<IAuditResponse> {
searchAuditLog(@RequiredParam() body: IAuditSearchRequest): Observable<IAuditResponse> {
return this._post(body, `${this._defaultModelPath}/search`); return this._post(body, `${this._defaultModelPath}/search`);
} }
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { filterEach, GenericService, RequiredParam, Validate } from '@iqser/common-ui'; import { filterEach, GenericService } from '@iqser/common-ui';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { import {
IDigitalSignatureRequest, IDigitalSignatureRequest,
@ -14,18 +14,15 @@ import { catchError, map } from 'rxjs/operators';
export class DigitalSignatureService extends GenericService<IDigitalSignatureRequest> { export class DigitalSignatureService extends GenericService<IDigitalSignatureRequest> {
protected readonly _defaultModelPath = 'digital-signature'; protected readonly _defaultModelPath = 'digital-signature';
@Validate() updateSignature(body: IPkcsDigitalSignatureRequest): Observable<unknown> {
updateSignature(@RequiredParam() body: IPkcsDigitalSignatureRequest): Observable<unknown> {
return this._put(body); return this._put(body);
} }
@Validate() saveSignature(body: IPkcsDigitalSignature): Observable<IPkcsDigitalSignatureRequest> {
saveSignature(@RequiredParam() body: IPkcsDigitalSignature): Observable<IPkcsDigitalSignatureRequest> {
return this._post(body); return this._post(body);
} }
@Validate() saveKmsSignature(body: IKmsDigitalSignature): Observable<IKmsDigitalSignatureRequest> {
saveKmsSignature(@RequiredParam() body: IKmsDigitalSignature): Observable<IKmsDigitalSignatureRequest> {
return this._post(body, `${this._defaultModelPath}/kms`); return this._post(body, `${this._defaultModelPath}/kms`);
} }

View File

@ -1,18 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService } from '@iqser/common-ui';
import { IRules } from '@red/domain'; import { IRules } from '@red/domain';
@Injectable() @Injectable()
export class RulesService extends GenericService<IRules> { export class RulesService extends GenericService<IRules> {
protected readonly _defaultModelPath = 'rules'; protected readonly _defaultModelPath = 'rules';
@Validate() download(dossierTemplateId: string) {
download(@RequiredParam() dossierTemplateId: string) {
return this._getOne([dossierTemplateId]); return this._getOne([dossierTemplateId]);
} }
@Validate() uploadRules(body: IRules) {
uploadRules(@RequiredParam() body: IRules) {
return this._post<unknown>(body); return this._post<unknown>(body);
} }
} }

View File

@ -1,18 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService } from '@iqser/common-ui';
import { ISmtpConfiguration } from '@red/domain'; import { ISmtpConfiguration } from '@red/domain';
@Injectable() @Injectable()
export class SmtpConfigService extends GenericService<unknown> { export class SmtpConfigService extends GenericService<unknown> {
protected readonly _defaultModelPath = 'configuration'; protected readonly _defaultModelPath = 'configuration';
@Validate() updateSMTPConfiguration(body: ISmtpConfiguration) {
updateSMTPConfiguration(@RequiredParam() body: ISmtpConfiguration) {
return this._post(body, `${this._defaultModelPath}/smtp`); return this._post(body, `${this._defaultModelPath}/smtp`);
} }
@Validate() testSMTPConfiguration(body: ISmtpConfiguration) {
testSMTPConfiguration(@RequiredParam() body: ISmtpConfiguration) {
return this._post(body, `${this._defaultModelPath}/smtp/test`); return this._post(body, `${this._defaultModelPath}/smtp/test`);
} }

View File

@ -3,7 +3,7 @@
[config]="dossiersChartConfig$ | async" [config]="dossiersChartConfig$ | async"
[radius]="80" [radius]="80"
[strokeWidth]="15" [strokeWidth]="15"
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: stats.numberOfActiveDossiers }]" [subtitles]="['dossier-template-stats.active-dossiers' | translate : { count: stats.numberOfActiveDossiers }]"
filterKey="dossierStatesFilters" filterKey="dossierStatesFilters"
></redaction-donut-chart> ></redaction-donut-chart>
@ -29,10 +29,10 @@
<div class="right-chart"> <div class="right-chart">
<redaction-donut-chart <redaction-donut-chart
[config]="documentsChartConfig$ | async" [config]="documentsChartConfig$ | async"
[helpModeKey]="'filter_dossier_list'"
[radius]="80" [radius]="80"
[strokeWidth]="15" [strokeWidth]="15"
[subtitles]="['dossier-template-stats.total-documents' | translate]" [subtitles]="['dossier-template-stats.total-documents' | translate]"
[helpModeKey]="'filter_dossier_list'"
filterKey="statusFilters" filterKey="statusFilters"
></redaction-donut-chart> ></redaction-donut-chart>
</div> </div>

View File

@ -11,7 +11,7 @@ import type {
ManualRedactionActions, ManualRedactionActions,
} from '@red/domain'; } from '@red/domain';
import { type AnnotationWrapper } from '@models/file/annotation.wrapper'; import { type AnnotationWrapper } from '@models/file/annotation.wrapper';
import { GenericService, IqserPermissionsService, List, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { GenericService, IqserPermissionsService, List, Toaster } from '@iqser/common-ui';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { dictionaryActionsTranslations, manualRedactionActionsTranslations } from '@translations/annotation-actions-translations'; import { dictionaryActionsTranslations, manualRedactionActionsTranslations } from '@translations/annotation-actions-translations';
@ -53,15 +53,13 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
super(); super();
} }
@Validate() async addComment(comment: string, annotationId: string, dossierId: string, fileId: string) {
async addComment(@RequiredParam() comment: string, @RequiredParam() annotationId: string, dossierId: string, fileId: string) {
const url = `${this._defaultModelPath}/comment/add/${dossierId}/${fileId}/${annotationId}`; const url = `${this._defaultModelPath}/comment/add/${dossierId}/${fileId}/${annotationId}`;
const request = await firstValueFrom(this._post<{ commentId: string }>({ text: comment }, url)); const request = await firstValueFrom(this._post<{ commentId: string }>({ text: comment }, url));
return request.commentId; return request.commentId;
} }
@Validate() deleteComment(commentId: string, annotationId: string, dossierId: string, fileId: string) {
deleteComment(@RequiredParam() commentId: string, @RequiredParam() annotationId: string, dossierId: string, fileId: string) {
const url = `${this._defaultModelPath}/comment/undo/${dossierId}/${fileId}/${annotationId}/${commentId}`; const url = `${this._defaultModelPath}/comment/undo/${dossierId}/${fileId}/${annotationId}/${commentId}`;
return firstValueFrom(super.delete({}, url)); return firstValueFrom(super.delete({}, url));
} }
@ -180,116 +178,73 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
} }
} }
@Validate() add(body: List<IAddRedactionRequest>, dossierId: string, fileId: string) {
add(@RequiredParam() body: List<IAddRedactionRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.#bulkRedaction}/add/${dossierId}/${fileId}`).pipe(this.#log('Add', body)); return this._post(body, `${this.#bulkRedaction}/add/${dossierId}/${fileId}`).pipe(this.#log('Add', body));
} }
@Validate() recategorize(body: List<IRecategorizationRequest>, dossierId: string, fileId: string) {
recategorize(
@RequiredParam() body: List<IRecategorizationRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.#bulkRedaction}/recategorize/${dossierId}/${fileId}`).pipe(this.#log('Recategorize', body)); return this._post(body, `${this.#bulkRedaction}/recategorize/${dossierId}/${fileId}`).pipe(this.#log('Recategorize', body));
} }
@Validate() requestRecategorize(body: List<IRecategorizationRequest>, dossierId: string, fileId: string) {
requestRecategorize(
@RequiredParam() body: List<IRecategorizationRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.#bulkRequest}/recategorize/${dossierId}/${fileId}`).pipe(this.#log('Request recategorize', body)); return this._post(body, `${this.#bulkRequest}/recategorize/${dossierId}/${fileId}`).pipe(this.#log('Request recategorize', body));
} }
@Validate() legalBasisChange(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
legalBasisChange(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.#bulkRedaction}/legalBasisChange/${dossierId}/${fileId}`).pipe( return this._post(body, `${this.#bulkRedaction}/legalBasisChange/${dossierId}/${fileId}`).pipe(
this.#log('Legal basis change', body), this.#log('Legal basis change', body),
); );
} }
@Validate() requestLegalBasisChange(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
requestLegalBasisChange(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.#bulkRequest}/legalBasis/${dossierId}/${fileId}`).pipe( return this._post(body, `${this.#bulkRequest}/legalBasis/${dossierId}/${fileId}`).pipe(
this.#log('Request legal basis change', body), this.#log('Request legal basis change', body),
); );
} }
@Validate() requestRemoveRedaction(body: List<IRemoveRedactionRequest>, dossierId: string, fileId: string) {
requestRemoveRedaction(
@RequiredParam() body: List<IRemoveRedactionRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.#bulkRequest}/remove/${dossierId}/${fileId}`).pipe(this.#log('Request remove', body)); return this._post(body, `${this.#bulkRequest}/remove/${dossierId}/${fileId}`).pipe(this.#log('Request remove', body));
} }
@Validate() approve(annotationIds: List, dossierId: string, fileId: string) {
approve(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(annotationIds, `${this._defaultModelPath}/bulk/approve/${dossierId}/${fileId}`).pipe( return this._post(annotationIds, `${this._defaultModelPath}/bulk/approve/${dossierId}/${fileId}`).pipe(
this.#log('Approve', annotationIds), this.#log('Approve', annotationIds),
this.#showToast('approve'), this.#showToast('approve'),
); );
} }
@Validate() decline(annotationIds: List, dossierId: string, fileId: string) {
decline(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(annotationIds, `${this._defaultModelPath}/bulk/decline/${dossierId}/${fileId}`).pipe( return this._post(annotationIds, `${this._defaultModelPath}/bulk/decline/${dossierId}/${fileId}`).pipe(
this.#log('Decline', annotationIds), this.#log('Decline', annotationIds),
); );
} }
@Validate() undo(annotationIds: List, dossierId: string, fileId: string) {
undo(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`; const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`;
return super.delete(annotationIds, url).pipe(this.#log('Undo', annotationIds)); return super.delete(annotationIds, url).pipe(this.#log('Undo', annotationIds));
} }
@Validate() remove(body: List<IRemoveRedactionRequest>, dossierId: string, fileId: string) {
remove(@RequiredParam() body: List<IRemoveRedactionRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.#bulkRedaction}/remove/${dossierId}/${fileId}`).pipe(this.#log('Remove', body)); return this._post(body, `${this.#bulkRedaction}/remove/${dossierId}/${fileId}`).pipe(this.#log('Remove', body));
} }
@Validate() requestAdd(body: List<IAddRedactionRequest>, dossierId: string, fileId: string) {
requestAdd(@RequiredParam() body: List<IAddRedactionRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.#bulkRequest}/add/${dossierId}/${fileId}`).pipe(this.#log('Request add', body)); return this._post(body, `${this.#bulkRequest}/add/${dossierId}/${fileId}`).pipe(this.#log('Request add', body));
} }
@Validate() forceRedaction(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
forceRedaction(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body)); return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body));
} }
@Validate() forceRequest(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
forceRequest(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.#bulkRequest}/force/${dossierId}/${fileId}`).pipe(this.#log('Request force redaction', body)); return this._post(body, `${this.#bulkRequest}/force/${dossierId}/${fileId}`).pipe(this.#log('Request force redaction', body));
} }
@Validate() resize(body: List<IResizeRequest>, dossierId: string, fileId: string) {
resize(@RequiredParam() body: List<IResizeRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.#bulkRedaction}/resize/${dossierId}/${fileId}`).pipe(this.#log('Resize', body)); return this._post(body, `${this.#bulkRedaction}/resize/${dossierId}/${fileId}`).pipe(this.#log('Resize', body));
} }
@Validate() requestResize(body: List<IResizeRequest>, dossierId: string, fileId: string) {
requestResize(@RequiredParam() body: List<IResizeRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.#bulkRequest}/resize/${dossierId}/${fileId}`).pipe(this.#log('Request resize', body)); return this._post(body, `${this.#bulkRequest}/resize/${dossierId}/${fileId}`).pipe(this.#log('Request resize', body));
} }
@ -302,10 +257,10 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
#showToast(action: ManualRedactionActions | DictionaryActions, isDictionary = false) { #showToast(action: ManualRedactionActions | DictionaryActions, isDictionary = false) {
return tap({ return tap({
next: () => this._toaster.success(getMessage(action, isDictionary), { positionClass: 'toast-file-preview' }), next: () => this._toaster.success(getMessage(action, isDictionary), { positionClass: 'toast-file-preview' }),
error: (error: HttpErrorResponse) => { error: (error: unknown) => {
const isConflict = error.status === HttpStatusCode.Conflict; const isConflict = (error as HttpErrorResponse).status === HttpStatusCode.Conflict;
this._toaster.error(getMessage(action, isDictionary, true, isConflict), { this._toaster.error(getMessage(action, isDictionary, true, isConflict), {
error, error: error as HttpErrorResponse,
positionClass: 'toast-file-preview', positionClass: 'toast-file-preview',
}); });
}, },
@ -315,10 +270,10 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
#showAddToDictionaryToast(body: List<IAddRedactionRequest>, dictionaryLabel?: string) { #showAddToDictionaryToast(body: List<IAddRedactionRequest>, dictionaryLabel?: string) {
return tap({ return tap({
next: () => this._toaster.success(getDictionaryMessage('add'), { positionClass: 'toast-file-preview' }), next: () => this._toaster.success(getDictionaryMessage('add'), { positionClass: 'toast-file-preview' }),
error: (error: HttpErrorResponse) => { error: (error: unknown) => {
const isConflict = error.status === HttpStatusCode.Conflict; const isConflict = (error as HttpErrorResponse).status === HttpStatusCode.Conflict;
this._toaster.error(getDictionaryMessage('add', true, isConflict), { this._toaster.error(getDictionaryMessage('add', true, isConflict), {
error, error: error as HttpErrorResponse,
params: { params: {
dictionaryName: dictionaryLabel, dictionaryName: dictionaryLabel,
content: body[0].value, content: body[0].value,

View File

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Inject, Injectable, NgZone } from '@angular/core'; import { ChangeDetectorRef, inject, Injectable, NgZone } from '@angular/core';
import { IHeaderElement, IManualRedactionEntry, ViewModes } from '@red/domain'; import { IHeaderElement, IManualRedactionEntry, ViewModes } from '@red/domain';
import { Core } from '@pdftron/webviewer'; import { Core } from '@pdftron/webviewer';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -9,7 +9,7 @@ import {
} from '@models/file/manual-redaction-entry.wrapper'; } from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service'; import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { BASE_HREF_FN, BaseHrefFn, IqserPermissionsService, isJustOne, shareDistinctLast } from '@iqser/common-ui'; import { BASE_HREF_FN, IqserPermissionsService, isJustOne, shareDistinctLast } from '@iqser/common-ui';
import { toPosition } from '../utils/pdf-calculation.utils'; import { toPosition } from '../utils/pdf-calculation.utils';
import { MultiSelectService } from './multi-select.service'; import { MultiSelectService } from './multi-select.service';
import { FilePreviewStateService } from './file-preview-state.service'; import { FilePreviewStateService } from './file-preview-state.service';
@ -48,8 +48,8 @@ export class PdfProxyService {
shareDistinctLast(), shareDistinctLast(),
); );
canPerformActions = true; canPerformActions = true;
readonly canPerformAnnotationActions$: Observable<boolean>; readonly canPerformAnnotationActions$: Observable<boolean>;
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #visibilityOffIcon = this._convertPath('/assets/icons/general/visibility-off.svg'); readonly #visibilityOffIcon = this._convertPath('/assets/icons/general/visibility-off.svg');
readonly #visibilityIcon = this._convertPath('/assets/icons/general/visibility.svg'); readonly #visibilityIcon = this._convertPath('/assets/icons/general/visibility.svg');
readonly #falsePositiveIcon = this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg'); readonly #falsePositiveIcon = this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg');
@ -59,7 +59,6 @@ export class PdfProxyService {
readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg'); readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg');
constructor( constructor(
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _manualRedactionService: ManualRedactionService, private readonly _manualRedactionService: ManualRedactionService,
private readonly _annotationsActionsService: AnnotationActionsService, private readonly _annotationsActionsService: AnnotationActionsService,
@ -117,6 +116,145 @@ export class PdfProxyService {
this._documentViewer.setRectangleToolStyles(color); this._documentViewer.setRectangleToolStyles(color);
} }
private _configureRectangleAnnotationPopup(annotation: Annotation) {
if (!this._pdf.isCompare || annotation.getPageNumber() % 2 === 1) {
const addRectangleButton = {
type: 'actionButton',
dataElement: TextPopups.ADD_RECTANGLE,
img: this.#addRedactionIcon,
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
onClick: () => this._addRectangleManualRedaction(),
};
this._pdf.instance.UI.annotationPopup.add([addRectangleButton]);
}
}
private _addRectangleManualRedaction() {
const activeAnnotation = this._annotationManager.selected[0];
const activePage = activeAnnotation.getPageNumber();
const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation)];
const manualRedactionEntry = this._getManualRedaction({ [activePage]: quads });
this._cleanUpSelectionAndButtonState();
this.manualAnnotationRequested$.next({ manualRedactionEntry, type: 'REDACTION' });
}
private _cleanUpSelectionAndButtonState() {
this._viewerHeaderService.disable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
this._viewerHeaderService.enable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
}
private _configureTextPopup() {
const popups: IHeaderElement[] = [];
if (!this._state.file.isApproved) {
// Adding directly to the false-positive dict is only available in dev-mode
if (this._userPreferenceService.areDevFeaturesEnabled) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_FALSE_POSITIVE,
img: this.#falsePositiveIcon,
title: this.#getTitle(ManualRedactionEntryTypes.FALSE_POSITIVE),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.FALSE_POSITIVE),
});
}
if (this._iqserPermissionsService.has(ROLES.redactions.write) || this._iqserPermissionsService.has(ROLES.redactions.request)) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_REDACTION,
img: this.#addRedactionIcon,
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION),
});
if (!this._iqserPermissionsService.has(ROLES.getRss)) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_DICTIONARY,
img: this.#addDictIcon,
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
});
}
}
}
this._pdf.configureTextPopups(popups);
return this._handleCustomActions();
}
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
const text = this._documentViewer.selectedText;
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
}
private _handleCustomActions() {
const isCurrentPageExcluded = this._state.file.isPageExcluded(this._pdf.currentPage);
if (this._viewModeService.viewMode === ViewModes.REDACTED) {
this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
} else {
this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
}
if (!isCurrentPageExcluded) {
if (this.canPerformActions) {
try {
this._pdf.instance.UI.enableTools([AnnotationToolNames.AnnotationCreateRectangle]);
} catch (e) {
// happens
}
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
if (this._documentViewer.selectedText.length > 2) {
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
}
} else {
this._pdf.instance.UI.disableTools([AnnotationToolNames.AnnotationCreateRectangle]);
this._pdf.disable(TEXT_POPUPS_TO_TOGGLE);
this._viewerHeaderService.disable(HEADER_ITEMS_TO_TOGGLE);
}
} else {
let textPopupElementsToDisable = [...TEXT_POPUPS_TO_TOGGLE];
let headerElementsToDisable = [...HEADER_ITEMS_TO_TOGGLE];
if (this.canPerformActions) {
this._pdf.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED);
this._viewerHeaderService.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED as HeaderElementType[]);
textPopupElementsToDisable = textPopupElementsToDisable.filter(
element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element),
);
headerElementsToDisable = headerElementsToDisable.filter(element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element));
}
this._pdf.disable(textPopupElementsToDisable);
this._viewerHeaderService.disable(headerElementsToDisable);
}
}
private _getManualRedaction(quads: Record<string, Quad[]>, text?: string, convertQuads = false): IManualRedactionEntry {
const entry: IManualRedactionEntry = { positions: [] };
for (const key of Object.keys(quads)) {
for (const quad of quads[key]) {
const page = parseInt(key, 10);
const pageHeight = this._documentViewer.getHeight(page);
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this._pdf.translateQuad(page, quad) : quad));
}
}
entry.value = text;
entry.rectangle = !text;
return entry;
}
#deactivateMultiSelect() { #deactivateMultiSelect() {
this._multiSelectService.deactivate(); this._multiSelectService.deactivate();
this._annotationManager.deselect(); this._annotationManager.deselect();
@ -236,146 +374,7 @@ export class PdfProxyService {
this._pdf.instance.UI.annotationPopup.add(actions); this._pdf.instance.UI.annotationPopup.add(actions);
} }
private _configureRectangleAnnotationPopup(annotation: Annotation) {
if (!this._pdf.isCompare || annotation.getPageNumber() % 2 === 1) {
const addRectangleButton = {
type: 'actionButton',
dataElement: TextPopups.ADD_RECTANGLE,
img: this.#addRedactionIcon,
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
onClick: () => this._addRectangleManualRedaction(),
};
this._pdf.instance.UI.annotationPopup.add([addRectangleButton]);
}
}
private _addRectangleManualRedaction() {
const activeAnnotation = this._annotationManager.selected[0];
const activePage = activeAnnotation.getPageNumber();
const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation)];
const manualRedactionEntry = this._getManualRedaction({ [activePage]: quads });
this._cleanUpSelectionAndButtonState();
this.manualAnnotationRequested$.next({ manualRedactionEntry, type: 'REDACTION' });
}
private _cleanUpSelectionAndButtonState() {
this._viewerHeaderService.disable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
this._viewerHeaderService.enable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
}
private _configureTextPopup() {
const popups: IHeaderElement[] = [];
if (!this._state.file.isApproved) {
// Adding directly to the false-positive dict is only available in dev-mode
if (this._userPreferenceService.areDevFeaturesEnabled) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_FALSE_POSITIVE,
img: this.#falsePositiveIcon,
title: this.#getTitle(ManualRedactionEntryTypes.FALSE_POSITIVE),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.FALSE_POSITIVE),
});
}
if (this._iqserPermissionsService.has(ROLES.redactions.write) || this._iqserPermissionsService.has(ROLES.redactions.request)) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_REDACTION,
img: this.#addRedactionIcon,
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION),
});
if (!this._iqserPermissionsService.has(ROLES.getRss)) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_DICTIONARY,
img: this.#addDictIcon,
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
});
}
}
}
this._pdf.configureTextPopups(popups);
return this._handleCustomActions();
}
#getTitle(type: ManualRedactionEntryType) { #getTitle(type: ManualRedactionEntryType) {
return this._translateService.instant(this._manualRedactionService.getTitle(type, this._state.dossier)); return this._translateService.instant(this._manualRedactionService.getTitle(type, this._state.dossier));
} }
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
const text = this._documentViewer.selectedText;
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
}
private _handleCustomActions() {
const isCurrentPageExcluded = this._state.file.isPageExcluded(this._pdf.currentPage);
if (this._viewModeService.viewMode === ViewModes.REDACTED) {
this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
} else {
this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
}
if (!isCurrentPageExcluded) {
if (this.canPerformActions) {
try {
this._pdf.instance.UI.enableTools([AnnotationToolNames.AnnotationCreateRectangle]);
} catch (e) {
// happens
}
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
if (this._documentViewer.selectedText.length > 2) {
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
}
} else {
this._pdf.instance.UI.disableTools([AnnotationToolNames.AnnotationCreateRectangle]);
this._pdf.disable(TEXT_POPUPS_TO_TOGGLE);
this._viewerHeaderService.disable(HEADER_ITEMS_TO_TOGGLE);
}
} else {
let textPopupElementsToDisable = [...TEXT_POPUPS_TO_TOGGLE];
let headerElementsToDisable = [...HEADER_ITEMS_TO_TOGGLE];
if (this.canPerformActions) {
this._pdf.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED);
this._viewerHeaderService.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED as HeaderElementType[]);
textPopupElementsToDisable = textPopupElementsToDisable.filter(
element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element),
);
headerElementsToDisable = headerElementsToDisable.filter(element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element));
}
this._pdf.disable(textPopupElementsToDisable);
this._viewerHeaderService.disable(headerElementsToDisable);
}
}
private _getManualRedaction(quads: Record<string, Quad[]>, text?: string, convertQuads = false): IManualRedactionEntry {
const entry: IManualRedactionEntry = { positions: [] };
for (const key of Object.keys(quads)) {
for (const quad of quads[key]) {
const page = parseInt(key, 10);
const pageHeight = this._documentViewer.getHeight(page);
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this._pdf.translateQuad(page, quad) : quad));
}
}
entry.value = text;
entry.rectangle = !text;
return entry;
}
} }

View File

@ -1,6 +1,6 @@
import { inject, Inject, Injectable, Injector } from '@angular/core'; import { inject, Injectable, Injector } from '@angular/core';
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer'; import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
import { BASE_HREF_FN, BaseHrefFn, ErrorService, getConfig, shareDistinctLast } from '@iqser/common-ui'; import { BASE_HREF_FN, ErrorService, getConfig, shareDistinctLast } from '@iqser/common-ui';
import { AppConfig, File, IHeaderElement } from '@red/domain'; import { AppConfig, File, IHeaderElement } from '@red/domain';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
@ -35,6 +35,7 @@ export class PdfViewer {
compareMode$: Observable<boolean>; compareMode$: Observable<boolean>;
totalPages$: Observable<number>; totalPages$: Observable<number>;
private readonly _convertPath = inject(BASE_HREF_FN);
#instance: WebViewerInstance; #instance: WebViewerInstance;
readonly #licenseKey = inject(LicenseService).activeLicenseKey; readonly #licenseKey = inject(LicenseService).activeLicenseKey;
readonly #config = getConfig<AppConfig>(); readonly #config = getConfig<AppConfig>();
@ -54,7 +55,6 @@ export class PdfViewer {
private readonly _injector: Injector, private readonly _injector: Injector,
private readonly _errorService: ErrorService, private readonly _errorService: ErrorService,
private readonly _userPreferenceService: UserPreferenceService, private readonly _userPreferenceService: UserPreferenceService,
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
) {} ) {}
get instance() { get instance() {

View File

@ -1,5 +1,5 @@
import { Inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { BASE_HREF_FN, BaseHrefFn, bool } from '@iqser/common-ui'; import { BASE_HREF_FN } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { HeaderElements } from '../../file-preview/utils/constants'; import { HeaderElements } from '../../file-preview/utils/constants';
@ -13,12 +13,12 @@ import Annotation = Core.Annotations.Annotation;
@Injectable() @Injectable()
export class ReadableRedactionsService { export class ReadableRedactionsService {
readonly active$: Observable<boolean>; readonly active$: Observable<boolean>;
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #enableIcon = this._convertPath('/assets/icons/general/redaction-preview.svg'); readonly #enableIcon = this._convertPath('/assets/icons/general/redaction-preview.svg');
readonly #disableIcon = this._convertPath('/assets/icons/general/redaction-final.svg'); readonly #disableIcon = this._convertPath('/assets/icons/general/redaction-final.svg');
readonly #active$ = new BehaviorSubject<boolean>(true); readonly #active$ = new BehaviorSubject<boolean>(true);
constructor( constructor(
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _pdf: PdfViewer, private readonly _pdf: PdfViewer,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _annotationManager: REDAnnotationManager, private readonly _annotationManager: REDAnnotationManager,

View File

@ -1,19 +1,19 @@
import { Inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { HeaderElements } from '../../file-preview/utils/constants'; import { HeaderElements } from '../../file-preview/utils/constants';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BASE_HREF_FN, BaseHrefFn } from '@iqser/common-ui'; import { BASE_HREF_FN } from '@iqser/common-ui';
import { PdfViewer } from './pdf-viewer.service'; import { PdfViewer } from './pdf-viewer.service';
import { REDDocumentViewer } from './document-viewer.service'; import { REDDocumentViewer } from './document-viewer.service';
@Injectable() @Injectable()
export class TooltipsService { export class TooltipsService {
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #enableIcon = this._convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg'); readonly #enableIcon = this._convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg');
readonly #disableIcon = this._convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg'); readonly #disableIcon = this._convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg');
constructor( constructor(
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _pdf: PdfViewer, private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer, private readonly _documentViewer: REDDocumentViewer,
private readonly _userPreferenceService: UserPreferenceService, private readonly _userPreferenceService: UserPreferenceService,

View File

@ -1,8 +1,8 @@
import { Inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { IHeaderElement, RotationTypes } from '@red/domain'; import { IHeaderElement, RotationTypes } from '@red/domain';
import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants'; import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BASE_HREF_FN, BaseHrefFn } from '@iqser/common-ui'; import { BASE_HREF_FN } from '@iqser/common-ui';
import { TooltipsService } from './tooltips.service'; import { TooltipsService } from './tooltips.service';
import { PageRotationService } from './page-rotation.service'; import { PageRotationService } from './page-rotation.service';
import { PdfViewer } from './pdf-viewer.service'; import { PdfViewer } from './pdf-viewer.service';
@ -23,6 +23,7 @@ const divider: IHeaderElement = {
export class ViewerHeaderService { export class ViewerHeaderService {
readonly events$: Observable<ViewerEvent>; readonly events$: Observable<ViewerEvent>;
toggleLoadAnnotations$: Observable<boolean>; toggleLoadAnnotations$: Observable<boolean>;
private readonly _convertPath = inject(BASE_HREF_FN);
#buttons: Map<HeaderElementType, IHeaderElement>; #buttons: Map<HeaderElementType, IHeaderElement>;
readonly #config = new Map<HeaderElementType, boolean>([ readonly #config = new Map<HeaderElementType, boolean>([
[HeaderElements.SHAPE_TOOL_GROUP_BUTTON, true], [HeaderElements.SHAPE_TOOL_GROUP_BUTTON, true],
@ -40,7 +41,6 @@ export class ViewerHeaderService {
readonly #events$ = new Subject<ViewerEvent>(); readonly #events$ = new Subject<ViewerEvent>();
constructor( constructor(
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _filesMapService: FilesMapService, private readonly _filesMapService: FilesMapService,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _pdf: PdfViewer, private readonly _pdf: PdfViewer,
@ -311,19 +311,6 @@ export class ViewerHeaderService {
this.enable([HeaderElements.COMPARE_BUTTON]); this.enable([HeaderElements.COMPARE_BUTTON]);
} }
#discardRotation(): void {
this._rotationService.discardRotation();
this.disable(ROTATION_ACTION_BUTTONS);
}
#toggleRotationActionButtons() {
if (this._rotationService.hasRotations) {
this.enable(ROTATION_ACTION_BUTTONS);
} else {
this.disable(ROTATION_ACTION_BUTTONS);
}
}
private _closeCompareMode() { private _closeCompareMode() {
this._pdf.closeCompareMode(); this._pdf.closeCompareMode();
const { dossierId, fileId } = this._pdf; const { dossierId, fileId } = this._pdf;
@ -353,4 +340,17 @@ export class ViewerHeaderService {
private _isEnabled(key: HeaderElementType): boolean { private _isEnabled(key: HeaderElementType): boolean {
return this.#config.get(key); return this.#config.get(key);
} }
#discardRotation(): void {
this._rotationService.discardRotation();
this.disable(ROTATION_ACTION_BUTTONS);
}
#toggleRotationActionButtons() {
if (this._rotationService.hasRotations) {
this.enable(ROTATION_ACTION_BUTTONS);
} else {
this.disable(ROTATION_ACTION_BUTTONS);
}
}
} }

View File

@ -1,3 +1,3 @@
<div [class.error]="isError" class="small-label"> <div [class.error]="isError" class="small-label">
{{ date | date: 'd MMM yyyy' }} {{ date | date : 'd MMM yyyy' }}
</div> </div>

View File

@ -5,7 +5,7 @@
<div class="small-label stats-subtitle"> <div class="small-label stats-subtitle">
<div> <div>
<mat-icon svgIcon="red:entries"></mat-icon> <mat-icon svgIcon="red:entries"></mat-icon>
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }} {{ 'edit-dossier-dialog.dictionary.entries' | translate : { length: (dossierDictionary?.entries || []).length } }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,6 +9,6 @@
</div> </div>
<div *ngIf="file.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate"> <div *ngIf="file.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
<mat-icon svgIcon="iqser:ocr"></mat-icon> <mat-icon svgIcon="iqser:ocr"></mat-icon>
{{ file.lastOCRTime | date: 'mediumDate' }} {{ file.lastOCRTime | date : 'mediumDate' }}
</div> </div>
</div> </div>

View File

@ -3,10 +3,10 @@
<mat-progress-bar <mat-progress-bar
[class.primary]="progressColor === 'primary'" [class.primary]="progressColor === 'primary'"
[class.white]="progressColor === 'white'" [class.white]="progressColor === 'white'"
[matTooltip]="numberOfPagesToOCR !== 0 ? numberOfOCRedPages + '/' + numberOfPagesToOCR : ''"
[mode]="numberOfPagesToOCR === 0 ? 'indeterminate' : 'determinate'" [mode]="numberOfPagesToOCR === 0 ? 'indeterminate' : 'determinate'"
[value]="(numberOfOCRedPages / numberOfPagesToOCR) * 100" [value]="(numberOfOCRedPages / numberOfPagesToOCR) * 100"
[matTooltip]="numberOfPagesToOCR !== 0 ? numberOfOCRedPages + '/' + numberOfPagesToOCR : ''"
class="ml-8 mr-8 w-100" class="ml-8 mr-8 w-100"
></mat-progress-bar> ></mat-progress-bar>
<span>{{ numberOfOCRedPages / numberOfPagesToOCR | percent: '0.0-0' }}</span> <span>{{ numberOfOCRedPages / numberOfPagesToOCR | percent : '0.0-0' }}</span>

View File

@ -3,7 +3,7 @@ import { DownloadStatus, IDownloadStatus, IDownloadStatusResponse, IPrepareDownl
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { ConfigService } from '@services/config.service'; import { ConfigService } from '@services/config.service';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { EntitiesService, mapEach, RequiredParam, TenantContextHolder, Validate } from '@iqser/common-ui'; import { EntitiesService, mapEach, TenantContextHolder } from '@iqser/common-ui';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
@Injectable() @Injectable()
@ -46,19 +46,16 @@ export class FileDownloadService extends EntitiesService<IDownloadStatus, Downlo
document.body.removeChild(anchor); document.body.removeChild(anchor);
} }
@Validate() prepareDownload(body: IPrepareDownloadRequest) {
prepareDownload(@RequiredParam() body: IPrepareDownloadRequest) {
return firstValueFrom(this._post(body, `${this._defaultModelPath}/prepare-option`)); return firstValueFrom(this._post(body, `${this._defaultModelPath}/prepare-option`));
} }
@Validate() generateToken(storageId: string) {
generateToken(@RequiredParam() storageId: string) {
const request = this._post<{ value: string }>({ value: storageId }, `${this._defaultModelPath}/generate-ott`); const request = this._post<{ value: string }>({ value: storageId }, `${this._defaultModelPath}/generate-ott`);
return firstValueFrom(request); return firstValueFrom(request);
} }
@Validate() delete(body: IRemoveDownloadRequest): Observable<unknown> {
delete(@RequiredParam() body: IRemoveDownloadRequest): Observable<unknown> {
return super._post(body, `${this._defaultModelPath}/delete`); return super._post(body, `${this._defaultModelPath}/delete`);
} }
} }

View File

@ -6,7 +6,7 @@ import { ConfigService } from '@services/config.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { IFileUploadResult, OverwriteFileOption, OverwriteFileOptions } from '@red/domain'; import { IFileUploadResult, OverwriteFileOption, OverwriteFileOptions } from '@red/domain';
import { isAcceptedFileType, isCsv, isZip } from '@utils/file-drop-utils'; import { isAcceptedFileType, isCsv, isZip } from '@utils/file-drop-utils';
import { ErrorMessageService, GenericService, HeadersConfiguration, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { ErrorMessageService, GenericService, HeadersConfiguration, Toaster } from '@iqser/common-ui';
import { FilesMapService } from '@services/files/files-map.service'; import { FilesMapService } from '@services/files/files-map.service';
import { switchMap, tap, throttleTime } from 'rxjs/operators'; import { switchMap, tap, throttleTime } from 'rxjs/operators';
import { FilesService } from '@services/files/files.service'; import { FilesService } from '@services/files/files.service';
@ -164,8 +164,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
} }
} }
@Validate() uploadFileForm(dossierId: string, keepManualRedactions = false, file?: Blob) {
uploadFileForm(@RequiredParam() dossierId: string, keepManualRedactions = false, file?: Blob) {
const formParams = new FormData(); const formParams = new FormData();
formParams.append('keepManualRedactions', keepManualRedactions.toString()); formParams.append('keepManualRedactions', keepManualRedactions.toString());
@ -241,14 +240,17 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
this._removeUpload(uploadFile); this._removeUpload(uploadFile);
} }
}, },
error: (err: HttpErrorResponse) => { error: (err: unknown) => {
uploadFile.completed = true; uploadFile.completed = true;
uploadFile.error = { uploadFile.error = {
// Extract error message // Extract error message
message: this._errorMessageService.getMessage(err, 'upload-status.error.generic'), message: this._errorMessageService.getMessage(err as HttpErrorResponse, 'upload-status.error.generic'),
}; };
this._removeUpload(uploadFile); this._removeUpload(uploadFile);
if (uploadFile.retryCount < 5 && ![HttpStatusCode.BadRequest, HttpStatusCode.Conflict].includes(err.status)) { if (
uploadFile.retryCount < 5 &&
![HttpStatusCode.BadRequest, HttpStatusCode.Conflict].includes((err as HttpErrorResponse).status)
) {
uploadFile.retryCount += 1; uploadFile.retryCount += 1;
this.scheduleUpload(uploadFile); this.scheduleUpload(uploadFile);
} }

View File

@ -1,4 +1,4 @@
import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { EntitiesService, List, mapEach, Toaster } from '@iqser/common-ui';
import { DossierTemplate, IDossierTemplate } from '@red/domain'; import { DossierTemplate, IDossierTemplate } from '@red/domain';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { firstValueFrom, forkJoin, Observable, of } from 'rxjs'; import { firstValueFrom, forkJoin, Observable, of } from 'rxjs';
@ -75,8 +75,7 @@ export class DossierTemplatesService extends EntitiesService<IDossierTemplate, D
); );
} }
@Validate() async createOrUpdate(body: IDossierTemplate) {
async createOrUpdate(@RequiredParam() body: IDossierTemplate) {
await firstValueFrom(this._post(body)); await firstValueFrom(this._post(body));
return await firstValueFrom(this.loadAll()); return await firstValueFrom(this.loadAll());
} }

View File

@ -1,4 +1,5 @@
import { EntitiesService, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; /* eslint-disable @typescript-eslint/member-ordering */
import { EntitiesService, mapEach, Toaster } from '@iqser/common-ui';
import { Dossier, DossierStats, IDossier, IDossierChanges, IDossierRequest } from '@red/domain'; import { Dossier, DossierStats, IDossier, IDossierChanges, IDossierRequest } from '@red/domain';
import { Observable, of, Subject } from 'rxjs'; import { Observable, of, Subject } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { catchError, map, switchMap, tap } from 'rxjs/operators';
@ -17,11 +18,10 @@ export abstract class DossiersService extends EntitiesService<IDossier, Dossier>
protected readonly _dossierStatsService = inject(DossierStatsService); protected readonly _dossierStatsService = inject(DossierStatsService);
protected readonly _dashboardStatsService = inject(DashboardStatsService); protected readonly _dashboardStatsService = inject(DashboardStatsService);
protected readonly _toaster = inject(Toaster); protected readonly _toaster = inject(Toaster);
protected abstract readonly _defaultModelPath: string;
protected readonly _entityClass = Dossier; protected readonly _entityClass = Dossier;
protected abstract readonly _defaultModelPath: string;
@Validate() createOrUpdate(dossier: IDossierRequest): Observable<Dossier> {
createOrUpdate(@RequiredParam() dossier: IDossierRequest): Observable<Dossier> {
const showToast = (error: HttpErrorResponse) => { const showToast = (error: HttpErrorResponse) => {
this._toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG); this._toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG);
return of(null); return of(null);

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EntitiesService, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; import { EntitiesService, mapEach } from '@iqser/common-ui';
import { DefaultColors, DefaultColorType, IDefaultColors } from '@red/domain'; import { DefaultColors, DefaultColorType, IDefaultColors } from '@red/domain';
import { forkJoin, Observable } from 'rxjs'; import { forkJoin, Observable } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators'; import { map, switchMap, tap } from 'rxjs/operators';
@ -29,11 +29,7 @@ export class DefaultColorsService extends EntitiesService<IDefaultColors, Defaul
); );
} }
@Validate() update(newColor: Partial<Record<DefaultColorType, string>>, dossierTemplateId: string): Observable<DefaultColors> {
update(
@RequiredParam() newColor: Partial<Record<DefaultColorType, string>>,
@RequiredParam() dossierTemplateId: string,
): Observable<DefaultColors> {
const oldColors = this.find(dossierTemplateId); const oldColors = this.find(dossierTemplateId);
const body = { ...oldColors, ...newColor }; const body = { ...oldColors, ...newColor };
return this._post(body, `${this._defaultModelPath}/${dossierTemplateId}`).pipe( return this._post(body, `${this._defaultModelPath}/${dossierTemplateId}`).pipe(

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { forkJoin, Observable, of, throwError, zip } from 'rxjs'; import { forkJoin, Observable, of, throwError, zip } from 'rxjs';
import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { EntitiesService, List, QueryParam, Toaster } from '@iqser/common-ui';
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain'; import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -31,8 +31,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
/** /**
* Retrieves all dictionary entries of an entry type * Retrieves all dictionary entries of an entry type
*/ */
@Validate()
getForType(@RequiredParam() dossierTemplateId: string, @RequiredParam() type: string, dossierId?: string): Observable<IDictionary> { getForType(dossierTemplateId: string, type: string, dossierId?: string): Observable<IDictionary> {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams); return this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams);
} }
@ -40,12 +40,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
/** /**
* Deletes entry types * Deletes entry types
*/ */
@Validate()
deleteDictionaries( deleteDictionaries(dictionaryIds: List, dossierTemplateId: string, dossierId?: string): Observable<unknown> {
@RequiredParam() dictionaryIds: List,
@RequiredParam() dossierTemplateId: string,
dossierId?: string,
): Observable<unknown> {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
const url = `${this._defaultModelPath}/type/${dossierTemplateId}/delete`; const url = `${this._defaultModelPath}/type/${dossierTemplateId}/delete`;
return this._post(dictionaryIds, url, queryParams).pipe( return this._post(dictionaryIds, url, queryParams).pipe(
@ -57,8 +53,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
/** /**
* Retrieve all entry types * Retrieve all entry types
*/ */
@Validate()
getAllDictionaries(@RequiredParam() dossierTemplateId: string, dossierId?: string) { getAllDictionaries(dossierTemplateId: string, dossierId?: string) {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams); return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams);
} }
@ -66,17 +62,12 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
/** /**
* Updates colors, hint and caseInsensitive of an entry type. * Updates colors, hint and caseInsensitive of an entry type.
*/ */
@Validate()
updateDictionary( updateDictionary(body: IUpdateDictionary, dossierTemplateId: string, type: string, dossierId?: string): Observable<unknown> {
@RequiredParam() body: IUpdateDictionary,
@RequiredParam() dossierTemplateId: string,
@RequiredParam() type: string,
dossierId?: string,
): Observable<unknown> {
const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`; const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`;
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._post(body, url, queryParams).pipe( return this._post(body, url, queryParams).pipe(
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)), catchError((error: unknown) => this.#addUpdateDictionaryErrorToast(error)),
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)), switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])), switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])),
); );
@ -85,11 +76,11 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
/** /**
* Creates entry type with colors, hint and caseInsensitive * Creates entry type with colors, hint and caseInsensitive
*/ */
@Validate()
addDictionary(@RequiredParam() dictionary: IDictionary, dossierId?: string): Observable<unknown> { addDictionary(dictionary: IDictionary, dossierId?: string): Observable<unknown> {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe( return this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe(
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)), catchError((error: unknown) => this.#addUpdateDictionaryErrorToast(error)),
switchMap(() => this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId)), switchMap(() => this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId)),
switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])), switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])),
); );
@ -126,8 +117,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
this._toaster.success(_('dictionary-overview.success.generic')); this._toaster.success(_('dictionary-overview.success.generic'));
} }
}, },
error: error => { error: (error: unknown) => {
if (error.status === 400) { if ((error as HttpErrorResponse).status === 400) {
this._toaster.error(_('dictionary-overview.error.400')); this._toaster.error(_('dictionary-overview.error.400'));
} else { } else {
this._toaster.error(_('dictionary-overview.error.generic')); this._toaster.error(_('dictionary-overview.error.generic'));
@ -227,21 +218,10 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
); );
} }
#addUpdateDictionaryErrorToast(error: HttpErrorResponse): Observable<never> {
if (error.status === HttpStatusCode.Conflict) {
this._toaster.error(_('add-edit-entity.error.entity-already-exists'));
} else if (error.status === HttpStatusCode.BadRequest) {
this._toaster.error(_('add-edit-entity.error.invalid-color-or-rank'));
} else {
this._toaster.error(_('add-edit-entity.error.generic'));
}
return throwError(() => error);
}
/** /**
* Add dictionary entries with entry type. * Add dictionary entries with entry type.
*/ */
@Validate()
private _addEntries( private _addEntries(
entries: List, entries: List,
dossierTemplateId: string, dossierTemplateId: string,
@ -261,7 +241,7 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
/** /**
* Delete dictionary entries with entry type. * Delete dictionary entries with entry type.
*/ */
@Validate()
private _deleteEntries( private _deleteEntries(
entries: List, entries: List,
dossierTemplateId: string, dossierTemplateId: string,
@ -275,4 +255,15 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`; const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`;
return this._post(entries, url, queryParams); return this._post(entries, url, queryParams);
} }
#addUpdateDictionaryErrorToast(error: HttpErrorResponse | unknown): Observable<never> {
if ((error as HttpErrorResponse).status === HttpStatusCode.Conflict) {
this._toaster.error(_('add-edit-entity.error.entity-already-exists'));
} else if ((error as HttpErrorResponse).status === HttpStatusCode.BadRequest) {
this._toaster.error(_('add-edit-entity.error.invalid-color-or-rank'));
} else {
this._toaster.error(_('add-edit-entity.error.generic'));
}
return throwError(() => error);
}
} }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Dossier, DossierAttributeConfig, DossierAttributeWithValue, IDossierAttribute, IDossierAttributeConfig } from '@red/domain'; import { Dossier, DossierAttributeConfig, DossierAttributeWithValue, IDossierAttribute, IDossierAttributeConfig } from '@red/domain';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; import { EntitiesService, List, mapEach } from '@iqser/common-ui';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
@Injectable({ @Injectable({
@ -21,16 +21,14 @@ export class DossierAttributesService extends EntitiesService<IDossierAttributeC
})); }));
} }
@Validate() setAttributes(dossier: Dossier, dossierAttributeList: List<IDossierAttribute>) {
setAttributes(@RequiredParam() dossier: Dossier, @RequiredParam() dossierAttributeList: List<IDossierAttribute>) {
return this._post<{ dossierAttributeList?: List<IDossierAttribute> }>( return this._post<{ dossierAttributeList?: List<IDossierAttribute> }>(
{ dossierAttributeList }, { dossierAttributeList },
`${this._defaultModelPath}/set/${dossier.id}`, `${this._defaultModelPath}/set/${dossier.id}`,
); );
} }
@Validate() delete(ids: List, dossierTemplateId: string): Observable<unknown> {
delete(@RequiredParam() ids: List, dossierTemplateId: string): Observable<unknown> {
const queryParams = ids.map(id => ({ key: 'dossierAttributeIds', value: id })); const queryParams = ids.map(id => ({ key: 'dossierAttributeIds', value: id }));
return this._post(null, `${this._defaultModelPath}/config/delete/${dossierTemplateId}`, queryParams); return this._post(null, `${this._defaultModelPath}/config/delete/${dossierTemplateId}`, queryParams);
} }
@ -43,21 +41,18 @@ export class DossierAttributesService extends EntitiesService<IDossierAttributeC
); );
} }
@Validate() createOrUpdate(attributeConfig: IDossierAttributeConfig, dossierTemplateId: string) {
createOrUpdate(@RequiredParam() attributeConfig: IDossierAttributeConfig, dossierTemplateId: string) {
const url = `${this._defaultModelPath}/config/${dossierTemplateId}`; const url = `${this._defaultModelPath}/config/${dossierTemplateId}`;
return firstValueFrom(this._post(attributeConfig, url)); return firstValueFrom(this._post(attributeConfig, url));
} }
@Validate() getConfig(dossierTemplateId: string): Observable<List<IDossierAttributeConfig>> {
getConfig(@RequiredParam() dossierTemplateId: string): Observable<List<IDossierAttributeConfig>> {
return this._getOne<{ dossierAttributeConfigs: List<IDossierAttributeConfig> }>(['config', dossierTemplateId]).pipe( return this._getOne<{ dossierAttributeConfigs: List<IDossierAttributeConfig> }>(['config', dossierTemplateId]).pipe(
map(res => res.dossierAttributeConfigs), map(res => res.dossierAttributeConfigs),
); );
} }
@Validate() getAttributes(dossierId: string): Observable<List<IDossierAttribute>> {
getAttributes(@RequiredParam() dossierId: string): Observable<List<IDossierAttribute>> {
return this._getOne<{ dossierAttributeList?: List<IDossierAttribute> }>([dossierId]).pipe(map(res => res.dossierAttributeList)); return this._getOne<{ dossierAttributeList?: List<IDossierAttribute> }>([dossierId]).pipe(map(res => res.dossierAttributeList));
} }
} }

View File

@ -1,5 +1,5 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { EntitiesService, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { EntitiesService, mapEach, Toaster } from '@iqser/common-ui';
import { DossierState, IDossierState } from '@red/domain'; import { DossierState, IDossierState } from '@red/domain';
import { EMPTY, firstValueFrom, forkJoin, Observable, switchMap } from 'rxjs'; import { EMPTY, firstValueFrom, forkJoin, Observable, switchMap } from 'rxjs';
import { DossierTemplatesService } from '../dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '../dossier-templates/dossier-templates.service';
@ -22,8 +22,7 @@ export class DossierStatesService extends EntitiesService<IDossierState, Dossier
readonly #dossierTemplatesService = inject(DossierTemplatesService); readonly #dossierTemplatesService = inject(DossierTemplatesService);
readonly #dossierStatesMapService = inject(DossierStatesMapService); readonly #dossierStatesMapService = inject(DossierStatesMapService);
@Validate() async createOrUpdate(state: IDossierState) {
async createOrUpdate(@RequiredParam() state: IDossierState) {
const showToast = (error: HttpErrorResponse) => { const showToast = (error: HttpErrorResponse) => {
this.#toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG); this.#toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG);
return EMPTY; return EMPTY;
@ -37,8 +36,7 @@ export class DossierStatesService extends EntitiesService<IDossierState, Dossier
return firstValueFrom(request); return firstValueFrom(request);
} }
@Validate() loadAllForTemplate(templateId: string) {
loadAllForTemplate(@RequiredParam() templateId: string) {
return this.loadAll(`${this._defaultModelPath}/dossier-template/${templateId}`).pipe( return this.loadAll(`${this._defaultModelPath}/dossier-template/${templateId}`).pipe(
tap(states => this.#dossierStatesMapService.set(templateId, states)), tap(states => this.#dossierStatesMapService.set(templateId, states)),
); );

View File

@ -1,4 +1,4 @@
import { EntitiesService, List, RequiredParam, Validate } from '@iqser/common-ui'; import { EntitiesService, List } from '@iqser/common-ui';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs'; import { BehaviorSubject, Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
@ -10,17 +10,16 @@ export type FileAttributesConfigMap = Readonly<Record<string, IFileAttributesCon
providedIn: 'root', providedIn: 'root',
}) })
export class FileAttributesService extends EntitiesService<IFileAttributeConfig, FileAttributeConfig> { export class FileAttributesService extends EntitiesService<IFileAttributeConfig, FileAttributeConfig> {
readonly fileAttributesConfig$ = new BehaviorSubject<FileAttributesConfigMap>({});
readonly isEditingFileAttribute$ = new BehaviorSubject(false);
protected readonly _defaultModelPath = 'fileAttributes'; protected readonly _defaultModelPath = 'fileAttributes';
protected readonly _entityClass = FileAttributeConfig; protected readonly _entityClass = FileAttributeConfig;
readonly fileAttributesConfig$ = new BehaviorSubject<FileAttributesConfigMap>({});
readonly isEditingFileAttribute$ = new BehaviorSubject(false);
/** /**
* Get the file attributes that can be used at importing csv. * Get the file attributes that can be used at importing csv.
*/ */
@Validate()
loadFileAttributesConfig(@RequiredParam() dossierTemplateId: string): Observable<IFileAttributesConfig> { loadFileAttributesConfig(dossierTemplateId: string): Observable<IFileAttributesConfig> {
const request$ = this._getOne<IFileAttributesConfig>(['config', dossierTemplateId]); const request$ = this._getOne<IFileAttributesConfig>(['config', dossierTemplateId]);
return request$.pipe( return request$.pipe(
tap(entities => entities.fileAttributeConfigs.sort((c1, c2) => c1.placeholder.localeCompare(c2.placeholder))), tap(entities => entities.fileAttributeConfigs.sort((c1, c2) => c1.placeholder.localeCompare(c2.placeholder))),
@ -41,8 +40,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
/** /**
* Add or update a file attribute that can be used at importing csv. * Add or update a file attribute that can be used at importing csv.
*/ */
@Validate()
setFileAttributesConfig(@RequiredParam() body: IFileAttributeConfig, @RequiredParam() dossierTemplateId: string) { setFileAttributesConfig(body: IFileAttributeConfig, dossierTemplateId: string) {
const url = `${this._defaultModelPath}/config/fileAttribute/${dossierTemplateId}`; const url = `${this._defaultModelPath}/config/fileAttribute/${dossierTemplateId}`;
return this._post<unknown>(body, url); return this._post<unknown>(body, url);
} }
@ -50,8 +49,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
/** /**
* Set file attributes base configuration and a list of file attributes, * Set file attributes base configuration and a list of file attributes,
*/ */
@Validate()
setFileAttributeConfig(@RequiredParam() body: IFileAttributesConfig, @RequiredParam() dossierTemplateId: string) { setFileAttributeConfig(body: IFileAttributesConfig, dossierTemplateId: string) {
const url = `${this._defaultModelPath}/config/baseConfig/${dossierTemplateId}`; const url = `${this._defaultModelPath}/config/baseConfig/${dossierTemplateId}`;
return this._put<unknown>(body, url); return this._put<unknown>(body, url);
} }
@ -59,8 +58,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
/** /**
* Set file attributes to an existing file * Set file attributes to an existing file
*/ */
@Validate()
setFileAttributes(@RequiredParam() body: FileAttributes, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { setFileAttributes(body: FileAttributes, dossierId: string, fileId: string) {
const url = `${this._defaultModelPath}/set/${dossierId}/${fileId}`; const url = `${this._defaultModelPath}/set/${dossierId}/${fileId}`;
return this._post<unknown>(body, url); return this._post<unknown>(body, url);
} }
@ -68,8 +67,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
/** /**
* Delete a specific file attribute. * Delete a specific file attribute.
*/ */
@Validate()
deleteFileAttributes(@RequiredParam() body: List, @RequiredParam() dossierTemplateId: string) { deleteFileAttributes(body: List, dossierTemplateId: string) {
const url = `${this._defaultModelPath}/config/fileAttribute/${dossierTemplateId}/delete`; const url = `${this._defaultModelPath}/config/fileAttribute/${dossierTemplateId}/delete`;
return this._post(body, url); return this._post(body, url);
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EntitiesService, RequiredParam, Validate } from '@iqser/common-ui'; import { EntitiesService } from '@iqser/common-ui';
import { ILegalBasis, Justification } from '@red/domain'; import { ILegalBasis, Justification } from '@red/domain';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -11,23 +11,19 @@ export class JustificationsService extends EntitiesService<ILegalBasis, Justific
protected readonly _defaultModelPath = 'legalBasis'; protected readonly _defaultModelPath = 'legalBasis';
protected readonly _entityClass = Justification; protected readonly _entityClass = Justification;
@Validate() createOrUpdate(justification: Justification, dossierTemplateId: string) {
createOrUpdate(@RequiredParam() justification: Justification, @RequiredParam() dossierTemplateId: string) {
return this._put(justification, `${this._defaultModelPath}/${dossierTemplateId}`); return this._put(justification, `${this._defaultModelPath}/${dossierTemplateId}`);
} }
@Validate() loadAll(dossierTemplateId: string): Observable<Justification[]> {
loadAll(@RequiredParam() dossierTemplateId: string): Observable<Justification[]> {
return super.loadAll(`${this._defaultModelPath}/${dossierTemplateId}`); return super.loadAll(`${this._defaultModelPath}/${dossierTemplateId}`);
} }
@Validate() delete(justificationNames: string[], dossierTemplateId: string): Observable<unknown> {
delete(@RequiredParam() justificationNames: string[], @RequiredParam() dossierTemplateId: string): Observable<unknown> {
return this._post(justificationNames, `${this._defaultModelPath}/${dossierTemplateId}/delete`); return this._post(justificationNames, `${this._defaultModelPath}/${dossierTemplateId}/delete`);
} }
@Validate() getForDossierTemplate(dossierTemplateId: string): Observable<Justification[]> {
getForDossierTemplate(@RequiredParam() dossierTemplateId: string): Observable<Justification[]> {
return super return super
.getAll(`${this._defaultModelPath}/${dossierTemplateId}`) .getAll(`${this._defaultModelPath}/${dossierTemplateId}`)
.pipe(map((entities: ILegalBasis[]) => entities.map(entity => new Justification(entity)))); .pipe(map((entities: ILegalBasis[]) => entities.map(entity => new Justification(entity))));

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { EntitiesService, List, mapEach, QueryParam, Toaster } from '@iqser/common-ui';
import { Dossier, File, IDossier, IFile, TrashDossier, TrashFile, TrashItem } from '@red/domain'; import { Dossier, File, IDossier, IFile, TrashDossier, TrashFile, TrashItem } from '@red/domain';
import { catchError, switchMap, take, tap } from 'rxjs/operators'; import { catchError, switchMap, take, tap } from 'rxjs/operators';
import { forkJoin, map, Observable, of } from 'rxjs'; import { forkJoin, map, Observable, of } from 'rxjs';
@ -44,8 +44,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
); );
} }
@Validate() restore(items: TrashItem[]): Observable<unknown> {
restore(@RequiredParam() items: TrashItem[]): Observable<unknown> {
return this.#executeAction( return this.#executeAction(
items, items,
(dossierIds: string[]) => this._restoreDossiers(dossierIds), (dossierIds: string[]) => this._restoreDossiers(dossierIds),
@ -53,8 +52,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
).pipe(tap(() => items.forEach(item => this.remove(item.id)))); ).pipe(tap(() => items.forEach(item => this.remove(item.id))));
} }
@Validate() hardDelete(items: TrashItem[]): Observable<unknown> {
hardDelete(@RequiredParam() items: TrashItem[]): Observable<unknown> {
return this.#executeAction( return this.#executeAction(
items, items,
(dossierIds: string[]) => this._hardDeleteDossiers(dossierIds), (dossierIds: string[]) => this._hardDeleteDossiers(dossierIds),
@ -104,6 +102,26 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
); );
} }
private _restoreDossiers(dossierIds: string[]): Observable<unknown> {
return this._post(dossierIds, 'deleted-dossiers/restore').pipe(switchMap(() => this._activeDossiersService.loadAll()));
}
private _hardDeleteDossiers(dossierIds: string[]): Observable<unknown> {
const body = dossierIds.map<QueryParam>(id => ({ key: 'dossierId', value: id }));
return this.delete(body, 'deleted-dossiers/hard-delete', body);
}
private _hardDeleteFiles(dossierId: string, fileIds: List) {
const queryParams = fileIds.map<QueryParam>(id => ({ key: 'fileIds', value: id }));
return super
.delete({}, `delete/hard-delete/${dossierId}`, queryParams)
.pipe(switchMap(() => this._dossierStatsService.getFor([dossierId])));
}
private _restoreFiles(dossierId: string, fileIds: List) {
return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
}
#executeAction(items: TrashItem[], dossiersFn: (...args) => Observable<unknown>, filesFn: (...args) => Observable<unknown>) { #executeAction(items: TrashItem[], dossiersFn: (...args) => Observable<unknown>, filesFn: (...args) => Observable<unknown>) {
const requests$ = []; const requests$ = [];
@ -121,24 +139,4 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
return forkJoin(requests$.map(r => r.pipe(take(1)))); return forkJoin(requests$.map(r => r.pipe(take(1))));
} }
private _restoreDossiers(@RequiredParam() dossierIds: string[]): Observable<unknown> {
return this._post(dossierIds, 'deleted-dossiers/restore').pipe(switchMap(() => this._activeDossiersService.loadAll()));
}
private _hardDeleteDossiers(@RequiredParam() dossierIds: string[]): Observable<unknown> {
const body = dossierIds.map<QueryParam>(id => ({ key: 'dossierId', value: id }));
return this.delete(body, 'deleted-dossiers/hard-delete', body);
}
private _hardDeleteFiles(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) {
const queryParams = fileIds.map<QueryParam>(id => ({ key: 'fileIds', value: id }));
return super
.delete({}, `delete/hard-delete/${dossierId}`, queryParams)
.pipe(switchMap(() => this._dossierStatsService.getFor([dossierId])));
}
private _restoreFiles(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) {
return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
}
} }

View File

@ -1,5 +1,5 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { GenericService, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, mapEach, QueryParam } from '@iqser/common-ui';
import { IWatermark, Watermark } from '@red/domain'; import { IWatermark, Watermark } from '@red/domain';
import { firstValueFrom, forkJoin, Observable } from 'rxjs'; import { firstValueFrom, forkJoin, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
@ -16,21 +16,18 @@ export class WatermarkService extends GenericService<IWatermark> {
protected readonly _defaultModelPath = 'watermark'; protected readonly _defaultModelPath = 'watermark';
readonly #watermarksMapService = inject(WatermarksMapService); readonly #watermarksMapService = inject(WatermarksMapService);
@Validate() async deleteWatermark(dossierTemplateId: string, watermarkId: number) {
async deleteWatermark(@RequiredParam() dossierTemplateId: string, @RequiredParam() watermarkId: number) {
await firstValueFrom(super.delete(null, `${this._defaultModelPath}/${watermarkId}`)); await firstValueFrom(super.delete(null, `${this._defaultModelPath}/${watermarkId}`));
return firstValueFrom(this.loadForDossierTemplate(dossierTemplateId)); return firstValueFrom(this.loadForDossierTemplate(dossierTemplateId));
} }
@Validate() async saveWatermark(body: IWatermark) {
async saveWatermark(@RequiredParam() body: IWatermark) {
const watermark = await firstValueFrom(this._post(body, `${this._defaultModelPath}`)); const watermark = await firstValueFrom(this._post(body, `${this._defaultModelPath}`));
await firstValueFrom(this.loadForDossierTemplate(watermark.dossierTemplateId)); await firstValueFrom(this.loadForDossierTemplate(watermark.dossierTemplateId));
return this.#watermarksMapService.get(watermark.dossierTemplateId, watermark.id); return this.#watermarksMapService.get(watermark.dossierTemplateId, watermark.id);
} }
@Validate() loadForDossierTemplate(dossierTemplateId: string): Observable<Watermark[]> {
loadForDossierTemplate(@RequiredParam() dossierTemplateId: string): Observable<Watermark[]> {
const queryParams: QueryParam[] = [{ key: 'dossierTemplateId', value: dossierTemplateId }]; const queryParams: QueryParam[] = [{ key: 'dossierTemplateId', value: dossierTemplateId }];
return this.getAll(this._defaultModelPath, queryParams).pipe( return this.getAll(this._defaultModelPath, queryParams).pipe(
mapEach(entity => new Watermark(entity)), mapEach(entity => new Watermark(entity)),
@ -38,13 +35,11 @@ export class WatermarkService extends GenericService<IWatermark> {
); );
} }
@Validate() loadAll(dossierTemplateIds: string[]): Observable<Watermark[]> {
loadAll(@RequiredParam() dossierTemplateIds: string[]): Observable<Watermark[]> {
return forkJoin(dossierTemplateIds.map(id => this.loadForDossierTemplate(id))).pipe(map(arrays => [].concat(...arrays))); return forkJoin(dossierTemplateIds.map(id => this.loadForDossierTemplate(id))).pipe(map(arrays => [].concat(...arrays)));
} }
@Validate() async isWatermarkUsed(watermarkId: number) {
async isWatermarkUsed(@RequiredParam() watermarkId: number) {
const queryParams: QueryParam[] = [{ key: 'watermarkId', value: watermarkId }]; const queryParams: QueryParam[] = [{ key: 'watermarkId', value: watermarkId }];
const result = await firstValueFrom(this.getAll<IsUsedResponse>(`${this._defaultModelPath}/used`, queryParams)); const result = await firstValueFrom(this.getAll<IsUsedResponse>(`${this._defaultModelPath}/used`, queryParams));
return result.value; return result.value;

View File

@ -1,5 +1,5 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { GenericService, Toaster } from '@iqser/common-ui';
import { Earmark, EarmarkOperation, EarmarkResponse } from '@red/domain'; import { Earmark, EarmarkOperation, EarmarkResponse } from '@red/domain';
import { catchError, map, tap } from 'rxjs/operators'; import { catchError, map, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
@ -13,8 +13,7 @@ export class EarmarksService extends GenericService<EarmarkResponse> {
protected readonly _defaultModelPath = ''; protected readonly _defaultModelPath = '';
readonly #toaster = inject(Toaster); readonly #toaster = inject(Toaster);
@Validate() getEarmarks(dossierId: string, fileId: string): Observable<AnnotationWrapper[]> {
getEarmarks(@RequiredParam() dossierId: string, @RequiredParam() fileId: string): Observable<AnnotationWrapper[]> {
return this._http.get<{ highlights: Earmark[] }>(`/${this.#getPath(dossierId, fileId)}`).pipe( return this._http.get<{ highlights: Earmark[] }>(`/${this.#getPath(dossierId, fileId)}`).pipe(
map(response => response.highlights), map(response => response.highlights),
map(highlights => highlights.map(highlight => AnnotationWrapper.fromEarmark(highlight))), map(highlights => highlights.map(highlight => AnnotationWrapper.fromEarmark(highlight))),
@ -23,13 +22,7 @@ export class EarmarksService extends GenericService<EarmarkResponse> {
); );
} }
@Validate() performHighlightsAction(ids: string[], dossierId: string, fileId: string, operation: EarmarkOperation) {
performHighlightsAction(
@RequiredParam() ids: string[],
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
@RequiredParam() operation: EarmarkOperation,
) {
return this._post({ ids }, `${this.#getPath(dossierId, fileId)}/${operation}`).pipe( return this._post({ ids }, `${this.#getPath(dossierId, fileId)}/${operation}`).pipe(
tap(() => this.#toaster.success(_('highlight-action-dialog.success'), { params: { operation } })), tap(() => this.#toaster.success(_('highlight-action-dialog.success'), { params: { operation } })),
); );

View File

@ -1,4 +1,4 @@
import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, HeadersConfiguration, List, QueryParam } from '@iqser/common-ui';
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { HttpEvent, HttpHeaders, HttpResponse } from '@angular/common/http'; import { HttpEvent, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -10,29 +10,21 @@ import { File, IPageRotationRequest } from '@red/domain';
providedIn: 'root', providedIn: 'root',
}) })
export class FileManagementService extends GenericService<unknown> { export class FileManagementService extends GenericService<unknown> {
readonly #filesService = inject(FilesService);
protected readonly _defaultModelPath = ''; protected readonly _defaultModelPath = '';
readonly #filesService = inject(FilesService);
@Validate() delete(files: List<File>, dossierId: string) {
delete(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
const fileIds = files.map(f => f.id); const fileIds = files.map(f => f.id);
return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this.#filesService.loadAll(dossierId))); return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this.#filesService.loadAll(dossierId)));
} }
@Validate() rotatePage(body: IPageRotationRequest, dossierId: string, fileId: string) {
rotatePage(@RequiredParam() body: IPageRotationRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `rotate/${dossierId}/${fileId}`); return this._post(body, `rotate/${dossierId}/${fileId}`);
} }
downloadOriginal(dossierId: string, fileId: string, observe?: 'events', indicator?: string): Observable<HttpEvent<Blob>>; downloadOriginal(dossierId: string, fileId: string, observe?: 'events', indicator?: string): Observable<HttpEvent<Blob>>;
downloadOriginal(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>; downloadOriginal(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>;
@Validate() downloadOriginal(dossierId: string, fileId: string, observe: 'events' | 'response' = 'events', indicator?: string) {
downloadOriginal(
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
observe: 'events' | 'response' = 'events',
indicator?: string,
) {
const queryParams: QueryParam[] = [{ key: 'inline', value: true }]; const queryParams: QueryParam[] = [{ key: 'inline', value: true }];
if (indicator) { if (indicator) {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EntitiesService, isArray, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { EntitiesService, isArray, List, mapEach, QueryParam } from '@iqser/common-ui';
import { File, IFile } from '@red/domain'; import { File, IFile } from '@red/domain';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
@ -45,41 +45,35 @@ export class FilesService extends EntitiesService<IFile, File> {
); );
} }
@Validate() async setAssignee(files: File | List<File>, assigneeId: string) {
async setAssignee(@RequiredParam() files: File | List<File>, assigneeId: string) {
const _files = asList(files); const _files = asList(files);
const url = `${this._defaultModelPath}/set-assignee/${_files[0].dossierId}/bulk`; const url = `${this._defaultModelPath}/set-assignee/${_files[0].dossierId}/bulk`;
return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]); return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]);
} }
@Validate() async setToNew(files: File | List<File>) {
async setToNew(@RequiredParam() files: File | List<File>) {
const _files = asList(files); const _files = asList(files);
return this.#makePost(_files, `${this._defaultModelPath}/new/${_files[0].dossierId}/bulk`); return this.#makePost(_files, `${this._defaultModelPath}/new/${_files[0].dossierId}/bulk`);
} }
@Validate() async setUnderApproval(files: File | List<File>, assigneeId: string) {
async setUnderApproval(@RequiredParam() files: File | List<File>, assigneeId: string) {
const _files = asList(files); const _files = asList(files);
const url = `${this._defaultModelPath}/under-approval/${_files[0].dossierId}/bulk`; const url = `${this._defaultModelPath}/under-approval/${_files[0].dossierId}/bulk`;
return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]); return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]);
} }
@Validate() async setReviewer(files: File | List<File>, assigneeId: string) {
async setReviewer(@RequiredParam() files: File | List<File>, assigneeId: string) {
const _files = asList(files); const _files = asList(files);
const url = `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`; const url = `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`;
return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]); return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]);
} }
@Validate() async setApproved(files: File | List<File>) {
async setApproved(@RequiredParam() files: File | List<File>) {
const _files = asList(files); const _files = asList(files);
return this.#makePost(_files, `${this._defaultModelPath}/approved/${_files[0].dossierId}/bulk`); return this.#makePost(_files, `${this._defaultModelPath}/approved/${_files[0].dossierId}/bulk`);
} }
@Validate() async setUnderReviewFor(files: File | List<File>) {
async setUnderReviewFor(@RequiredParam() files: File | List<File>) {
const _files = asList(files); const _files = asList(files);
return this.#makePost(_files, `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`); return this.#makePost(_files, `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`);
} }

View File

@ -1,12 +1,11 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, HeadersConfiguration, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, HeadersConfiguration } from '@iqser/common-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class RedactionImportService extends GenericService<void> { export class RedactionImportService extends GenericService<void> {
protected readonly _defaultModelPath = 'import-redactions'; protected readonly _defaultModelPath = 'import-redactions';
@Validate() importRedactions(dossierId: string, fileId: string, file: Blob, pagesToImport: Set<number> | null) {
importRedactions(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, file: Blob, pagesToImport: Set<number> | null) {
const formParams = new FormData(); const formParams = new FormData();
if (file !== undefined) { if (file !== undefined) {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, QueryParam } from '@iqser/common-ui';
import { IRedactionLog, ISectionGrid } from '@red/domain'; import { IRedactionLog, ISectionGrid } from '@red/domain';
import { catchError, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
import { of } from 'rxjs'; import { of } from 'rxjs';
@ -10,8 +10,7 @@ import { of } from 'rxjs';
export class RedactionLogService extends GenericService<unknown> { export class RedactionLogService extends GenericService<unknown> {
protected readonly _defaultModelPath = ''; protected readonly _defaultModelPath = '';
@Validate() getRedactionLog(dossierId: string, fileId: string, withManualRedactions?: boolean) {
getRedactionLog(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, withManualRedactions?: boolean) {
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
if (withManualRedactions) { if (withManualRedactions) {
queryParams.push({ key: 'withManualRedactions', value: withManualRedactions }); queryParams.push({ key: 'withManualRedactions', value: withManualRedactions });
@ -24,8 +23,7 @@ export class RedactionLogService extends GenericService<unknown> {
); );
} }
@Validate() getSectionGrid(dossierId: string, fileId: string) {
getSectionGrid(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._getOne<ISectionGrid>([dossierId, fileId], 'sectionGrid'); return this._getOne<ISectionGrid>([dossierId, fileId], 'sectionGrid');
} }
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, QueryParam } from '@iqser/common-ui';
import { IRssData, IRssEntry, RssEntry } from '@red/domain'; import { IRssData, IRssEntry, RssEntry } from '@red/domain';
import { catchError, map, tap } from 'rxjs/operators'; import { catchError, map, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
@ -10,8 +10,7 @@ import { saveAs } from 'file-saver';
export class RssService extends GenericService<void> { export class RssService extends GenericService<void> {
protected readonly _defaultModelPath = 'import-redactions'; protected readonly _defaultModelPath = 'import-redactions';
@Validate() getRSSData(dossierId: string, fileId: string): Observable<RssEntry> {
getRSSData(@RequiredParam() dossierId: string, @RequiredParam() fileId: string): Observable<RssEntry> {
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
queryParams.push({ key: 'fileId', value: fileId }); queryParams.push({ key: 'fileId', value: fileId });
@ -22,8 +21,7 @@ export class RssService extends GenericService<void> {
); );
} }
@Validate() getRSSExportData(dossierId: string, fileId: string): Observable<any> {
getRSSExportData(@RequiredParam() dossierId: string, @RequiredParam() fileId: string): Observable<any> {
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
queryParams.push({ key: 'fileId', value: fileId }); queryParams.push({ key: 'fileId', value: fileId });
@ -33,21 +31,11 @@ export class RssService extends GenericService<void> {
); );
} }
@Validate() override(dossierId: string, fileId: string, componentOverrides: Record<string, string>): Observable<void> {
override(
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
@RequiredParam() componentOverrides: Record<string, string>,
): Observable<void> {
return this._post({ componentOverrides }, `rss/override/${dossierId}/${fileId}`); return this._post({ componentOverrides }, `rss/override/${dossierId}/${fileId}`);
} }
@Validate() revertOverride(dossierId: string, fileId: string, components: string[]): Observable<void> {
revertOverride(
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
@RequiredParam() components: string[],
): Observable<void> {
return this._post({ components }, `rss/override/revert/${dossierId}/${fileId}`); return this._post({ components }, `rss/override/revert/${dossierId}/${fileId}`);
} }
@ -69,8 +57,7 @@ export class RssService extends GenericService<void> {
); );
} }
@Validate() private _getRSSDataAsXML(dossierId: string, fileId: string) {
private _getRSSDataAsXML(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
queryParams.push({ key: 'fileId', value: fileId }); queryParams.push({ key: 'fileId', value: fileId });

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, mapEach } from '@iqser/common-ui';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { IViewedPage, IViewedPagesRequest, ViewedPage } from '@red/domain'; import { IViewedPage, IViewedPagesRequest, ViewedPage } from '@red/domain';
import { firstValueFrom, of } from 'rxjs'; import { firstValueFrom, of } from 'rxjs';
@ -10,20 +10,17 @@ import { firstValueFrom, of } from 'rxjs';
export class ViewedPagesService extends GenericService<unknown> { export class ViewedPagesService extends GenericService<unknown> {
protected readonly _defaultModelPath = 'viewedPages'; protected readonly _defaultModelPath = 'viewedPages';
@Validate() add(body: IViewedPagesRequest, dossierId: string, fileId: string) {
add(@RequiredParam() body: IViewedPagesRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const modelPath = `${this._defaultModelPath}/${dossierId}/${fileId}`; const modelPath = `${this._defaultModelPath}/${dossierId}/${fileId}`;
return firstValueFrom(this._post(body, modelPath)); return firstValueFrom(this._post(body, modelPath));
} }
@Validate() remove(dossierId: string, fileId: string, page: number) {
remove(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, @RequiredParam() page: number) {
const modelPath = `${this._defaultModelPath}/${dossierId}/${fileId}/${page}`; const modelPath = `${this._defaultModelPath}/${dossierId}/${fileId}/${page}`;
return firstValueFrom(super.delete({}, modelPath)); return firstValueFrom(super.delete({}, modelPath));
} }
@Validate() load(dossierId: string, fileId: string) {
load(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const request = this._getOne<{ pages?: IViewedPage[] }>([dossierId, fileId]).pipe( const request = this._getOne<{ pages?: IViewedPage[] }>([dossierId, fileId]).pipe(
map(res => res.pages), map(res => res.pages),
catchError(() => of([] as IViewedPage[])), catchError(() => of([] as IViewedPage[])),

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService } from '@iqser/common-ui';
import { IGeneralConfiguration } from '@red/domain'; import { IGeneralConfiguration } from '@red/domain';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -13,8 +13,7 @@ export class GeneralSettingsService extends GenericService<IGeneralConfiguration
return this._getOne(['general']); return this._getOne(['general']);
} }
@Validate() updateGeneralConfigurations(body: IGeneralConfiguration) {
updateGeneralConfigurations(@RequiredParam() body: IGeneralConfiguration) {
return this._post<unknown>(body, `${this._defaultModelPath}/general`); return this._post<unknown>(body, `${this._defaultModelPath}/general`);
} }
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, QueryParam } from '@iqser/common-ui';
import { ILicense, ILicenseReport, ILicenseReportRequest, ILicenses } from '@red/domain'; import { ILicense, ILicenseReport, ILicenseReportRequest, ILicenses } from '@red/domain';
import { BehaviorSubject, firstValueFrom, Observable, of } from 'rxjs'; import { BehaviorSubject, firstValueFrom, Observable, of } from 'rxjs';
import { catchError, filter, tap } from 'rxjs/operators'; import { catchError, filter, tap } from 'rxjs/operators';
@ -128,8 +128,7 @@ export class LicenseService extends GenericService<ILicenseReport> {
this.setSelectedLicense(this.activeLicense); this.setSelectedLicense(this.activeLicense);
} }
@Validate() getReport(body: ILicenseReportRequest, limit?: number, offset?: number) {
getReport(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
if (limit) { if (limit) {
queryParams.push({ key: 'limit', value: limit }); queryParams.push({ key: 'limit', value: limit });

View File

@ -1,15 +1,5 @@
import { Inject, Injectable, OnDestroy } from '@angular/core'; import { Inject, Injectable, OnDestroy } from '@angular/core';
import { import { BASE_HREF, EntitiesService, getConfig, List, mapEach, QueryParam, TenantContextHolder } from '@iqser/common-ui';
BASE_HREF,
EntitiesService,
getConfig,
List,
mapEach,
QueryParam,
RequiredParam,
TenantContextHolder,
Validate,
} from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { EMPTY, firstValueFrom, iif, merge, Observable, of, Subscription, timer } from 'rxjs'; import { EMPTY, firstValueFrom, iif, merge, Observable, of, Subscription, timer } from 'rxjs';
import { AppConfig, Dossier, INotification, Notification, NotificationTypes } from '@red/domain'; import { AppConfig, Dossier, INotification, Notification, NotificationTypes } from '@red/domain';
@ -57,8 +47,7 @@ export class NotificationsService extends EntitiesService<INotification, Notific
this.#subscription.unsubscribe(); this.#subscription.unsubscribe();
} }
@Validate() async toggleNotificationRead(body: List, setRead: boolean) {
async toggleNotificationRead(@RequiredParam() body: List, @RequiredParam() setRead: boolean) {
let queryParam: QueryParam; let queryParam: QueryParam;
if (setRead !== undefined && setRead !== null) { if (setRead !== undefined && setRead !== null) {
queryParam = { key: 'setRead', value: setRead }; queryParam = { key: 'setRead', value: setRead };
@ -78,6 +67,41 @@ export class NotificationsService extends EntitiesService<INotification, Notific
); );
} }
private _new(notification: INotification) {
const message = this._translate(notification, notificationsTranslations[notification.notificationType] as string);
return new Notification(notification, message);
}
private _translate(notification: INotification, translation: string): string {
const fileId = notification.target.fileId;
const dossierId = notification.target.dossierId;
const dossier = this._dossiersCacheService.get(dossierId);
const fileName = notification.target.fileName;
const dossierName = notification.target?.dossierName ?? dossier?.dossierName;
const downloadHref = `/ui/${this._tenantContextHolder.currentTenant}/main/downloads`;
return this._translateService.instant(translation, {
fileHref: this._getFileHref(dossier, fileId),
dossierHref: this._getDossierHref(dossier),
dossierName: dossierName ?? this._translateService.instant(_('notifications.deleted-dossier')),
fileName: fileName ?? this._translateService.instant(_('file')),
user: this._getUsername(notification.userId),
downloadHref: downloadHref,
});
}
private _getFileHref(dossier: Dossier, fileId: string): string {
return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null;
}
private _getDossierHref(dossier: Dossier): string {
return dossier ? `${this._baseHref}/${this._tenantContextHolder.currentTenant}${dossier.routerLink}` : null;
}
private _getUsername(userId: string | undefined) {
return this._userService.getName(userId) || this._translateService.instant(_('unknown'));
}
#initTimerAndChanges() { #initTimerAndChanges() {
const timer$ = timer(0, CHANGED_CHECK_INTERVAL).pipe( const timer$ = timer(0, CHANGED_CHECK_INTERVAL).pipe(
switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))), switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))),
@ -119,39 +143,4 @@ export class NotificationsService extends EntitiesService<INotification, Notific
#loadNotificationsIfChanged(): Observable<Notification[]> { #loadNotificationsIfChanged(): Observable<Notification[]> {
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll(), EMPTY))); return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll(), EMPTY)));
} }
private _new(notification: INotification) {
const message = this._translate(notification, notificationsTranslations[notification.notificationType] as string);
return new Notification(notification, message);
}
private _translate(notification: INotification, translation: string): string {
const fileId = notification.target.fileId;
const dossierId = notification.target.dossierId;
const dossier = this._dossiersCacheService.get(dossierId);
const fileName = notification.target.fileName;
const dossierName = notification.target?.dossierName ?? dossier?.dossierName;
const downloadHref = `/ui/${this._tenantContextHolder.currentTenant}/main/downloads`;
return this._translateService.instant(translation, {
fileHref: this._getFileHref(dossier, fileId),
dossierHref: this._getDossierHref(dossier),
dossierName: dossierName ?? this._translateService.instant(_('notifications.deleted-dossier')),
fileName: fileName ?? this._translateService.instant(_('file')),
user: this._getUsername(notification.userId),
downloadHref: downloadHref,
});
}
private _getFileHref(dossier: Dossier, fileId: string): string {
return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null;
}
private _getDossierHref(dossier: Dossier): string {
return dossier ? `${this._baseHref}/${this._tenantContextHolder.currentTenant}${dossier.routerLink}` : null;
}
private _getUsername(userId: string | undefined) {
return this._userService.getName(userId) || this._translateService.instant(_('unknown'));
}
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { GenericService, List, QueryParam, Toaster } from '@iqser/common-ui';
import { Dossier, File, IPageExclusionRequest } from '@red/domain'; import { Dossier, File, IPageExclusionRequest } from '@red/domain';
import { catchError, switchMap, tap } from 'rxjs/operators'; import { catchError, switchMap, tap } from 'rxjs/operators';
import { FilesService } from './files/files.service'; import { FilesService } from './files/files.service';
@ -26,18 +26,15 @@ export class ReanalysisService extends GenericService<unknown> {
super(); super();
} }
@Validate() excludePages(body: IPageExclusionRequest, dossierId: string, file: File) {
excludePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) {
return this._post(body, `exclude-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file))); return this._post(body, `exclude-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file)));
} }
@Validate() includePages(body: IPageExclusionRequest, dossierId: string, file: File) {
includePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) {
return this._post(body, `include-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file))); return this._post(body, `include-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file)));
} }
@Validate() reanalyzeFilesForDossier(files: List<File>, dossierId: string, params?: ReanalyzeQueryParams) {
reanalyzeFilesForDossier(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) {
const fileIds = files.map(f => f.id); const fileIds = files.map(f => f.id);
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
if (params?.force) { if (params?.force) {
@ -50,8 +47,7 @@ export class ReanalysisService extends GenericService<unknown> {
return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId))); return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
} }
@Validate() toggleAnalysis(dossierId: string, files: List<File>, excluded?: boolean) {
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: List<File>, excluded?: boolean) {
const fileIds = files.map(f => f.id); const fileIds = files.map(f => f.id);
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
if (excluded) { if (excluded) {
@ -63,8 +59,7 @@ export class ReanalysisService extends GenericService<unknown> {
); );
} }
@Validate() toggleAutomaticAnalysis(dossierId: string, files: File[]) {
toggleAutomaticAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: File[]) {
const fileIds = files.map(file => file.id); const fileIds = files.map(file => file.id);
const excluded = !files[0].excludedFromAutomaticAnalysis; const excluded = !files[0].excludedFromAutomaticAnalysis;
const queryParams: QueryParam[] = [{ key: 'excluded', value: excluded }]; const queryParams: QueryParam[] = [{ key: 'excluded', value: excluded }];
@ -82,14 +77,12 @@ export class ReanalysisService extends GenericService<unknown> {
); );
} }
@Validate() ocrFiles(files: List<File>, dossierId: string) {
ocrFiles(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
const fileIds = files.map(f => f.id); const fileIds = files.map(f => f.id);
return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMap(() => this._filesService.loadAll(dossierId))); return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
} }
@Validate() async reanalyzeDossier(dossier: Dossier, force?: boolean) {
async reanalyzeDossier(@RequiredParam() dossier: Dossier, force?: boolean) {
const { dossierId } = dossier; const { dossierId } = dossier;
const queryParams: QueryParam[] = []; const queryParams: QueryParam[] = [];
if (force) { if (force) {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, HeadersConfiguration, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, HeadersConfiguration } from '@iqser/common-ui';
import { IPlaceholdersResponse, IReportTemplate } from '@red/domain'; import { IPlaceholdersResponse, IReportTemplate } from '@red/domain';
import { firstValueFrom, Observable, of } from 'rxjs'; import { firstValueFrom, Observable, of } from 'rxjs';
import { HttpResponse } from '@angular/common/http'; import { HttpResponse } from '@angular/common/http';
@ -11,8 +11,7 @@ import { catchError, map } from 'rxjs/operators';
export class ReportTemplateService extends GenericService<unknown> { export class ReportTemplateService extends GenericService<unknown> {
protected readonly _defaultModelPath = 'templateUpload'; protected readonly _defaultModelPath = 'templateUpload';
@Validate() uploadTemplateForm(dossierTemplateId: string, multiFileReport?: boolean, file?: Blob) {
uploadTemplateForm(@RequiredParam() dossierTemplateId: string, multiFileReport?: boolean, file?: Blob) {
const formParams = new FormData(); const formParams = new FormData();
if (multiFileReport !== undefined) { if (multiFileReport !== undefined) {
@ -27,35 +26,26 @@ export class ReportTemplateService extends GenericService<unknown> {
}); });
} }
@Validate() delete(dossierTemplateId: string, templateId: string) {
delete(@RequiredParam() dossierTemplateId: string, @RequiredParam() templateId: string) {
return super.delete({}, `${this._defaultModelPath}/${dossierTemplateId}/${templateId}`); return super.delete({}, `${this._defaultModelPath}/${dossierTemplateId}/${templateId}`);
} }
@Validate() getAvailableReportTemplates(dossierTemplateId: string) {
getAvailableReportTemplates(@RequiredParam() dossierTemplateId: string) {
const request = this.getAll<IReportTemplate[]>(`${this._defaultModelPath}/${dossierTemplateId}`); const request = this.getAll<IReportTemplate[]>(`${this._defaultModelPath}/${dossierTemplateId}`);
return firstValueFrom(request.pipe(map(templates => this._sortAvailableReportTypes(templates)))); return firstValueFrom(request.pipe(map(templates => this._sortAvailableReportTypes(templates))));
} }
@Validate() getAvailablePlaceholders(dossierTemplateId: string) {
getAvailablePlaceholders(@RequiredParam() dossierTemplateId: string) {
return this._getOne<IPlaceholdersResponse>([dossierTemplateId], 'placeholders'); return this._getOne<IPlaceholdersResponse>([dossierTemplateId], 'placeholders');
} }
@Validate() getTemplatesByPlaceholder(dossierTemplateId: string, attributeId: string) {
getTemplatesByPlaceholder(@RequiredParam() dossierTemplateId: string, @RequiredParam() attributeId: string) {
return this._post<IReportTemplate[]>({ value: attributeId }, `templates/${dossierTemplateId}`).pipe(catchError(() => of([]))); return this._post<IReportTemplate[]>({ value: attributeId }, `templates/${dossierTemplateId}`).pipe(catchError(() => of([])));
} }
downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'response'): Observable<HttpResponse<Blob>>; downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'response'): Observable<HttpResponse<Blob>>;
downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'body'): Observable<Blob>; downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'body'): Observable<Blob>;
@Validate() downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'body' | 'response' = 'body') {
downloadReportTemplate(
@RequiredParam() dossierTemplateId: string,
@RequiredParam() templateId: string,
observe: 'body' | 'response' = 'body',
) {
return this._http.request('get', `/${this._defaultModelPath}/${dossierTemplateId}/${templateId}`, { return this._http.request('get', `/${this._defaultModelPath}/${dossierTemplateId}/${templateId}`, {
responseType: 'blob', responseType: 'blob',
observe: observe, observe: observe,

@ -1 +1 @@
Subproject commit e46649c656813bbe9252678295f4913bb705d937 Subproject commit ba4d7f6d0594706f33fcb6af54c46b3e68f4c905

View File

@ -22,17 +22,17 @@
"*.{ts,js,html}": "eslint --fix" "*.{ts,js,html}": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "15.1.2", "@angular/animations": "16.0.1",
"@angular/cdk": "15.1.2", "@angular/cdk": "15.1.2",
"@angular/common": "15.1.2", "@angular/common": "16.0.1",
"@angular/compiler": "15.1.2", "@angular/compiler": "16.0.1",
"@angular/core": "15.1.2", "@angular/core": "16.0.1",
"@angular/forms": "15.1.2", "@angular/forms": "16.0.1",
"@angular/material": "15.1.2", "@angular/material": "15.1.2",
"@angular/platform-browser": "15.1.2", "@angular/platform-browser": "16.0.1",
"@angular/platform-browser-dynamic": "15.1.2", "@angular/platform-browser-dynamic": "16.0.1",
"@angular/router": "15.1.2", "@angular/router": "16.0.1",
"@angular/service-worker": "15.1.2", "@angular/service-worker": "16.0.1",
"@materia-ui/ngx-monaco-editor": "^6.0.0", "@materia-ui/ngx-monaco-editor": "^6.0.0",
"@messageformat/core": "^3.1.0", "@messageformat/core": "^3.1.0",
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^14.0.0",
@ -61,17 +61,17 @@
"@nx/angular": "16.1.4" "@nx/angular": "16.1.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "15.1.4", "@angular-devkit/build-angular": "16.0.1",
"@angular-devkit/core": "15.1.4", "@angular-devkit/core": "16.0.1",
"@angular-devkit/schematics": "15.1.4", "@angular-devkit/schematics": "16.0.1",
"@angular-eslint/builder": "15.2.1", "@angular-eslint/builder": "15.2.1",
"@angular-eslint/eslint-plugin": "16.0.1", "@angular-eslint/eslint-plugin": "16.0.1",
"@angular-eslint/eslint-plugin-template": "16.0.1", "@angular-eslint/eslint-plugin-template": "16.0.1",
"@angular-eslint/schematics": "15.2.1", "@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "16.0.1", "@angular-eslint/template-parser": "16.0.1",
"@angular/cli": "~15.1.0", "@angular/cli": "16.0.1",
"@angular/compiler-cli": "15.1.2", "@angular/compiler-cli": "16.0.1",
"@angular/language-service": "15.1.2", "@angular/language-service": "16.0.1",
"@bartholomej/ngx-translate-extract": "^8.0.2", "@bartholomej/ngx-translate-extract": "^8.0.2",
"@nx/eslint-plugin": "16.1.4", "@nx/eslint-plugin": "16.1.4",
"@nx/jest": "16.1.4", "@nx/jest": "16.1.4",

4098
yarn.lock

File diff suppressed because it is too large Load Diff