2023-11-28 14:15:16 +01:00
|
|
|
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';
|
2024-04-16 10:02:17 +02:00
|
|
|
import { Tenant } from '@app/core/model/tenant/tenant';
|
2023-11-28 14:15:16 +01:00
|
|
|
import { User, UserAdditionalInfo, UserContactInfo, UserRole } from '@app/core/model/user/user';
|
2024-04-16 10:02:17 +02:00
|
|
|
import { TenantLookup } from '@app/core/query/tenant.lookup';
|
2023-11-28 14:15:16 +01:00
|
|
|
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';
|
2024-04-16 10:02:17 +02:00
|
|
|
import { TenantService } from '@app/core/services/tenant/tenant.service';
|
2023-11-28 14:15:16 +01:00
|
|
|
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';
|
2023-12-01 12:18:20 +01:00
|
|
|
import { IsActiveTypePipe } from '@common/formatting/pipes/is-active-type.pipe';
|
2023-11-28 14:15:16 +01:00
|
|
|
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';
|
2018-11-27 18:33:17 +01:00
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
2023-10-06 10:10:53 +02:00
|
|
|
import * as FileSaver from 'file-saver';
|
2023-11-28 14:15:16 +01:00
|
|
|
import { Observable } from 'rxjs';
|
|
|
|
import { takeUntil } from 'rxjs/operators';
|
|
|
|
import { nameof } from 'ts-simple-nameof';
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
@Component({
|
|
|
|
templateUrl: './user-listing.component.html',
|
|
|
|
styleUrls: ['./user-listing.component.scss']
|
|
|
|
})
|
|
|
|
export class UserListingComponent extends BaseListingComponent<User, UserLookup> implements OnInit {
|
|
|
|
publish = false;
|
|
|
|
userSettingsKey = { key: 'UserListingUserSettings' };
|
|
|
|
propertiesAvailableForOrder: ColumnDefinition[];
|
|
|
|
|
|
|
|
@ViewChild('roleCellTemplate', { static: true }) roleCellTemplate?: TemplateRef<any>;
|
|
|
|
@ViewChild('nameCellTemplate', { static: true }) nameCellTemplate?: TemplateRef<any>;
|
|
|
|
@ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent;
|
|
|
|
|
|
|
|
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('.'),
|
2024-04-16 10:02:17 +02:00
|
|
|
[nameof<User>(x => x.globalRoles), nameof<UserRole>(x => x.id)].join('.'),
|
|
|
|
[nameof<User>(x => x.globalRoles), nameof<UserRole>(x => x.role)].join('.'),
|
|
|
|
[nameof<User>(x => x.tenantRoles), nameof<UserRole>(x => x.id)].join('.'),
|
|
|
|
[nameof<User>(x => x.tenantRoles), nameof<UserRole>(x => x.role)].join('.'),
|
2023-11-28 14:15:16 +01:00
|
|
|
[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;
|
2018-10-05 17:00:54 +02:00
|
|
|
|
2024-04-16 10:02:17 +02:00
|
|
|
|
2018-10-05 17:00:54 +02:00
|
|
|
constructor(
|
2023-11-28 14:15:16 +01:00
|
|
|
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
|
2018-10-05 17:00:54 +02:00
|
|
|
) {
|
2023-11-28 14:15:16 +01:00
|
|
|
super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService);
|
|
|
|
// Lookup setup
|
|
|
|
// Default lookup values are defined in the user settings class.
|
|
|
|
this.lookup = this.initializeLookup();
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
ngOnInit() {
|
|
|
|
super.ngOnInit();
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
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);
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
lookup.project = {
|
|
|
|
fields: this.lookupFields
|
|
|
|
};
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
return lookup;
|
|
|
|
}
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
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')
|
|
|
|
},
|
|
|
|
{
|
2024-04-16 10:02:17 +02:00
|
|
|
prop: nameof<User>(x => x.globalRoles),
|
2023-11-28 14:15:16 +01:00
|
|
|
languageName: 'USER-LISTING.FIELDS.ROLES',
|
|
|
|
alwaysShown: true,
|
|
|
|
maxWidth: 300,
|
|
|
|
cellTemplate: this.roleCellTemplate
|
2023-12-01 12:18:20 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
prop: nameof<User>(x => x.isActive),
|
|
|
|
sortable: true,
|
|
|
|
languageName: 'USER-LISTING.FIELDS.IS-ACTIVE',
|
|
|
|
pipe: this.pipeService.getPipe<IsActiveTypePipe>(IsActiveTypePipe)
|
|
|
|
},
|
2023-11-28 14:15:16 +01:00
|
|
|
]);
|
|
|
|
this.propertiesAvailableForOrder = this.gridColumns.filter(x => x.sortable);
|
|
|
|
}
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
//
|
|
|
|
// Listing Component functions
|
|
|
|
//
|
|
|
|
onColumnsChanged(event: ColumnsChangedEvent) {
|
|
|
|
super.onColumnsChanged(event);
|
|
|
|
this.onColumnsChangedInternal(event.properties.map(x => x.toString()));
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
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);
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
protected loadListing(): Observable<QueryResult<User>> {
|
|
|
|
return this.userService.query(this.lookup);
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
2018-02-01 15:04:36 +01:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
onCallbackSuccess(): void {
|
|
|
|
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success);
|
2024-01-25 19:55:42 +01:00
|
|
|
this.refresh();
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
2020-10-07 15:35:23 +02:00
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
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()
|
2020-10-07 15:35:23 +02:00
|
|
|
.pipe(takeUntil(this._destroyed))
|
|
|
|
.subscribe(response => {
|
|
|
|
const blob = new Blob([response.body], { type: 'application/csv' });
|
2023-10-25 16:47:48 +02:00
|
|
|
const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
|
2020-10-07 15:35:23 +02:00
|
|
|
FileSaver.saveAs(blob, filename);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-11-28 14:15:16 +01:00
|
|
|
//
|
|
|
|
// Avatar
|
|
|
|
//
|
2020-12-08 15:25:55 +01:00
|
|
|
public setDefaultAvatar(ev: Event) {
|
|
|
|
(ev.target as HTMLImageElement).src = 'assets/images/profile-placeholder.png';
|
|
|
|
}
|
2018-02-01 15:04:36 +01:00
|
|
|
}
|