Bulk delete users & other improvements

This commit is contained in:
Adina Țeudan 2021-04-07 16:33:14 +03:00
parent bf684108e4
commit 37b9236f61
15 changed files with 232 additions and 85 deletions

View File

@ -29,8 +29,8 @@ import { AdminDialogService } from './services/admin-dialog-service.service';
import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.component'; import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.component';
import { SmtpAuthDialogComponent } from './dialogs/smtp-auth-dialog/smtp-auth-dialog.component'; import { SmtpAuthDialogComponent } from './dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-edit-user-dialog.component'; import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-edit-user-dialog.component';
import { ConfirmDeleteUserDialogComponent } from './dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component';
import { UsersStatsComponent } from './components/users-stats/users-stats.component'; import { UsersStatsComponent } from './components/users-stats/users-stats.component';
import { ConfirmDeleteUsersDialogComponent } from './dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component';
const dialogs = [ const dialogs = [
AddEditRuleSetDialogComponent, AddEditRuleSetDialogComponent,
@ -40,7 +40,7 @@ const dialogs = [
EditColorDialogComponent, EditColorDialogComponent,
SmtpAuthDialogComponent, SmtpAuthDialogComponent,
AddEditUserDialogComponent, AddEditUserDialogComponent,
ConfirmDeleteUserDialogComponent ConfirmDeleteUsersDialogComponent
]; ];
const screens = [ const screens = [

View File

@ -41,7 +41,7 @@
[routerLinkActiveOptions]="{ exact: true }" [routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active" routerLinkActive="active"
translate="user-management" translate="user-management"
*ngIf="root && userPreferenceService.areDevFeaturesEnabled" *ngIf="root && permissionService.canManageUsers() && userPreferenceService.areDevFeaturesEnabled"
></a> ></a>
<a <a

View File

@ -12,6 +12,7 @@ import { UserService } from '../../../../services/user.service';
export class AddEditUserDialogComponent { export class AddEditUserDialogComponent {
public userForm: FormGroup; public userForm: FormGroup;
public ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN']; public ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
private ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
constructor( constructor(
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
@ -22,7 +23,16 @@ export class AddEditUserDialogComponent {
const rolesControls = this.ROLES.reduce( const rolesControls = this.ROLES.reduce(
(prev, role) => ({ (prev, role) => ({
...prev, ...prev,
[role]: [this.user && this.user.roles.indexOf(role) !== -1] [role]: [
{
value: this.user && this.user.roles.indexOf(role) !== -1,
disabled:
this.user &&
Object.keys(this.ROLE_REQUIREMENTS).reduce((value, key) => {
return value || (role === this.ROLE_REQUIREMENTS[key] && this.user.roles.indexOf(key) !== -1);
}, false)
}
]
}), }),
{} {}
); );
@ -37,14 +47,13 @@ export class AddEditUserDialogComponent {
} }
private _setRolesRequirements() { private _setRolesRequirements() {
const requirements = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' }; for (const key of Object.keys(this.ROLE_REQUIREMENTS)) {
for (const key of Object.keys(requirements)) {
this.userForm.controls[key].valueChanges.subscribe((checked) => { this.userForm.controls[key].valueChanges.subscribe((checked) => {
if (checked) { if (checked) {
this.userForm.patchValue({ [requirements[key]]: true }); this.userForm.patchValue({ [this.ROLE_REQUIREMENTS[key]]: true });
this.userForm.controls[requirements[key]].disable(); this.userForm.controls[this.ROLE_REQUIREMENTS[key]].disable();
} else { } else {
this.userForm.controls[requirements[key]].enable(); this.userForm.controls[this.ROLE_REQUIREMENTS[key]].enable();
} }
}); });
} }

View File

@ -1,41 +0,0 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service';
@Component({
selector: 'redaction-confirm-delete-user-dialog',
templateUrl: './confirm-delete-user-dialog.component.html',
styleUrls: ['./confirm-delete-user-dialog.component.scss']
})
export class ConfirmDeleteUserDialogComponent implements OnInit {
public checkboxes = [{ value: false }, { value: false }];
public showToast = false;
public projectsCount: number;
constructor(
@Inject(MAT_DIALOG_DATA) public user: User,
private readonly _appStateService: AppStateService,
public dialogRef: MatDialogRef<ConfirmDeleteUserDialogComponent>
) {
this.projectsCount = this._appStateService.allProjects.filter((pw) => pw.memberIds.indexOf(user.userId) !== -1).length;
}
ngOnInit(): void {}
async deleteUser() {
if (this.valid) {
this.dialogRef.close(true);
} else {
this.showToast = true;
}
}
public get valid() {
return this.checkboxes[0].value && this.checkboxes[1].value;
}
public cancel() {
this.dialogRef.close();
}
}

View File

@ -1,15 +1,15 @@
<section class="dialog"> <section class="dialog">
<div class="dialog-header heading-l" translate="confirm-delete-user.title"></div> <div class="dialog-header heading-l" [translate]="'confirm-delete-users.title.' + type"></div>
<div class="inline-dialog-toast toast-error" *ngIf="showToast"> <div class="inline-dialog-toast toast-error" *ngIf="showToast">
<div translate="confirm-delete-user.toast-error"></div> <div translate="confirm-delete-users.toast-error"></div>
<a (click)="showToast = false" class="toast-close-button"> <a (click)="showToast = false" class="toast-close-button">
<mat-icon svgIcon="red:close"></mat-icon> <mat-icon svgIcon="red:close"></mat-icon>
</a> </a>
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<div class="heading" translate="confirm-delete-user.warning"></div> <div class="heading" translate="confirm-delete-users.warning"></div>
<mat-checkbox <mat-checkbox
*ngFor="let checkbox of checkboxes; let idx = index" *ngFor="let checkbox of checkboxes; let idx = index"
@ -17,15 +17,15 @@
color="primary" color="primary"
[class.error]="!checkbox.value && showToast" [class.error]="!checkbox.value && showToast"
> >
{{ 'confirm-delete-user.checkbox-' + (idx + 1) | translate: { projectsCount: projectsCount } }} {{ 'confirm-delete-users.' + checkbox.label | translate: { projectsCount: projectsCount } }}
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
<button color="primary" mat-flat-button (click)="deleteUser()"> <button color="primary" mat-flat-button (click)="deleteUser()">
{{ 'confirm-delete-user.delete' | translate }} {{ 'confirm-delete-users.delete.' + type | translate }}
</button> </button>
<div class="all-caps-label cancel" (click)="cancel()" translate="confirm-delete-user.cancel"></div> <div class="all-caps-label cancel" (click)="cancel()" [translate]="'confirm-delete-users.cancel.' + type"></div>
</div> </div>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button> <redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
</section> </section>

View File

@ -0,0 +1,55 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service';
@Component({
selector: 'redaction-confirm-delete-users-dialog',
templateUrl: './confirm-delete-users-dialog.component.html',
styleUrls: ['./confirm-delete-users-dialog.component.scss']
})
export class ConfirmDeleteUsersDialogComponent implements OnInit {
public checkboxes = [
{ value: false, label: 'impacted-projects' },
{ value: false, label: 'impacted-documents.' + this.type }
];
public showToast = false;
public projectsCount: number;
constructor(
@Inject(MAT_DIALOG_DATA) public users: User[],
private readonly _appStateService: AppStateService,
public dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>
) {
this.projectsCount = this._appStateService.allProjects.filter((pw) => {
for (const user of this.users) {
if (pw.memberIds.indexOf(user.userId) !== -1) {
return true;
}
}
return false;
}).length;
}
ngOnInit(): void {}
async deleteUser() {
if (this.valid) {
this.dialogRef.close(true);
} else {
this.showToast = true;
}
}
public get valid() {
return this.checkboxes[0].value && this.checkboxes[1].value;
}
public cancel() {
this.dialogRef.close();
}
public get type(): 'bulk' | 'single' {
return this.users.length > 1 ? 'bulk' : 'single';
}
}

View File

@ -25,17 +25,47 @@
<div class="red-content-inner"> <div class="red-content-inner">
<div class="left-container" [class.extended]="collapsedDetails"> <div class="left-container" [class.extended]="collapsedDetails">
<div class="header-item"> <div class="header-item">
<div class="select-all-container">
<div
(click)="toggleSelectAll()"
[class.active]="areAllUsersSelected"
class="select-oval always-visible"
*ngIf="!areAllUsersSelected && !areSomeUsersSelected"
></div>
<mat-icon *ngIf="areAllUsersSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon
*ngIf="areSomeUsersSelected && !areAllUsersSelected"
(click)="toggleSelectAll()"
class="selection-icon"
svgIcon="red:radio-indeterminate"
></mat-icon>
</div>
<span class="all-caps-label"> <span class="all-caps-label">
{{ 'user-listing.table-header.title' | translate: { length: displayedUsers.length } }} {{ 'user-listing.table-header.title' | translate: { length: displayedUsers.length } }}
</span> </span>
<ng-container *ngIf="true || (areSomeUsersSelected && !loading)">
<redaction-circle-button
(action)="bulkDelete()"
[disabled]="!canDeleteSelected"
[tooltip]="canDeleteSelected ? 'user-listing.bulk.delete' : 'user-listing.bulk.delete-disabled'"
tooltipPosition="after"
type="dark-bg"
icon="red:trash"
></redaction-circle-button>
</ng-container>
<mat-spinner *ngIf="loading" diameter="15"></mat-spinner>
</div> </div>
<div class="table-header" redactionSyncWidth="table-item"> <div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder"></div>
<redaction-table-col-name label="user-listing.table-col-names.name"></redaction-table-col-name> <redaction-table-col-name label="user-listing.table-col-names.name"></redaction-table-col-name>
<redaction-table-col-name label="user-listing.table-col-names.email"></redaction-table-col-name> <redaction-table-col-name label="user-listing.table-col-names.email"></redaction-table-col-name>
<redaction-table-col-name label="user-listing.table-col-names.active"></redaction-table-col-name> <redaction-table-col-name label="user-listing.table-col-names.active" class="flex-center"></redaction-table-col-name>
<redaction-table-col-name label="user-listing.table-col-names.roles"></redaction-table-col-name> <redaction-table-col-name label="user-listing.table-col-names.roles"></redaction-table-col-name>
@ -46,12 +76,16 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines --> <!-- Table lines -->
<div class="table-item" *cdkVirtualFor="let user of displayedUsers"> <div class="table-item" *cdkVirtualFor="let user of displayedUsers">
<div class="pr-0" (click)="toggleUserSelected($event, user)">
<div *ngIf="!isUserSelected(user)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isUserSelected(user)" svgIcon="red:radio-selected"></mat-icon>
</div>
<div> <div>
<redaction-initials-avatar [user]="user" [withName]="true" [showYou]="true" [alwaysShowName]="true"></redaction-initials-avatar> <redaction-initials-avatar [user]="user" [withName]="true" [showYou]="true" [alwaysShowName]="true"></redaction-initials-avatar>
</div> </div>
<div class="small-label">{{ user.email || '-' }}</div> <div class="small-label">{{ user.email || '-' }}</div>
<div> <div class="center">
<mat-slide-toggle [checked]="userService.isActive(user)" color="primary" (toggleChange)="changeActive(user)"></mat-slide-toggle> <mat-slide-toggle [checked]="userService.isActive(user)" color="primary" (toggleChange)="toggleActive(user)"></mat-slide-toggle>
</div> </div>
<div class="small-label">{{ getDisplayRoles(user) }}</div> <div class="small-label">{{ getDisplayRoles(user) }}</div>
<div class="actions-container"> <div class="actions-container">
@ -64,7 +98,7 @@
> >
</redaction-circle-button> </redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="openDeleteUserDialog(user, $event)" (action)="openDeleteUserDialog([user], $event)"
tooltip="user-listing.action.delete" tooltip="user-listing.action.delete"
type="dark-bg" type="dark-bg"
icon="red:trash" icon="red:trash"

View File

@ -1,13 +1,23 @@
.left-container { .left-container {
width: calc(100vw - 353px); width: calc(100vw - 353px);
.header-item {
padding: 0 24px 0 10px;
}
redaction-table-col-name::ng-deep {
> div {
padding: 0 13px 0 10px !important;
}
}
cdk-virtual-scroll-viewport { cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper { ::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr 1fr auto 11px; grid-template-columns: auto 2fr 1fr 1fr 1fr auto 11px;
.table-item { .table-item {
> div:not(.scrollbar-placeholder) { > div:not(.scrollbar-placeholder) {
padding: 0 24px; padding-left: 10px;
&.center { &.center {
align-items: center; align-items: center;
@ -18,7 +28,7 @@
&.has-scrollbar:hover { &.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper { ::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr 1fr auto; grid-template-columns: auto 2fr 1fr 1fr 1fr auto;
} }
} }
} }

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service'; import { PermissionsService } from '../../../../services/permissions.service';
import { UserService } from '../../../../services/user.service'; import { UserService } from '../../../../services/user.service';
import { User, UserControllerService } from '@redaction/red-ui-http'; import { RuleSetModel, User, UserControllerService } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../../utils/debounce'; import { debounce } from '../../../../utils/debounce';
import { AdminDialogService } from '../../services/admin-dialog-service.service'; import { AdminDialogService } from '../../services/admin-dialog-service.service';
@ -16,11 +16,13 @@ import { TranslateChartService } from '../../../../services/translate-chart.serv
}) })
export class UserListingScreenComponent implements OnInit { export class UserListingScreenComponent implements OnInit {
public viewReady = false; public viewReady = false;
public loading = false;
public collapsedDetails = false; public collapsedDetails = false;
public chartData: DoughnutChartConfig[]; public chartData: DoughnutChartConfig[] = [];
public users: User[]; public users: User[];
public displayedUsers: User[] = []; public displayedUsers: User[] = [];
public searchForm: FormGroup; public searchForm: FormGroup;
public selectedUsersIds: string[] = [];
constructor( constructor(
public readonly permissionsService: PermissionsService, public readonly permissionsService: PermissionsService,
@ -52,34 +54,34 @@ export class UserListingScreenComponent implements OnInit {
$event.stopPropagation(); $event.stopPropagation();
this._adminDialogService.openAddEditUserDialog(user, async (result) => { this._adminDialogService.openAddEditUserDialog(user, async (result) => {
if (result === 'DELETE') { if (result === 'DELETE') {
this.openDeleteUserDialog(user); this.openDeleteUserDialog([user]);
} else { } else {
this.viewReady = false; this.loading = true;
if (result.action === 'CREATE') { if (result.action === 'CREATE') {
await this._userControllerService.createUser(result.user).toPromise(); await this._userControllerService.createUser(result.user).toPromise();
} else if (result.action === 'UPDATE') { } else if (result.action === 'UPDATE') {
await this._userControllerService.updateProfile(result.user).toPromise(); await this._userControllerService.updateProfile(result.user, user.userId).toPromise();
} }
await this._loadData(); await this._loadData();
} }
}); });
} }
public openDeleteUserDialog(user: User, $event?: MouseEvent) { public openDeleteUserDialog(users: User[], $event?: MouseEvent) {
$event?.stopPropagation(); $event?.stopPropagation();
this._adminDialogService.openConfirmDeleteUserDialog(user, async () => { this._adminDialogService.openConfirmDeleteUsersDialog(users, async () => {
this.viewReady = false; this.loading = true;
await this._userControllerService.deleteUser(user.userId).toPromise(); await this._userControllerService.deleteUsers(users.map((u) => u.userId)).toPromise();
await this._loadData(); await this._loadData();
}); });
} }
private async _loadData() { private async _loadData() {
this.viewReady = false;
this.users = (await this._userControllerService.getAllUsers({}).toPromise()).users; this.users = (await this._userControllerService.getAllUsers({}).toPromise()).users;
this._executeSearch(); this._executeSearch();
this._computeStats(); this._computeStats();
this.viewReady = true; this.viewReady = true;
this.loading = false;
} }
private _computeStats() { private _computeStats() {
@ -123,8 +125,8 @@ export class UserListingScreenComponent implements OnInit {
return user.roles.map((role) => this._translateService.instant('roles.' + role)).join(', ') || this._translateService.instant('roles.NO_ROLE'); return user.roles.map((role) => this._translateService.instant('roles.' + role)).join(', ') || this._translateService.instant('roles.NO_ROLE');
} }
public async changeActive(user: User) { public async toggleActive(user: User) {
this.viewReady = false; this.loading = true;
user.roles = this.userService.isActive(user) ? [] : ['RED_USER']; user.roles = this.userService.isActive(user) ? [] : ['RED_USER'];
await this._userControllerService.addRoleToUsers(user.roles, user.userId).toPromise(); await this._userControllerService.addRoleToUsers(user.roles, user.userId).toPromise();
await this._loadData(); await this._loadData();
@ -133,4 +135,42 @@ export class UserListingScreenComponent implements OnInit {
public toggleCollapsedDetails() { public toggleCollapsedDetails() {
this.collapsedDetails = !this.collapsedDetails; this.collapsedDetails = !this.collapsedDetails;
} }
toggleUserSelected($event: MouseEvent, user: User) {
$event.stopPropagation();
const idx = this.selectedUsersIds.indexOf(user.userId);
if (idx === -1) {
this.selectedUsersIds.push(user.userId);
} else {
this.selectedUsersIds.splice(idx, 1);
}
}
public toggleSelectAll() {
if (this.areSomeUsersSelected) {
this.selectedUsersIds = [];
} else {
this.selectedUsersIds = this.displayedUsers.map((user) => user.userId);
}
}
public get areAllUsersSelected() {
return this.displayedUsers.length !== 0 && this.selectedUsersIds.length === this.displayedUsers.length;
}
public get areSomeUsersSelected() {
return this.selectedUsersIds.length > 0;
}
public isUserSelected(user: User) {
return this.selectedUsersIds.indexOf(user.userId) !== -1;
}
public async bulkDelete() {
this.openDeleteUserDialog(this.users.filter((u) => this.isUserSelected(u)));
}
public get canDeleteSelected(): boolean {
return this.selectedUsersIds.indexOf(this.userService.userId) === -1;
}
} }

View File

@ -23,7 +23,7 @@ import { EditColorDialogComponent } from '../dialogs/edit-color-dialog/edit-colo
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { SmtpAuthDialogComponent } from '../dialogs/smtp-auth-dialog/smtp-auth-dialog.component'; import { SmtpAuthDialogComponent } from '../dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
import { AddEditUserDialogComponent } from '../dialogs/add-edit-user-dialog/add-edit-user-dialog.component'; import { AddEditUserDialogComponent } from '../dialogs/add-edit-user-dialog/add-edit-user-dialog.component';
import { ConfirmDeleteUserDialogComponent } from '../dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component'; import { ConfirmDeleteUsersDialogComponent } from '../dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component';
const dialogConfig = { const dialogConfig = {
width: '662px', width: '662px',
@ -188,10 +188,10 @@ export class AdminDialogService {
return ref; return ref;
} }
public openConfirmDeleteUserDialog(user?: User, cb?: Function): MatDialogRef<ConfirmDeleteUserDialogComponent> { public openConfirmDeleteUsersDialog(users: User[], cb?: Function): MatDialogRef<ConfirmDeleteUsersDialogComponent> {
const ref = this._dialog.open(ConfirmDeleteUserDialogComponent, { const ref = this._dialog.open(ConfirmDeleteUsersDialogComponent, {
...dialogConfig, ...dialogConfig,
data: user, data: users,
autoFocus: true autoFocus: true
}); });

View File

@ -278,6 +278,16 @@ export class PermissionsService {
return user.isAdmin; return user.isAdmin;
} }
isUserAdmin(user?: UserWrapper) {
if (!user) {
user = this._userService.user;
}
if (!user) {
return false;
}
return user.isUserAdmin;
}
isUser(user?: UserWrapper) { isUser(user?: UserWrapper) {
if (!user) { if (!user) {
user = this._userService.user; user = this._userService.user;
@ -297,4 +307,8 @@ export class PermissionsService {
} }
return fileStatus.status === 'UNASSIGNED' || fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL'; return fileStatus.status === 'UNASSIGNED' || fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL';
} }
canManageUsers(user?: UserWrapper) {
return this.isUserAdmin(user);
}
} }

View File

@ -30,6 +30,10 @@ export class UserWrapper {
return this.roles.indexOf('RED_ADMIN') >= 0; return this.roles.indexOf('RED_ADMIN') >= 0;
} }
get isUserAdmin() {
return this.roles.indexOf('RED_USER_ADMIN') >= 0;
}
get hasAnyREDRoles() { get hasAnyREDRoles() {
return this.isUser || this.isManager || this.isAdmin; return this.isUser || this.isManager || this.isAdmin;
} }

View File

@ -758,13 +758,25 @@
"checkbox-2": "All inputted details on the documents will be lost", "checkbox-2": "All inputted details on the documents will be lost",
"toast-error": "Please confirm that you understand the ramifications of your action!" "toast-error": "Please confirm that you understand the ramifications of your action!"
}, },
"confirm-delete-user": { "confirm-delete-users": {
"title": "Delete User from Workspace", "title": {
"single": "Delete User from Workspace",
"bulk": "Delete Users from Workspace"
},
"warning": "Warning: this cannot be undone!", "warning": "Warning: this cannot be undone!",
"checkbox-1": "{{projectsCount}} projects will be impacted", "impacted-projects": "{{projectsCount}} projects will be impacted",
"checkbox-2": "All documents waiting review from the user will be impacted", "impacted-documents": {
"delete": "Delete User", "single": "All documents pending review from the user will be impacted",
"cancel": "Keep User", "bulk": "All documents pending review from the users will be impacted"
},
"delete": {
"single": "Delete User",
"bulk": "Delete Users"
},
"cancel": {
"single": "Keep User",
"bulk": "Keep Users"
},
"toast-error": "Please confirm that you understand the ramifications of your action!" "toast-error": "Please confirm that you understand the ramifications of your action!"
}, },
"document-info": { "document-info": {
@ -786,6 +798,10 @@
"edit": "Edit User", "edit": "Edit User",
"delete": "Delete User" "delete": "Delete User"
}, },
"bulk": {
"delete": "Delete Users",
"delete-disabled": "You cannot delete your own account."
},
"search": "Search...", "search": "Search...",
"add-new": "New User" "add-new": "New User"
}, },

View File

@ -54,3 +54,9 @@
left: 100%; left: 100%;
transform: rotate(-90deg) translateY(3px) translateX(3px); transform: rotate(-90deg) translateY(3px) translateX(3px);
} }
.mat-tooltip[style*='transform-origin: left center']:after {
top: 50%;
left: 0;
transform: rotate(90deg) translateY(3px) translateX(-3px);
}