refdactored users listing
This commit is contained in:
parent
e6b22a5b27
commit
00e5c69fac
|
@ -217,7 +217,7 @@ const appRoutes: Routes = [
|
|||
},
|
||||
{
|
||||
path: 'users',
|
||||
loadChildren: () => import('./ui/admin/user/user.module').then(m => m.UserModule),
|
||||
loadChildren: () => import('./ui/admin/user/user.module').then(m => m.UsersModule),
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
title: 'GENERAL.TITLES.USERS'
|
||||
|
|
|
@ -28,5 +28,11 @@ export enum AppPermission {
|
|||
BrowseTenant = "BrowseTenant",
|
||||
EditTenant = "EditTenant",
|
||||
DeleteTenant = "DeleteTenant",
|
||||
|
||||
//User
|
||||
BrowseUser = "BrowseUser",
|
||||
EditUser = "EditUser",
|
||||
DeleteUser = "DeleteUser",
|
||||
ExportUsers = "ExportUsers",
|
||||
}
|
||||
|
||||
|
|
|
@ -27,14 +27,20 @@ import { UserDescriptionTemplateRole } from '@app/core/common/enum/user-descript
|
|||
export class EnumUtils {
|
||||
constructor(private language: TranslateService) { }
|
||||
|
||||
public getEnumValues(T): Array<any> {
|
||||
let keys = Object.keys(T);
|
||||
keys = keys.slice(0, keys.length / 2);
|
||||
const values: Array<Number> = keys.map(Number);
|
||||
return values;
|
||||
public getEnumValues<T>(T): Array<T> {
|
||||
|
||||
//getting all numeric values
|
||||
const numericValues: T[] = Object.keys(T).map(key => T[key]).filter(value => typeof (value) === 'number');
|
||||
if (numericValues.length > 0) { return numericValues; }
|
||||
|
||||
//getting all string values
|
||||
const stringValues: T[] = Object.keys(T).map(key => T[key]).filter(value => typeof (value) === 'string');
|
||||
if (stringValues.length > 0) { return stringValues; }
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
convertFromPrincipalAppRole(status: AppRole): string {
|
||||
toAppRoleString(status: AppRole): string {
|
||||
switch (status) {
|
||||
case AppRole.Admin: return this.language.instant('TYPES.APP-ROLE.ADMIN');
|
||||
case AppRole.User: return this.language.instant('TYPES.APP-ROLE.USER');
|
||||
|
|
|
@ -57,9 +57,9 @@ export class DmpBlueprintEditorComponent extends BaseEditor<DmpBlueprintEditorMo
|
|||
selectedSystemFields: Array<DmpBlueprintSystemFieldType> = [];
|
||||
dmpBlueprintSectionFieldCategory = DmpBlueprintSectionFieldCategory;
|
||||
dmpBlueprintSystemFieldType = DmpBlueprintSystemFieldType;
|
||||
public dmpBlueprintSystemFieldTypeEnum = this.enumUtils.getEnumValues(DmpBlueprintSystemFieldType);
|
||||
public dmpBlueprintSystemFieldTypeEnum = this.enumUtils.getEnumValues<DmpBlueprintSystemFieldType>(DmpBlueprintSystemFieldType);
|
||||
dmpBlueprintExtraFieldDataType = DmpBlueprintExtraFieldDataType;
|
||||
public dmpBlueprintExtraFieldDataTypeEnum = this.enumUtils.getEnumValues(DmpBlueprintExtraFieldDataType);
|
||||
public dmpBlueprintExtraFieldDataTypeEnum = this.enumUtils.getEnumValues<DmpBlueprintExtraFieldDataType>(DmpBlueprintExtraFieldDataType);
|
||||
|
||||
blueprintsAutoCompleteConfiguration: MultipleAutoCompleteConfiguration = {
|
||||
filterFn: this.filterDescriptionTempaltes.bind(this),
|
||||
|
|
|
@ -3,15 +3,17 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { FormArray, UntypedFormGroup } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service';
|
||||
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
|
||||
import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service';
|
||||
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
|
||||
// import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item';
|
||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { IsActive } from '@app/core/common/enum/is-active.enum';
|
||||
import { AppPermission } from '@app/core/common/enum/permission.enum';
|
||||
import { ReferenceType, ReferenceTypePersist, ResultFieldsMappingConfiguration } from '@app/core/model/reference-type/reference-type';
|
||||
import { ReferenceFieldDataType } from '@app/core/common/enum/reference-field-data-type';
|
||||
import { ReferenceTypeExternalApiHTTPMethodType } from '@app/core/common/enum/reference-type-external-api-http-method-type';
|
||||
import { ReferenceTypeSourceType } from '@app/core/common/enum/reference-type-source-type';
|
||||
import { ReferenceType, ReferenceTypePersist } from '@app/core/model/reference-type/reference-type';
|
||||
import { AuthService } from '@app/core/services/auth/auth.service';
|
||||
import { LoggingService } from '@app/core/services/logging/logging-service';
|
||||
import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
|
||||
|
@ -23,12 +25,9 @@ import { FilterService } from '@common/modules/text-filter/filter-service';
|
|||
import { Guid } from '@common/types/guid';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { DependencyPropertyEditorModel, QueryConfigEditorModel, ReferenceTypeEditorModel, ReferenceTypeFieldEditorModel, ReferenceTypeSourceBaseConfigurationEditorModel, ReferenceTypeSourceBaseDependencyEditorModel, ReferenceTypeStaticOptionEditorModel, ResultFieldsMappingConfigurationEditorModel } from './reference-type-editor.model';
|
||||
import { ReferenceTypeEditorResolver } from './reference-type-editor.resolver';
|
||||
import { ReferenceTypeEditorService } from './reference-type-editor.service';
|
||||
import { DependencyPropertyEditorModel, QueryConfigEditorModel, ReferenceTypeEditorModel, ReferenceTypeFieldEditorModel, ReferenceTypeSourceBaseConfigurationEditorModel, ReferenceTypeSourceBaseDependencyEditorModel, ReferenceTypeStaticOptionEditorModel, ResultFieldsMappingConfigurationEditorModel } from './reference-type-editor.model';
|
||||
import { ReferenceFieldDataType } from '@app/core/common/enum/reference-field-data-type';
|
||||
import { ReferenceTypeSourceType } from '@app/core/common/enum/reference-type-source-type';
|
||||
import { ReferenceTypeExternalApiHTTPMethodType } from '@app/core/common/enum/reference-type-external-api-http-method-type';
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -45,9 +44,9 @@ export class ReferenceTypeEditorComponent extends BaseEditor<ReferenceTypeEditor
|
|||
showInactiveDetails = false;
|
||||
referenceTypeSourceType = ReferenceTypeSourceType;
|
||||
referenceTypeExternalApiHTTPMethodType = ReferenceTypeExternalApiHTTPMethodType;
|
||||
public referenceTypeSourceTypeEnum = this.enumUtils.getEnumValues(ReferenceTypeSourceType);
|
||||
public referenceFieldDataTypeEnum = this.enumUtils.getEnumValues(ReferenceFieldDataType);
|
||||
public referenceTypeExternalApiHTTPMethodTypeEnum = this.enumUtils.getEnumValues(ReferenceTypeExternalApiHTTPMethodType);
|
||||
public referenceTypeSourceTypeEnum = this.enumUtils.getEnumValues<ReferenceTypeSourceType>(ReferenceTypeSourceType);
|
||||
public referenceFieldDataTypeEnum = this.enumUtils.getEnumValues<ReferenceFieldDataType>(ReferenceFieldDataType);
|
||||
public referenceTypeExternalApiHTTPMethodTypeEnum = this.enumUtils.getEnumValues<ReferenceTypeExternalApiHTTPMethodType>(ReferenceTypeExternalApiHTTPMethodType);
|
||||
referenceTypes: ReferenceType[] = null;
|
||||
sourceKeys: string[] = [];
|
||||
sourceKeysMap: Map<string, string[]> = new Map<string, string[]>();
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<form class="user-roles-criteria" [formGroup]="formGroup">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<span class="d-flex col-auto align-items-center pr-0 show">{{'CRITERIA.USERS.SHOW' | translate}}:</span>
|
||||
<mat-form-field appearance="outline" class="sort-form col-auto">
|
||||
<mat-select placeholder="{{'CRITERIA.USERS.ROLE' | translate}}" formControlName="appRoles" multiple>
|
||||
<mat-option *ngFor="let role of rolesEnum" [value]="role">{{enumUtils.convertFromPrincipalAppRole(role)}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="search-form ml-auto col-auto" floatLabel="never">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<input matInput placeholder="{{'CRITERIA.USERS.LABEL'| translate}}" formControlName="like">
|
||||
<!-- <mat-error *ngIf="formGroup.get('like').hasError('backendError')">{{formGroup.get('like').getError('backendError').message}}</mat-error> -->
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -1,33 +0,0 @@
|
|||
.user-roles-criteria {
|
||||
|
||||
.container-fluid {
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.show {
|
||||
color: #A8A8A8;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
text-align: left;
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.search-form mat-icon {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .search-form .mat-form-field-wrapper {
|
||||
background-color: white !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep .sort-form .mat-form-field-wrapper {
|
||||
background-color: white !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-form-field-appearance-outline .mat-form-field-infix {
|
||||
padding: 0.3rem 0rem 0.6rem 0rem !important;
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { AppRole } from '@app/core/common/enum/app-role';
|
||||
import { UserCriteria } from '@app/core/query/user/user-criteria';
|
||||
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
|
||||
import { BaseCriteriaComponent } from '@app/ui/misc/criteria/base-criteria.component';
|
||||
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-criteria-component',
|
||||
templateUrl: './user-criteria.component.html',
|
||||
styleUrls: ['./user-criteria.component.scss']
|
||||
})
|
||||
export class UserCriteriaComponent extends BaseCriteriaComponent implements OnInit {
|
||||
|
||||
public rolesEnum = this.enumUtils.getEnumValues(AppRole);
|
||||
public criteria: UserCriteria = new UserCriteria();
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
public enumUtils: EnumUtils
|
||||
) {
|
||||
super(new ValidationErrorModel());
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
if (this.criteria == null) { this.criteria = new UserCriteria(); }
|
||||
if (this.formGroup == null) { this.formGroup = this.buildForm(); }
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
setCriteria(criteria: UserCriteria): void {
|
||||
this.criteria = criteria;
|
||||
this.formGroup = this.buildForm();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
this.formGroup.valueChanges
|
||||
.pipe(takeUntil(this._destroyed), debounceTime(300))
|
||||
.subscribe(x => this.controlModified());
|
||||
}
|
||||
public fromJSONObject(item: any): UserCriteria {
|
||||
this.criteria = new UserCriteria();
|
||||
this.criteria.label = item.Label;
|
||||
this.criteria.appRoles = item.appRoles;
|
||||
return this.criteria;
|
||||
}
|
||||
|
||||
buildForm(): UntypedFormGroup {
|
||||
return this.formBuilder.group({
|
||||
like: [this.criteria.label, []],
|
||||
appRoles: [this.criteria.appRoles, []],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<div class="d-flex align-items-center gap-1-rem">
|
||||
|
||||
<button mat-flat-button [matMenuTriggerFor]="filterMenu" #filterMenuTrigger="matMenuTrigger" (click)="updateFilters()" class="filter-button">
|
||||
<mat-icon aria-hidden="false" [matBadgeHidden]="!appliedFilterCount" [matBadge]="appliedFilterCount" matBadgeColor="warn" matBadgeSize="small">filter_alt</mat-icon>
|
||||
{{'COMMONS.LISTING-COMPONENT.SEARCH-FILTER-BTN' | translate}}
|
||||
</button>
|
||||
|
||||
|
||||
<mat-menu #filterMenu>
|
||||
<div class="p-3" (click)="$event?.stopPropagation?.()">
|
||||
<div class="search-listing-filters-container">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<h4>{{'USER-LISTING.FILTER.TITLE' | translate}}</h4>
|
||||
<button color="accent" mat-button (click)="clearFilters()">
|
||||
{{'COMMONS.LISTING-COMPONENT.CLEAR-ALL-FILTERS' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-slide-toggle labelPosition="before" [(ngModel)]="internalFilters.isActive">
|
||||
{{'USER-LISTING.FILTER.IS-ACTIVE' | translate}}
|
||||
</mat-slide-toggle>
|
||||
|
||||
<div class="d-flex justify-content-end align-items-center mt-4 gap-1-rem">
|
||||
<button mat-stroked-button color="primary" (click)="filterMenuTrigger?.closeMenu()">
|
||||
{{'USER-LISTING.FILTER.CANCEL' | translate}}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="filterMenuTrigger.closeMenu(); applyFilters();">
|
||||
{{'USER-LISTING.FILTER.APPLY-FILTERS' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
||||
<app-expandable-search-field [(value)]=internalFilters.like (valueChange)="onSearchTermChange($event)" />
|
||||
</div>
|
|
@ -0,0 +1,25 @@
|
|||
.user-listing-filters {
|
||||
|
||||
}
|
||||
|
||||
::ng-deep.mat-mdc-menu-panel {
|
||||
max-width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
:host::ng-deep.mat-mdc-menu-content:not(:empty) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
.filter-button{
|
||||
padding-top: .6rem;
|
||||
padding-bottom: .6rem;
|
||||
// .mat-icon{
|
||||
// font-size: 1.5em;
|
||||
// width: 1.2em;
|
||||
// height: 1.2em;
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { IsActive } from '@app/core/common/enum/is-active.enum';
|
||||
import { UserFilter } from '@app/core/query/user.lookup';
|
||||
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
|
||||
import { BaseComponent } from '@common/base/base.component';
|
||||
import { nameof } from 'ts-simple-nameof';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-listing-filters',
|
||||
templateUrl: './user-listing-filters.component.html',
|
||||
styleUrls: ['./user-listing-filters.component.scss']
|
||||
})
|
||||
export class UserListingFiltersComponent extends BaseComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input() readonly filter: UserFilter;
|
||||
@Output() filterChange = new EventEmitter<UserFilter>();
|
||||
|
||||
// * State
|
||||
internalFilters: UserListingFilters = this._getEmptyFilters();
|
||||
|
||||
protected appliedFilterCount: number = 0;
|
||||
constructor(
|
||||
public enumUtils: EnumUtils,
|
||||
) { super(); }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
const filterChange = changes[nameof<UserListingFiltersComponent>(x => x.filter)]?.currentValue as UserFilter;
|
||||
if (filterChange) {
|
||||
this.updateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onSearchTermChange(searchTerm: string): void {
|
||||
this.applyFilters()
|
||||
}
|
||||
|
||||
|
||||
protected updateFilters(): void {
|
||||
this.internalFilters = this._parseToInternalFilters(this.filter);
|
||||
this.appliedFilterCount = this._computeAppliedFilters(this.internalFilters);
|
||||
}
|
||||
|
||||
protected applyFilters(): void {
|
||||
const { isActive, like } = this.internalFilters ?? {}
|
||||
this.filterChange.emit({
|
||||
...this.filter,
|
||||
like,
|
||||
isActive: isActive ? [IsActive.Active] : [IsActive.Inactive]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
private _parseToInternalFilters(inputFilter: UserFilter): UserListingFilters {
|
||||
if (!inputFilter) {
|
||||
return this._getEmptyFilters();
|
||||
}
|
||||
|
||||
let { excludedIds, ids, isActive, like } = inputFilter;
|
||||
|
||||
return {
|
||||
isActive: (isActive ?? [])?.includes(IsActive.Active) || !isActive?.length,
|
||||
like: like
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private _getEmptyFilters(): UserListingFilters {
|
||||
return {
|
||||
isActive: true,
|
||||
like: null,
|
||||
}
|
||||
}
|
||||
|
||||
private _computeAppliedFilters(filters: UserListingFilters): number {
|
||||
let count = 0;
|
||||
if (filters?.isActive) {
|
||||
count++
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
clearFilters() {
|
||||
this.internalFilters = this._getEmptyFilters();
|
||||
}
|
||||
}
|
||||
|
||||
interface UserListingFilters {
|
||||
isActive: boolean;
|
||||
like: string;
|
||||
}
|
|
@ -1,28 +1,27 @@
|
|||
<form class="row user-role-editor" [formGroup]="formGroup" (ngSubmit)="formSubmit()">
|
||||
<form class="row user-role-editor" *ngIf="formGroup" [formGroup]="formGroup" (ngSubmit)="formSubmit()">
|
||||
<div *ngIf="!this.nowEditing"class="roles col">
|
||||
<ng-container *ngFor="let role of this.formGroup.get('appRoles').value">
|
||||
<ng-container *ngFor="let role of this.formGroup.get('roles').value">
|
||||
<div>
|
||||
<span class="user-role" [ngClass]="{'user': role == 0, 'manager': role == 1, 'admin': role == 2, 'dataset-template-editor': role == 3}">
|
||||
{{getPrincipalAppRoleWithLanguage(role)}}
|
||||
<span class="user-role" [ngClass]="{'user': role == appRole.User, 'manager': role == appRole.Manager, 'admin': role == appRole.Admin, 'dataset-template-editor': role == appRole.DatasetTemplateEditor}">
|
||||
{{enumUtils.toAppRoleString(role)}}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<mat-form-field *ngIf="this.nowEditing" class="select-role roles-width-180 col-auto">
|
||||
<mat-select formControlName="appRoles" multiple required>
|
||||
<ng-container *ngFor="let role of getPrincipalAppRoleValues()">
|
||||
<mat-option [value]="role">{{getPrincipalAppRoleWithLanguage(role)}}</mat-option>
|
||||
<mat-select formControlName="roles" multiple required>
|
||||
<ng-container *ngFor="let role of appRoleEnumValues">
|
||||
<mat-option [value]="role">{{enumUtils.toAppRoleString(role)}}</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="getFormControl('appRoles').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
|
||||
<mat-error *ngIf="formGroup.get('roles').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<button *ngIf="!this.nowEditing" class="col-auto" mat-icon-button color="primary" type="button" (click)="editItem()">
|
||||
<!-- <mat-icon class="mat-24">edit</mat-icon> -->
|
||||
<span class="row-action">{{'USERS.ACTIONS.EDIT' | translate}}</span>
|
||||
<mat-icon class="mat-24" matTooltip="{{'USER-LISTING.ACTIONS.EDIT' | translate}}">edit</mat-icon>
|
||||
<span class="row-action"></span>
|
||||
</button>
|
||||
<button *ngIf="this.nowEditing" class="col-auto" mat-icon-button color="primary" type="submit">
|
||||
<!-- <mat-icon class="mat-24">save</mat-icon> -->
|
||||
<span class="row-action">{{'USERS.ACTIONS.SAVE' | translate}}</span>
|
||||
<button *ngIf="this.nowEditing" class="col-auto save-button" mat-icon-button color="primary" type="submit">
|
||||
<mat-icon class="mat-24" matTooltip="{{'USER-LISTING.ACTIONS.SAVE' | translate}}">save</mat-icon>
|
||||
</button>
|
||||
</form>
|
||||
|
|
|
@ -102,6 +102,10 @@
|
|||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-form-field-wrapper {
|
||||
|
|
|
@ -1,68 +1,89 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { AppRole } from '@app/core/common/enum/app-role';
|
||||
import { UserListingModel } from '@app/core/model/user/user-listing';
|
||||
import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, UntypedFormGroup } from '@angular/forms';
|
||||
import { User, UserRolePatchPersist } from '@app/core/model/user/user';
|
||||
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
|
||||
import { UserServiceOld } from '@app/core/services/user/user.service-old';
|
||||
import { UserService } from '@app/core/services/user/user.service';
|
||||
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
|
||||
import { BaseEditor } from '@common/base/base-editor';
|
||||
import { BaseComponent } from '@common/base/base.component';
|
||||
import { Validation, ValidationContext } from '@common/forms/validation/validation-context';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { UserRolePatchEditorModel } from './user-role-editor.model';
|
||||
import { AuthService } from '@app/core/services/auth/auth.service';
|
||||
import { AppPermission } from '@app/core/common/enum/permission.enum';
|
||||
import { DescriptionTemplateTypeEditorService } from '@app/ui/admin/description-types/editor/description-template-type-editor.service';
|
||||
import { UserRoleEditorService } from './user-role-editor.service';
|
||||
import { LoggingService } from '@app/core/services/logging/logging-service';
|
||||
import { FormService } from '@common/forms/form-service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
|
||||
import { AppRole } from '@app/core/common/enum/app-role';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-role-editor-component',
|
||||
templateUrl: './user-role-editor.component.html',
|
||||
styleUrls: ['./user-role-editor.component.scss']
|
||||
styleUrls: ['./user-role-editor.component.scss'],
|
||||
providers: [UserRoleEditorService]
|
||||
})
|
||||
export class UserRoleEditorComponent extends BaseComponent implements OnInit {
|
||||
|
||||
@Input() public item: UserListingModel;
|
||||
@Input() public item: User;
|
||||
public formGroup: UntypedFormGroup = null;
|
||||
public nowEditing = false;
|
||||
lookupParams: any;
|
||||
editorModel: UserRolePatchEditorModel;
|
||||
appRole = AppRole;
|
||||
public appRoleEnumValues = this.enumUtils.getEnumValues<AppRole>(AppRole);
|
||||
|
||||
constructor(
|
||||
private language: TranslateService,
|
||||
private userService: UserServiceOld,
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private userService: UserService,
|
||||
private formService: FormService,
|
||||
private logger: LoggingService,
|
||||
private enumUtils: EnumUtils,
|
||||
private uiNotificationService: UiNotificationService
|
||||
private uiNotificationService: UiNotificationService,
|
||||
private authService: AuthService,
|
||||
private userRoleEditorService: UserRoleEditorService,
|
||||
private httpErrorHandlingService: HttpErrorHandlingService,
|
||||
) { super(); }
|
||||
|
||||
ngOnInit() {
|
||||
if (this.formGroup == null) { this.formGroup = this.buildForm(); }
|
||||
if (this.formGroup == null) { this.prepareForm(this.item); }
|
||||
}
|
||||
|
||||
buildForm(): UntypedFormGroup {
|
||||
const context: ValidationContext = this.createValidationContext();
|
||||
|
||||
return this.formBuilder.group({
|
||||
appRoles: new UntypedFormControl({ value: this.item.appRoles, disabled: true }, context.getValidation('appRoles').validators)
|
||||
});
|
||||
prepareForm(data: User) {
|
||||
try {
|
||||
this.editorModel = data ? new UserRolePatchEditorModel().fromModel(data) : new UserRolePatchEditorModel();
|
||||
this.buildForm();
|
||||
} catch (error) {
|
||||
this.logger.error('Could not parse user role item: ' + data + error);
|
||||
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
createValidationContext(): ValidationContext {
|
||||
const validationContext: ValidationContext = new ValidationContext();
|
||||
const validationArray: Validation[] = new Array<Validation>();
|
||||
buildForm() {
|
||||
this.formGroup = this.editorModel.buildForm(null, this.authService.hasPermission(AppPermission.EditUser));
|
||||
this.userRoleEditorService.setValidationErrorModel(this.editorModel.validationErrorModel);
|
||||
}
|
||||
|
||||
validationArray.push({ key: 'appRoles' });
|
||||
persistEntity(onSuccess?: (response) => void): void {
|
||||
const formData = this.formService.getValue(this.formGroup.value) as UserRolePatchPersist;
|
||||
|
||||
validationContext.validation = validationArray;
|
||||
return validationContext;
|
||||
this.userService.persistRoles(formData)
|
||||
.pipe(takeUntil(this._destroyed)).subscribe(
|
||||
complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(),
|
||||
error => this.onCallbackError(error)
|
||||
);
|
||||
}
|
||||
|
||||
formSubmit(): void {
|
||||
this.formService.touchAllFormFields(this.formGroup);
|
||||
if (!this.isFormValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modifiedItem = this.item;
|
||||
modifiedItem.appRoles = this.getFormControl('appRoles').value;
|
||||
|
||||
if (!this.isFormValid()) { return; }
|
||||
this.userService.updateRoles(modifiedItem)
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
.subscribe(
|
||||
(res) => this.onCallbackSuccess(),
|
||||
(error) => this.onCallbackError(error)
|
||||
);
|
||||
this.persistEntity();
|
||||
}
|
||||
|
||||
editItem(): void {
|
||||
|
@ -70,75 +91,28 @@ export class UserRoleEditorComponent extends BaseComponent implements OnInit {
|
|||
this.nowEditing = true;
|
||||
}
|
||||
|
||||
isFormValid(): boolean {
|
||||
this.touchAllFormFields(this.formGroup);
|
||||
this.validateAllFormFields(this.formGroup);
|
||||
public isFormValid() {
|
||||
return this.formGroup.valid;
|
||||
}
|
||||
|
||||
getFormData(): any {
|
||||
return this.formGroup.value;
|
||||
}
|
||||
|
||||
getFormControl(controlName: string): AbstractControl {
|
||||
return this.formGroup.get(controlName);
|
||||
}
|
||||
|
||||
validateAllFormFields(formControl: AbstractControl) {
|
||||
if (formControl instanceof UntypedFormControl) {
|
||||
formControl.updateValueAndValidity({ emitEvent: false });
|
||||
} else if (formControl instanceof UntypedFormGroup) {
|
||||
Object.keys(formControl.controls).forEach(item => {
|
||||
const control = formControl.get(item);
|
||||
this.validateAllFormFields(control);
|
||||
});
|
||||
} else if (formControl instanceof UntypedFormArray) {
|
||||
formControl.controls.forEach(item => {
|
||||
this.validateAllFormFields(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
touchAllFormFields(formControl: AbstractControl) {
|
||||
if (formControl instanceof UntypedFormControl) {
|
||||
formControl.markAsTouched();
|
||||
} else if (formControl instanceof UntypedFormGroup) {
|
||||
Object.keys(formControl.controls).forEach(item => {
|
||||
const control = formControl.get(item);
|
||||
this.touchAllFormFields(control);
|
||||
});
|
||||
} else if (formControl instanceof UntypedFormArray) {
|
||||
formControl.controls.forEach(item => {
|
||||
this.touchAllFormFields(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onCallbackSuccess() {
|
||||
this.nowEditing = false;
|
||||
this.formGroup.disable();
|
||||
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
|
||||
}
|
||||
|
||||
onCallbackError(error: any) {
|
||||
this.validateAllFormFields(this.formGroup);
|
||||
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Error);
|
||||
onCallbackError(errorResponse: HttpErrorResponse) {
|
||||
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
|
||||
if (error.statusCode === 400) {
|
||||
this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error);
|
||||
this.formService.validateAllFormFields(this.formGroup);
|
||||
} else {
|
||||
this.uiNotificationService.snackBarNotification(error.getMessagesString(), SnackBarNotificationLevel.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
getPrincipalAppRoleValues(): Number[] {
|
||||
let keys: string[] = Object.keys(AppRole);
|
||||
keys = keys.slice(0, keys.length / 2);
|
||||
const values: Number[] = keys.map(Number);
|
||||
return values;
|
||||
}
|
||||
|
||||
getPrincipalAppRoleWithLanguage(role: AppRole): string {
|
||||
let result = '';
|
||||
this.language.get(this.enumUtils.convertFromPrincipalAppRole(role))
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
.subscribe((value: string) => {
|
||||
result = value;
|
||||
});
|
||||
return result;
|
||||
clearErrorModel() {
|
||||
this.editorModel.validationErrorModel.clear();
|
||||
this.formService.validateAllFormFields(this.formGroup);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms";
|
||||
import { User, UserRolePatchPersist } from "@app/core/model/user/user";
|
||||
import { BackendErrorValidator } from "@common/forms/validation/custom-validator";
|
||||
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
|
||||
import { Validation, ValidationContext } from "@common/forms/validation/validation-context";
|
||||
import { Guid } from "@common/types/guid";
|
||||
|
||||
export class UserRolePatchEditorModel implements UserRolePatchPersist {
|
||||
id: Guid;
|
||||
roles: String[] = [];
|
||||
hash: string;
|
||||
|
||||
permissions: string[];
|
||||
|
||||
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
|
||||
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
|
||||
|
||||
constructor() { }
|
||||
|
||||
public fromModel(item: User): UserRolePatchEditorModel {
|
||||
if (item) {
|
||||
this.id = item.id;
|
||||
if (item.roles != null)this.roles = item.roles.map(x => x.role);
|
||||
this.hash = item.hash;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup {
|
||||
if (context == null) { context = this.createValidationContext(); }
|
||||
|
||||
return this.formBuilder.group({
|
||||
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
|
||||
roles: [{ value: this.roles, disabled: disabled }, context.getValidation('roles').validators],
|
||||
hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators],
|
||||
});
|
||||
}
|
||||
|
||||
createValidationContext(): ValidationContext {
|
||||
const baseContext: ValidationContext = new ValidationContext();
|
||||
const baseValidationArray: Validation[] = new Array<Validation>();
|
||||
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] });
|
||||
baseValidationArray.push({ key: 'roles', validators: [BackendErrorValidator(this.validationErrorModel, 'roles')] });
|
||||
baseValidationArray.push({ key: 'hash', validators: [] });
|
||||
|
||||
baseContext.validation = baseValidationArray;
|
||||
return baseContext;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
|
||||
|
||||
@Injectable()
|
||||
export class UserRoleEditorService {
|
||||
private validationErrorModel: ValidationErrorModel;
|
||||
|
||||
public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void {
|
||||
this.validationErrorModel = validationErrorModel;
|
||||
}
|
||||
|
||||
public getValidationErrorModel(): ValidationErrorModel {
|
||||
return this.validationErrorModel;
|
||||
}
|
||||
}
|
|
@ -1,57 +1,77 @@
|
|||
<div class="main-content">
|
||||
<div class="container-fluid user-listing">
|
||||
<div class="d-flex align-items-center">
|
||||
<h3>{{'USERS.LISTING.TITLE' | translate}}</h3>
|
||||
<div class="col-auto ml-auto">
|
||||
<button mat-raised-button type="button" class="export-btn" (click)="exportUsers()">{{'USERS.LISTING.EXPORT' | translate}}</button>
|
||||
<div class="row user-listing">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
|
||||
<div class="row mb-4 mt-3">
|
||||
<div class="col">
|
||||
<h4>{{'USER-LISTING.TITLE' | translate}}</h4>
|
||||
<app-navigation-breadcrumb />
|
||||
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button mat-raised-button class="create-btn" (click)="export()" *ngIf="authService.hasPermission(authService.permissionEnum.ExportUsers)">
|
||||
<mat-icon>download</mat-icon>
|
||||
{{'USER-LISTING.ACTIONS.EXPORT' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-user-criteria-component></app-user-criteria-component>
|
||||
<!-- <mat-card class="mat-card"> -->
|
||||
<div class="mat-elevation-z6">
|
||||
<mat-table [dataSource]="dataSource" matSort>
|
||||
|
||||
<!-- Column Definition: Label -->
|
||||
<ng-container cdkColumnDef="avatar">
|
||||
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<img mat-card-avatar *ngIf="row.avatarUrl" class="my-mat-card-avatar" [src]="row.avatarUrl" (error)="this.setDefaultAvatar($event)">
|
||||
<img mat-card-avatar *ngIf="!row.avatarUrl" class="my-mat-card-avatar" [src]="'assets/images/profile-placeholder.png'">
|
||||
</mat-cell>
|
||||
<app-hybrid-listing [rows]="gridRows" [columns]="gridColumns" [visibleColumns]="visibleColumns" [count]="totalElements" [offset]="currentPageNumber" [limit]="lookup.page.size" [defaultSort]="lookup.order?.items" [externalSorting]="true" (rowActivated)="onUserRowActivated($event)" (pageLoad)="alterPage($event)" (columnSort)="onColumnSort($event)" (columnsChanged)="onColumnsChanged($event)" [listItemTemplate]="listItemTemplate">
|
||||
|
||||
<app-user-listing-filters hybrid-listing-filters [(filter)]="lookup" (filterChange)="filterChanged($event)" />
|
||||
|
||||
<app-user-settings-picker [key]="userSettingsKey" [userPreference]="lookup" (onSettingSelected)="changeSetting($event)" [autoSelectUserSettings]="autoSelectUserSettings" user-preference-settings />
|
||||
</app-hybrid-listing>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #listItemTemplate let-item="item" let-isColumnSelected="isColumnSelected">
|
||||
|
||||
<div class="d-flex align-items-center p-3 gap-1-rem">
|
||||
<div class="row">
|
||||
<ng-container *ngIf="isColumnSelected('name')">
|
||||
<a class="buttonLinkClass" [routerLink]="'./' + item?.id" class="col-12" (click)="$event.stopPropagation()">{{item?.name | nullifyValue}}</a>
|
||||
<br />
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef>{{'USERS.LISTING.NAME' | translate}}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<!-- <img mat-card-avatar *ngIf="row.avatarUrl" class="my-mat-card-avatar" [src]="row.avatarUrl"> -->
|
||||
{{row.name}}
|
||||
</mat-cell>
|
||||
<ng-container *ngIf="isColumnSelected('createdAt')">
|
||||
<span class="col-12">
|
||||
{{'USER-LISTING.FIELDS.CREATED-AT' | translate}}:
|
||||
<small>
|
||||
{{item?.createdAt | dateTimeFormatter : 'short' | nullifyValue}}
|
||||
</small>
|
||||
</span>
|
||||
<br>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="email">
|
||||
<mat-header-cell *matHeaderCellDef>{{'USERS.LISTING.EMAIL' | translate}}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{row.email}}</mat-cell>
|
||||
<ng-container *ngIf="isColumnSelected('updatedAt')">
|
||||
<span class="col-12">
|
||||
{{'USER-LISTING.FIELDS.UPDATED-AT' | translate}}:
|
||||
<small>
|
||||
{{item?.updatedAt | dateTimeFormatter : 'short' | nullifyValue}}
|
||||
</small>
|
||||
</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-container cdkColumnDef="lastloggedin">
|
||||
<mat-header-cell *matHeaderCellDef>{{'USERS.LISTING.LAST-LOGGED-IN' | translate}}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{row.lastloggedin | date:'shortDate'}}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Column Definition: Roles -->
|
||||
<ng-container cdkColumnDef="roles">
|
||||
<mat-header-cell *matHeaderCellDef>{{'USERS.LISTING.ROLES' | translate}}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<ng-template #roleCellTemplate let-row="row" let-item>
|
||||
<div class="row">
|
||||
<app-user-role-editor-component style="width: 100%;" [item]="row"></app-user-role-editor-component>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<!-- <div class="col-auto status-chip"
|
||||
[ngClass]="{'status-chip-finalized': row.status === userStatuses.Finalized, 'status-chip-draft' : row.status === userStatuses.Draft}">
|
||||
{{enumUtils.toUserStatusString(row.status) | nullifyValue}}
|
||||
</div> -->
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
<ng-template #nameCellTemplate let-row="row" let-item>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<img *ngIf="row?.additionalInfo?.avatarUrl" class="user-avatar" [src]="row?.additionalInfo?.avatarUrl" (error)="this.setDefaultAvatar($event)">
|
||||
<img *ngIf="!row?.additionalInfo?.avatarUrl" class="user-avatar" [src]="'assets/images/profile-placeholder.png'">
|
||||
</div>
|
||||
<div class="col">{{row.name}}</div>
|
||||
|
||||
</mat-table>
|
||||
<mat-paginator #paginator [length]="dataSource?.totalCount" [pageSizeOptions]="[10, 25, 100]"></mat-paginator>
|
||||
<!-- </mat-card> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
|
@ -1,91 +1,68 @@
|
|||
.mat-table {
|
||||
margin-top: 47px;
|
||||
margin-bottom: 20px;
|
||||
background: #fafafa 0% 0% no-repeat padding-box;
|
||||
// box-shadow: 0px 5px 12px #00000038;
|
||||
border-radius: 4px;
|
||||
.user-listing {
|
||||
margin-top: 1.3rem;
|
||||
margin-left: 1rem;
|
||||
margin-right: 2rem;
|
||||
|
||||
.mat-header-row{
|
||||
background: #f3f5f8;
|
||||
}
|
||||
|
||||
mat-header-cell:first-of-type {
|
||||
padding-left: 46px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
mat-cell:first-of-type {
|
||||
padding-left: 46px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.my-mat-card-avatar {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
margin-right: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.user-listing {
|
||||
margin-top: 2rem;
|
||||
margin-left: 1rem;
|
||||
margin-right: 2rem;
|
||||
|
||||
.mat-card {
|
||||
margin: 1em 0;
|
||||
margin: 16px 0;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.mat-row {
|
||||
cursor: pointer;
|
||||
min-height: 4.5em;
|
||||
}
|
||||
|
||||
mat-row:hover {
|
||||
background-color: #eef5f6;
|
||||
}
|
||||
|
||||
mat-row:nth-child(odd) {
|
||||
// background-color: #0c748914;
|
||||
// background-color: #eef0fb;
|
||||
.mat-fab-bottom-right {
|
||||
float: right;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
background: #ffffff 0% 0% no-repeat padding-box;
|
||||
}
|
||||
.create-btn {
|
||||
border-radius: 30px;
|
||||
width: 145px;
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--primary-color);
|
||||
}
|
||||
background-color: var(--secondary-color);
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
// color: #000;
|
||||
|
||||
.export-icon {
|
||||
font-size: 20px;
|
||||
.button-text{
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-paginator-container {
|
||||
// flex-direction: row-reverse !important;
|
||||
// justify-content: space-between !important;
|
||||
// height: 30px;
|
||||
// min-height: 30px !important;
|
||||
background-color: #f6f6f6;
|
||||
.dlt-btn {
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
// ::ng-deep .mat-paginator-page-size {
|
||||
// height: 43px;
|
||||
// }
|
||||
.status-chip{
|
||||
|
||||
// ::ng-deep .mat-paginator-range-label {
|
||||
// margin: 15px 32px 0 24px !important;
|
||||
// }
|
||||
border-radius: 20px;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
padding-top: 0.2em;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
// ::ng-deep .mat-paginator-range-actions {
|
||||
// width: 55% !important;
|
||||
// min-height: 43px !important;
|
||||
// justify-content: space-between;
|
||||
// }
|
||||
.status-chip-finalized{
|
||||
color: #568b5a;
|
||||
background: #9dd1a1 0% 0% no-repeat padding-box;
|
||||
}
|
||||
|
||||
// ::ng-deep .mat-paginator-navigation-previous {
|
||||
// margin-left: auto !important;
|
||||
// }
|
||||
.status-chip-draft{
|
||||
color: #00c4ff;
|
||||
background: #d3f5ff 0% 0% no-repeat padding-box;
|
||||
}
|
||||
|
||||
// ::ng-deep .mat-icon-button {
|
||||
// height: 30px !important;
|
||||
// font-size: 12px !important;
|
||||
// }
|
||||
.user-avatar {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
|
|
@ -1,155 +1,195 @@
|
|||
|
||||
import { Observable, merge as observableMerge, of as observableOf } from 'rxjs';
|
||||
|
||||
import { DataSource } from '@angular/cdk/table';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatomoService } from '@app/core/services/matomo/matomo-service';
|
||||
import { BaseComponent } from '@common/base/base.component';
|
||||
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { IsActive } from '@app/core/common/enum/is-active.enum';
|
||||
import { User, UserAdditionalInfo, UserContactInfo, UserRole } from '@app/core/model/user/user';
|
||||
import { UserLookup } from '@app/core/query/user.lookup';
|
||||
import { AuthService } from '@app/core/services/auth/auth.service';
|
||||
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
|
||||
import { UserService } from '@app/core/services/user/user.service';
|
||||
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
|
||||
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
|
||||
import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
|
||||
import { BaseListingComponent } from '@common/base/base-listing-component';
|
||||
import { PipeService } from '@common/formatting/pipe.service';
|
||||
import { DataTableDateTimeFormatPipe } from '@common/formatting/pipes/date-time-format.pipe';
|
||||
import { QueryResult } from '@common/model/query-result';
|
||||
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
|
||||
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
|
||||
import { ColumnDefinition, ColumnsChangedEvent, HybridListingComponent, PageLoadEvent, RowActivateEvent } from '@common/modules/hybrid-listing/hybrid-listing.component';
|
||||
import { Guid } from '@common/types/guid';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import * as FileSaver from 'file-saver';
|
||||
import { catchError, map, startWith, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { DataTableRequest } from '../../../../core/model/data-table/data-table-request';
|
||||
import { UserListingModel } from '../../../../core/model/user/user-listing';
|
||||
import { UserCriteria } from '../../../../core/query/user/user-criteria';
|
||||
import { UserServiceOld } from '../../../../core/services/user/user.service-old';
|
||||
import { SnackBarNotificationComponent } from '../../../../library/notification/snack-bar/snack-bar-notification.component';
|
||||
// import { BreadcrumbItem } from '../../../misc/breadcrumb/definition/breadcrumb-item';
|
||||
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
|
||||
import { UserCriteriaComponent } from './criteria/user-criteria.component';
|
||||
|
||||
export class UsersDataSource extends DataSource<UserListingModel> {
|
||||
|
||||
totalCount = 0;
|
||||
|
||||
constructor(
|
||||
private _service: UserServiceOld,
|
||||
private _paginator: MatPaginator,
|
||||
private _sort: MatSort,
|
||||
private _languageService: TranslateService,
|
||||
private _snackBar: MatSnackBar,
|
||||
private _criteria: UserCriteriaComponent
|
||||
) {
|
||||
super();
|
||||
//this._paginator.page.pipe(takeUntil(this._destroyed)).subscribe((pageEvent: PageEvent) => {
|
||||
// this.store.dispatch(new LoadPhotosRequestAction(pageEvent.pageIndex, pageEvent.pageSize))
|
||||
//})
|
||||
}
|
||||
|
||||
connect(): Observable<UserListingModel[]> {
|
||||
const displayDataChanges = [
|
||||
this._paginator.page
|
||||
//this._sort.matSortChange
|
||||
];
|
||||
|
||||
// If the user changes the sort order, reset back to the first page.
|
||||
//this._sort.matSortChange.pipe(takeUntil(this._destroyed)).subscribe(() => {
|
||||
// this._paginator.pageIndex = 0;
|
||||
//})
|
||||
|
||||
return observableMerge(...displayDataChanges).pipe(
|
||||
startWith(null),
|
||||
switchMap(() => {
|
||||
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
|
||||
let fields: Array<string> = new Array();
|
||||
if (this._sort.active) { fields = this._sort.direction === 'asc' ? ['+' + this._sort.active] : ['-' + this._sort.active]; }
|
||||
const request = new DataTableRequest<UserCriteria>(startIndex, this._paginator.pageSize, { fields: fields });
|
||||
request.criteria = this._criteria.getFormData();
|
||||
return this._service.getPaged(request);
|
||||
}),
|
||||
catchError((error: any) => {
|
||||
this._snackBar.openFromComponent(SnackBarNotificationComponent, {
|
||||
data: { message: 'GENERAL.SNACK-BAR.FORMS-BAD-REQUEST', language: this._languageService },
|
||||
duration: 3000,
|
||||
});
|
||||
this._criteria.onCallbackError(error);
|
||||
return observableOf(null);
|
||||
}),
|
||||
map(result => {
|
||||
return result;
|
||||
}),
|
||||
map(result => {
|
||||
if (!result) { return []; }
|
||||
if (this._paginator.pageIndex === 0) { this.totalCount = result.totalCount; }
|
||||
//result.data.forEach((element: any) => {
|
||||
// const roles: String[] = [];
|
||||
// element.roles.forEach((role: any) => {
|
||||
// this._languageService.get(this._utilities.convertFromPrincipalAppRole(role)).pipe(takeUntil(this._destroyed)).subscribe(
|
||||
// value => roles.push(value)
|
||||
// );
|
||||
// });
|
||||
// element.roles = roles;
|
||||
//});
|
||||
return result.data;
|
||||
}),);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
import { Observable } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { nameof } from 'ts-simple-nameof';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-listing-component',
|
||||
templateUrl: './user-listing.component.html',
|
||||
styleUrls: ['./user-listing.component.scss']
|
||||
})
|
||||
export class UserListingComponent extends BaseComponent implements OnInit, AfterViewInit {
|
||||
export class UserListingComponent extends BaseListingComponent<User, UserLookup> implements OnInit {
|
||||
publish = false;
|
||||
userSettingsKey = { key: 'UserListingUserSettings' };
|
||||
propertiesAvailableForOrder: ColumnDefinition[];
|
||||
|
||||
@ViewChild(MatPaginator, { static: true }) _paginator: MatPaginator;
|
||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||
@ViewChild(UserCriteriaComponent, { static: true }) criteria: UserCriteriaComponent;
|
||||
@ViewChild('roleCellTemplate', { static: true }) roleCellTemplate?: TemplateRef<any>;
|
||||
@ViewChild('nameCellTemplate', { static: true }) nameCellTemplate?: TemplateRef<any>;
|
||||
@ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent;
|
||||
|
||||
// breadCrumbs: Observable<BreadcrumbItem[]>;
|
||||
dataSource: UsersDataSource | null;
|
||||
displayedColumns: String[] = ['avatar', 'name', 'email', 'lastloggedin', 'roles'];
|
||||
private readonly lookupFields: string[] = [
|
||||
nameof<User>(x => x.id),
|
||||
nameof<User>(x => x.name),
|
||||
[nameof<User>(x => x.contacts), nameof<UserContactInfo>(x => x.id)].join('.'),
|
||||
[nameof<User>(x => x.contacts), nameof<UserContactInfo>(x => x.type)].join('.'),
|
||||
[nameof<User>(x => x.contacts), nameof<UserContactInfo>(x => x.value)].join('.'),
|
||||
[nameof<User>(x => x.roles), nameof<UserRole>(x => x.id)].join('.'),
|
||||
[nameof<User>(x => x.roles), nameof<UserRole>(x => x.role)].join('.'),
|
||||
[nameof<User>(x => x.additionalInfo), nameof<UserAdditionalInfo>(x => x.avatarUrl)].join('.'),
|
||||
nameof<User>(x => x.updatedAt),
|
||||
nameof<User>(x => x.createdAt),
|
||||
nameof<User>(x => x.hash),
|
||||
nameof<User>(x => x.isActive)
|
||||
];
|
||||
|
||||
rowIdentity = x => x.id;
|
||||
|
||||
constructor(
|
||||
private userService: UserServiceOld,
|
||||
private languageService: TranslateService,
|
||||
public snackBar: MatSnackBar,
|
||||
private httpClient: HttpClient,
|
||||
private matomoService: MatomoService,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected uiNotificationService: UiNotificationService,
|
||||
protected httpErrorHandlingService: HttpErrorHandlingService,
|
||||
protected queryParamsService: QueryParamsService,
|
||||
private userService: UserService,
|
||||
public authService: AuthService,
|
||||
private pipeService: PipeService,
|
||||
public enumUtils: EnumUtils,
|
||||
private language: TranslateService,
|
||||
private dialog: MatDialog,
|
||||
private fileUtils: FileUtils
|
||||
) {
|
||||
super();
|
||||
super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService);
|
||||
// Lookup setup
|
||||
// Default lookup values are defined in the user settings class.
|
||||
this.lookup = this.initializeLookup();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.matomoService.trackPageView('Admin: Users');
|
||||
// this.breadCrumbs = observableOf([{
|
||||
// parentComponentName: null,
|
||||
// label: this.languageService.instant('NAV-BAR.USERS-BREADCRUMB'),
|
||||
// url: "/users"
|
||||
// }]);
|
||||
//this.refresh(); //called on ngAfterViewInit with default criteria
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
this.criteria.setRefreshCallback(() => this.refresh());
|
||||
this.criteria.setCriteria(this.getDefaultCriteria());
|
||||
this.criteria.controlModified();
|
||||
protected initializeLookup(): UserLookup {
|
||||
const lookup = new UserLookup();
|
||||
lookup.metadata = { countAll: true };
|
||||
lookup.page = { offset: 0, size: this.ITEMS_PER_PAGE };
|
||||
lookup.isActive = [IsActive.Active];
|
||||
lookup.order = { items: [this.toDescSortField(nameof<User>(x => x.createdAt))] };
|
||||
this.updateOrderUiFields(lookup.order);
|
||||
|
||||
lookup.project = {
|
||||
fields: this.lookupFields
|
||||
};
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
protected setupColumns() {
|
||||
this.gridColumns.push(...[{
|
||||
prop: nameof<User>(x => x.name),
|
||||
sortable: true,
|
||||
languageName: 'USER-LISTING.FIELDS.NAME',
|
||||
cellTemplate: this.nameCellTemplate
|
||||
},
|
||||
{
|
||||
prop: nameof<User>(x => x.contacts),
|
||||
sortable: true,
|
||||
languageName: 'USER-LISTING.FIELDS.CONTACT-INFO',
|
||||
valueFunction: (item: User) => (item?.contacts ?? []).map(x => x.value).join(', ')
|
||||
},
|
||||
{
|
||||
prop: nameof<User>(x => x.createdAt),
|
||||
sortable: true,
|
||||
languageName: 'USER-LISTING.FIELDS.CREATED-AT',
|
||||
pipe: this.pipeService.getPipe<DataTableDateTimeFormatPipe>(DataTableDateTimeFormatPipe).withFormat('short')
|
||||
},
|
||||
{
|
||||
prop: nameof<User>(x => x.updatedAt),
|
||||
sortable: true,
|
||||
languageName: 'USER-LISTING.FIELDS.UPDATED-AT',
|
||||
pipe: this.pipeService.getPipe<DataTableDateTimeFormatPipe>(DataTableDateTimeFormatPipe).withFormat('short')
|
||||
},
|
||||
{
|
||||
prop: nameof<User>(x => x.roles),
|
||||
languageName: 'USER-LISTING.FIELDS.ROLES',
|
||||
alwaysShown: true,
|
||||
maxWidth: 300,
|
||||
cellTemplate: this.roleCellTemplate
|
||||
}
|
||||
]);
|
||||
this.propertiesAvailableForOrder = this.gridColumns.filter(x => x.sortable);
|
||||
}
|
||||
|
||||
//
|
||||
// Listing Component functions
|
||||
//
|
||||
onColumnsChanged(event: ColumnsChangedEvent) {
|
||||
super.onColumnsChanged(event);
|
||||
this.onColumnsChangedInternal(event.properties.map(x => x.toString()));
|
||||
}
|
||||
|
||||
private onColumnsChangedInternal(columns: string[]) {
|
||||
// Here are defined the projection fields that always requested from the api.
|
||||
const fields = new Set(this.lookupFields);
|
||||
this.gridColumns.map(x => x.prop)
|
||||
.filter(x => !columns?.includes(x as string))
|
||||
.forEach(item => {
|
||||
fields.delete(item as string)
|
||||
});
|
||||
this.lookup.project = { fields: [...fields] };
|
||||
this.onPageLoad({ offset: 0 } as PageLoadEvent);
|
||||
}
|
||||
|
||||
protected loadListing(): Observable<QueryResult<User>> {
|
||||
return this.userService.query(this.lookup);
|
||||
}
|
||||
|
||||
public deleteType(id: Guid) {
|
||||
if (id) {
|
||||
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
data: {
|
||||
isDeleteConfirmation: true,
|
||||
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'),
|
||||
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
|
||||
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
|
||||
if (result) {
|
||||
this.userService.delete(id).pipe(takeUntil(this._destroyed))
|
||||
.subscribe(
|
||||
complete => this.onCallbackSuccess(),
|
||||
error => this.onCallbackError(error)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._paginator.pageSize = 10;
|
||||
this._paginator.pageIndex = 0;
|
||||
this.dataSource = new UsersDataSource(this.userService, this._paginator, this.sort, this.languageService, this.snackBar, this.criteria);
|
||||
}
|
||||
|
||||
getDefaultCriteria(): UserCriteria {
|
||||
const defaultCriteria = new UserCriteria();
|
||||
return defaultCriteria;
|
||||
onCallbackSuccess(): void {
|
||||
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success);
|
||||
this.ngOnInit();
|
||||
}
|
||||
|
||||
// Export user mails
|
||||
exportUsers() {
|
||||
this.userService.downloadCSV()
|
||||
onUserRowActivated(event: RowActivateEvent, baseRoute: string = null) {
|
||||
// Override default event to prevent click action
|
||||
}
|
||||
|
||||
//
|
||||
// Export
|
||||
//
|
||||
|
||||
export() { //TODO: send lookup to backend to export only filtered
|
||||
this.userService.exportCSV()
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
.subscribe(response => {
|
||||
const blob = new Blob([response.body], { type: 'application/csv' });
|
||||
|
@ -158,6 +198,9 @@ export class UserListingComponent extends BaseComponent implements OnInit, After
|
|||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Avatar
|
||||
//
|
||||
public setDefaultAvatar(ev: Event) {
|
||||
(ev.target as HTMLImageElement).src = 'assets/images/profile-placeholder.png';
|
||||
}
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { FormattingModule } from '@app/core/formatting.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonFormattingModule } from '@common/formatting/common-formatting.module';
|
||||
import { CommonFormsModule } from '@common/forms/common-forms.module';
|
||||
import { HybridListingModule } from '@common/modules/hybrid-listing/hybrid-listing.module';
|
||||
import { TextFilterModule } from '@common/modules/text-filter/text-filter.module';
|
||||
import { UserSettingsModule } from '@common/modules/user-settings/user-settings.module';
|
||||
import { CommonUiModule } from '@common/ui/common-ui.module';
|
||||
import { UserCriteriaComponent } from './listing/criteria/user-criteria.component';
|
||||
import { UserRoleEditorComponent } from './listing/role-editor/user-role-editor.component';
|
||||
import { UserListingFiltersComponent } from './listing/filters/user-listing-filters.component';
|
||||
import { UserListingComponent } from './listing/user-listing.component';
|
||||
import { UserRoutingModule } from './user.routing';
|
||||
import { UsersRoutingModule } from './user.routing';
|
||||
import { UserRoleEditorComponent } from './listing/role-editor/user-role-editor.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonUiModule,
|
||||
CommonFormsModule,
|
||||
FormattingModule,
|
||||
UserRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
UserListingComponent,
|
||||
UserCriteriaComponent,
|
||||
UserRoleEditorComponent
|
||||
// UserEditorComponent,
|
||||
UserRoleEditorComponent,
|
||||
UserListingFiltersComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CommonUiModule,
|
||||
CommonFormsModule,
|
||||
CommonFormattingModule,
|
||||
UsersRoutingModule,
|
||||
HybridListingModule,
|
||||
TextFilterModule,
|
||||
UserSettingsModule
|
||||
]
|
||||
})
|
||||
|
||||
export class UserModule { }
|
||||
export class UsersModule { }
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { UserListingComponent } from './listing/user-listing.component';
|
||||
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { AuthGuard } from "@app/core/auth-guard.service";
|
||||
import { UserListingComponent } from "./listing/user-listing.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: UserListingComponent, canActivate: [AdminAuthGuard] },
|
||||
// { path: ':id', component: UserProfileComponent }
|
||||
];
|
||||
{
|
||||
path: '',
|
||||
component: UserListingComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{ path: '**', loadChildren: () => import('@common/modules/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule) },
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class UserRoutingModule { }
|
||||
export class UsersRoutingModule { }
|
||||
|
|
|
@ -1664,20 +1664,25 @@
|
|||
"CANCEL": "Cancel"
|
||||
}
|
||||
},
|
||||
"USERS": {
|
||||
"LISTING": {
|
||||
"USER-LISTING": {
|
||||
"TITLE": "Users",
|
||||
"EMAIL": "Email",
|
||||
"LAST-LOGGED-IN": "Last Logged In",
|
||||
"LABEL": "Label",
|
||||
"FIELDS": {
|
||||
"CONTACT-INFO": "Email",
|
||||
"ROLES": "Roles",
|
||||
"NAME": "Name",
|
||||
"PERMISSIONS": "Permissions",
|
||||
"EXPORT": "Export users"
|
||||
"UPDATED-AT": "Updated",
|
||||
"CREATED-AT": "Created"
|
||||
},
|
||||
"FILTER": {
|
||||
"TITLE": "Filters",
|
||||
"IS-ACTIVE": "Is Active",
|
||||
"CANCEL": "Cancel",
|
||||
"APPLY-FILTERS": "Apply filters"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"EDIT": "Edit",
|
||||
"SAVE": "Save"
|
||||
"SAVE": "Save",
|
||||
"EXPORT": "Export users"
|
||||
}
|
||||
},
|
||||
"TYPES": {
|
||||
|
|
Loading…
Reference in New Issue