From cf4e1a4bd93995fd4cae49c81fb86b9e1cbd1381 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 1 Sep 2020 18:41:42 +0300 Subject: [PATCH] UI: Inline tenant profile create/edit. --- .../home/components/home-components.module.ts | 7 +- ...tenant-profile-autocomplete.component.html | 29 ++++- .../tenant-profile-autocomplete.component.ts | 79 +++++++++++++- .../tenant-profile-dialog.component.html | 53 +++++++++ .../tenant-profile-dialog.component.ts | 101 ++++++++++++++++++ .../profile/tenant-profile.component.ts | 6 +- .../home/pages/tenant/tenant.component.html | 3 +- .../home/pages/tenant/tenant.component.ts | 3 + ui-ngx/src/app/shared/models/tenant.model.ts | 10 +- .../assets/locale/locale.constant-en_US.json | 5 +- 10 files changed, 277 insertions(+), 19 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 787a6f7317..93e87dc185 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -86,6 +86,7 @@ import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog. import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component'; import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component'; import { TenantProfileComponent } from './profile/tenant-profile.component'; +import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.component'; @NgModule({ declarations: @@ -154,7 +155,8 @@ import { TenantProfileComponent } from './profile/tenant-profile.component'; FilterUserInfoDialogComponent, FilterPredicateValueComponent, TenantProfileAutocompleteComponent, - TenantProfileComponent + TenantProfileComponent, + TenantProfileDialogComponent ], imports: [ CommonModule, @@ -212,7 +214,8 @@ import { TenantProfileComponent } from './profile/tenant-profile.component'; FiltersEditComponent, UserFilterDialogComponent, TenantProfileAutocompleteComponent, - TenantProfileComponent + TenantProfileComponent, + TenantProfileDialogComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html index 0f892cc3be..5a81b2b20f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html @@ -20,6 +20,8 @@ #tenantProfileInput formControlName="tenantProfile" [required]="required" + (keydown)="tenantProfileEnter($event)" + (keypress)="tenantProfileEnter($event)" [matAutocomplete]="tenantProfileAutocomplete"> + - - - {{ translate.get('tenant-profile.no-tenant-profiles-matching', {entity: searchText}) | async }} - + +
+
+ tenant-profile.no-tenant-profiles-found +
+ + + {{ translate.get('tenant-profile.no-tenant-profiles-matching', + {entity: truncate.transform(searchText, true, 6, '...')}) | async }} + + + + tenant-profile.create-new-tenant-profile + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts index 99ee16c5d3..b4a1c4af94 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Observable } from 'rxjs'; import { PageLink } from '@shared/models/page/page-link'; @@ -27,7 +27,12 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { TenantProfileId } from '@shared/models/id/tenant-profile-id'; import { EntityInfoData } from '@shared/models/entity.models'; import { TenantProfileService } from '@core/http/tenant-profile.service'; -import { entityIdEquals } from '../../../../shared/models/id/entity-id'; +import { entityIdEquals } from '@shared/models/id/entity-id'; +import { TruncatePipe } from '@shared//pipe/truncate.pipe'; +import { ENTER } from '@angular/cdk/keycodes'; +import { TenantProfile } from '@shared/models/tenant.model'; +import { MatDialog } from '@angular/material/dialog'; +import { TenantProfileDialogComponent, TenantProfileDialogData } from './tenant-profile-dialog.component'; @Component({ selector: 'tb-tenant-profile-autocomplete', @@ -60,6 +65,9 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor, @Input() disabled: boolean; + @Output() + tenantProfileUpdated = new EventEmitter(); + @ViewChild('tenantProfileInput', {static: true}) tenantProfileInput: ElementRef; filteredTenantProfiles: Observable>; @@ -70,8 +78,10 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor, constructor(private store: Store, public translate: TranslateService, + public truncate: TruncatePipe, private tenantProfileService: TenantProfileService, - private fb: FormBuilder) { + private fb: FormBuilder, + private dialog: MatDialog) { this.selectTenantProfileFormGroup = this.fb.group({ tenantProfile: [null] }); @@ -168,4 +178,67 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor, }, 0); } + textIsNotEmpty(text: string): boolean { + return (text && text.length > 0); + } + + tenantProfileEnter($event: KeyboardEvent) { + if ($event.keyCode === ENTER) { + $event.preventDefault(); + if (!this.modelValue) { + this.createTenantProfile($event, this.searchText); + } + } + } + + createTenantProfile($event: Event, profileName: string) { + $event.preventDefault(); + const tenantProfile: TenantProfile = { + id: null, + name: profileName + }; + this.openTenantProfileDialog(tenantProfile, true); + } + + editTenantProfile($event: Event) { + $event.preventDefault(); + this.tenantProfileService.getTenantProfile(this.modelValue.id).subscribe( + (tenantProfile) => { + this.openTenantProfileDialog(tenantProfile, false); + } + ); + } + + openTenantProfileDialog(tenantProfile: TenantProfile, isAdd: boolean) { + this.dialog.open(TenantProfileDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + tenantProfile + } + }).afterClosed().subscribe( + (savedTenantProfile) => { + if (!savedTenantProfile) { + setTimeout(() => { + this.tenantProfileInput.nativeElement.blur(); + this.tenantProfileInput.nativeElement.focus(); + }, 0); + } else { + this.tenantProfileService.getTenantProfileInfo(savedTenantProfile.id.id).subscribe( + (profile) => { + this.modelValue = new TenantProfileId(profile.id.id); + this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: true}); + if (isAdd) { + this.propagateChange(this.modelValue); + } else { + this.tenantProfileUpdated.next(savedTenantProfile.id); + } + } + ); + } + } + ); + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html new file mode 100644 index 0000000000..79bf7983a7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html @@ -0,0 +1,53 @@ + +
+ +

{{ (isAdd ? 'tenant-profile.add' : 'tenant-profile.edit' ) | translate }}

+ + +
+ + +
+
+ + +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts new file mode 100644 index 0000000000..8134c4934d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.ts @@ -0,0 +1,101 @@ +/// +/// Copyright © 2016-2020 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 { + AfterViewInit, + Component, + ComponentFactoryResolver, + Inject, + Injector, + SkipSelf, + ViewChild +} from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormControl, FormGroupDirective, NgForm } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { TenantProfile } from '@shared/models/tenant.model'; +import { TenantProfileComponent } from './tenant-profile.component'; +import { TenantProfileService } from '@core/http/tenant-profile.service'; + +export interface TenantProfileDialogData { + tenantProfile: TenantProfile; + isAdd: boolean; +} + +@Component({ + selector: 'tb-tenant-profile-dialog', + templateUrl: './tenant-profile-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: TenantProfileDialogComponent}], + styleUrls: [] +}) +export class TenantProfileDialogComponent extends + DialogComponent implements ErrorStateMatcher, AfterViewInit { + + isAdd: boolean; + tenantProfile: TenantProfile; + + submitted = false; + + @ViewChild('tenantProfileComponent', {static: true}) tenantProfileComponent: TenantProfileComponent; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: TenantProfileDialogData, + public dialogRef: MatDialogRef, + private componentFactoryResolver: ComponentFactoryResolver, + private injector: Injector, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + private tenantProfileService: TenantProfileService) { + super(store, router, dialogRef); + this.isAdd = this.data.isAdd; + this.tenantProfile = this.data.tenantProfile; + } + + ngAfterViewInit(): void { + if (this.isAdd) { + setTimeout(() => { + this.tenantProfileComponent.entityForm.markAsDirty(); + }, 0); + } + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + if (this.tenantProfileComponent.entityForm.valid) { + this.tenantProfile = {...this.tenantProfile, ...this.tenantProfileComponent.entityFormValue()}; + this.tenantProfileService.saveTenantProfile(this.tenantProfile).subscribe( + (tenantProfile) => { + this.dialogRef.close(tenantProfile); + } + ); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts index 8a0fabe971..7b14624e81 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, Input } from '@angular/core'; +import { Component, Inject, Input, Optional } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -36,8 +36,8 @@ export class TenantProfileComponent extends EntityComponent { constructor(protected store: Store, protected translate: TranslateService, - @Inject('entity') protected entityValue: TenantProfile, - @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + @Optional() @Inject('entity') protected entityValue: TenantProfile, + @Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, protected fb: FormBuilder) { super(store, fb, entityValue, entitiesTableConfigValue); } diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html index 1b37efd149..dd603d4a91 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html @@ -52,7 +52,8 @@ + formControlName="tenantProfileId" + (tenantProfileUpdated)="onTenantProfileUpdated()">
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index 566086939b..f139deb1b5 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -88,4 +88,7 @@ export class TenantComponent extends ContactBasedComponent { })); } + onTenantProfileUpdated() { + this.entitiesTableConfig.table.updateData(false); + } } diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index a0602515d3..378dca7c25 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -25,11 +25,11 @@ export interface TenantProfileData { export interface TenantProfile extends BaseData { name: string; - description: string; - default: boolean; - isolatedTbCore: boolean; - isolatedTbRuleEngine: boolean; - profileData: TenantProfileData; + description?: string; + default?: boolean; + isolatedTbCore?: boolean; + isolatedTbRuleEngine?: boolean; + profileData?: TenantProfileData; } export interface Tenant extends ContactBased { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 746356452c..ff3531517b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1680,6 +1680,7 @@ "tenant-profile": "Tenant profile", "tenant-profiles": "Tenant profiles", "add": "Add tenant profile", + "edit": "Edit tenant profile", "tenant-profile-details": "Tenant profile details", "no-tenant-profiles-text": "No tenant profiles found", "search": "Search tenant profiles", @@ -1699,7 +1700,9 @@ "delete-tenant-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 tenant profile} other {# tenant profiles} }?", "delete-tenant-profiles-text": "Be careful, after the confirmation all selected tenant profiles will be removed and all related data will become unrecoverable.", "set-default-tenant-profile-title": "Are you sure you want to make the tenant profile '{{tenantProfileName}}' root?", - "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified." + "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified.", + "no-tenant-profiles-found": "No tenant profiles found.", + "create-new-tenant-profile": "Create a new one!" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",