+
+
-
+
();
+
+ @ViewChild('container', { static: true }) container: ElementRef;
public expandedTeam = false;
@@ -16,8 +22,15 @@ export class TeamMembersComponent implements OnInit {
ngOnInit(): void {}
+ public get maxTeamMembersBeforeExpand(): number {
+ const width = this.container.nativeElement.offsetWidth;
+ // 32px element width + margin right (2px or 12px)
+ const elementWidth = this.largeSpacing ? 46 : 34;
+ return Math.floor(width / elementWidth) - (this.canAdd ? 1 : 0);
+ }
+
public get displayedMembers(): string[] {
- return this.expandedTeam || !this.overflowCount ? this.memberIds : this.memberIds.slice(0, 7);
+ return this.expandedTeam || !this.overflowCount ? this.memberIds : this.memberIds.slice(0, this.maxTeamMembersBeforeExpand - 1);
}
public toggleExpandedTeam() {
@@ -25,6 +38,6 @@ export class TeamMembersComponent implements OnInit {
}
public get overflowCount() {
- return this.memberIds.length > 8 ? this.memberIds.length - 7 : 0;
+ return this.memberIds.length > this.maxTeamMembersBeforeExpand ? this.memberIds.length - (this.maxTeamMembersBeforeExpand - 1) : 0;
}
}
diff --git a/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.html b/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.html
index 1ed555c90..c964cc9b6 100644
--- a/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.html
+++ b/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.html
@@ -44,7 +44,5 @@
-
+
diff --git a/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.html b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.html
index 83c55d073..c787272c3 100644
--- a/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.html
+++ b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.html
@@ -1,48 +1,57 @@
diff --git a/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss
index f11829a84..27860197e 100644
--- a/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss
+++ b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss
@@ -1,3 +1,67 @@
-.red-input-group {
- max-width: 200px;
+@import '../../../assets/styles/red-mixins';
+
+.search-container {
+ width: 560px;
+ margin-top: 16px;
+}
+
+.members-list {
+ max-height: 220px;
+ height: 220px;
+ margin-top: 16px;
+ overflow-y: hidden;
+ width: 587px;
+
+ &:hover {
+ overflow-y: auto;
+ @include scroll-bar;
+ }
+
+ > div {
+ margin-bottom: 2px;
+ padding: 3px 5px;
+ border-radius: 4px;
+ cursor: pointer;
+ position: relative;
+ transition: background-color ease-in-out 0.1s;
+ width: 560px;
+ box-sizing: border-box;
+
+ mat-icon {
+ display: none;
+ position: absolute;
+ right: 13px;
+ top: 12px;
+ width: 14px;
+ height: 14px;
+ }
+
+ &.selected {
+ background-color: $green-2;
+
+ mat-icon.add {
+ display: initial;
+ }
+ }
+
+ &:hover {
+ background-color: $grey-2;
+
+ &.selected {
+ mat-icon.remove {
+ display: initial;
+ }
+
+ mat-icon.add {
+ display: none;
+ }
+ }
+
+ &:not(.selected) {
+ mat-icon.add {
+ display: initial;
+ }
+ }
+ }
+ }
}
diff --git a/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts
index 9b6a0e4da..f486f9186 100644
--- a/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts
+++ b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts
@@ -1,15 +1,16 @@
import { Component, Inject } from '@angular/core';
-import { Project, ProjectControllerService, StatusControllerService } from '@redaction/red-ui-http';
+import { ProjectControllerService, StatusControllerService } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AppStateService } from '../../state/app-state.service';
import { UserService } from '../../user/user.service';
import { NotificationService, NotificationType } from '../../notification/notification.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper';
+import { ProjectWrapper } from '../../state/model/project.wrapper';
class DialogData {
type: 'file' | 'project';
- project?: Project;
+ project?: ProjectWrapper;
files?: FileStatusWrapper[];
}
@@ -19,7 +20,8 @@ class DialogData {
styleUrls: ['./assign-owner-dialog.component.scss']
})
export class AssignOwnerDialogComponent {
- usersForm: FormGroup;
+ public usersForm: FormGroup;
+ public searchForm: FormGroup;
constructor(
public readonly userService: UserService,
@@ -39,7 +41,10 @@ export class AssignOwnerDialogComponent {
const project = this.data.project;
this.usersForm = this._formBuilder.group({
singleUser: [project?.ownerId, Validators.required],
- userList: [project?.memberIds]
+ userList: [[...project?.memberIds]]
+ });
+ this.searchForm = this._formBuilder.group({
+ query: ['']
});
}
@@ -57,22 +62,30 @@ export class AssignOwnerDialogComponent {
}
}
+ public get selectedSingleUser(): string {
+ return this.usersForm.get('singleUser').value;
+ }
+
+ public get selectedUserList(): string[] {
+ return this.usersForm.get('userList').value;
+ }
+
async saveUsers() {
try {
if (this.data.type === 'project') {
- const ownerId = this.usersForm.get('singleUser').value;
- const memberIds = this.usersForm.get('userList').value;
- const project = Object.assign({}, this.data.project);
- project.memberIds = memberIds;
- project.ownerId = ownerId;
- await this._appStateService.addOrUpdateProject(project);
+ const ownerId = this.selectedSingleUser;
+ const memberIds = this.selectedUserList;
+ const pw = Object.assign({}, this.data.project);
+ pw.project.memberIds = memberIds;
+ pw.project.ownerId = ownerId;
+ await this._appStateService.addOrUpdateProject(pw.project);
this._notificationService.showToastNotification(
- 'Successfully assigned ' + this.userService.getNameForId(ownerId) + ' to project: ' + project.projectName
+ 'Successfully assigned ' + this.userService.getNameForId(ownerId) + ' to project: ' + pw.project.projectName
);
}
if (this.data.type === 'file') {
- const reviewerId = this.usersForm.get('singleUser').value;
+ const reviewerId = this.selectedSingleUser;
const promises = this.data.files.map((file) =>
this._statusControllerService.assignProjectOwner(this._appStateService.activeProjectId, file.fileId, reviewerId).toPromise()
@@ -95,10 +108,57 @@ export class AssignOwnerDialogComponent {
}
get singleUsersSelectOptions() {
- return this.data.type === 'file' ? this._appStateService.activeProject.project.memberIds : this.userService.managerUsers.map((m) => m.userId);
+ return this.data.type === 'file' ? this._appStateService.activeProject.memberIds : this.userService.managerUsers.map((m) => m.userId);
}
get multiUsersSelectOptions() {
- return this.userService.eligibleUsers.map((m) => m.userId);
+ const searchQuery = this.searchForm.get('query').value;
+ return this.userService.eligibleUsers
+ .filter((user) => this.userService.getNameForId(user.userId).toLowerCase().includes(searchQuery.toLowerCase()))
+ .map((user) => user.userId);
+ }
+
+ public isMemberSelected(userId: string): boolean {
+ return this.selectedUserList.indexOf(userId) !== -1;
+ }
+
+ public toggleSelected(userId: string) {
+ if (this.isMemberSelected(userId)) {
+ const idx = this.selectedUserList.indexOf(userId);
+ this.selectedUserList.splice(idx, 1);
+ } else {
+ this.selectedUserList.push(userId);
+ }
+ }
+
+ public get changed(): boolean {
+ if (this.data.type === 'project') {
+ if (this.data.project.ownerId !== this.selectedSingleUser) {
+ return true;
+ }
+
+ const initial = this.data.project.memberIds.sort();
+ const current = this.selectedUserList.sort();
+
+ if (initial.length !== current.length) {
+ return true;
+ }
+
+ for (let idx = 0; idx < initial.length; ++idx) {
+ if (initial[idx] !== current[idx]) {
+ return true;
+ }
+ }
+ } else if (this.data.type === 'file') {
+ const reviewerId = this.selectedSingleUser;
+
+ for (const file of this.data.files) {
+ if (file.currentReviewer !== reviewerId) {
+ return true;
+ }
+ }
+ }
+
+ return false;
}
}
diff --git a/apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.html b/apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.html
similarity index 78%
rename from apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.html
rename to apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.html
index a2c0d6dd9..8e8451696 100644
--- a/apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.html
+++ b/apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.html
@@ -6,15 +6,13 @@
-
-
-
-
+
diff --git a/apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.scss b/apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.scss
similarity index 100%
rename from apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.scss
rename to apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.scss
diff --git a/apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.ts b/apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.ts
similarity index 93%
rename from apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.ts
rename to apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.ts
index bd3896fee..c5761b8f7 100644
--- a/apps/red-ui/src/app/common/confirmation-dialog/confirmation-dialog.component.ts
+++ b/apps/red-ui/src/app/dialogs/confirmation-dialog/confirmation-dialog.component.ts
@@ -11,8 +11,7 @@ export class ConfirmationDialogInput {
constructor(options: ConfirmationDialogInput) {
this.title = options.title || ConfirmationDialogInput.default().title;
this.question = options.question || ConfirmationDialogInput.default().question;
- this.confirmationText =
- options.confirmationText || ConfirmationDialogInput.default().confirmationText;
+ this.confirmationText = options.confirmationText || ConfirmationDialogInput.default().confirmationText;
this.denyText = options.denyText || ConfirmationDialogInput.default().denyText;
}
diff --git a/apps/red-ui/src/app/dialogs/dialog.service.ts b/apps/red-ui/src/app/dialogs/dialog.service.ts
index 5d3b05588..5fc08a369 100644
--- a/apps/red-ui/src/app/dialogs/dialog.service.ts
+++ b/apps/red-ui/src/app/dialogs/dialog.service.ts
@@ -9,7 +9,7 @@ import {
TypeValue,
DictionaryControllerService
} from '@redaction/red-ui-http';
-import { ConfirmationDialogComponent, ConfirmationDialogInput } from '../common/confirmation-dialog/confirmation-dialog.component';
+import { ConfirmationDialogComponent, ConfirmationDialogInput } from './confirmation-dialog/confirmation-dialog.component';
import { NotificationService, NotificationType } from '../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { AppStateService } from '../state/app-state.service';
@@ -23,7 +23,7 @@ import { ProjectWrapper } from '../state/model/project.wrapper';
import { AddEditDictionaryDialogComponent } from '../screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component';
const dialogConfig = {
- width: '600px',
+ width: '662px',
maxWidth: '90vw',
autoFocus: false
};
diff --git a/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.html b/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.html
index 276771ef2..28ca2dd54 100644
--- a/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.html
+++ b/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.html
@@ -10,42 +10,23 @@
{{ 'project-overview.file-listing.file-entry.status' | translate: fileStatus }}
- {{
- 'project-overview.file-listing.file-entry.number-of-pages'
- | translate: fileStatus
- }}
+ {{ 'project-overview.file-listing.file-entry.number-of-pages' | translate: fileStatus }}
- {{
- 'project-overview.file-listing.file-entry.number-of-analyses'
- | translate: fileStatus
- }}
+ {{ 'project-overview.file-listing.file-entry.number-of-analyses' | translate: fileStatus }}
- {{
- 'project-overview.file-listing.file-entry.added'
- | translate: { added: fileStatus.added | date: 'short' }
- }}
+ {{ 'project-overview.file-listing.file-entry.added' | translate: { added: fileStatus.added | date: 'short' } }}
- {{
- 'project-overview.file-listing.file-entry.last-updated'
- | translate: { lastUpdated: fileStatus.lastUpdated | date: 'short' }
- }}
+ {{ 'project-overview.file-listing.file-entry.last-updated' | translate: { lastUpdated: fileStatus.lastUpdated | date: 'short' } }}