diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html index f7d5e3bd4..4577007ca 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html @@ -1,33 +1,36 @@
- +
{{ appStateService.activeDossier.dossier.dossierName }}
- +
-
- +
+ + + + +
@@ -136,3 +139,21 @@
{{ appStateService.activeDossier.dossier.description }}
+ + + + + + + + diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts index 4144e4501..696bdd114 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts @@ -2,12 +2,13 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } fro import { AppStateService } from '@state/app-state.service'; import { groupBy } from '@utils/functions'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; -import { Router } from '@angular/router'; import { PermissionsService } from '@services/permissions.service'; import { TranslateChartService } from '@services/translate-chart.service'; import { StatusSorter } from '@utils/sorters/status-sorter'; -import { DossiersDialogService } from '../../services/dossiers-dialog.service'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; +import { UserService } from '@services/user.service'; +import { User } from '@redaction/red-ui-http'; +import { NotificationService } from '@services/notification.service'; @Component({ selector: 'redaction-dossier-details', @@ -16,6 +17,8 @@ import { FilterModel } from '@shared/components/filters/popup-filter/model/filte }) export class DossierDetailsComponent implements OnInit { documentsChartData: DoughnutChartConfig[] = []; + owner: User; + editingOwner = false; @Input() filters: { needsWorkFilters: FilterModel[]; statusFilters: FilterModel[] }; @Output() filtersChanged = new EventEmitter(); @Output() openAssignDossierMembersDialog = new EventEmitter(); @@ -27,8 +30,8 @@ export class DossierDetailsComponent implements OnInit { readonly translateChartService: TranslateChartService, readonly permissionsService: PermissionsService, private readonly _changeDetectorRef: ChangeDetectorRef, - private readonly _dialogService: DossiersDialogService, - private readonly _router: Router + private readonly _userService: UserService, + private readonly _notificationService: NotificationService ) {} get memberIds(): string[] { @@ -39,7 +42,12 @@ export class DossierDetailsComponent implements OnInit { return this.appStateService.activeDossier.hasFiles; } + get managers() { + return this._userService.managerUsers; + } + ngOnInit(): void { + this.owner = this._userService.getUserById(this.appStateService.activeDossier.ownerId); this.calculateChartConfig(); this.appStateService.fileChanged.subscribe(() => { this.calculateChartConfig(); @@ -47,23 +55,25 @@ export class DossierDetailsComponent implements OnInit { } calculateChartConfig(): void { - if (this.appStateService.activeDossier) { - const groups = groupBy(this.appStateService.activeDossier?.files, 'status'); - this.documentsChartData = []; - for (const key of Object.keys(groups)) { - this.documentsChartData.push({ - value: groups[key].length, - color: key, - label: key, - key: key - }); - } - this.documentsChartData.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]); - this.documentsChartData = this.translateChartService.translateStatus( - this.documentsChartData - ); - this._changeDetectorRef.detectChanges(); + if (!this.appStateService.activeDossier) { + return; } + + const groups = groupBy(this.appStateService.activeDossier?.files, 'status'); + this.documentsChartData = []; + for (const key of Object.keys(groups)) { + this.documentsChartData.push({ + value: groups[key].length, + color: key, + label: key, + key: key + }); + } + this.documentsChartData.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]); + this.documentsChartData = this.translateChartService.translateStatus( + this.documentsChartData + ); + this._changeDetectorRef.detectChanges(); } toggleFilter(filterType: 'needsWorkFilters' | 'statusFilters', key: string): void { @@ -71,4 +81,16 @@ export class DossierDetailsComponent implements OnInit { filter.checked = !filter.checked; this.filtersChanged.emit(this.filters); } + + async assignOwner(user: User | string) { + this.owner = typeof user === 'string' ? this._userService.getUserById(user) : user; + const dw = Object.assign({}, this.appStateService.activeDossier); + dw.dossier.ownerId = this.owner.userId; + await this.appStateService.addOrUpdateDossier(dw.dossier); + + const ownerName = this._userService.getNameForId(this.owner.userId); + const dossierName = this.appStateService.activeDossier.name; + const msg = 'Successfully assigned ' + ownerName + ' to dossier: ' + dossierName; + this._notificationService.showToastNotification(msg); + } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss index 2c332b871..a0f607c1e 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss @@ -100,10 +100,6 @@ cdk-virtual-scroll-viewport { margin-left: 6px; } -.ml-14 { - margin-left: 14px; -} - .mr-4 { margin-right: 4px; } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html index 1e65d6365..cf31ac7d4 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html @@ -39,121 +39,58 @@
- + +
- {{ appStateService.activeFile.status | translate }} - {{ 'by' | translate }}: + {{ status | translate }} + {{ 'by' | translate }}:
- - -
-
+ +
- -
-
- - - - - - - - -
-
-
+
- - - - - - - + > - + >
{ if (!document.fullscreenElement) { this.fullScreen = false; @@ -253,9 +248,9 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, rebuildFilters(deletePreviousAnnotations: boolean = false) { const startTime = new Date().getTime(); if (deletePreviousAnnotations) { - const annotationsToDelete = this.activeViewer?.annotManager?.getAnnotationsList() || []; + const annotationsToDelete = this._instance?.annotManager?.getAnnotationsList() || []; try { - this.activeViewer?.annotManager?.deleteAnnotations(annotationsToDelete, { + this._instance?.annotManager?.deleteAnnotations(annotationsToDelete, { imported: true, force: true }); @@ -339,10 +334,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, $event, async (response: ManualAnnotationResponse) => { if (response?.annotationId) { - const annotation = this.activeViewer.annotManager.getAnnotationById( + const annotation = this._instance.annotManager.getAnnotationById( response.manualRedactionEntryWrapper.rectId ); - this.activeViewer.annotManager.deleteAnnotation(annotation); + this._instance.annotManager.deleteAnnotation(annotation); this.fileData.fileStatus = await this.appStateService.reloadActiveFile(); const distinctPages = $event.manualRedactionEntry.positions .map(p => p.page) @@ -396,21 +391,23 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, } viewerPageChanged($event: any) { - if (typeof $event === 'number') { - this._scrollViews(); - if (!this._workloadComponent?.multiSelectActive) { - this.shouldDeselectAnnotationsOnPageChange = true; - } - - // Add current page in URL query params - this._router.navigate([], { - queryParams: { - page: $event - }, - queryParamsHandling: 'merge' - }); - this._changeDetectorRef.detectChanges(); + if (typeof $event !== 'number') { + return; } + + this._scrollViews(); + if (!this._workloadComponent.multiSelectActive) { + this.shouldDeselectAnnotationsOnPageChange = true; + } + + // Add current page in URL query params + const extras: NavigationExtras = { + queryParams: { page: $event }, + queryParamsHandling: 'merge' + }; + this._router.navigate([], extras).then(); + + this._changeDetectorRef.detectChanges(); } viewerReady($event: WebViewerInstance) { @@ -461,41 +458,29 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, async assignToMe() { await this._fileActionService.assignToMe(this.fileData.fileStatus, async () => { await this.appStateService.reloadActiveFile(); - this.resetReviewerForm(); this._updateCanPerformActions(); }); } - assignReviewer() { - const reviewerId = this.reviewerForm.get('reviewer').value; + async assignReviewer(user: User | string) { + const reviewerId = typeof user === 'string' ? user : user.userId; + const reviewerName = this.userService.getNameForId(reviewerId); - this._statusControllerService - .setFileReviewer( - this.fileData.fileStatus.dossierId, - this.fileData.fileStatus.fileId, - reviewerId - ) - .subscribe(async () => { - this._notificationService.showToastNotification( - 'Successfully assigned ' + - this.userService.getNameForId(reviewerId) + - ' to file: ' + - this.fileData.fileStatus.filename - ); - await this.appStateService.reloadActiveFile(); - this._updateCanPerformActions(); - this.editingReviewer = false; - }); + const { dossierId, fileId, filename } = this.fileData.fileStatus; + await this._statusControllerService + .setFileReviewer(dossierId, fileId, reviewerId) + .toPromise(); + + const msg = `Successfully assigned ${reviewerName} to file: ${filename}`; + this._notificationService.showToastNotification(msg); + await this.appStateService.reloadActiveFile(); + this._updateCanPerformActions(); + this.editingReviewer = false; } - resetReviewerForm() { - this.reviewerForm.setValue({ reviewer: this.appStateService.activeFile.currentReviewer }); - } - - /* Close fullscreen */ closeFullScreen() { if (!!document.fullscreenElement && document.exitFullscreen) { - document.exitFullscreen(); + document.exitFullscreen().then(); } } @@ -529,6 +514,30 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, return false; } + get currentReviewer(): string { + return this.appStateService.activeFile.currentReviewer; + } + + get status(): FileStatus.StatusEnum { + return this.appStateService.activeFile.status; + } + + get statusBarConfig(): [{ length: number; color: FileStatus.StatusEnum }] { + return [{ length: 1, color: this.status }]; + } + + get isUnderReviewOrApproval(): boolean { + return this.status === 'UNDER_REVIEW' || this.status === 'UNDER_APPROVAL'; + } + + get canAssignReviewer(): boolean { + return ( + !this.currentReviewer && + this.permissionsService.canAssignUser() && + this.appStateService.activeDossier.hasMoreThanOneReviewer + ); + } + // async openSSRFilePreview() { window.open(`/pdf-preview/${this.dossierId}/${this.fileId}`, '_blank'); @@ -664,9 +673,9 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, } private _findAndDeleteAnnotation(id: string) { - const viewerAnnotation = this.activeViewer.annotManager.getAnnotationById(id); + const viewerAnnotation = this._instance.annotManager.getAnnotationById(id); if (viewerAnnotation) { - this.activeViewer.annotManager.deleteAnnotation(viewerAnnotation, { + this._instance.annotManager.deleteAnnotation(viewerAnnotation, { imported: true, force: true }); diff --git a/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.html b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.html new file mode 100644 index 000000000..224c7d34a --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.html @@ -0,0 +1,36 @@ +
+
+ + + + + + + + +
+ + + + +
+ + + + diff --git a/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.scss b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.scss new file mode 100644 index 000000000..2af8428eb --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.scss @@ -0,0 +1,10 @@ +.space-between { + justify-content: space-between; +} + +.red-input-group mat-select { + display: flex; + flex-direction: column; + justify-content: center; + height: 40px; +} diff --git a/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.ts b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.ts new file mode 100644 index 000000000..0ee6a2e5c --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.ts @@ -0,0 +1,34 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { User } from '@redaction/red-ui-http'; +import { UserService } from '@services/user.service'; + +@Component({ + selector: 'redaction-assign-user-dropdown', + templateUrl: './assign-user-dropdown.component.html', + styleUrls: ['./assign-user-dropdown.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AssignUserDropdownComponent { + private _currentUser: User | string; + oldUser: User | string; + + @Input() options: (User | string)[]; + @Output() save = new EventEmitter(); + @Output() cancel = new EventEmitter(); + + @Input() + set value(value: User | string) { + if (this.oldUser === undefined) this.oldUser = value; + this._currentUser = value; + } + + get value(): User | string { + return this._currentUser; + } + + constructor(private readonly _userService: UserService) {} + + getContext(user: User | string) { + return typeof user === 'string' ? { userId: user } : { user: user }; + } +} diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index ea408fa11..6c575b23c 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -36,6 +36,7 @@ import { SideNavComponent } from '@shared/components/side-nav/side-nav.component import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; import { QuickFiltersComponent } from './components/filters/quick-filters/quick-filters.component'; import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component'; +import { AssignUserDropdownComponent } from './components/assign-user-dropdown/assign-user-dropdown.component'; const buttons = [ ChevronButtonComponent, @@ -64,6 +65,8 @@ const components = [ SelectComponent, SideNavComponent, DictionaryManagerComponent, + QuickFiltersComponent, + AssignUserDropdownComponent, ...buttons ]; @@ -85,9 +88,9 @@ const modules = [ ]; @NgModule({ - declarations: [...components, ...utils, QuickFiltersComponent], + declarations: [...components, ...utils], imports: [CommonModule, ...modules, MonacoEditorModule], - exports: [...modules, ...components, ...utils, QuickFiltersComponent], + exports: [...modules, ...components, ...utils], providers: [ { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] }, { diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 225d28f75..3db02bc14 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -121,12 +121,11 @@ export class PermissionsService { !fileStatus.isError && !fileStatus.isApproved; - const isNotCurrentReviewer = fileStatus.currentReviewer !== this._userService.userId; const isTheOnlyReviewer = !this._appStateService.activeDossier.hasMoreThanOneReviewer; if (precondition) { if ( - (fileStatus.isUnassigned || (fileStatus.isUnderReview && isNotCurrentReviewer)) && + (fileStatus.isUnassigned || (fileStatus.isUnderReview && !this.isFileReviewer())) && (this.isApprover() || isTheOnlyReviewer) ) { return true; diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 690cbabb6..3f55d1e1f 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -386,8 +386,6 @@ "reviewer": "Assigned to", "unassigned": "Unassigned", "assign-reviewer": "Assign Reviewer", - "cancel-assign-reviewer": "Cancel", - "save-assign-reviewer": "Save", "assign-me": "Assign to me", "last-reviewer": "Last Reviewed by:", "fullscreen": "Full Screen (F)", @@ -401,6 +399,10 @@ "jump-last": "Jump to last page" } }, + "assign-user": { + "cancel": "Cancel", + "save": "Save" + }, "annotation-actions": { "message": { "manual-redaction": { diff --git a/apps/red-ui/src/assets/styles/red-page-layout.scss b/apps/red-ui/src/assets/styles/red-page-layout.scss index 6da8319a1..8220af014 100644 --- a/apps/red-ui/src/assets/styles/red-page-layout.scss +++ b/apps/red-ui/src/assets/styles/red-page-layout.scss @@ -253,6 +253,10 @@ section.settings { margin-bottom: 8px !important; } +.ml-14 { + margin-left: 14px; +} + .pb-24 { padding-bottom: 24px; }