Edit project approvers

This commit is contained in:
Adina Țeudan 2021-05-04 15:53:47 +03:00
parent 103c23a194
commit 534c5ea3dc
8 changed files with 145 additions and 53 deletions

View File

@ -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)"
>
<redaction-initials-avatar [userId]="userId" size="large" color="gray"></redaction-initials-avatar>
<div class="remove">
<div class="remove" *ngIf="canRemoveMember(userId)">
<mat-icon svgIcon="red:close"></mat-icon>
</div>
</div>

View File

@ -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<string>();
@ -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;
}
}

View File

@ -14,17 +14,39 @@
</mat-form-field>
</div>
<ng-container *ngIf="data.type === 'project'">
<div class="all-caps-label mt-10" [translate]="'assign-' + data.type + '-owner.dialog.members'"></div>
<div class="all-caps-label mt-16" [translate]="'assign-' + data.type + '-owner.dialog.approvers'"></div>
<redaction-team-members
[memberIds]="selectedUserList"
[memberIds]="selectedApproversList"
[canAdd]="false"
[largeSpacing]="true"
[canRemove]="true"
[unremovableMembers]="[selectedSingleUser]"
(remove)="toggleSelected($event)"
[perLine]="13"
></redaction-team-members>
<pre *ngIf="selectedUserList.length === 0" class="info" [innerHTML]="'assign-' + data.type + '-owner.dialog.no-members' | translate"></pre>
<pre
*ngIf="selectedApproversList.length === 0"
class="info"
[innerHTML]="'assign-' + data.type + '-owner.dialog.no-approvers' | translate"
></pre>
<div class="all-caps-label mt-16" [translate]="'assign-' + data.type + '-owner.dialog.reviewers'"></div>
<redaction-team-members
[memberIds]="selectedReviewersList"
[canAdd]="false"
[largeSpacing]="true"
[canRemove]="true"
[unremovableMembers]="[selectedSingleUser]"
(remove)="toggleSelected($event)"
[perLine]="13"
></redaction-team-members>
<pre
*ngIf="selectedReviewersList.length === 0"
class="info"
[innerHTML]="'assign-' + data.type + '-owner.dialog.no-reviewers' | translate"
></pre>
<redaction-search-input
[width]="560"
@ -36,8 +58,13 @@
<div class="members-list">
<div *ngFor="let userId of multiUsersSelectOptions" [class.selected]="isMemberSelected(userId)" (click)="toggleSelected(userId)">
<redaction-initials-avatar [userId]="userId" [withName]="true" size="large"></redaction-initials-avatar>
<mat-icon svgIcon="red:check" class="add"></mat-icon>
<mat-icon svgIcon="red:close" class="remove"></mat-icon>
<div class="actions">
<div class="make-approver" (click)="toggleApprover(userId, $event)">
<redaction-round-checkbox class="mr-8" [active]="isApprover(userId)"></redaction-round-checkbox>
<span [translate]="'assign-' + data.type + '-owner.dialog.make-approver'"></span>
</div>
<mat-icon *ngIf="!isOwner(userId)" [svgIcon]="'red:' + (isMemberSelected(userId) ? 'close' : 'check')"></mat-icon>
</div>
</div>
</div>
</ng-container>

View File

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

View File

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

View File

@ -49,6 +49,10 @@ export class ProjectWrapper {
return this.project.memberIds;
}
get approverIds() {
return this.project.approverIds;
}
get files() {
return this._files;
}

View File

@ -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": {

View File

@ -11,6 +11,7 @@
*/
export interface Project {
approverIds?: Array<string>;
date?: string;
description?: string;
downloadFileTypes?: Array<Project.DownloadFileTypesEnum>;