src/app/core/entity-details/entity-details/entity-details.component.ts
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.
AbstractEntityDetailsComponent
OnChanges
selector | app-entity-details |
imports |
AblePurePipe
MatButtonModule
MatMenuModule
FontAwesomeModule
Angulartics2OnModule
MatTabsModule
TabStateModule
MatTooltipModule
MatProgressBarModule
NgIf
NgForOf
ViewTitleComponent
DynamicComponentDirective
EntityActionsMenuComponent
EntityArchivedInfoComponent
RouterLink
CommonModule
ViewActionsComponent
|
styleUrls | ./entity-details.component.scss |
templateUrl | ./entity-details.component.html |
Properties |
Methods |
|
Inputs |
panels | |
Type : Panel[]
|
|
Default value : []
|
|
The configuration for the panels on this details page. |
entity | |
Type : Entity
|
|
Inherited from
AbstractEntityDetailsComponent
|
|
Defined in
AbstractEntityDetailsComponent:28
|
entityType | |
Type : string
|
|
Inherited from
AbstractEntityDetailsComponent
|
|
Defined in
AbstractEntityDetailsComponent:24
|
id | |
Type : string
|
|
Inherited from
AbstractEntityDetailsComponent
|
|
Defined in
AbstractEntityDetailsComponent:27
|
Protected Async loadEntity |
loadEntity()
|
Inherited from
AbstractEntityDetailsComponent
|
Defined in
AbstractEntityDetailsComponent:62
|
Returns :
any
|
Protected subscribeToEntityChanges |
subscribeToEntityChanges()
|
Inherited from
AbstractEntityDetailsComponent
|
Defined in
AbstractEntityDetailsComponent:49
|
Returns :
void
|
entityConstructor |
Type : EntityConstructor
|
Inherited from
AbstractEntityDetailsComponent
|
Defined in
AbstractEntityDetailsComponent:25
|
isLoading |
Type : boolean
|
Inherited from
AbstractEntityDetailsComponent
|
Defined in
AbstractEntityDetailsComponent:21
|
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%;
}