diff --git a/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.html b/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.html
index 817338b65..0c04aa61c 100644
--- a/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.html
+++ b/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.html
@@ -3,11 +3,11 @@
*ngFor="let userId of displayedMembers"
class="member"
[class.large-spacing]="largeSpacing"
- [class.can-remove]="canRemove"
- (click)="canRemove && remove.emit(userId)"
+ [class.can-remove]="canRemoveMember(userId)"
+ (click)="canRemoveMember(userId) && remove.emit(userId)"
>
-
diff --git a/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.ts b/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.ts
index 34c83603f..ed1a49f2d 100644
--- a/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.ts
+++ b/apps/red-ui/src/app/modules/projects/components/team-members/team-members.component.ts
@@ -12,6 +12,7 @@ export class TeamMembersComponent implements OnInit {
@Input() public canAdd = true;
@Input() public largeSpacing = false;
@Input() public canRemove = false;
+ @Input() public unremovableMembers: string[] = [];
@Output() public openAssignProjectMembersDialog = new EventEmitter();
@Output() public remove = new EventEmitter();
@@ -38,4 +39,8 @@ export class TeamMembersComponent implements OnInit {
public get overflowCount() {
return this.memberIds.length > this.maxTeamMembersBeforeExpand ? this.memberIds.length - (this.maxTeamMembersBeforeExpand - 1) : 0;
}
+
+ public canRemoveMember(userId: string) {
+ return this.canRemove && this.unremovableMembers.indexOf(userId) === -1;
+ }
}
diff --git a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html
index ceaecbb0e..f91abc1cf 100644
--- a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html
+++ b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html
@@ -14,17 +14,39 @@
-
+
-
+
+
+
+
+
+
diff --git a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss
index 3c383fe6e..5ef1a840a 100644
--- a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss
+++ b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss
@@ -8,6 +8,11 @@
margin-top: 16px;
}
+redaction-team-members {
+ margin-top: -4px;
+ display: block;
+}
+
.members-list {
max-height: 220px;
height: 220px;
@@ -30,40 +35,35 @@
width: 560px;
box-sizing: border-box;
- mat-icon {
+ .make-approver {
+ display: flex;
+ align-items: center;
+ }
+
+ .actions {
display: none;
position: absolute;
right: 13px;
- top: 12px;
- width: 14px;
- height: 14px;
+ top: 0;
+ height: 38px;
+ align-items: center;
+
+ mat-icon {
+ width: 14px;
+ height: 14px;
+ margin-left: 24px;
+ }
}
&.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;
- }
+ .actions {
+ display: flex;
}
}
}
@@ -72,7 +72,3 @@
.info {
margin-top: 4px;
}
-
-.mt-10 {
- margin-top: 10px;
-}
diff --git a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts
index dff977c46..a3fbff6ae 100644
--- a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts
+++ b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts
@@ -42,11 +42,18 @@ export class AssignOwnerDialogComponent {
const project = this.data.project;
this.usersForm = this._formBuilder.group({
singleUser: [project?.ownerId, Validators.required],
- userList: [[...project?.memberIds]]
+ approvers: [[...project?.approverIds]],
+ members: [[...project?.memberIds]]
});
this.searchForm = this._formBuilder.group({
query: ['']
});
+ this.usersForm.get('singleUser').valueChanges.subscribe((singleUser) => {
+ if (!this.isApprover(singleUser)) {
+ this.toggleApprover(singleUser);
+ }
+ // If it is an approver, it is already a member, no need to check
+ });
}
if (this.data.type === 'file') {
@@ -67,8 +74,20 @@ export class AssignOwnerDialogComponent {
return this.usersForm.get('singleUser').value;
}
- public get selectedUserList(): string[] {
- return this.usersForm.get('userList').value;
+ public get selectedApproversList(): string[] {
+ return this.usersForm.get('approvers').value;
+ }
+
+ public get selectedReviewersList(): string[] {
+ return this.selectedUsersList.filter((m) => this.selectedApproversList.indexOf(m) === -1);
+ }
+
+ public get selectedUsersList(): string[] {
+ return this.usersForm.get('members').value;
+ }
+
+ public isOwner(userId: string): boolean {
+ return userId === this.selectedSingleUser;
}
async saveUsers() {
@@ -76,9 +95,11 @@ export class AssignOwnerDialogComponent {
try {
if (this.data.type === 'project') {
const ownerId = this.selectedSingleUser;
- const memberIds = this.selectedUserList;
+ const memberIds = this.selectedUsersList;
+ const approverIds = this.selectedApproversList;
const pw = Object.assign({}, this.data.project);
pw.project.memberIds = memberIds;
+ pw.project.approverIds = approverIds;
pw.project.ownerId = ownerId;
result = await this._appStateService.addOrUpdateProject(pw.project);
}
@@ -118,18 +139,56 @@ export class AssignOwnerDialogComponent {
}
public isMemberSelected(userId: string): boolean {
- return this.selectedUserList.indexOf(userId) !== -1;
+ return this.selectedUsersList.indexOf(userId) !== -1;
+ }
+
+ public isApprover(userId: string): boolean {
+ return this.selectedApproversList.indexOf(userId) !== -1;
+ }
+
+ public toggleApprover(userId: string, $event?: MouseEvent) {
+ $event?.stopPropagation();
+
+ if (this.isOwner(userId) && this.isApprover(userId)) {
+ return;
+ }
+
+ if (this.isApprover(userId)) {
+ this.selectedApproversList.splice(this.selectedApproversList.indexOf(userId), 1);
+ } else {
+ this.selectedApproversList.push(userId);
+ if (!this.isMemberSelected(userId)) {
+ this.toggleSelected(userId);
+ }
+ }
}
public toggleSelected(userId: string) {
if (this.isMemberSelected(userId)) {
- const idx = this.selectedUserList.indexOf(userId);
- this.selectedUserList.splice(idx, 1);
+ this.selectedUsersList.splice(this.selectedUsersList.indexOf(userId), 1);
+
+ if (this.isApprover(userId)) {
+ this.selectedApproversList.splice(this.selectedApproversList.indexOf(userId), 1);
+ }
} else {
- this.selectedUserList.push(userId);
+ this.selectedUsersList.push(userId);
}
}
+ private _compareLists(l1: string[], l2: string[]) {
+ if (l1.length !== l2.length) {
+ return true;
+ }
+
+ for (let idx = 0; idx < l1.length; ++idx) {
+ if (l1[idx] !== l2[idx]) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public get changed(): boolean {
if (this.data.ignoreChanged) {
return true;
@@ -140,18 +199,15 @@ export class AssignOwnerDialogComponent {
return true;
}
- const initial = this.data.project.memberIds.sort();
- const current = this.selectedUserList.sort();
+ const initialMembers = this.data.project.memberIds.sort();
+ const currentMembers = this.selectedUsersList.sort();
- if (initial.length !== current.length) {
+ const initialApprovers = this.data.project.approverIds.sort();
+ const currentApprovers = this.selectedApproversList.sort();
+
+ if (this._compareLists(initialMembers, currentMembers) || this._compareLists(initialApprovers, currentApprovers)) {
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;
diff --git a/apps/red-ui/src/app/state/model/project.wrapper.ts b/apps/red-ui/src/app/state/model/project.wrapper.ts
index 517635fbd..de4798f62 100644
--- a/apps/red-ui/src/app/state/model/project.wrapper.ts
+++ b/apps/red-ui/src/app/state/model/project.wrapper.ts
@@ -49,6 +49,10 @@ export class ProjectWrapper {
return this.project.memberIds;
}
+ get approverIds() {
+ return this.project.approverIds;
+ }
+
get files() {
return this._files;
}
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index 309d67fab..06a15f6a7 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -455,11 +455,14 @@
"single-user": "Owner",
"multi-user": "Review Team",
"title": "Manage Dossier Team",
- "members": "Members",
+ "approvers": "Approvers",
+ "reviewers": "Reviewers",
"save": "Save Changes",
"cancel": "Cancel",
"search": "Search...",
- "no-members": "No members yet.\nSelect from the list below."
+ "no-approvers": "No approvers yet.\nSelect from the list below.",
+ "no-reviewers": "No reviewers yet.\nSelect from the list below.",
+ "make-approver": "Make Approver"
}
},
"project-member-guard": {
diff --git a/libs/red-ui-http/src/lib/model/project.ts b/libs/red-ui-http/src/lib/model/project.ts
index dfb74d8dc..e3bbacdd3 100644
--- a/libs/red-ui-http/src/lib/model/project.ts
+++ b/libs/red-ui-http/src/lib/model/project.ts
@@ -11,6 +11,7 @@
*/
export interface Project {
+ approverIds?: Array;
date?: string;
description?: string;
downloadFileTypes?: Array;