Asymmetrik/ngx-starter

View on GitHub
src/app/core/admin/user/list-users/admin-list-users.component.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { CdkMenu, CdkMenuItem } from '@angular/cdk/menu';
import { OverlayModule } from '@angular/cdk/overlay';
import { CdkTableModule } from '@angular/cdk/table';
import { AsyncPipe, NgClass } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, DestroyRef, OnInit, computed, inject, signal, viewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { RouterLink } from '@angular/router';

import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
import { Observable, of } from 'rxjs';
import { catchError, first, switchMap } from 'rxjs/operators';

import {
    CdkMenuItemRouterLinkDirective,
    PagingOptions,
    PagingResults,
    SearchInputComponent,
    SkipToDirective,
    SortDirection
} from '../../../../common';
import { DialogService, isDialogActionOK } from '../../../../common/dialog';
import { JoinPipe } from '../../../../common/pipes';
import { SystemAlertComponent, SystemAlertService } from '../../../../common/system-alert';
import {
    ActionsMenuColumnComponent,
    ActionsMenuTemplateDirective,
    AgoDateColumnComponent,
    AsyExpandableListColumnComponent,
    AsyFilterDirective,
    AsyHeaderListFilterComponent,
    AsyHeaderSortComponent,
    AsySortDirective,
    AsyTableDataSource,
    AsyTableEmptyStateComponent,
    ColumnChooserComponent,
    DateColumnComponent,
    ItemTemplateDirective,
    PaginatorComponent,
    SidebarComponent,
    TextColumnComponent
} from '../../../../common/table';
import { Role, User } from '../../../auth';
import { ExportConfigService } from '../../../export-config.service';
import { APP_CONFIG } from '../../../tokens';
import { AdminUsersService } from '../admin-users.service';
import { UserRoleFilterDirective } from './user-role-filter.directive';

@Component({
    templateUrl: './admin-list-users.component.html',
    styleUrls: ['./admin-list-users.component.scss'],
    standalone: true,
    imports: [
        SkipToDirective,
        SystemAlertComponent,
        SearchInputComponent,
        RouterLink,
        CdkTableModule,
        OverlayModule,
        AsySortDirective,
        AsyFilterDirective,
        AsyHeaderSortComponent,
        AsyHeaderListFilterComponent,
        UserRoleFilterDirective,
        NgClass,
        AsyTableEmptyStateComponent,
        SidebarComponent,
        ColumnChooserComponent,
        PaginatorComponent,
        AsyncPipe,
        JoinPipe,
        CdkMenu,
        CdkMenuItem,
        CdkMenuItemRouterLinkDirective,
        AsyExpandableListColumnComponent,
        ItemTemplateDirective,
        DateColumnComponent,
        AgoDateColumnComponent,
        TextColumnComponent,
        ActionsMenuColumnComponent,
        ActionsMenuTemplateDirective,
        NgbTooltip
    ]
})
export class AdminListUsersComponent implements OnInit {
    readonly #destroyRef = inject(DestroyRef);
    readonly #dialogService = inject(DialogService);
    readonly #adminUsersService = inject(AdminUsersService);
    readonly #exportConfigService = inject(ExportConfigService);
    readonly #alertService = inject(SystemAlertService);
    readonly #config = inject(APP_CONFIG);

    readonly filter = viewChild.required(AsyFilterDirective);

    readonly allowDeleteUser = computed(() => this.#config()?.allowDeleteUser ?? true);

    readonly possibleRoles: Role[] = Role.ROLES;

    readonly columns = [
        {
            key: 'name',
            label: 'Name',
            selected: true
        },
        {
            key: 'username',
            label: 'Username',
            selected: true
        },
        {
            key: '_id',
            label: 'ID',
            selected: false
        },
        {
            key: 'organization',
            label: 'Organization',
            selected: false
        },
        {
            key: 'phone',
            label: 'Phone',
            selected: false
        },
        {
            key: 'email',
            label: 'Email',
            selected: false
        },
        {
            key: 'acceptedEua',
            label: 'EUA',
            selected: false
        },
        {
            key: 'lastLogin',
            label: 'Last Login',
            selected: true
        },
        {
            key: 'created',
            label: 'Created',
            selected: false
        },
        {
            key: 'updated',
            label: 'Updated',
            selected: false
        },
        {
            key: 'externalRoles',
            label: 'External Roles',
            selected: false
        },
        {
            key: 'externalGroups',
            label: 'External Groups',
            selected: false
        },
        {
            key: 'roles',
            label: 'Roles',
            selected: true
        },
        {
            key: 'teams',
            label: 'Teams'
        }
    ];

    readonly displayedColumns = signal<string[]>([]);

    readonly dataSource = new AsyTableDataSource<User>(
        (request) => this.loadData(request.pagingOptions, request.search, request.filter),
        'admin-list-users-component',
        {
            sortField: 'lastLogin',
            sortDir: SortDirection.desc
        }
    );

    ngOnInit() {
        this.#alertService.clearAllAlerts();
        this.columnsChanged(this.columns.filter((c) => c.selected).map((c) => c.key));
    }

    loadData(
        pagingOptions: PagingOptions,
        search: string,
        query: object
    ): Observable<PagingResults<User>> {
        return this.#adminUsersService.search(pagingOptions, query, search);
    }

    columnsChanged(columns: string[]) {
        setTimeout(() => {
            this.displayedColumns.set([...columns, 'actionsMenu']);
        });
    }

    clearFilters() {
        this.dataSource.search('');
        this.filter().clearFilter();
    }

    confirmDeleteUser(user: User) {
        this.#dialogService
            .confirm(
                'Delete user?',
                `Are you sure you want to delete the user: <strong>"${user.name}"</strong>?<br/>This action cannot be undone.`,
                'Delete'
            )
            .closed.pipe(
                first(),
                isDialogActionOK(),
                switchMap(() => this.#adminUsersService.removeUser(user._id)),
                catchError((error: unknown) => {
                    if (error instanceof HttpErrorResponse) {
                        this.#alertService.addClientErrorAlert(error);
                    }
                    return of(null);
                }),
                takeUntilDestroyed(this.#destroyRef)
            )
            .subscribe(() => this.dataSource.reload());
    }

    exportCurrentView() {
        const viewColumns = this.columns
            .filter((column) => this.displayedColumns().includes(column.key))
            .map((column) => ({ key: column.key, title: column.label }));

        this.#exportConfigService
            .postExportConfig('user', {
                q: this.dataSource.filterEvent$.value,
                s: this.dataSource.searchEvent$.value,
                sort: this.dataSource.sortEvent$.value.sortField,
                dir: this.dataSource.sortEvent$.value.sortDir,
                cols: viewColumns
            })
            .pipe(takeUntilDestroyed(this.#destroyRef))
            .subscribe((response) => {
                window.open(`/api/admin/users/csv/${response._id}`);
            });
    }
}