entities-table.component.html 17.1 KB
<!--

    Copyright © 2016-2024 The Thingsboard Authors

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

-->
<mat-drawer-container hasBackdrop="false" class="tb-absolute-fill">
  <mat-drawer *ngIf="entitiesTableConfig.detailsPanelEnabled"
              class="tb-details-drawer mat-elevation-z4"
              #drawer
              mode="over"
              position="end"
              [opened]="isDetailsOpen">
    <tb-entity-details-panel
      #entityDetailsPanel
      [entitiesTableConfig]="entitiesTableConfig"
      [entityId]="isDetailsOpen ? dataSource.currentEntity?.id : null"
      (closeEntityDetails)="isDetailsOpen = false; detailsPanelOpened.emit(isDetailsOpen);"
      (entityUpdated)="onEntityUpdated($event)"
      (entityAction)="onEntityAction($event)"
    >
    </tb-entity-details-panel>
  </mat-drawer>
  <mat-drawer-content>
    <div class="mat-padding tb-entity-table tb-absolute-fill">
      <div fxLayout="column" class="tb-entity-table-content tb-outlined-border">
        <mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()">
          <div class="mat-toolbar-tools">
            <div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container">
              <span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
              <tb-anchor #entityTableHeader></tb-anchor>
              <tb-timewindow *ngIf="entitiesTableConfig.useTimePageLink" [(ngModel)]="timewindow"
                              (ngModelChange)="onTimewindowChange()"
                              asButton strokedButton historyOnly [forAllTimeEnabled]="entitiesTableConfig.forAllTimeEnabled"></tb-timewindow>
            </div>
            <span fxFlex></span>
            <div [fxShow]="addEnabled()">
              <button mat-icon-button [disabled]="isLoading$ | async"
                      *ngIf="!entitiesTableConfig.addActionDescriptors.length; else addActions"
                      (click)="addEntity($event)"
                      matTooltip="{{ translations.add | translate }}"
                      matTooltipPosition="above">
                <mat-icon>add</mat-icon>
              </button>
              <ng-template #addActions>
                <button mat-icon-button [disabled]="isLoading$ | async"
                        *ngIf="entitiesTableConfig.addActionDescriptors.length === 1; else addActionsMenu"
                        [fxShow]="entitiesTableConfig.addActionDescriptors[0].isEnabled()"
                        (click)="entitiesTableConfig.addActionDescriptors[0].onAction($event)"
                        matTooltip="{{ entitiesTableConfig.addActionDescriptors[0].name }}"
                        matTooltipPosition="above">
                  <tb-icon>{{entitiesTableConfig.addActionDescriptors[0].icon}}</tb-icon>
                </button>
                <ng-template #addActionsMenu>
                  <button mat-icon-button [disabled]="isLoading$ | async"
                          matTooltip="{{ translations.add | translate }}"
                          matTooltipPosition="above"
                          [matMenuTriggerFor]="addActionsMenu">
                    <mat-icon>add</mat-icon>
                  </button>
                  <mat-menu #addActionsMenu="matMenu" xPosition="before">
                    <button mat-menu-item *ngFor="let actionDescriptor of entitiesTableConfig.addActionDescriptors"
                            [disabled]="isLoading$ | async"
                            [fxShow]="actionDescriptor.isEnabled()"
                            (click)="actionDescriptor.onAction($event)">
                      <tb-icon matMenuItemIcon>{{actionDescriptor.icon}}</tb-icon>
                      <span>{{ actionDescriptor.name }}</span>
                    </button>
                  </mat-menu>
                </ng-template>
              </ng-template>
            </div>
            <button mat-icon-button [disabled]="isLoading$ | async"
                    [fxShow]="actionDescriptor.isEnabled()" *ngFor="let actionDescriptor of headerActionDescriptors"
                    matTooltip="{{ actionDescriptor.name }}"
                    matTooltipPosition="above"
                    (click)="actionDescriptor.onAction($event)">
              <tb-icon>{{actionDescriptor.icon}}</tb-icon>
            </button>
            <button mat-icon-button [disabled]="isLoading$ | async" (click)="updateData()"
                    matTooltip="{{ 'action.refresh' | translate }}"
                    matTooltipPosition="above">
              <mat-icon>refresh</mat-icon>
            </button>
            <button *ngIf="entitiesTableConfig.searchEnabled"
                    mat-icon-button [disabled]="isLoading$ | async" (click)="enterFilterMode()"
                    matTooltip="{{ translations.search | translate }}"
                    matTooltipPosition="above">
              <mat-icon>search</mat-icon>
            </button>
          </div>
        </mat-toolbar>
        <mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="textSearchMode && dataSource.selection.isEmpty()">
          <div class="mat-toolbar-tools">
            <button mat-icon-button
                    matTooltip="{{ translations.search | translate }}"
                    matTooltipPosition="above">
              <mat-icon>search</mat-icon>
            </button>
            <mat-form-field fxFlex>
              <mat-label>&nbsp;</mat-label>
              <input #searchInput matInput
                     [formControl]="textSearch"
                     placeholder="{{ translations.search | translate }}"/>
            </mat-form-field>
            <button mat-icon-button (click)="exitFilterMode()"
                    matTooltip="{{ 'action.close' | translate }}"
                    matTooltipPosition="above">
              <mat-icon>close</mat-icon>
            </button>
          </div>
        </mat-toolbar>
        <mat-toolbar *ngIf="entitiesTableConfig.selectionEnabled" class="mat-mdc-table-toolbar" color="primary" [fxShow]="!dataSource.selection.isEmpty()">
          <div class="mat-toolbar-tools">
            <span class="tb-entity-table-info">
              {{ translations.selectedEntities | translate:{count: dataSource.selection.selected.length} }}
            </span>
            <span fxFlex></span>
            <button mat-icon-button [disabled]="isLoading$ | async"
                    [fxShow]="actionDescriptor.isEnabled" *ngFor="let actionDescriptor of groupActionDescriptors"
                    matTooltip="{{ actionDescriptor.name }}"
                    matTooltipPosition="above"
                    (click)="actionDescriptor.onAction($event, dataSource.selection.selected)">
              <tb-icon>{{actionDescriptor.icon}}</tb-icon>
            </button>
          </div>
        </mat-toolbar>
        <div fxFlex class="table-container">
          <table mat-table [dataSource]="dataSource" [trackBy]="trackByEntityId"
                     matSort [matSortActive]="pageLink.sortOrder?.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
            <ng-container matColumnDef="select" sticky>
              <mat-header-cell *matHeaderCellDef style="width: 40px;">
                <mat-checkbox (change)="$event ? dataSource.masterToggle() : null"
                              [checked]="dataSource.selection.hasValue() && (dataSource.isAllSelected() | async)"
                              [indeterminate]="dataSource.selection.hasValue() && !(dataSource.isAllSelected() | async)">
                </mat-checkbox>
              </mat-header-cell>
              <mat-cell *matCellDef="let entity">
                <mat-checkbox (click)="$event.stopPropagation()"
                              [fxShow]="entitiesTableConfig.entitySelectionEnabled(entity)"
                              (change)="$event ? dataSource.selection.toggle(entity) : null"
                              [checked]="dataSource.selection.isSelected(entity)">
                </mat-checkbox>
              </mat-cell>
            </ng-container>
            <ng-container [matColumnDef]="column.key" *ngFor="let column of entityColumns; trackBy: trackByColumnKey;">
              <mat-header-cell [ngClass]="{'mat-number-cell': column.isNumberColumn}"
                               [fxHide.lt-lg]="column.mobileHide"
                               *matHeaderCellDef [ngStyle]="headerCellStyle(column)" mat-sort-header [disabled]="!column.sortable">
                {{ column.ignoreTranslate ? column.title : (column.title | translate) }} </mat-header-cell>
              <mat-cell [ngClass]="{'mat-number-cell': column.isNumberColumn}"
                        [fxHide.lt-lg]="column.mobileHide"
                        *matCellDef="let entity; let row = index"
                        [matTooltip]="cellTooltip(entity, column, row)"
                        matTooltipPosition="above"
                        [ngStyle]="cellStyle(entity, column, row)">
                <ng-container *ngIf="column.type == 'link' && column.entityURL(entity) as detailsPageURL; else defaultContent">
                  <a [routerLink]="detailsPageURL" [innerHTML]="cellContent(entity, column, row)" (click)="$event.stopPropagation();"></a>
                </ng-container>
                <ng-template #defaultContent>
                  <span [innerHTML]="cellContent(entity, column, row)"></span>
                </ng-template>
                <ng-template [ngIf]="column.actionCell">
                  <ng-container [ngSwitch]="column.actionCell.type">
                    <ng-template [ngSwitchCase]="cellActionType.COPY_BUTTON">
                      <tb-copy-button
                        [disabled]="isLoading$ | async"
                        [fxShow]="column.actionCell.isEnabled(entity)"
                        [copyText]="column.actionCell.onAction(null, entity)"
                        tooltipText="{{ column.actionCell.nameFunction ? column.actionCell.nameFunction(entity) : column.actionCell.name }}"
                        tooltipPosition="above"
                        [icon]="column.actionCell.icon"
                        [style]="column.actionCell.style">
                      </tb-copy-button>
                    </ng-template>
                    <ng-template ngSwitchDefault>
                      <button mat-icon-button [disabled]="isLoading$ | async"
                              [fxShow]="column.actionCell.isEnabled(entity)"
                              matTooltip="{{ column.actionCell.nameFunction ? column.actionCell.nameFunction(entity) : column.actionCell.name }}"
                              matTooltipPosition="above"
                              (click)="column.actionCell.onAction($event, entity)">
                        <tb-icon [ngStyle]="column.actionCell.style">
                          {{column.actionCell.icon}}
                        </tb-icon>
                      </button>
                    </ng-template>
                  </ng-container>
                </ng-template>
              </mat-cell>
            </ng-container>
            <ng-container [matColumnDef]="column.key" *ngFor="let column of actionColumns; trackBy: trackByColumnKey;">
              <mat-header-cell *matHeaderCellDef [ngStyle]="headerCellStyle(column)" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell>
              <mat-cell *matCellDef="let entity; let row = index"
                        [ngStyle]="cellStyle(entity, column, row)">
                <button mat-icon-button [disabled]="isLoading$ | async"
                        [fxShow]="column.actionDescriptor.isEnabled(entity)"
                        matTooltip="{{ column.actionDescriptor.nameFunction ? column.actionDescriptor.nameFunction(entity) : column.actionDescriptor.name }}"
                        matTooltipPosition="above"
                        (click)="column.actionDescriptor.onAction($event, entity)">
                  <tb-icon [ngStyle]="column.actionDescriptor.style">
                    {{column.actionDescriptor.icon}}
                  </tb-icon>
                </button>
              </mat-cell>
            </ng-container>
            <ng-container matColumnDef="actions" stickyEnd>
              <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (cellActionDescriptors.length * 48) + 'px',
                                                                    maxWidth: (cellActionDescriptors.length * 48) + 'px',
                                                                    width: (cellActionDescriptors.length * 48) + 'px' }">
                {{ entitiesTableConfig.actionsColumnTitle ? (entitiesTableConfig.actionsColumnTitle | translate) : '' }}
              </mat-header-cell>
              <mat-cell *matCellDef="let entity" [ngStyle.gt-md]="{ minWidth: (cellActionDescriptors.length * 48) + 'px',
                                                                    maxWidth: (cellActionDescriptors.length * 48) + 'px',
                                                                    width: (cellActionDescriptors.length * 48) + 'px' }">
                <div [fxHide]="cellActionDescriptors.length !== 1" fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
                  <button mat-icon-button [disabled]="(isLoading$ | async) || !actionDescriptor.isEnabled(entity)"
                          *ngFor="let actionDescriptor of cellActionDescriptors"
                          matTooltip="{{ actionDescriptor.nameFunction ? actionDescriptor.nameFunction(entity) : actionDescriptor.name }}"
                          matTooltipPosition="above"
                          (click)="actionDescriptor.onAction($event, entity)">
                    <tb-icon [ngStyle]="actionDescriptor.style">
                      {{actionDescriptor.iconFunction ?  actionDescriptor.iconFunction(entity) : actionDescriptor.icon}}
                    </tb-icon>
                  </button>
                </div>
                <div fxHide [fxShow.lt-lg]="cellActionDescriptors.length !== 1" *ngIf="cellActionDescriptors.length">
                  <button mat-icon-button
                          (click)="$event.stopPropagation()"
                          [matMenuTriggerFor]="cellActionsMenu">
                    <mat-icon class="material-icons">more_vert</mat-icon>
                  </button>
                  <mat-menu #cellActionsMenu="matMenu" xPosition="before">
                    <button mat-menu-item *ngFor="let actionDescriptor of cellActionDescriptors"
                            [disabled]="isLoading$ | async"
                            [fxShow]="actionDescriptor.isEnabled(entity)"
                            (click)="actionDescriptor.onAction($event, entity)">
                      <tb-icon matMenuItemIcon [ngStyle]="actionDescriptor.style">
                        {{actionDescriptor.iconFunction ?  actionDescriptor.iconFunction(entity) : actionDescriptor.icon}}
                      </tb-icon>
                      <span>{{ actionDescriptor.nameFunction ? actionDescriptor.nameFunction(entity) : actionDescriptor.name }}</span>
                    </button>
                  </mat-menu>
                </div>
              </mat-cell>
            </ng-container>
            <mat-header-row [ngClass]="{'mat-row-select': selectionEnabled}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
            <mat-row [fxShow]="!dataSource.dataLoading"
                     [ngClass]="{'mat-row-select': selectionEnabled,
                                 'mat-selected': dataSource.selection.isSelected(entity),
                                 'tb-current-entity': dataSource.isCurrentEntity(entity),
                                 'tb-pointer': entitiesTableConfig.rowPointer}"
                     *matRowDef="let entity; columns: displayedColumns;" (click)="onRowClick($event, entity)"></mat-row>
          </table>
          <span [fxShow]="!(isLoading$ | async) && (dataSource.isEmpty() | async) && !dataSource.dataLoading"
                fxLayoutAlign="center center"
                class="no-data-found">{{ translations.noEntities | translate }}</span>
          <span [fxShow]="dataSource.dataLoading"
                fxLayoutAlign="center center"
                class="no-data-found">{{ 'common.loading' | translate }}</span>
        </div>
        <mat-divider *ngIf="displayPagination"></mat-divider>
        <mat-paginator *ngIf="displayPagination"
                       [length]="dataSource.total() | async"
                       [pageIndex]="pageLink.page"
                       [pageSize]="pageLink.pageSize"
                       [pageSizeOptions]="pageSizeOptions"
                       [hidePageSize]="hidePageSize"
                       showFirstLastButtons></mat-paginator>
      </div>
    </div>
  </mat-drawer-content>
</mat-drawer-container>