diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index ddb76c6fd..eddefe81f 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -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 ]; diff --git a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html b/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html index 0bb7f56fd..1062de200 100644 --- a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html +++ b/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html @@ -41,7 +41,7 @@ [routerLinkActiveOptions]="{ exact: true }" routerLinkActive="active" translate="user-management" - *ngIf="root && userPreferenceService.areDevFeaturesEnabled" + *ngIf="root && permissionService.canManageUsers() && userPreferenceService.areDevFeaturesEnabled" > + +
+ + +
+
+ +
+ +
+ +
diff --git a/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.scss b/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.scss new file mode 100644 index 000000000..8938e46bf --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.scss @@ -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; +} diff --git a/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.ts b/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.ts new file mode 100644 index 000000000..79a35e9ed --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.ts @@ -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 {} +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html new file mode 100644 index 000000000..742318f8e --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html @@ -0,0 +1,50 @@ +
+
+ {{ (user ? 'add-edit-user.title.edit' : 'add-edit-user.title.new') | translate }} +
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ + + + + + +
+ +
+ + {{ 'roles.' + role | translate }} + +
+
+
+ +
+ + + + +
+
+
+ + +
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.scss new file mode 100644 index 000000000..65fd1e545 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.scss @@ -0,0 +1,7 @@ +.roles-wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + grid-row-gap: 8px; + margin-top: 8px; + width: 300px; +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts new file mode 100644 index 000000000..1a849a645 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts @@ -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, + @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; + }, []); + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html index a9f8b28d8..2af43a9c1 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html @@ -3,16 +3,28 @@ {{ 'confirm-delete-file-attribute.title' | translate: { name: fileAttribute.label } }} +
+
+
+ + +
+
- + {{ 'confirm-delete-file-attribute.checkbox-' + (idx + 1) | translate }}
-
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss index 910d55c69..ddf782c9b 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss @@ -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; + } } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts index 75f80f5b7..8e86022bc 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts @@ -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() { diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.html new file mode 100644 index 000000000..d6d712eb2 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.html @@ -0,0 +1,31 @@ +
+
+ +
+
+ + + +
+ +
+
+ + + {{ 'confirm-delete-users.' + checkbox.label | translate: { projectsCount: projectsCount } }} + +
+ +
+ +
+
+ +
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.scss new file mode 100644 index 000000000..ddf782c9b --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.scss @@ -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; + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.ts new file mode 100644 index 000000000..ffe134598 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.ts @@ -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 + ) { + 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'; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html index 3d60e39bf..3b20c9a98 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html @@ -2,9 +2,15 @@