diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts index 443ed5c06..4245fb641 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts @@ -48,7 +48,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { ) {} private get _buttons(): Action[] { - return [ + const actions: Action[] = [ { id: 'delete-files-btn', type: ActionTypes.circleBtn, @@ -161,7 +161,9 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { checked: !this.#allFilesAreExcluded, show: this.#canToggleAnalysis, }, - ].filter(btn => btn.show); + ]; + + return actions.filter(btn => btn.show); } ngOnChanges() { diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts index 657dead78..3cce7aa73 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/table-item/table-item.component.ts @@ -1,11 +1,10 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { Dossier, File, IFileAttributeConfig } from '@red/domain'; @Component({ selector: 'redaction-table-item [file] [dossier] [displayedAttributes] [dossierTemplateId]', templateUrl: './table-item.component.html', styleUrls: ['./table-item.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableItemComponent { @Input() file: File; diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts index 35da040ca..f2a7488d8 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/workflow-item/workflow-item.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; import { Dossier, File, IFileAttributeConfig } from '@red/domain'; import { Debounce } from '@iqser/common-ui'; @@ -6,7 +6,6 @@ import { Debounce } from '@iqser/common-ui'; selector: 'redaction-workflow-item [file] [dossier] [displayedAttributes]', templateUrl: './workflow-item.component.html', styleUrls: ['./workflow-item.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class WorkflowItemComponent implements OnInit { @Input() file: File; diff --git a/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts b/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts index 343e61cd6..6ba3e94ff 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts @@ -29,7 +29,7 @@ export class BulkActionsService { this._assignFiles(files, WorkflowFileStatuses.UNDER_APPROVAL, true); } else { this._loadingService.start(); - await firstValueFrom(this._filesService.setUnderApprovalFor(files, dossier.id, dossier.approverIds[0])); + await this._filesService.setUnderApproval(files, dossier.approverIds[0]); this._loadingService.stop(); } } @@ -82,13 +82,13 @@ export class BulkActionsService { async backToUnderReview(files: File[]): Promise { this._loadingService.start(); - await firstValueFrom(this._filesService.setUnderReviewFor(files, files[0].dossierId)); + await this._filesService.setUnderReviewFor(files); this._loadingService.stop(); } async setToNew(files: File[]): Promise { this._loadingService.start(); - await firstValueFrom(this._filesService.setToNewFor(files, files[0].dossierId)); + await this._filesService.setToNew(files); this._loadingService.stop(); } @@ -113,13 +113,13 @@ export class BulkActionsService { }), async () => { this._loadingService.start(); - await firstValueFrom(this._filesService.setApprovedFor(files, files[0].dossierId)); + await this._filesService.setApproved(files); this._loadingService.stop(); }, ); } else { this._loadingService.start(); - await firstValueFrom(this._filesService.setApprovedFor(files, files[0].dossierId)); + await this._filesService.setApproved(files); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts b/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts index e8ee23ee7..b4a1db772 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/user-management/user-management.component.ts @@ -7,7 +7,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { UserService } from '@users/user.service'; import { FilesService } from '@services/files/files.service'; import { TranslateService } from '@ngx-translate/core'; -import { combineLatest, combineLatestWith, firstValueFrom, Observable, switchMap } from 'rxjs'; +import { combineLatest, combineLatestWith, Observable, switchMap } from 'rxjs'; import { FilePreviewStateService } from '../../services/file-preview-state.service'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; @@ -99,22 +99,21 @@ export class UserManagementComponent { async assignReviewer(file: File, user: User | string) { const assigneeId = typeof user === 'string' ? user : user?.id; - const reviewerName = this.userService.getName(assigneeId); - const { dossierId, filename } = file; this.loadingService.start(); if (!assigneeId || file.isApproved) { - await firstValueFrom(this.filesService.setAssignee([file], dossierId, assigneeId)); + await this.filesService.setAssignee(file, assigneeId); } else if (file.isNew || file.isUnderReview) { - await firstValueFrom(this.filesService.setReviewerFor([file], dossierId, assigneeId)); + await this.filesService.setReviewer(file, assigneeId); } else { - await firstValueFrom(this.filesService.setUnderApprovalFor([file], dossierId, assigneeId)); + await this.filesService.setUnderApproval(file, assigneeId); } this.loadingService.stop(); - this.toaster.success(_('assignment.reviewer'), { params: { reviewerName, filename } }); + const translateParams = { reviewerName: this.userService.getName(assigneeId), filename: file.filename }; + this.toaster.success(_('assignment.reviewer'), { params: translateParams }); this.editingReviewer = false; } diff --git a/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts b/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts index 55273e8db..12091decb 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts @@ -1,5 +1,5 @@ import { List } from '@iqser/common-ui'; -import { AnnotationWrapper } from '../../../models/file/annotation.wrapper'; +import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { ALLOWED_KEYBOARD_SHORTCUTS } from './constants'; export function stopAndPrevent($event: T) { diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.html b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.html index 9541fae3e..0fffc25b6 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts index 9e56ff3ec..e693b56d4 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts @@ -120,9 +120,10 @@ export class FileActionsComponent implements OnChanges { } private get _buttons(): Action[] { - return [ + const fileId = this.file.fileId; + const actions: Action[] = [ { - id: 'delete-file-btn', + id: 'delete-file-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._openDeleteFileDialog($event), tooltip: _('dossier-overview.delete.action'), @@ -130,7 +131,7 @@ export class FileActionsComponent implements OnChanges { show: this.showDelete, }, { - id: 'assign-btn', + id: 'assign-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._assign($event), tooltip: this.assignTooltip, @@ -138,7 +139,7 @@ export class FileActionsComponent implements OnChanges { show: this.showAssign, }, { - id: 'assign-to-me-btn', + id: 'assign-to-me-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._assignToMe($event), tooltip: _('dossier-overview.assign-me'), @@ -146,7 +147,7 @@ export class FileActionsComponent implements OnChanges { show: this.showAssignToSelf, }, { - id: 'open-import-redactions-dialog-btn', + id: 'open-import-redactions-dialog-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._openImportRedactionsDialog($event), tooltip: _('dossier-overview.import-redactions'), @@ -154,7 +155,7 @@ export class FileActionsComponent implements OnChanges { show: this.showImportRedactions && !this._iqserPermissionsService.has(ROLES.getRss), }, { - id: 'download-file-btn', + id: 'download-file-btn-' + fileId, type: ActionTypes.downloadBtn, files: [this.file], dossier: this.dossier, @@ -163,7 +164,7 @@ export class FileActionsComponent implements OnChanges { disabled: this.file.processingStatus === ProcessingFileStatuses.ERROR, }, { - id: 'toggle-document-info-btn', + id: 'toggle-document-info-btn-' + fileId, type: ActionTypes.circleBtn, action: () => this._documentInfoService.toggle(), tooltip: _('file-preview.document-info'), @@ -172,7 +173,7 @@ export class FileActionsComponent implements OnChanges { show: !!this._documentInfoService, }, { - id: 'toggle-exclude-pages-btn', + id: 'toggle-exclude-pages-btn-' + fileId, type: ActionTypes.circleBtn, action: () => this._excludedPagesService.toggle(), tooltip: _('file-preview.exclude-pages'), @@ -185,15 +186,15 @@ export class FileActionsComponent implements OnChanges { !this._iqserPermissionsService.has(ROLES.getRss), }, { - id: 'set-file-to-new-btn', + id: 'set-file-to-new-btn-' + fileId, type: ActionTypes.circleBtn, - action: ($event: MouseEvent) => this._setToNew($event), + action: ($event: MouseEvent) => this.#setToNew($event), tooltip: _('dossier-overview.back-to-new'), icon: 'red:undo', show: this.showSetToNew, }, { - id: 'set-file-under-approval-btn', + id: 'set-file-under-approval-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._setFileUnderApproval($event), tooltip: _('dossier-overview.under-approval'), @@ -201,7 +202,7 @@ export class FileActionsComponent implements OnChanges { show: this.showUnderApproval, }, { - id: 'set-file-under-review-btn', + id: 'set-file-under-review-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._setFileUnderReview($event), tooltip: _('dossier-overview.under-review'), @@ -209,7 +210,7 @@ export class FileActionsComponent implements OnChanges { show: this.showUnderReview, }, { - id: 'set-file-approved-btn', + id: 'set-file-approved-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this.setFileApproved($event), tooltip: this.file.canBeApproved ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'), @@ -218,7 +219,7 @@ export class FileActionsComponent implements OnChanges { show: this.showApprove, }, { - id: 'toggle-automatic-analysis-btn', + id: 'toggle-automatic-analysis-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._toggleAutomaticAnalysis($event), tooltip: _('dossier-overview.stop-auto-analysis'), @@ -226,7 +227,7 @@ export class FileActionsComponent implements OnChanges { show: this.canDisableAutoAnalysis, }, { - id: 'reanalyse-file-preview-btn', + id: 'reanalyse-file-preview-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._reanalyseFile($event), tooltip: _('file-preview.reanalyse-notification'), @@ -236,7 +237,7 @@ export class FileActionsComponent implements OnChanges { disabled: this.file.isProcessing, }, { - id: 'toggle-automatic-analysis-btn', + id: 'toggle-automatic-analysis-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._toggleAutomaticAnalysis($event), tooltip: _('dossier-overview.start-auto-analysis'), @@ -245,7 +246,7 @@ export class FileActionsComponent implements OnChanges { show: this.canEnableAutoAnalysis, }, { - id: 'set-under-approval-btn', + id: 'set-under-approval-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._setFileUnderApproval($event), tooltip: _('dossier-overview.under-approval'), @@ -253,7 +254,7 @@ export class FileActionsComponent implements OnChanges { show: this.showUndoApproval, }, { - id: 'ocr-file-btn', + id: 'ocr-file-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._ocrFile($event), tooltip: _('dossier-overview.ocr-file'), @@ -261,7 +262,7 @@ export class FileActionsComponent implements OnChanges { show: this.showOCR, }, { - id: 'reanalyse-file-btn', + id: 'reanalyse-file-btn-' + fileId, type: ActionTypes.circleBtn, action: ($event: MouseEvent) => this._reanalyseFile($event), tooltip: _('dossier-overview.reanalyse.action'), @@ -269,7 +270,7 @@ export class FileActionsComponent implements OnChanges { show: this.showReanalyseDossierOverview, }, { - id: 'toggle-analysis-btn', + id: 'toggle-analysis-btn-' + fileId, type: ActionTypes.toggle, action: () => this._toggleAnalysis(), disabled: !this.canToggleAnalysis, @@ -278,7 +279,9 @@ export class FileActionsComponent implements OnChanges { checked: !this.file.excluded, show: this.showToggleAnalysis, }, - ].filter(btn => btn.show); + ]; + + return actions.filter(btn => btn.show); } ngOnChanges(changes: SimpleChanges) { @@ -292,7 +295,7 @@ export class FileActionsComponent implements OnChanges { async setFileApproved($event: MouseEvent) { $event.stopPropagation(); if (!this.file.analysisRequired && !this.file.hasUpdates) { - await this._setFileApproved(); + await this.#setFileApproved(); return; } @@ -311,7 +314,7 @@ export class FileActionsComponent implements OnChanges { : null, denyText: this.file.analysisRequired ? _('confirmation-dialog.approve-file-without-analysis.denyText') : null, }), - () => this._setFileApproved(), + () => this.#setFileApproved(), ); } @@ -466,16 +469,16 @@ export class FileActionsComponent implements OnChanges { this._changeRef.markForCheck(); } - private async _setFileApproved() { + async #setFileApproved() { this._loadingService.start(); - await firstValueFrom(this._filesService.setApprovedFor([this.file], this.file.dossierId)); + await this._filesService.setApproved(this.file); this._loadingService.stop(); } - private async _setToNew($event: MouseEvent) { + async #setToNew($event: MouseEvent) { $event.stopPropagation(); this._loadingService.start(); - await firstValueFrom(this._filesService.setToNewFor([this.file], this.file.dossierId)); + await this._filesService.setToNew(this.file); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts index 9870066b4..1263d9173 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts @@ -1,14 +1,13 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { UserService } from '@users/user.service'; -import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { Dossier, File, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; +import { getCurrentUser, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; +import { FormBuilder, Validators } from '@angular/forms'; +import { File, User, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FilesService } from '@services/files/files.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { PermissionsService } from '@services/permissions.service'; -import { firstValueFrom } from 'rxjs'; import { moveElementInArray } from '@utils/functions'; class DialogData { @@ -24,59 +23,37 @@ class DialogData { }) export class AssignReviewerApproverDialogComponent { readonly iconButtonTypes = IconButtonTypes; - readonly form: UntypedFormGroup; - readonly mode: 'reviewer' | 'approver'; - dossier: Dossier; + readonly currentUser = getCurrentUser(); + readonly mode = this.#mode; + readonly dossier = this._activeDossiersService.find(this.data.files[0].dossierId); + readonly userOptions = this.#userOptions; + readonly form = this.#form; constructor( readonly userService: UserService, private readonly _toaster: Toaster, - private readonly _formBuilder: UntypedFormBuilder, + private readonly _formBuilder: FormBuilder, private readonly _activeDossiersService: ActiveDossiersService, private readonly _filesService: FilesService, private readonly _loadingService: LoadingService, readonly permissionsService: PermissionsService, private readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: DialogData, - ) { - this.dossier = this._activeDossiersService.find(this.data.files[0].dossierId); - this.form = this._getForm(); - this.mode = - data.targetStatus === WorkflowFileStatuses.UNDER_APPROVAL || data.targetStatus === WorkflowFileStatuses.APPROVED - ? 'approver' - : 'reviewer'; - } + ) {} get selectedUser(): string { - const value = this.form.get('user').value; + const value = this.form.controls.user.value; return value === 'undefined' ? undefined : value; } - get userOptions() { - const unassignUser = this._canUnassignFiles && this.data.withUnassignedOption ? ['undefined'] : []; - const cannotAssignUser = !this.permissionsService.canAssignUser(this.data.files, this.dossier); - - if (this.mode === 'reviewer') { - if (this.dossier.hasReviewers && cannotAssignUser) { - return [...unassignUser]; - } - return this._customSort([...this.dossier.memberIds, ...unassignUser]); - } - - if (this.dossier.approverIds.length > 1 && cannotAssignUser) { - return [...unassignUser]; - } - - return this._customSort([...this.dossier.approverIds, ...unassignUser]); - } - get changed(): boolean { if (this.data.ignoreChanged) { return true; } + const selectedUser = this.selectedUser; for (const file of this.data.files) { - if (file.assignee !== this.selectedUser) { + if (file.assignee !== selectedUser) { return true; } } @@ -84,71 +61,100 @@ export class AssignReviewerApproverDialogComponent { return false; } - private get _canUnassignFiles() { + get #mode() { + const isUnderApproval = this.data.targetStatus === WorkflowFileStatuses.UNDER_APPROVAL; + const isApproved = this.data.targetStatus === WorkflowFileStatuses.APPROVED; + return isUnderApproval || isApproved ? 'approver' : 'reviewer'; + } + + get #userOptions() { + const unassignUser = this.#canUnassignUser && this.data.withUnassignedOption ? ['undefined'] : []; + const cannotAssignUser = !this.permissionsService.canAssignUser(this.data.files, this.dossier); + + if (this.mode === 'reviewer') { + if (this.dossier.hasReviewers && cannotAssignUser) { + return [...unassignUser]; + } + + return this.#customSort([...this.dossier.memberIds, ...unassignUser]); + } + + if (this.dossier.approverIds.length > 1 && cannotAssignUser) { + return [...unassignUser]; + } + + return this.#customSort([...this.dossier.approverIds, ...unassignUser]); + } + + get #canUnassignUser() { return this.permissionsService.canUnassignUser(this.data.files, this.dossier); } - /** Initialize the form with: - * the id of the current reviewer of the files list if there is only one reviewer for all of them; - * or the id of the current user - **/ - - private get _uniqueReviewers(): Set { + get #uniqueReviewers(): Set { const uniqueReviewers = new Set(); + for (const file of this.data.files) { if (file.assignee) { uniqueReviewers.add(file.assignee); } } + return uniqueReviewers; } - private get _user(): string { + get #user(): string { const userOptions = this.userOptions; - if (this.data.withCurrentUserAsDefault && userOptions.includes(this.userService.currentUser.id)) { - return this.userService.currentUser.id; + if (this.data.withCurrentUserAsDefault && userOptions.includes(this.currentUser.id)) { + return this.currentUser.id; } - const uniqueReviewers = [...this._uniqueReviewers.values()]; - const user = uniqueReviewers.length === 1 ? uniqueReviewers[0] : this.userService.currentUser.id; - + const uniqueReviewers = [...this.#uniqueReviewers.values()]; + const user = uniqueReviewers.length === 1 ? uniqueReviewers[0] : this.currentUser.id; return userOptions.indexOf(user) >= 0 ? userOptions[userOptions.indexOf(user)] : user; } + get #form() { + const user = this.#user; + return this._formBuilder.group({ + // Allow a null reviewer if a previous reviewer exists (= it's not the first assignment) & current user is allowed to unassign + user: [user, this.#canUnassignUser && !user ? Validators.required : null], + }); + } + async save() { this._loadingService.start(); + const selectedUser = this.selectedUser; + try { - if (!this.selectedUser || this.data.targetStatus === WorkflowFileStatuses.APPROVED) { - await firstValueFrom(this._filesService.setAssignee(this.data.files, this.dossier.id, this.selectedUser)); + if (!selectedUser || this.data.targetStatus === WorkflowFileStatuses.APPROVED) { + await this._filesService.setAssignee(this.data.files, selectedUser); } else if (this.mode === 'reviewer') { - await firstValueFrom(this._filesService.setReviewerFor(this.data.files, this.dossier.id, this.selectedUser)); + await this._filesService.setReviewer(this.data.files, selectedUser); } else { - await firstValueFrom(this._filesService.setUnderApprovalFor(this.data.files, this.dossier.id, this.selectedUser)); + await this._filesService.setUnderApproval(this.data.files, selectedUser); } } catch (error) { this._toaster.error(_('error.http.generic'), { params: error }); } - this._loadingService.stop(); + this._loadingService.stop(); this._dialogRef.close(true); } - private _getForm(): UntypedFormGroup { - return this._formBuilder.group({ - // Allow a null reviewer if a previous reviewer exists (= it's not the first assignment) & current user is allowed to unassign - user: [this._user, this._canUnassignFiles && !this._user ? Validators.required : null], - }); - } - - private _customSort(ids: string[]) { + #customSort(ids: string[]) { let sorted = ids.sort((a, b) => this.userService.getName(a).localeCompare(this.userService.getName(b))); - if (this.data.files.length === 1 && this.data.files[0].assignee) { + + const fileHasAssignee = this.data.files.length === 1 && this.data.files[0].assignee; + + if (fileHasAssignee) { sorted = moveElementInArray(sorted, this.data.files[0].assignee, 0); } - if (this.data.withUnassignedOption) { - sorted = moveElementInArray(sorted, 'undefined', this.data.files[0].assignee && this.data.files.length === 1 ? 1 : 0); + + if (sorted.includes('undefined')) { + sorted = moveElementInArray(sorted, 'undefined', fileHasAssignee ? 1 : 0); } + return sorted; } } diff --git a/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts b/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts index 046268d4e..c378a33a0 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/services/file-assign.service.ts @@ -1,10 +1,9 @@ import { Injectable } from '@angular/core'; -import { UserService } from '@users/user.service'; import { Dossier, File, User, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; import { DossiersDialogService } from './dossiers-dialog.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FilesService } from '@services/files/files.service'; -import { ConfirmationDialogInput, LoadingService, Toaster } from '@iqser/common-ui'; +import { ConfirmationDialogInput, getCurrentUser, LoadingService, Toaster } from '@iqser/common-ui'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { firstValueFrom } from 'rxjs'; @@ -12,18 +11,15 @@ const atLeastOneAssignee = (files: File[]) => files.reduce((acc, fs) => acc || ! @Injectable() export class FileAssignService { - readonly currentUser: User; + readonly currentUser = getCurrentUser(); constructor( - userService: UserService, private readonly _toaster: Toaster, private readonly _filesService: FilesService, private readonly _loadingService: LoadingService, private readonly _dialogService: DossiersDialogService, private readonly _activeDossiersService: ActiveDossiersService, - ) { - this.currentUser = userService.currentUser; - } + ) {} async assignToMe(files: File[]): Promise { const assignReq = async () => { @@ -31,10 +27,11 @@ export class FileAssignService { if (files[0].isNew) { await this._makeAssignFileRequest(this.currentUser.id, 'UNDER_REVIEW', files); } else { - await firstValueFrom(this._filesService.setAssignee(files, files[0].dossierId, this.currentUser.id)); + await this._filesService.setAssignee(files, this.currentUser.id); } this._loadingService.stop(); }; + if (atLeastOneAssignee(files)) { const dialogInput = new ConfirmationDialogInput({ title: _('confirmation-dialog.assign-file-to-me.title'), @@ -88,17 +85,19 @@ export class FileAssignService { private async _makeAssignFileRequest(userId: string, targetStatus: WorkflowFileStatus, files: File[]) { this._loadingService.start(); + try { if (!userId || targetStatus === WorkflowFileStatuses.APPROVED) { - await firstValueFrom(this._filesService.setAssignee(files, files[0].dossierId, userId)); + await this._filesService.setAssignee(files, userId); } else if (targetStatus === WorkflowFileStatuses.UNDER_REVIEW) { - await firstValueFrom(this._filesService.setReviewerFor(files, files[0].dossierId, userId)); + await this._filesService.setReviewer(files, userId); } else { - await firstValueFrom(this._filesService.setUnderApprovalFor(files, files[0].dossierId, userId)); + await this._filesService.setUnderApproval(files, userId); } } catch (error) { this._toaster.error(_('error.http.generic'), { params: error }); } + this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts b/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts index 872e16569..62f6e255f 100644 --- a/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts @@ -30,7 +30,7 @@ export class ExpandableFileActionsComponent implements OnChanges { expanded = false; @ViewChild(MatMenuTrigger) matMenu: MatMenuTrigger; - trackBy = trackByFactory(); + readonly trackBy = trackByFactory(); constructor( private readonly _fileDownloadService: FileDownloadService, diff --git a/apps/red-ui/src/app/services/files/files.service.ts b/apps/red-ui/src/app/services/files/files.service.ts index 69ee46922..e4abe53db 100644 --- a/apps/red-ui/src/app/services/files/files.service.ts +++ b/apps/red-ui/src/app/services/files/files.service.ts @@ -1,13 +1,15 @@ import { Injectable } from '@angular/core'; -import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; +import { EntitiesService, isArray, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { File, IFile } from '@red/domain'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { UserService } from '@users/user.service'; import { FilesMapService } from './files-map.service'; import { map, switchMap, tap } from 'rxjs/operators'; import { DossierStatsService } from '../dossiers/dossier-stats.service'; import { NGXLogger } from 'ngx-logger'; +const asList = (value: T | List): List => (isArray(value) ? value : [value]); + @Injectable({ providedIn: 'root', }) @@ -44,46 +46,47 @@ export class FilesService extends EntitiesService { } @Validate() - setAssignee(@RequiredParam() files: List, @RequiredParam() dossierId: string, assigneeId: string) { - const url = `${this._defaultModelPath}/set-assignee/${dossierId}/bulk`; - const fileIds = files.map(f => f.id); - return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId))); + async setAssignee(@RequiredParam() files: File | List, assigneeId: string) { + const _files = asList(files); + const url = `${this._defaultModelPath}/set-assignee/${_files[0].dossierId}/bulk`; + return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]); } @Validate() - setToNewFor(@RequiredParam() files: List, @RequiredParam() dossierId: string) { - const url = `${this._defaultModelPath}/new/${dossierId}/bulk`; - const fileIds = files.map(f => f.id); - return this._post(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId))); + async setToNew(@RequiredParam() files: File | List) { + const _files = asList(files); + return this.#makePost(_files, `${this._defaultModelPath}/new/${_files[0].dossierId}/bulk`); } @Validate() - setUnderApprovalFor(@RequiredParam() files: List, @RequiredParam() dossierId: string, assigneeId: string) { - const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`; - const fileIds = files.map(f => f.id); - return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId))); + async setUnderApproval(@RequiredParam() files: File | List, assigneeId: string) { + const _files = asList(files); + const url = `${this._defaultModelPath}/under-approval/${_files[0].dossierId}/bulk`; + return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]); } @Validate() - setReviewerFor(@RequiredParam() files: List, @RequiredParam() dossierId: string, assigneeId: string) { - const url = `${this._defaultModelPath}/under-review/${dossierId}/bulk`; - const fileIds = files.map(f => f.id); - return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId))); + async setReviewer(@RequiredParam() files: File | List, assigneeId: string) { + const _files = asList(files); + const url = `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`; + return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]); } @Validate() - setApprovedFor(@RequiredParam() files: List, @RequiredParam() dossierId: string) { - const fileIds = files.map(f => f.id); - return this._post(fileIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`).pipe( - switchMap(() => this.loadAll(dossierId)), - ); + async setApproved(@RequiredParam() files: File | List) { + const _files = asList(files); + return this.#makePost(_files, `${this._defaultModelPath}/approved/${_files[0].dossierId}/bulk`); } @Validate() - setUnderReviewFor(@RequiredParam() files: List, @RequiredParam() dossierId: string) { + async setUnderReviewFor(@RequiredParam() files: File | List) { + const _files = asList(files); + return this.#makePost(_files, `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`); + } + + async #makePost(files: List, url: string, queryParams?: List) { const fileIds = files.map(f => f.id); - return this._post(fileIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`).pipe( - switchMap(() => this.loadAll(dossierId)), - ); + await firstValueFrom(this._post(fileIds, url, queryParams)); + return firstValueFrom(this.loadAll(files[0].dossierId)); } } diff --git a/apps/red-ui/src/app/services/notifications.service.ts b/apps/red-ui/src/app/services/notifications.service.ts index 3a8297b12..399cc5734 100644 --- a/apps/red-ui/src/app/services/notifications.service.ts +++ b/apps/red-ui/src/app/services/notifications.service.ts @@ -89,19 +89,6 @@ export class NotificationsService extends EntitiesService