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