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 { Tenant } from '@app/core/model/tenant/tenant'; import { User, UserAdditionalInfo, UserContactInfo, UserRole } from '@app/core/model/user/user'; import { TenantLookup } from '@app/core/query/tenant.lookup'; 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 { TenantService } from '@app/core/services/tenant/tenant.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 { IsActiveTypePipe } from '@common/formatting/pipes/is-active-type.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 { Observable } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { nameof } from 'ts-simple-nameof'; @Component({ templateUrl: './user-listing.component.html', styleUrls: ['./user-listing.component.scss'] }) export class UserListingComponent extends BaseListingComponent implements OnInit { publish = false; userSettingsKey = { key: 'UserListingUserSettings' }; propertiesAvailableForOrder: ColumnDefinition[]; @ViewChild('roleCellTemplate', { static: true }) roleCellTemplate?: TemplateRef; @ViewChild('nameCellTemplate', { static: true }) nameCellTemplate?: TemplateRef; @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; private readonly lookupFields: string[] = [ nameof(x => x.id), nameof(x => x.name), [nameof(x => x.contacts), nameof(x => x.id)].join('.'), [nameof(x => x.contacts), nameof(x => x.type)].join('.'), [nameof(x => x.contacts), nameof(x => x.value)].join('.'), [nameof(x => x.globalRoles), nameof(x => x.id)].join('.'), [nameof(x => x.globalRoles), nameof(x => x.role)].join('.'), [nameof(x => x.tenantRoles), nameof(x => x.id)].join('.'), [nameof(x => x.tenantRoles), nameof(x => x.role)].join('.'), [nameof(x => x.additionalInfo), nameof(x => x.avatarUrl)].join('.'), nameof(x => x.updatedAt), nameof(x => x.createdAt), nameof(x => x.hash), nameof(x => x.isActive) ]; rowIdentity = x => x.id; constructor( 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(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService); // Lookup setup // Default lookup values are defined in the user settings class. this.lookup = this.initializeLookup(); } ngOnInit() { super.ngOnInit(); } 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(x => x.createdAt))] }; this.updateOrderUiFields(lookup.order); lookup.project = { fields: this.lookupFields }; return lookup; } protected setupColumns() { this.gridColumns.push(...[{ prop: nameof(x => x.name), sortable: true, languageName: 'USER-LISTING.FIELDS.NAME', cellTemplate: this.nameCellTemplate }, { prop: nameof(x => x.contacts), sortable: true, languageName: 'USER-LISTING.FIELDS.CONTACT-INFO', valueFunction: (item: User) => (item?.contacts ?? []).map(x => x.value).join(', ') }, { prop: nameof(x => x.createdAt), sortable: true, languageName: 'USER-LISTING.FIELDS.CREATED-AT', pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') }, { prop: nameof(x => x.updatedAt), sortable: true, languageName: 'USER-LISTING.FIELDS.UPDATED-AT', pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') }, { prop: nameof(x => x.globalRoles), languageName: 'USER-LISTING.FIELDS.ROLES', alwaysShown: true, maxWidth: 300, cellTemplate: this.roleCellTemplate }, { prop: nameof(x => x.isActive), sortable: true, languageName: 'USER-LISTING.FIELDS.IS-ACTIVE', pipe: this.pipeService.getPipe(IsActiveTypePipe) }, ]); 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> { 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) ); } }); } } onCallbackSuccess(): void { this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success); this.refresh(); } 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' }); const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition')); FileSaver.saveAs(blob, filename); }); } // // Avatar // public setDefaultAvatar(ev: Event) { (ev.target as HTMLImageElement).src = 'assets/images/profile-placeholder.png'; } }