File

src/app/core/entity-details/entity-details/entity-details.component.ts

Description

This component can be used to display an entity in more detail. It groups subcomponents in panels. Any component that is registered (has the DynamicComponent decorator) can be used as a subcomponent. The subcomponents will be provided with the Entity object and the creating new status, as well as its static config.

Extends

AbstractEntityDetailsComponent

Implements

OnChanges

Metadata

Index

Properties
Methods
Inputs

Inputs

panels
Type : Panel[]
Default value : []

The configuration for the panels on this details page.

entity
Type : Entity
entityType
Type : string
id
Type : string

Methods

Protected Async loadEntity
loadEntity()
Returns : any
Protected subscribeToEntityChanges
subscribeToEntityChanges()
Returns : void

Properties

entityConstructor
Type : EntityConstructor
isLoading
Type : boolean
import {
  Component,
  inject,
  Input,
  OnChanges,
  SimpleChanges,
} from "@angular/core";
import { RouterLink } from "@angular/router";
import { Panel, PanelComponent, PanelConfig } from "../EntityDetailsConfig";
import { MatButtonModule } from "@angular/material/button";
import { MatMenuModule } from "@angular/material/menu";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { Angulartics2OnModule } from "angulartics2";
import { MatTabsModule } from "@angular/material/tabs";
import { TabStateModule } from "../../../utils/tab-state/tab-state.module";
import { MatTooltipModule } from "@angular/material/tooltip";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { CommonModule, NgForOf, NgIf } from "@angular/common";
import { ViewTitleComponent } from "../../common-components/view-title/view-title.component";
import { DynamicComponentDirective } from "../../config/dynamic-components/dynamic-component.directive";
import { EntityActionsMenuComponent } from "../entity-actions-menu/entity-actions-menu.component";
import { EntityArchivedInfoComponent } from "../entity-archived-info/entity-archived-info.component";
import { UntilDestroy } from "@ngneat/until-destroy";
import { RouteTarget } from "../../../route-target";
import { AbstractEntityDetailsComponent } from "../abstract-entity-details/abstract-entity-details.component";
import { ViewActionsComponent } from "../../common-components/view-actions/view-actions.component";
import { AblePurePipe } from "@casl/angular";
import { SessionSubject } from "../../session/auth/session-info";

/**
 * This component can be used to display an entity in more detail.
 * It groups subcomponents in panels.
 * Any component that is registered (has the `DynamicComponent` decorator) can be used as a subcomponent.
 * The subcomponents will be provided with the Entity object and the creating new status, as well as its static config.
 */
@RouteTarget("EntityDetails")
@UntilDestroy()
@Component({
  selector: "app-entity-details",
  templateUrl: "./entity-details.component.html",
  styleUrls: ["./entity-details.component.scss"],
  imports: [
    AblePurePipe,
    MatButtonModule,
    MatMenuModule,
    FontAwesomeModule,
    Angulartics2OnModule,
    MatTabsModule,
    TabStateModule,
    MatTooltipModule,
    MatProgressBarModule,
    NgIf,
    NgForOf,
    ViewTitleComponent,
    DynamicComponentDirective,
    EntityActionsMenuComponent,
    EntityArchivedInfoComponent,
    RouterLink,
    CommonModule,
    ViewActionsComponent,
  ],
})
export class EntityDetailsComponent
  extends AbstractEntityDetailsComponent
  implements OnChanges
{
  /**
   * The configuration for the panels on this details page.
   */
  @Input() panels: Panel[] = [];

  private session = inject(SessionSubject);

  override async ngOnChanges(changes: SimpleChanges) {
    await super.ngOnChanges(changes);

    if (changes.id || changes.entity || changes.panels) {
      this.initPanels();
    }
  }

  private initPanels() {
    let filteredPanels = this.panels
      .filter((p) =>
        this.hasRequiredRole({ permittedUserRoles: p?.permittedUserRoles }),
      )
      .map((p) => ({
        title: p.title,
        components: p.components.map((c) => ({
          title: c.title,
          component: c.component,
          config: this.getPanelConfig(c),
        })),
      }));

    const hasUserSecurityPanel = filteredPanels.some((panel) =>
      panel.components.some((c) => c.component === "UserSecurity"),
    );

    if (this.entityConstructor?.enableUserAccounts && !hasUserSecurityPanel) {
      filteredPanels.push({
        title: $localize`:Panel title:User Account`,
        components: [
          {
            title: "",
            component: "UserSecurity",
            config: this.getPanelConfig({ component: "UserSecurity" }),
          },
        ],
      });
    }

    this.panels = filteredPanels;
  }

  /**
   * Checks if the current user has access based on permitted user roles.
   * Accepts a config object containing an optional `permittedUserRoles` array.
   * Returns true if roles are not specified or if the user matches any role.
   */
  private hasRequiredRole({
    permittedUserRoles,
  }: {
    permittedUserRoles?: string[];
  }): boolean {
    if (!permittedUserRoles || permittedUserRoles.length === 0) return true;
    const userRoles = this.session.value.roles;
    return permittedUserRoles.some((role) => userRoles.includes(role));
  }

  private getPanelConfig(c: PanelComponent): PanelConfig {
    let panelConfig: PanelConfig = {
      entity: this.entity,
      creatingNew: this.entity?.isNew,
    };
    if (typeof c.config === "object" && !Array.isArray(c.config)) {
      panelConfig = { ...c.config, ...panelConfig };
    } else {
      panelConfig.config = c.config;
    }
    return panelConfig;
  }
}
<!-- Header: title + actions -->
<app-view-title>
  @if (entityConstructor?.icon) {
    <fa-icon
      [icon]="entityConstructor.icon"
      class="standard-icon-with-text margin-left-regular"
    ></fa-icon>
  }
  @if (!entity?.isNew) {
    {{ entity?.toString() }}
  } @else {
    <span i18n="Title when adding a new entity">
      Adding new {{ this.entityConstructor?.label }}
    </span>
  }
</app-view-title>

<app-view-actions>
  <app-entity-actions-menu
    [entity]="entity"
    [navigateOnDelete]="true"
    [showExpanded]="true"
  >
    <button
      mat-menu-item
      [routerLink]="['/admin/entity', entity?.getType()]"
      [queryParams]="{ mode: 'details' }"
      queryParamsHandling="merge"
      *ngIf="
        ('update' | ablePure: 'Config' | async) &&
        !entityConstructor.isInternalEntity
      "
    >
      <fa-icon
        class="standard-icon-with-text color-accent"
        icon="tools"
      ></fa-icon>
      <span i18n>Edit Data Structure</span>
    </button>
  </app-entity-actions-menu>
</app-view-actions>

<app-entity-archived-info [entity]="entity"></app-entity-archived-info>

<!-- Content: tabbed components -->
<mat-tab-group appTabStateMemo [preserveContent]="true">
  <mat-tab
    *ngFor="let panelConfig of panels"
    [disabled]="entity?.isNew || unsavedChanges.pending"
  >
    <ng-template mat-tab-label>
      <span
        [matTooltipDisabled]="!entity?.isNew"
        matTooltip="Save the new record to create it before accessing other details"
        i18n-matTooltip="
          Tooltip explaining disabled sections when creating new entity
        "
      >
        {{ panelConfig.title }}
      </span>
    </ng-template>

    <ng-template matTabContent>
      <div *ngIf="isLoading" class="process-spinner">
        <mat-progress-bar mode="indeterminate"></mat-progress-bar>
      </div>

      <div
        *ngFor="let componentConfig of panelConfig.components; let j = index"
        class="padding-top-large"
      >
        <h3 *ngIf="componentConfig.title && componentConfig.title !== ''">
          {{ componentConfig.title }}
        </h3>
        <ng-template
          *ngIf="componentConfig.config?.entity"
          [appDynamicComponent]="componentConfig"
        ></ng-template>
        <br *ngIf="j < panelConfig.components.length - 1" />
      </div>
    </ng-template>
  </mat-tab>
</mat-tab-group>

./entity-details.component.scss

:host {
  display: block;
  height: 100%;
}

.mat-mdc-tab-group {
  height: 100%;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""