Pull request #141: RED-679: User management
Merge in RED/ui from RED-679 to master * commit '37b9236f615fed03e1b218273549646110294acc': Bulk delete users & other improvements Delete file attribute / user warning styles User management updates Delete user User management WIP
This commit is contained in:
commit
801b13e192
@ -28,6 +28,9 @@ import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
import { AdminDialogService } from './services/admin-dialog-service.service';
|
||||
import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.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 { UsersStatsComponent } from './components/users-stats/users-stats.component';
|
||||
import { ConfirmDeleteUsersDialogComponent } from './dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component';
|
||||
|
||||
const dialogs = [
|
||||
AddEditRuleSetDialogComponent,
|
||||
@ -35,7 +38,9 @@ const dialogs = [
|
||||
AddEditFileAttributeDialogComponent,
|
||||
ConfirmDeleteFileAttributeDialogComponent,
|
||||
EditColorDialogComponent,
|
||||
SmtpAuthDialogComponent
|
||||
SmtpAuthDialogComponent,
|
||||
AddEditUserDialogComponent,
|
||||
ConfirmDeleteUsersDialogComponent
|
||||
];
|
||||
|
||||
const screens = [
|
||||
@ -49,7 +54,8 @@ const screens = [
|
||||
FileAttributesListingScreenComponent,
|
||||
LicenseInformationScreenComponent,
|
||||
UserListingScreenComponent,
|
||||
WatermarkScreenComponent
|
||||
WatermarkScreenComponent,
|
||||
SmtpConfigScreenComponent
|
||||
];
|
||||
|
||||
const components = [
|
||||
@ -58,7 +64,7 @@ const components = [
|
||||
TabsComponent,
|
||||
ComboChartComponent,
|
||||
ComboSeriesVerticalComponent,
|
||||
SmtpConfigScreenComponent,
|
||||
UsersStatsComponent,
|
||||
...dialogs,
|
||||
...screens
|
||||
];
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
routerLinkActive="active"
|
||||
translate="user-management"
|
||||
*ngIf="root && userPreferenceService.areDevFeaturesEnabled"
|
||||
*ngIf="root && permissionService.canManageUsers() && userPreferenceService.areDevFeaturesEnabled"
|
||||
></a>
|
||||
|
||||
<a
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
<div class="collapsed-wrapper">
|
||||
<redaction-circle-button (action)="toggleCollapse.emit()" icon="red:expand" tooltip="user-stats.expand" tooltipPosition="before"></redaction-circle-button>
|
||||
<div class="all-caps-label" translate="user-stats.title"></div>
|
||||
</div>
|
||||
|
||||
<div class="header-wrapper mt-8">
|
||||
<div class="heading-xl flex-1" translate="user-stats.title"></div>
|
||||
<redaction-circle-button
|
||||
(action)="toggleCollapse.emit()"
|
||||
icon="red:collapse"
|
||||
tooltip="user-stats.collapse"
|
||||
tooltipPosition="before"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-44">
|
||||
<redaction-simple-doughnut-chart
|
||||
[config]="chartData"
|
||||
[strokeWidth]="15"
|
||||
[radius]="63"
|
||||
[subtitle]="'user-stats.chart.users'"
|
||||
totalType="sum"
|
||||
direction="row"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
@ -0,0 +1,19 @@
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
|
||||
.heading-xl {
|
||||
max-width: 88%;
|
||||
}
|
||||
|
||||
redaction-circle-button {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 270px;
|
||||
}
|
||||
}
|
||||
|
||||
.mt-44 {
|
||||
margin-top: 44px;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-users-stats',
|
||||
templateUrl: './users-stats.component.html',
|
||||
styleUrls: ['./users-stats.component.scss']
|
||||
})
|
||||
export class UsersStatsComponent implements OnInit {
|
||||
@Output() public toggleCollapse = new EventEmitter();
|
||||
@Input() chartData: DoughnutChartConfig[];
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<section class="dialog">
|
||||
<div class="dialog-header heading-l">
|
||||
{{ (user ? 'add-edit-user.title.edit' : 'add-edit-user.title.new') | translate }}
|
||||
</div>
|
||||
|
||||
<form (submit)="save()" [formGroup]="userForm">
|
||||
<div class="dialog-content">
|
||||
<div class="red-input-group required w-300">
|
||||
<label translate="add-edit-user.form.first-name"></label>
|
||||
<input formControlName="firstName" name="firstName" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="red-input-group required w-300">
|
||||
<label translate="add-edit-user.form.last-name"></label>
|
||||
<input formControlName="lastName" name="lastName" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="red-input-group required w-300">
|
||||
<label translate="add-edit-user.form.email"></label>
|
||||
<input formControlName="email" name="email" type="email" />
|
||||
</div>
|
||||
|
||||
<!-- <div class="red-input-group required w-300">-->
|
||||
<!-- <label translate="add-edit-user.form.password"></label>-->
|
||||
<!-- <input formControlName="password" name="password" type="password" />-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="red-input-group">
|
||||
<label translate="add-edit-user.form.role"></label>
|
||||
<div class="roles-wrapper">
|
||||
<mat-checkbox [formControlName]="role" *ngFor="let role of ROLES" color="primary">
|
||||
{{ 'roles.' + role | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="userForm.invalid || !changed" color="primary" mat-flat-button type="submit">
|
||||
{{ (user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate }}
|
||||
</button>
|
||||
|
||||
<redaction-icon-button *ngIf="user" icon="red:trash" type="show-bg" (action)="delete()" text="add-edit-user.actions.delete"></redaction-icon-button>
|
||||
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="add-edit-user.actions.cancel"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
|
||||
</section>
|
||||
@ -0,0 +1,7 @@
|
||||
.roles-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-row-gap: 8px;
|
||||
margin-top: 8px;
|
||||
width: 300px;
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { User } from '@redaction/red-ui-http';
|
||||
import { UserService } from '../../../../services/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-user-dialog',
|
||||
templateUrl: './add-edit-user-dialog.component.html',
|
||||
styleUrls: ['./add-edit-user-dialog.component.scss']
|
||||
})
|
||||
export class AddEditUserDialogComponent {
|
||||
public userForm: FormGroup;
|
||||
public ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
|
||||
private ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _userService: UserService,
|
||||
public dialogRef: MatDialogRef<AddEditUserDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public user: User
|
||||
) {
|
||||
const rolesControls = this.ROLES.reduce(
|
||||
(prev, role) => ({
|
||||
...prev,
|
||||
[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)
|
||||
}
|
||||
]
|
||||
}),
|
||||
{}
|
||||
);
|
||||
this.userForm = this._formBuilder.group({
|
||||
firstName: [this.user?.firstName, Validators.required],
|
||||
lastName: [this.user?.lastName, Validators.required],
|
||||
email: [{ value: this.user?.email, disabled: !!user }, [Validators.required, Validators.email]],
|
||||
// password: [this.user?.password, Validators.required],
|
||||
...rolesControls
|
||||
});
|
||||
this._setRolesRequirements();
|
||||
}
|
||||
|
||||
private _setRolesRequirements() {
|
||||
for (const key of Object.keys(this.ROLE_REQUIREMENTS)) {
|
||||
this.userForm.controls[key].valueChanges.subscribe((checked) => {
|
||||
if (checked) {
|
||||
this.userForm.patchValue({ [this.ROLE_REQUIREMENTS[key]]: true });
|
||||
this.userForm.controls[this.ROLE_REQUIREMENTS[key]].disable();
|
||||
} else {
|
||||
this.userForm.controls[this.ROLE_REQUIREMENTS[key]].enable();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get changed(): boolean {
|
||||
if (!this.user) return true;
|
||||
|
||||
for (const key of Object.keys(this.userForm.getRawValue())) {
|
||||
if (this.user[key] !== this.userForm.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async save() {
|
||||
this.dialogRef.close({
|
||||
action: this.user ? 'UPDATE' : 'CREATE',
|
||||
user: { ...this.userForm.getRawValue(), roles: this.activeRoles }
|
||||
});
|
||||
}
|
||||
|
||||
public async delete() {
|
||||
this.dialogRef.close('DELETE');
|
||||
}
|
||||
|
||||
public get activeRoles(): string[] {
|
||||
return this.ROLES.reduce((acc, role) => {
|
||||
if (this.userForm.get(role).value) {
|
||||
acc.push(role);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
@ -3,16 +3,28 @@
|
||||
{{ 'confirm-delete-file-attribute.title' | translate: { name: fileAttribute.label } }}
|
||||
</div>
|
||||
|
||||
<div class="inline-dialog-toast toast-error" *ngIf="showToast">
|
||||
<div translate="confirm-delete-file-attribute.toast-error"></div>
|
||||
<a (click)="showToast = false" class="toast-close-button">
|
||||
<mat-icon svgIcon="red:close"></mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<div class="heading" translate="confirm-delete-file-attribute.warning"></div>
|
||||
|
||||
<mat-checkbox *ngFor="let checkbox of checkboxes; let idx = index" [(ngModel)]="checkbox.value" color="primary">
|
||||
<mat-checkbox
|
||||
*ngFor="let checkbox of checkboxes; let idx = index"
|
||||
[(ngModel)]="checkbox.value"
|
||||
color="primary"
|
||||
[class.error]="!checkbox.value && showToast"
|
||||
>
|
||||
{{ 'confirm-delete-file-attribute.checkbox-' + (idx + 1) | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!valid" color="primary" mat-flat-button (click)="deleteFileAttribute()">
|
||||
<button color="primary" mat-flat-button (click)="deleteFileAttribute()">
|
||||
{{ 'confirm-delete-file-attribute.delete' | translate }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" (click)="cancel()" translate="confirm-delete-file-attribute.cancel"></div>
|
||||
|
||||
@ -8,6 +8,10 @@
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
mat-checkbox:not(:last-of-type) {
|
||||
margin-bottom: 6px;
|
||||
mat-checkbox {
|
||||
width: 100%;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ export class ConfirmDeleteFileAttributeDialogComponent {
|
||||
public fileAttribute: FileAttributeConfig;
|
||||
public ruleSetId: string;
|
||||
public checkboxes = [{ value: false }, { value: false }];
|
||||
public showToast = false;
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
@ -28,8 +29,12 @@ export class ConfirmDeleteFileAttributeDialogComponent {
|
||||
}
|
||||
|
||||
async deleteFileAttribute() {
|
||||
await this._fileAttributesService.deleteFileAttributesConfiguration(this.ruleSetId, this.fileAttribute.id).toPromise();
|
||||
this.dialogRef.close(true);
|
||||
if (this.valid) {
|
||||
await this._fileAttributesService.deleteFileAttributesConfiguration(this.ruleSetId, this.fileAttribute.id).toPromise();
|
||||
this.dialogRef.close(true);
|
||||
} else {
|
||||
this.showToast = true;
|
||||
}
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
<section class="dialog">
|
||||
<div class="dialog-header heading-l" [translate]="'confirm-delete-users.title.' + type"></div>
|
||||
|
||||
<div class="inline-dialog-toast toast-error" *ngIf="showToast">
|
||||
<div translate="confirm-delete-users.toast-error"></div>
|
||||
<a (click)="showToast = false" class="toast-close-button">
|
||||
<mat-icon svgIcon="red:close"></mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<div class="heading" translate="confirm-delete-users.warning"></div>
|
||||
|
||||
<mat-checkbox
|
||||
*ngFor="let checkbox of checkboxes; let idx = index"
|
||||
[(ngModel)]="checkbox.value"
|
||||
color="primary"
|
||||
[class.error]="!checkbox.value && showToast"
|
||||
>
|
||||
{{ 'confirm-delete-users.' + checkbox.label | translate: { projectsCount: projectsCount } }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button color="primary" mat-flat-button (click)="deleteUser()">
|
||||
{{ 'confirm-delete-users.delete.' + type | translate }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" (click)="cancel()" [translate]="'confirm-delete-users.cancel.' + type"></div>
|
||||
</div>
|
||||
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
|
||||
</section>
|
||||
@ -0,0 +1,17 @@
|
||||
@import '../../../../../assets/styles/red-variables';
|
||||
|
||||
.dialog-header {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
mat-checkbox {
|
||||
width: 100%;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,15 @@
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs class="flex-1" [root]="true"></redaction-admin-breadcrumbs>
|
||||
|
||||
<redaction-search-input [form]="searchForm" [placeholder]="'user-listing.search'"></redaction-search-input>
|
||||
|
||||
<div class="actions">
|
||||
<redaction-search-input [form]="searchForm" [placeholder]="'user-listing.search'"></redaction-search-input>
|
||||
<redaction-icon-button
|
||||
(action)="openAddEditUserDialog($event)"
|
||||
*ngIf="permissionsService.isManager()"
|
||||
icon="red:plus"
|
||||
text="user-listing.add-new"
|
||||
type="primary"
|
||||
></redaction-icon-button>
|
||||
<redaction-circle-button
|
||||
class="ml-6"
|
||||
*ngIf="permissionsService.isUser()"
|
||||
@ -17,33 +23,99 @@
|
||||
</div>
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="left-container" [class.extended]="collapsedDetails">
|
||||
<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">
|
||||
{{ 'user-listing.table-header.title' | translate: { length: displayedUsers.length } }}
|
||||
</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 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.email"></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>
|
||||
|
||||
<div></div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<!-- Table lines -->
|
||||
<div class="table-item" *cdkVirtualFor="let user of displayedUsers">
|
||||
<div>
|
||||
<redaction-initials-avatar [userId]="user.userId" [withName]="true" size="large"></redaction-initials-avatar>
|
||||
<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>
|
||||
<redaction-initials-avatar [user]="user" [withName]="true" [showYou]="true" [alwaysShowName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="small-label">{{ user.email || '-' }}</div>
|
||||
<div class="center">
|
||||
<mat-slide-toggle [checked]="userService.isActive(user)" color="primary" (toggleChange)="toggleActive(user)"></mat-slide-toggle>
|
||||
</div>
|
||||
<div class="small-label">{{ getDisplayRoles(user) }}</div>
|
||||
<div class="actions-container">
|
||||
<div class="action-buttons">
|
||||
<redaction-circle-button
|
||||
(action)="openAddEditUserDialog($event, user)"
|
||||
tooltip="user-listing.action.edit"
|
||||
type="dark-bg"
|
||||
icon="red:edit"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
<redaction-circle-button
|
||||
(action)="openDeleteUserDialog([user], $event)"
|
||||
tooltip="user-listing.action.delete"
|
||||
type="dark-bg"
|
||||
icon="red:trash"
|
||||
[disabled]="user.userId === userService.userId"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>{{ user.email || '-' }}</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</div>
|
||||
|
||||
<div class="right-container"></div>
|
||||
<div class="right-container" redactionHasScrollbar [class.collapsed]="collapsedDetails">
|
||||
<redaction-users-stats (toggleCollapse)="toggleCollapsedDetails()" [chartData]="chartData"></redaction-users-stats>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -1,20 +1,34 @@
|
||||
.left-container {
|
||||
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 {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 1fr 1fr 11px;
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr auto 11px;
|
||||
|
||||
.table-item {
|
||||
> div {
|
||||
padding: 0 24px;
|
||||
> div:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
|
||||
&.center {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,9 +38,25 @@
|
||||
display: flex;
|
||||
width: 353px;
|
||||
min-width: 353px;
|
||||
padding: 16px 16px 16px 24px;
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
redaction-users-stats {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
width: calc(353px - 24px);
|
||||
justify-content: flex-end;
|
||||
|
||||
redaction-search-input:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
redaction-icon-button:not(:last-child) {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,33 +1,176 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PermissionsService } from '../../../../services/permissions.service';
|
||||
import { UserService } from '../../../../services/user.service';
|
||||
import { User } from '@redaction/red-ui-http';
|
||||
import { RuleSetModel, User, UserControllerService } from '@redaction/red-ui-http';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { debounce } from '../../../../utils/debounce';
|
||||
import { AdminDialogService } from '../../services/admin-dialog-service.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { TranslateChartService } from '../../../../services/translate-chart.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-user-listing-screen',
|
||||
templateUrl: './user-listing-screen.component.html',
|
||||
styleUrls: ['./user-listing-screen.component.scss']
|
||||
})
|
||||
export class UserListingScreenComponent {
|
||||
export class UserListingScreenComponent implements OnInit {
|
||||
public viewReady = false;
|
||||
public loading = false;
|
||||
public collapsedDetails = false;
|
||||
public chartData: DoughnutChartConfig[] = [];
|
||||
public users: User[];
|
||||
public displayedUsers: User[];
|
||||
public displayedUsers: User[] = [];
|
||||
public searchForm: FormGroup;
|
||||
public selectedUsersIds: string[] = [];
|
||||
|
||||
constructor(public readonly permissionsService: PermissionsService, private readonly userService: UserService, private readonly _formBuilder: FormBuilder) {
|
||||
this.users = this.userService.allUsers;
|
||||
this.displayedUsers = [...this.users];
|
||||
|
||||
constructor(
|
||||
public readonly permissionsService: PermissionsService,
|
||||
public readonly userService: UserService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _adminDialogService: AdminDialogService,
|
||||
private readonly _userControllerService: UserControllerService,
|
||||
private readonly _translateChartService: TranslateChartService
|
||||
) {
|
||||
this.searchForm = this._formBuilder.group({
|
||||
query: ['']
|
||||
});
|
||||
|
||||
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
|
||||
this.searchForm.valueChanges.subscribe(() => this._executeSearch());
|
||||
}
|
||||
|
||||
public async ngOnInit() {
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
@debounce(200)
|
||||
private _executeSearch(value: { query: string }) {
|
||||
this.displayedUsers = this.users.filter((user) => this.userService.getName(user).toLowerCase().includes(value.query.toLowerCase()));
|
||||
private _executeSearch() {
|
||||
const value = this.searchForm.get('query').value;
|
||||
this.displayedUsers = this.users.filter((user) => this.userService.getName(user).toLowerCase().includes(value.toLowerCase()));
|
||||
}
|
||||
|
||||
public openAddEditUserDialog($event: MouseEvent, user?: User) {
|
||||
$event.stopPropagation();
|
||||
this._adminDialogService.openAddEditUserDialog(user, async (result) => {
|
||||
if (result === 'DELETE') {
|
||||
this.openDeleteUserDialog([user]);
|
||||
} else {
|
||||
this.loading = true;
|
||||
if (result.action === 'CREATE') {
|
||||
await this._userControllerService.createUser(result.user).toPromise();
|
||||
} else if (result.action === 'UPDATE') {
|
||||
await this._userControllerService.updateProfile(result.user, user.userId).toPromise();
|
||||
}
|
||||
await this._loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public openDeleteUserDialog(users: User[], $event?: MouseEvent) {
|
||||
$event?.stopPropagation();
|
||||
this._adminDialogService.openConfirmDeleteUsersDialog(users, async () => {
|
||||
this.loading = true;
|
||||
await this._userControllerService.deleteUsers(users.map((u) => u.userId)).toPromise();
|
||||
await this._loadData();
|
||||
});
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
this.users = (await this._userControllerService.getAllUsers({}).toPromise()).users;
|
||||
this._executeSearch();
|
||||
this._computeStats();
|
||||
this.viewReady = true;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
private _computeStats() {
|
||||
this.chartData = this._translateChartService.translateRoles(
|
||||
[
|
||||
{
|
||||
value: this.users.filter((user) => !this.userService.isActive(user)).length,
|
||||
color: 'INACTIVE',
|
||||
label: 'INACTIVE'
|
||||
},
|
||||
{
|
||||
value: this.users.filter((user) => user.roles.length === 1 && user.roles[0] === 'RED_USER').length,
|
||||
color: 'REGULAR',
|
||||
label: 'REGULAR'
|
||||
},
|
||||
{
|
||||
value: this.users.filter((user) => this.userService.isManager(user) && !this.userService.isAdmin(user)).length,
|
||||
color: 'MANAGER',
|
||||
label: 'RED_MANAGER'
|
||||
},
|
||||
{
|
||||
value: this.users.filter((user) => this.userService.isManager(user) && this.userService.isAdmin(user)).length,
|
||||
color: 'MANAGER_ADMIN',
|
||||
label: 'MANAGER_ADMIN'
|
||||
},
|
||||
{
|
||||
value: this.users.filter((user) => this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)).length,
|
||||
color: 'USER_ADMIN',
|
||||
label: 'RED_USER_ADMIN'
|
||||
},
|
||||
{
|
||||
value: this.users.filter((user) => this.userService.isAdmin(user) && !this.userService.isManager(user)).length,
|
||||
color: 'ADMIN',
|
||||
label: 'RED_ADMIN'
|
||||
}
|
||||
].filter((type) => type.value > 0)
|
||||
);
|
||||
}
|
||||
|
||||
public getDisplayRoles(user: User) {
|
||||
return user.roles.map((role) => this._translateService.instant('roles.' + role)).join(', ') || this._translateService.instant('roles.NO_ROLE');
|
||||
}
|
||||
|
||||
public async toggleActive(user: User) {
|
||||
this.loading = true;
|
||||
user.roles = this.userService.isActive(user) ? [] : ['RED_USER'];
|
||||
await this._userControllerService.addRoleToUsers(user.roles, user.userId).toPromise();
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
public toggleCollapsedDetails() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,8 @@ import {
|
||||
RuleSetControllerService,
|
||||
RuleSetModel,
|
||||
SMTPConfigurationModel,
|
||||
TypeValue
|
||||
TypeValue,
|
||||
User
|
||||
} from '@redaction/red-ui-http';
|
||||
import { AddEditFileAttributeDialogComponent } from '../dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
|
||||
import { AddEditDictionaryDialogComponent } from '../dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component';
|
||||
@ -21,6 +22,8 @@ import { ConfirmDeleteFileAttributeDialogComponent } from '../dialogs/confirm-de
|
||||
import { EditColorDialogComponent } from '../dialogs/edit-color-dialog/edit-color-dialog.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
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 { ConfirmDeleteUsersDialogComponent } from '../dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component';
|
||||
|
||||
const dialogConfig = {
|
||||
width: '662px',
|
||||
@ -168,4 +171,36 @@ export class AdminDialogService {
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openAddEditUserDialog(user?: User, cb?: Function): MatDialogRef<AddEditUserDialogComponent> {
|
||||
const ref = this._dialog.open(AddEditUserDialogComponent, {
|
||||
...dialogConfig,
|
||||
data: user,
|
||||
autoFocus: true
|
||||
});
|
||||
|
||||
ref.afterClosed().subscribe((result) => {
|
||||
if (result && cb) {
|
||||
cb(result);
|
||||
}
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openConfirmDeleteUsersDialog(users: User[], cb?: Function): MatDialogRef<ConfirmDeleteUsersDialogComponent> {
|
||||
const ref = this._dialog.open(ConfirmDeleteUsersDialogComponent, {
|
||||
...dialogConfig,
|
||||
data: users,
|
||||
autoFocus: true
|
||||
});
|
||||
|
||||
ref.afterClosed().subscribe((result) => {
|
||||
if (result && cb) {
|
||||
cb(result);
|
||||
}
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="appStateService.activeProject">
|
||||
<div class="collapsed-wrapper mt-8">
|
||||
<div class="collapsed-wrapper">
|
||||
<redaction-circle-button
|
||||
(action)="toggleCollapse.emit()"
|
||||
icon="red:expand"
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
redaction-circle-button {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
top: -8px;
|
||||
left: 290px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div [className]="colorClass + ' oval ' + size + (hasBorder ? ' border' : '')" [matTooltipPosition]="'above'" [matTooltip]="displayName">
|
||||
{{ initials }}
|
||||
</div>
|
||||
<div *ngIf="withName" class="clamp-2 username">
|
||||
<div *ngIf="withName" class="clamp-2 username" [class.always-visible]="alwaysShowName" [class.disabled]="disabled">
|
||||
{{ displayName || ('initials-avatar.unassigned' | translate) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
|
||||
.username {
|
||||
margin-left: 6px;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { UserService } from '../../../../services/user.service';
|
||||
import { User } from '@redaction/red-ui-http';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-initials-avatar',
|
||||
@ -9,11 +10,12 @@ import { User } from '@redaction/red-ui-http';
|
||||
})
|
||||
export class InitialsAvatarComponent implements OnInit, OnChanges {
|
||||
@Input() public userId: string;
|
||||
@Input() public user: User;
|
||||
@Input() public color = 'lightgray';
|
||||
@Input() public size: 'small' | 'large' = 'small';
|
||||
@Input() public withName = false;
|
||||
|
||||
public _user: User;
|
||||
@Input() public showYou = false;
|
||||
@Input() public alwaysShowName = false;
|
||||
|
||||
public displayName: string;
|
||||
|
||||
@ -21,17 +23,23 @@ export class InitialsAvatarComponent implements OnInit, OnChanges {
|
||||
|
||||
public colorClass: string;
|
||||
|
||||
constructor(private readonly _userService: UserService) {}
|
||||
constructor(private readonly _userService: UserService, private readonly _translateService: TranslateService) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnChanges(): void {
|
||||
const isSystemUser = this.userId && this.userId.toLowerCase() === 'system';
|
||||
if (!isSystemUser) {
|
||||
this._user = this._userService.getUserById(this.userId);
|
||||
this.displayName = this._userService.getName(this._user);
|
||||
if (this.user) {
|
||||
this.userId = this.user.userId;
|
||||
} else {
|
||||
this.user = this._userService.getUserById(this.userId);
|
||||
}
|
||||
this.displayName =
|
||||
this._userService.getName(this.user) +
|
||||
(this.showYou && this._userService.userId === this.userId ? ` (${this._translateService.instant('initials-avatar.you')})` : '');
|
||||
this.initials = this._getInitials();
|
||||
this.colorClass = this._colorClass();
|
||||
this.colorClass = this._colorClass;
|
||||
} else {
|
||||
this.displayName = 'System';
|
||||
this.initials = 'SY';
|
||||
@ -40,11 +48,11 @@ export class InitialsAvatarComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
private _getInitials() {
|
||||
if (!this._user) {
|
||||
if (!this.user) {
|
||||
return '?';
|
||||
} else {
|
||||
return this._userService
|
||||
.getName(this._user)
|
||||
.getName(this.user)
|
||||
.split(' ')
|
||||
.filter((value) => value !== ' ')
|
||||
.filter((value, idx) => idx < 2)
|
||||
@ -53,10 +61,13 @@ export class InitialsAvatarComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
private _colorClass() {
|
||||
private get _colorClass() {
|
||||
if (this._userService.userId === this.userId) {
|
||||
return 'red-white';
|
||||
}
|
||||
if (this.disabled) {
|
||||
return 'inactive';
|
||||
}
|
||||
if (this.color.includes('-')) {
|
||||
return this.color;
|
||||
}
|
||||
@ -64,6 +75,10 @@ export class InitialsAvatarComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
public get hasBorder(): boolean {
|
||||
return !!this._user && this._userService.userId !== this.userId && this._userService.isManager(this._user);
|
||||
return !!this.user && this._userService.userId !== this.userId && this._userService.isManager(this.user);
|
||||
}
|
||||
|
||||
public get disabled(): boolean {
|
||||
return !this._userService.isActive(this.user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
&:not(.column) {
|
||||
> *:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@
|
||||
|
||||
.text-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
@ -10,7 +10,7 @@ export enum NotificationType {
|
||||
INFO = 'INFO'
|
||||
}
|
||||
|
||||
export class Action {
|
||||
export class ToastAction {
|
||||
title: string;
|
||||
action?: Function;
|
||||
}
|
||||
@ -31,7 +31,7 @@ export class NotificationService {
|
||||
message: string,
|
||||
title?: string,
|
||||
notificationType: NotificationType = NotificationType.INFO,
|
||||
options?: Partial<IndividualConfig> & { actions?: Action[] }
|
||||
options?: Partial<IndividualConfig> & { actions?: ToastAction[] }
|
||||
): ActiveToast<any> {
|
||||
switch (notificationType) {
|
||||
case NotificationType.ERROR:
|
||||
|
||||
@ -278,6 +278,16 @@ export class PermissionsService {
|
||||
return user.isAdmin;
|
||||
}
|
||||
|
||||
isUserAdmin(user?: UserWrapper) {
|
||||
if (!user) {
|
||||
user = this._userService.user;
|
||||
}
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return user.isUserAdmin;
|
||||
}
|
||||
|
||||
isUser(user?: UserWrapper) {
|
||||
if (!user) {
|
||||
user = this._userService.user;
|
||||
@ -297,4 +307,8 @@ export class PermissionsService {
|
||||
}
|
||||
return fileStatus.status === 'UNASSIGNED' || fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL';
|
||||
}
|
||||
|
||||
canManageUsers(user?: UserWrapper) {
|
||||
return this.isUserAdmin(user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,4 +11,8 @@ export class TranslateChartService {
|
||||
public translateStatus(config: DoughnutChartConfig[]): DoughnutChartConfig[] {
|
||||
return config.map((val) => ({ ...val, label: this._translateService.instant(val.label) }));
|
||||
}
|
||||
|
||||
public translateRoles(config: DoughnutChartConfig[]): DoughnutChartConfig[] {
|
||||
return config.map((val) => ({ ...val, label: this._translateService.instant(`roles.${val.label}`).toLowerCase() }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,10 @@ export class UserWrapper {
|
||||
return this.roles.indexOf('RED_ADMIN') >= 0;
|
||||
}
|
||||
|
||||
get isUserAdmin() {
|
||||
return this.roles.indexOf('RED_USER_ADMIN') >= 0;
|
||||
}
|
||||
|
||||
get hasAnyREDRoles() {
|
||||
return this.isUser || this.isManager || this.isAdmin;
|
||||
}
|
||||
@ -114,6 +118,27 @@ export class UserService {
|
||||
return user.roles?.indexOf('RED_USER') >= 0;
|
||||
}
|
||||
|
||||
isUserAdmin(user?: User): boolean {
|
||||
if (!user) {
|
||||
user = this.user;
|
||||
}
|
||||
return user.roles?.indexOf('RED_USER_ADMIN') >= 0;
|
||||
}
|
||||
|
||||
isAdmin(user?: User): boolean {
|
||||
if (!user) {
|
||||
user = this.user;
|
||||
}
|
||||
return user.roles?.indexOf('RED_ADMIN') >= 0;
|
||||
}
|
||||
|
||||
isActive(user?: User): boolean {
|
||||
if (!user) {
|
||||
user = this.user;
|
||||
}
|
||||
return user.roles?.length > 0;
|
||||
}
|
||||
|
||||
private _hasAnyRedRole(u: User) {
|
||||
return u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0 || u.roles.indexOf('RED_ADMIN') >= 0;
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ import { tap } from 'rxjs/operators';
|
||||
import { humanize } from '../utils/functions';
|
||||
import { FileStatusWrapper } from '../models/file/file-status.wrapper';
|
||||
import { ProjectWrapper } from './model/project.wrapper';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export interface AppState {
|
||||
projects: ProjectWrapper[];
|
||||
|
||||
@ -417,7 +417,8 @@
|
||||
"show": "Show"
|
||||
},
|
||||
"initials-avatar": {
|
||||
"unassigned": "Unassigned"
|
||||
"unassigned": "Unassigned",
|
||||
"you": "You"
|
||||
},
|
||||
"assign-file-owner": {
|
||||
"dialog": {
|
||||
@ -754,7 +755,29 @@
|
||||
"delete": "Delete Attribute",
|
||||
"cancel": "Keep Attribute",
|
||||
"checkbox-1": "All documents it is used on will be impacted",
|
||||
"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!"
|
||||
},
|
||||
"confirm-delete-users": {
|
||||
"title": {
|
||||
"single": "Delete User from Workspace",
|
||||
"bulk": "Delete Users from Workspace"
|
||||
},
|
||||
"warning": "Warning: this cannot be undone!",
|
||||
"impacted-projects": "{{projectsCount}} projects will be impacted",
|
||||
"impacted-documents": {
|
||||
"single": "All documents pending review from the user will be impacted",
|
||||
"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!"
|
||||
},
|
||||
"document-info": {
|
||||
"title": "Introduce File Attributes",
|
||||
@ -767,9 +790,46 @@
|
||||
},
|
||||
"table-col-names": {
|
||||
"name": "Name",
|
||||
"email": "Email"
|
||||
"email": "Email",
|
||||
"active": "Active",
|
||||
"roles": "Roles"
|
||||
},
|
||||
"search": "Search..."
|
||||
"action": {
|
||||
"edit": "Edit User",
|
||||
"delete": "Delete User"
|
||||
},
|
||||
"bulk": {
|
||||
"delete": "Delete Users",
|
||||
"delete-disabled": "You cannot delete your own account."
|
||||
},
|
||||
"search": "Search...",
|
||||
"add-new": "New User"
|
||||
},
|
||||
"add-edit-user": {
|
||||
"title": {
|
||||
"new": "Add New User",
|
||||
"edit": "Edit User"
|
||||
},
|
||||
"form": {
|
||||
"first-name": "First Name",
|
||||
"last-name": "Last Name",
|
||||
"email": "Email",
|
||||
"role": "Role"
|
||||
},
|
||||
"actions": {
|
||||
"save": "Save User",
|
||||
"save-changes": "Save Changes",
|
||||
"delete": "Delete User",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
},
|
||||
"user-stats": {
|
||||
"title": "Users",
|
||||
"chart": {
|
||||
"users": "Users in Workspace"
|
||||
},
|
||||
"expand": "Show Details",
|
||||
"collapse": "Hide Details"
|
||||
},
|
||||
"rules-screen": {
|
||||
"error": {
|
||||
@ -1015,5 +1075,15 @@
|
||||
"stream-download": {
|
||||
"abort": "You have an active download, closing this window will cause it to stop!",
|
||||
"error": "Failed to download {{filename}}. Please try again ..."
|
||||
},
|
||||
"roles": {
|
||||
"RED_USER": "User",
|
||||
"RED_MANAGER": "Manager",
|
||||
"RED_USER_ADMIN": "Users Admin",
|
||||
"RED_ADMIN": "Application Admin",
|
||||
"NO_ROLE": "No role defined",
|
||||
"INACTIVE": "Inactive",
|
||||
"MANAGER_ADMIN": "Manager & Admin",
|
||||
"REGULAR": "Regular"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
.mat-button,
|
||||
.mat-flat-button {
|
||||
font-family: Inter, sans-serif !important;
|
||||
border-radius: 17px !important;
|
||||
font-size: 13px !important;
|
||||
height: 34px;
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
}
|
||||
|
||||
.mat-checkbox-label {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 13px;
|
||||
color: $accent;
|
||||
|
||||
@ -30,3 +29,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-checkbox.error .mat-checkbox-label {
|
||||
color: $red-1;
|
||||
}
|
||||
|
||||
@ -58,6 +58,11 @@
|
||||
&.white-dark {
|
||||
border: 1px solid $grey-4;
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
background-color: $grey-6;
|
||||
color: $grey-7;
|
||||
}
|
||||
}
|
||||
|
||||
.oval {
|
||||
@ -106,7 +111,8 @@
|
||||
background-color: $grey-3;
|
||||
}
|
||||
|
||||
.UNDER_REVIEW {
|
||||
.UNDER_REVIEW,
|
||||
.REGULAR {
|
||||
stroke: $yellow-1;
|
||||
background-color: $yellow-1;
|
||||
}
|
||||
@ -116,7 +122,8 @@
|
||||
background-color: $blue-4;
|
||||
}
|
||||
|
||||
.APPROVED {
|
||||
.APPROVED,
|
||||
.ADMIN {
|
||||
stroke: $blue-3;
|
||||
background-color: $blue-3;
|
||||
}
|
||||
@ -126,7 +133,8 @@
|
||||
background-color: $grey-1;
|
||||
}
|
||||
|
||||
.OCR_PROCESSING {
|
||||
.OCR_PROCESSING,
|
||||
.USER_ADMIN {
|
||||
stroke: $green-2;
|
||||
background-color: $green-2;
|
||||
}
|
||||
@ -156,6 +164,17 @@
|
||||
background-color: rgba(#0389ec, 0.1);
|
||||
}
|
||||
|
||||
.INACTIVE {
|
||||
stroke: $grey-5;
|
||||
background-color: $grey-5;
|
||||
}
|
||||
|
||||
.MANAGER,
|
||||
.MANAGER_ADMIN {
|
||||
stroke: $red-1;
|
||||
background-color: $red-1;
|
||||
}
|
||||
|
||||
.select-oval {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
@ -125,6 +125,11 @@ form {
|
||||
border-color: $red-1;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: $grey-2;
|
||||
color: rgba($grey-1, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.hex-color-input {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
@import 'red-variables';
|
||||
|
||||
.mat-list-item {
|
||||
font-family: Inter, sans-serif;
|
||||
color: $grey-1 !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 16px !important;
|
||||
|
||||
@ -55,6 +55,12 @@ $gn-next-mat-theme: mat-light-theme(
|
||||
|
||||
@include angular-material-theme($gn-next-mat-theme);
|
||||
|
||||
$custom-typography: mat-typography-config(
|
||||
$font-family: 'Inter, sans-serif'
|
||||
);
|
||||
|
||||
@include angular-material-typography($custom-typography);
|
||||
|
||||
.mat-flat-button {
|
||||
min-width: unset !important;
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
}
|
||||
|
||||
.mat-menu-item {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 13px;
|
||||
color: $accent;
|
||||
padding: 0 8px;
|
||||
|
||||
@ -121,7 +121,7 @@ body {
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1600px) {
|
||||
redaction-initials-avatar .username {
|
||||
redaction-initials-avatar .username:not(.always-visible) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
@import 'red-variables';
|
||||
|
||||
.mat-select-panel .mat-option {
|
||||
font-family: Inter, sans-serif;
|
||||
|
||||
&:hover:not(.mat-option-disabled),
|
||||
&:focus:not(.mat-option-disabled) {
|
||||
background-color: $grey-6;
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
|
||||
$toast-width: 400px;
|
||||
|
||||
.toast-container .ngx-toastr {
|
||||
.toast-container .ngx-toastr,
|
||||
.inline-dialog-toast {
|
||||
padding: 11px 16px;
|
||||
border-radius: 8px;
|
||||
background-image: none;
|
||||
@ -90,6 +91,7 @@ $toast-width: 400px;
|
||||
|
||||
.toast-error {
|
||||
background-color: $red-1;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.toast-warning {
|
||||
@ -99,3 +101,9 @@ $toast-width: 400px;
|
||||
.toast-info {
|
||||
background-color: $grey-1;
|
||||
}
|
||||
|
||||
.inline-dialog-toast {
|
||||
width: calc(100% - 96px);
|
||||
margin-left: 32px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
@ -26,10 +26,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mat-slide-toggle-content {
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
&.mat-primary.mat-checked {
|
||||
.mat-slide-toggle-bar {
|
||||
background-color: $primary;
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
border-radius: 3px !important;
|
||||
padding: 10px;
|
||||
margin: 12px !important;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
color: $white !important;
|
||||
@ -55,3 +54,9 @@
|
||||
left: 100%;
|
||||
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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user