refdactored users listing

This commit is contained in:
Diamantis Tziotzios 2023-11-28 15:15:16 +02:00
parent e6b22a5b27
commit 00e5c69fac
22 changed files with 765 additions and 610 deletions

View File

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

View File

@ -28,5 +28,11 @@ export enum AppPermission {
BrowseTenant = "BrowseTenant",
EditTenant = "EditTenant",
DeleteTenant = "DeleteTenant",
//User
BrowseUser = "BrowseUser",
EditUser = "EditUser",
DeleteUser = "DeleteUser",
ExportUsers = "ExportUsers",
}

View File

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

View File

@ -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),

View File

@ -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[]>();

View File

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

View File

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

View File

@ -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, []],
});
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -102,6 +102,10 @@
padding-left: 10px;
padding-right: 10px;
}
.save-button {
margin-bottom: 1em;
}
}
:host ::ng-deep .mat-form-field-wrapper {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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