committed by
GitHub
42 changed files with 894 additions and 135 deletions
@ -0,0 +1,96 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 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. |
|||
|
|||
--> |
|||
<ng-container *ngIf="panelMode; else componentMode"> |
|||
<ng-container *ngTemplateOutlet="calculatedFieldsFilterPanel"></ng-container> |
|||
</ng-container> |
|||
<ng-template #componentMode> |
|||
<ng-container *ngIf="buttonMode; else calculatedFieldsFilter"> |
|||
<button color="primary" |
|||
matTooltip="{{ 'calculated-fields.calculated-fields-filter' | translate }}" |
|||
matTooltipPosition="above" |
|||
mat-stroked-button |
|||
(click)="toggleCfFilterPanel($event)"> |
|||
<mat-icon>filter_list</mat-icon>{{ 'calculated-fields.calculated-fields-filter' | translate }} |
|||
</button> |
|||
</ng-container> |
|||
</ng-template> |
|||
<ng-template #calculatedFieldsFilterPanel> |
|||
<form class="mat-content mat-padding flex flex-col" (ngSubmit)="update()"> |
|||
<ng-container *ngTemplateOutlet="calculatedFieldsFilter"></ng-container> |
|||
<div class="tb-panel-actions flex flex-row items-center justify-end"> |
|||
<button type="button" |
|||
mat-button |
|||
color="primary" |
|||
(click)="reset()"> |
|||
{{ 'action.reset' | translate }} |
|||
</button> |
|||
<span class="flex-1"></span> |
|||
<button type="button" |
|||
mat-button |
|||
(click)="cancel()"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button type="submit" |
|||
mat-raised-button |
|||
color="primary" |
|||
[disabled]="cfFilterForm.invalid || !cfFilterForm.dirty"> |
|||
{{ 'action.update' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</ng-template> |
|||
<ng-template #calculatedFieldsFilter> |
|||
<div class="tb-form-panel tb-calculated-fields-config-component no-padding no-border" [formGroup]="cfFilterForm"> |
|||
<div class="tb-form-row column-xs"> |
|||
<div class="fixed-title-width" translate>calculated-fields.calculated-field-types</div> |
|||
<tb-string-items-list subscriptSizing="dynamic" |
|||
formControlName="types" |
|||
appearance="outline" |
|||
fieldClass="flex" |
|||
class="flex-1" |
|||
placeholder="{{ !cfFilterForm.get('types').value?.length ? ('calculated-fields.any-type' | translate) : '' }}" |
|||
[predefinedValues]="types"> |
|||
</tb-string-items-list> |
|||
</div> |
|||
<div class="tb-form-row column-xs"> |
|||
<div class="fixed-title-width" translate>alarm-rule.target-entity-type</div> |
|||
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic"> |
|||
<mat-select formControlName="entityType" placeholder="{{ 'alarm-rule.any-type' | translate }}"> |
|||
<mat-option>{{ 'alarm-rule.any-type' | translate }}</mat-option> |
|||
<mat-option *ngFor="let type of listEntityTypes" [value]="type"> |
|||
{{ entityTypeTranslations.get(type)?.type | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
</div> |
|||
@if (cfFilterForm.get('entityType').value) { |
|||
<div class="tb-form-row column-xs"> |
|||
<div class="fixed-title-width" translate>alarm-rule.target-entities</div> |
|||
<tb-entity-list appearance="outline" |
|||
subscriptSizing="dynamic" |
|||
class="flex flex-1" |
|||
inlineField |
|||
syncIdsWithDB |
|||
labelText="{{'entity.entity-list' | translate}}" |
|||
[entityType]="cfFilterForm.get('entityType').value" |
|||
formControlName="entities"> |
|||
</tb-entity-list> |
|||
</div> |
|||
} |
|||
</div> |
|||
</ng-template> |
|||
@ -0,0 +1,56 @@ |
|||
/** |
|||
* Copyright © 2016-2025 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. |
|||
*/ |
|||
|
|||
@import '../scss/constants'; |
|||
|
|||
:host { |
|||
display: flex; |
|||
max-width: 100%; |
|||
.mdc-button { |
|||
max-width: 100%; |
|||
} |
|||
} |
|||
|
|||
:host ::ng-deep { |
|||
.mdc-button { |
|||
.mat-icon { |
|||
min-width: 24px; |
|||
} |
|||
.mdc-button__label { |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
} |
|||
} |
|||
|
|||
::ng-deep { |
|||
.tb-calculated-fields-config-component { |
|||
max-width: 100%; |
|||
width: 600px; |
|||
min-width: 100%; |
|||
flex: 1; |
|||
|
|||
tb-entity-subtype-list { |
|||
flex: 1; |
|||
@media #{$mat-gt-xs} { |
|||
width: 180px; |
|||
} |
|||
.mdc-evolution-chip-set__chips { |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,281 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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.
|
|||
///
|
|||
|
|||
import { |
|||
Component, |
|||
DestroyRef, |
|||
ElementRef, |
|||
forwardRef, |
|||
Inject, |
|||
InjectionToken, |
|||
Input, |
|||
OnInit, |
|||
Optional, |
|||
TemplateRef, |
|||
ViewChild, |
|||
ViewContainerRef |
|||
} from '@angular/core'; |
|||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
|||
import { coerceBoolean } from '@shared/decorators/coercion'; |
|||
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
|||
import { TemplatePortal } from '@angular/cdk/portal'; |
|||
import { deepClone, isArraysEqualIgnoreUndefined, isDefinedAndNotNull, isEmpty, isUndefinedOrNull } from '@core/utils'; |
|||
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; |
|||
import { fromEvent, Subscription } from 'rxjs'; |
|||
import { POSITION_MAP } from '@shared/models/overlay.models'; |
|||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|||
import { |
|||
calculatedFieldsEntityTypeList, |
|||
CalculatedFieldsQuery, |
|||
calculatedFieldTypes, |
|||
CalculatedFieldTypeTranslations |
|||
} from '@shared/models/calculated-field.models'; |
|||
import { StringItemsOption } from '@shared/components/string-items-list.component'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
export const CALCULATED_FIELDS_CONFIG_DATA = new InjectionToken<any>('CalculatedFieldsFilterConfigData'); |
|||
|
|||
export interface CalculatedFieldsFilterConfigData { |
|||
panelMode: boolean; |
|||
userMode: boolean; |
|||
filterConfig: CalculatedFieldsQuery; |
|||
initialFilterConfig?: CalculatedFieldsQuery; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-calculated-fields-filter-config', |
|||
templateUrl: './calculated-fields-filter-config.component.html', |
|||
styleUrls: ['./calculated-fields-filter-config.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => CalculatedFieldsFilterConfigComponent), |
|||
multi: true |
|||
} |
|||
] |
|||
}) |
|||
export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlValueAccessor { |
|||
|
|||
@ViewChild('calculatedFieldsFilterPanel') |
|||
calculatedFieldsFilterPanel: TemplateRef<any>; |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@coerceBoolean() |
|||
@Input() |
|||
buttonMode = true; |
|||
|
|||
@coerceBoolean() |
|||
@Input() |
|||
userMode = false; |
|||
|
|||
@coerceBoolean() |
|||
@Input() |
|||
propagatedFilter = true; |
|||
|
|||
@Input() |
|||
initialCfFilterConfig: CalculatedFieldsQuery = { |
|||
types: [], |
|||
entityType: null, |
|||
entities: [] |
|||
}; |
|||
|
|||
panelMode = false; |
|||
|
|||
cfFilterForm: FormGroup; |
|||
|
|||
panelResult: CalculatedFieldsQuery = null; |
|||
|
|||
entityType = EntityType; |
|||
|
|||
listEntityTypes = calculatedFieldsEntityTypeList; |
|||
entityTypeTranslations = entityTypeTranslations; |
|||
|
|||
readonly types: StringItemsOption[] = calculatedFieldTypes.map(item => ({ |
|||
name: this.translate.instant(CalculatedFieldTypeTranslations.get(item).name), |
|||
value: item |
|||
})); |
|||
|
|||
private cfFilterOverlayRef: OverlayRef; |
|||
private cfFilterConfig: CalculatedFieldsQuery; |
|||
private resizeWindows: Subscription; |
|||
|
|||
private propagateChange = (_: any) => {}; |
|||
|
|||
constructor(@Optional() @Inject(CALCULATED_FIELDS_CONFIG_DATA) |
|||
private data: CalculatedFieldsFilterConfigData | undefined, |
|||
@Optional() private overlayRef: OverlayRef, |
|||
private fb: FormBuilder, |
|||
private overlay: Overlay, |
|||
private nativeElement: ElementRef, |
|||
private viewContainerRef: ViewContainerRef, |
|||
private destroyRef: DestroyRef, |
|||
private translate: TranslateService) { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
if (this.data) { |
|||
this.panelMode = this.data.panelMode; |
|||
this.userMode = this.data.userMode; |
|||
this.cfFilterConfig = this.data.filterConfig; |
|||
this.initialCfFilterConfig = this.data.initialFilterConfig; |
|||
if (this.panelMode && !this.initialCfFilterConfig) { |
|||
this.initialCfFilterConfig = deepClone(this.cfFilterConfig); |
|||
} |
|||
} |
|||
this.cfFilterForm = this.fb.group({ |
|||
types: [null, []], |
|||
entityType: [null, []], |
|||
entities: [null, []] |
|||
}); |
|||
this.cfFilterForm.valueChanges.pipe( |
|||
takeUntilDestroyed(this.destroyRef) |
|||
).subscribe( |
|||
() => { |
|||
if (!this.buttonMode) { |
|||
this.cfConfigUpdated(this.cfFilterForm.value); |
|||
} |
|||
} |
|||
); |
|||
if (this.panelMode) { |
|||
this.updateCfConfigForm(this.cfFilterConfig); |
|||
} |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(_fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.cfFilterForm.disable({emitEvent: false}); |
|||
} else { |
|||
this.cfFilterForm.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(cfFilterConfig?: CalculatedFieldsQuery): void { |
|||
this.cfFilterConfig = cfFilterConfig; |
|||
if (!this.initialCfFilterConfig && cfFilterConfig) { |
|||
this.initialCfFilterConfig = deepClone(cfFilterConfig); |
|||
} |
|||
this.updateCfConfigForm(cfFilterConfig); |
|||
} |
|||
|
|||
toggleCfFilterPanel($event: Event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
const config = new OverlayConfig({ |
|||
panelClass: 'tb-filter-panel', |
|||
backdropClass: 'cdk-overlay-transparent-backdrop', |
|||
hasBackdrop: true, |
|||
maxHeight: '80vh', |
|||
height: 'min-content', |
|||
minWidth: '' |
|||
}); |
|||
config.hasBackdrop = true; |
|||
config.positionStrategy = this.overlay.position() |
|||
.flexibleConnectedTo(this.nativeElement) |
|||
.withPositions([POSITION_MAP.bottomLeft]); |
|||
|
|||
this.cfFilterOverlayRef = this.overlay.create(config); |
|||
this.cfFilterOverlayRef.backdropClick().subscribe(() => { |
|||
this.cfFilterOverlayRef.dispose(); |
|||
}); |
|||
this.cfFilterOverlayRef.attach(new TemplatePortal(this.calculatedFieldsFilterPanel, |
|||
this.viewContainerRef)); |
|||
this.resizeWindows = fromEvent(window, 'resize').subscribe(() => { |
|||
this.cfFilterOverlayRef.updatePosition(); |
|||
}); |
|||
} |
|||
|
|||
cancel() { |
|||
this.updateCfConfigForm(this.cfFilterConfig); |
|||
if (this.overlayRef) { |
|||
this.overlayRef.dispose(); |
|||
} else { |
|||
this.resizeWindows.unsubscribe(); |
|||
this.cfFilterOverlayRef.dispose(); |
|||
} |
|||
} |
|||
|
|||
update() { |
|||
this.cfConfigUpdated(this.cfFilterForm.value); |
|||
this.cfFilterForm.markAsPristine(); |
|||
if (this.panelMode) { |
|||
this.panelResult = this.cfFilterConfig; |
|||
} |
|||
if (this.overlayRef) { |
|||
this.overlayRef.dispose(); |
|||
} else { |
|||
this.resizeWindows.unsubscribe(); |
|||
this.cfFilterOverlayRef.dispose(); |
|||
} |
|||
} |
|||
|
|||
reset() { |
|||
const cfFilterConfig = this.cfFilterFromFormValue(this.cfFilterForm.value); |
|||
if (!this.cfFilterConfigEquals(cfFilterConfig, this.initialCfFilterConfig)) { |
|||
this.updateCfConfigForm(this.initialCfFilterConfig); |
|||
this.cfFilterForm.markAsDirty(); |
|||
} |
|||
} |
|||
|
|||
private cfFilterConfigEquals = (filter1?: CalculatedFieldsQuery, filter2?: CalculatedFieldsQuery): boolean => { |
|||
if (filter1 === filter2) { |
|||
return true; |
|||
} |
|||
if ((isUndefinedOrNull(filter1) || isEmpty(filter1)) && (isUndefinedOrNull(filter2) || isEmpty(filter2))) { |
|||
return true; |
|||
} else if (isDefinedAndNotNull(filter1) && isDefinedAndNotNull(filter2)) { |
|||
if (!isArraysEqualIgnoreUndefined(filter1.types, filter2.types)) { |
|||
return false; |
|||
} |
|||
if (!isArraysEqualIgnoreUndefined(filter1.entities, filter2.entities)) { |
|||
return false; |
|||
} |
|||
return filter1.entityType !== filter2.entityType; |
|||
} |
|||
return false; |
|||
}; |
|||
|
|||
private updateCfConfigForm(cfFilterConfig?: CalculatedFieldsQuery) { |
|||
this.cfFilterForm.patchValue({ |
|||
types: cfFilterConfig?.types ?? [], |
|||
entityType: cfFilterConfig?.entityType ?? null, |
|||
entities: cfFilterConfig?.entities ?? [], |
|||
}, {emitEvent: false}); |
|||
} |
|||
|
|||
private cfConfigUpdated(formValue: any) { |
|||
this.cfFilterConfig = this.cfFilterFromFormValue(formValue); |
|||
this.propagateChange(this.cfFilterConfig); |
|||
} |
|||
|
|||
private cfFilterFromFormValue(formValue: any): CalculatedFieldsQuery { |
|||
return { |
|||
types: formValue?.types ?? [], |
|||
entityType: formValue?.entityType ?? null, |
|||
entities: formValue?.entities ?? [], |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 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. |
|||
|
|||
--> |
|||
<tb-calculated-fields-filter-config [ngModel]="calculatedFieldsTableConfig.calculatedFieldFilterConfig" |
|||
(ngModelChange)="calculatedFieldsFilterChanged($event)"> |
|||
</tb-calculated-fields-filter-config> |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* Copyright © 2016-2025 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. |
|||
*/ |
|||
|
|||
:host { |
|||
padding-right: 8px; |
|||
overflow: hidden; |
|||
max-width: 100%; |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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.
|
|||
///
|
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; |
|||
import { CalculatedField, CalculatedFieldsQuery } from "@shared/models/calculated-field.models"; |
|||
import { CalculatedFieldsTableConfig } from '@home/components/calculated-fields/calculated-fields-table-config'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-calculated-fields-table-header', |
|||
templateUrl: './calculated-fields-header.component.html', |
|||
styleUrls: ['./calculated-fields-header.component.scss'] |
|||
}) |
|||
export class CalculatedFieldsHeaderComponent extends EntityTableHeaderComponent<CalculatedField> { |
|||
|
|||
get calculatedFieldsTableConfig(): CalculatedFieldsTableConfig { |
|||
return this.entitiesTableConfig as CalculatedFieldsTableConfig; |
|||
} |
|||
|
|||
constructor(protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
calculatedFieldsFilterChanged(calculatedFieldFilterConfig: CalculatedFieldsQuery) { |
|||
this.calculatedFieldsTableConfig.calculatedFieldFilterConfig = calculatedFieldFilterConfig; |
|||
this.calculatedFieldsTableConfig.getTable().resetSortAndFilter(true, true); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { RouterModule, Routes } from '@angular/router'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import { MenuId } from '@core/services/menu.models'; |
|||
import { CalculatedFieldsTableComponent } from '@home/components/calculated-fields/calculated-fields-table.component'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'calculatedFields', |
|||
component: CalculatedFieldsTableComponent, |
|||
data: { |
|||
auth: [Authority.TENANT_ADMIN], |
|||
title: 'entity.type-calculated-fields', |
|||
breadcrumb: { |
|||
menuId: MenuId.calculated_fields |
|||
}, |
|||
isPage: true, |
|||
} |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule], |
|||
providers: [] |
|||
}) |
|||
export class CalculatedFieldsRoutingModule { } |
|||
@ -0,0 +1,34 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; |
|||
import { HomeComponentsModule } from '@modules/home/components/home-components.module'; |
|||
import { CalculatedFieldsRoutingModule } from '@home/pages/calculated-fields/calculated-fields-routing.module'; |
|||
|
|||
@NgModule({ |
|||
declarations: [], |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
HomeComponentsModule, |
|||
HomeDialogsModule, |
|||
CalculatedFieldsRoutingModule |
|||
] |
|||
}) |
|||
export class CalculatedFieldsModule { } |
|||
Loading…
Reference in new issue