Commit c7b57ca543f36e4691efae2e01d8e262afc04426

Authored by Igor Kulikov
1 parent e1069608

Add Device Wizard improvements

... ... @@ -85,7 +85,7 @@
85 85 </mat-step>
86 86 <mat-step [stepControl]="alarmRulesFormGroup">
87 87 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
88   - <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate:
  88 + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
89 89 {count: alarmRulesFormGroup.get('alarms').value ?
90 90 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
91 91 <tb-device-profile-alarms
... ...
... ... @@ -20,9 +20,9 @@ import {
20 20 EventEmitter,
21 21 forwardRef,
22 22 Input,
23   - NgZone,
  23 + NgZone, OnChanges,
24 24 OnInit,
25   - Output,
  25 + Output, SimpleChanges,
26 26 ViewChild
27 27 } from '@angular/core';
28 28 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
... ... @@ -55,7 +55,7 @@ import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './a
55 55 multi: true
56 56 }]
57 57 })
58   -export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit {
  58 +export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges {
59 59
60 60 selectDeviceProfileFormGroup: FormGroup;
61 61
... ... @@ -168,11 +168,22 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
168 168 );
169 169 }
170 170
  171 + ngOnChanges(changes: SimpleChanges): void {
  172 + for (const propName of Object.keys(changes)) {
  173 + const change = changes[propName];
  174 + if (!change.firstChange && change.currentValue !== change.previousValue) {
  175 + if (propName === 'transportType') {
  176 + this.writeValue(null);
  177 + }
  178 + }
  179 + }
  180 + }
  181 +
171 182 selectDefaultDeviceProfileIfNeeded(): void {
172 183 if (this.selectDefaultProfile && !this.modelValue) {
173 184 this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe(
174 185 (profile) => {
175   - if (profile) {
  186 + if (profile && !this.transportType || (profile.transportType === this.transportType)) {
176 187 this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false});
177 188 this.updateView(profile);
178 189 }
... ...
... ... @@ -42,7 +42,7 @@
42 42 <mat-expansion-panel [expanded]="true">
43 43 <mat-expansion-panel-header>
44 44 <mat-panel-title>
45   - <div>{{'device-profile.alarm-rules' | translate:
  45 + <div>{{'device-profile.alarm-rules-with-count' | translate:
46 46 {count: deviceProfileDataFormGroup.get('alarms').value ?
47 47 deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div>
48 48 </mat-panel-title>
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div style="min-width: 1000px;">
  18 +<div>
19 19 <mat-toolbar color="primary">
20 20 <h2 translate>device.add-device-text</h2>
21 21 <span fxFlex></span>
... ... @@ -29,7 +29,7 @@
29 29 </mat-progress-bar>
30 30 <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
31 31 <div mat-dialog-content>
32   - <mat-horizontal-stepper [linear]="true" #addDeviceWizardStepper (selectionChange)="changeStep($event)">
  32 + <mat-horizontal-stepper [linear]="true" [labelPosition]="labelPosition" #addDeviceWizardStepper (selectionChange)="changeStep($event)">
33 33 <ng-template matStepperIcon="edit">
34 34 <mat-icon>check</mat-icon>
35 35 </ng-template>
... ... @@ -48,60 +48,60 @@
48 48 <mat-label translate>device.label</mat-label>
49 49 <input matInput formControlName="label">
50 50 </mat-form-field>
51   - <mat-form-field class="mat-block">
  51 + <mat-form-field class="mat-block" style="padding-bottom: 14px;">
52 52 <mat-label translate>device-profile.transport-type</mat-label>
53 53 <mat-select formControlName="transportType" required>
54 54 <mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
55 55 {{deviceTransportTypeTranslations.get(type) | translate}}
56 56 </mat-option>
57 57 </mat-select>
  58 + <mat-hint *ngIf="deviceWizardFormGroup.get('transportType').value">
  59 + {{deviceTransportTypeHints.get(deviceWizardFormGroup.get('transportType').value) | translate}}
  60 + </mat-hint>
58 61 <mat-error *ngIf="deviceWizardFormGroup.get('transportType').hasError('required')">
59 62 {{ 'device-profile.transport-type-required' | translate }}
60 63 </mat-error>
61 64 </mat-form-field>
62   - <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;">
63   - {{ 'device.is-gateway' | translate }}
64   - </mat-checkbox>
65   - <mat-form-field class="mat-block">
66   - <mat-label translate>device.description</mat-label>
67   - <textarea matInput formControlName="description" rows="2"></textarea>
68   - </mat-form-field>
69   - </fieldset>
70   - </form>
71   - </mat-step>
72   - <mat-step [stepControl]="profileConfigFormGroup">
73   - <form [formGroup]="profileConfigFormGroup" style="padding-bottom: 16px;">
74   - <ng-template matStepLabel>{{ 'device.wizard.profile-configuration' | translate}}</ng-template>
75   - <mat-radio-group fxLayout="column" fxFlex formControlName="addProfileType">
76   - <mat-radio-button [value]="0" color="primary">
77   - <section>
78   - <span translate>device.wizard.existing-device-profile</span>
  65 + <div fxLayout="row" fxLayoutGap="16px">
  66 + <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
  67 + <mat-radio-button [value]="0" color="primary">
  68 + <span translate>device.wizard.existing-device-profile</span>
  69 + </mat-radio-button>
  70 + <mat-radio-button [value]="1" color="primary">
  71 + <span translate>device.wizard.new-device-profile</span>
  72 + </mat-radio-button>
  73 + </mat-radio-group>
  74 + <div fxLayout="column">
79 75 <tb-device-profile-autocomplete
80   - [required]="profileConfigFormGroup.get('addProfileType').value === 0"
  76 + [required]="!createProfile"
81 77 [transportType]="deviceWizardFormGroup.get('transportType').value"
82 78 formControlName="deviceProfileId"
  79 + (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}"
83 80 [addNewProfile]="false"
  81 + [selectDefaultProfile]="true"
84 82 [editProfileEnabled]="false">
85 83 </tb-device-profile-autocomplete>
86   - </section>
87   - </mat-radio-button>
88   - <mat-radio-button [value]="1" color="primary">
89   - <section fxLayout="column">
90   - <span translate>device.wizard.new-device-profile</span>
91 84 <mat-form-field fxFlex class="mat-block">
92   - <mat-label translate>device-profile.device-profile</mat-label>
  85 + <mat-label translate>device-profile.new-device-profile-name</mat-label>
93 86 <input matInput formControlName="newDeviceProfileTitle"
94   - [required]="profileConfigFormGroup.get('addProfileType').value === 1">
95   - <mat-error *ngIf="profileConfigFormGroup.get('newDeviceProfileTitle').hasError('required')">
96   - {{ 'device-profile.device-profile-required' | translate }}
  87 + [required]="createProfile">
  88 + <mat-error *ngIf="deviceWizardFormGroup.get('newDeviceProfileTitle').hasError('required')">
  89 + {{ 'device-profile.new-device-profile-name-required' | translate }}
97 90 </mat-error>
98 91 </mat-form-field>
99   - </section>
100   - </mat-radio-button>
101   - </mat-radio-group>
  92 + </div>
  93 + </div>
  94 + <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;">
  95 + {{ 'device.is-gateway' | translate }}
  96 + </mat-checkbox>
  97 + <mat-form-field class="mat-block">
  98 + <mat-label translate>device.description</mat-label>
  99 + <textarea matInput formControlName="description" rows="2"></textarea>
  100 + </mat-form-field>
  101 + </fieldset>
102 102 </form>
103 103 </mat-step>
104   - <mat-step [stepControl]="transportConfigFormGroup" *ngIf="createdProfile">
  104 + <mat-step [stepControl]="transportConfigFormGroup" *ngIf="createTransportConfiguration">
105 105 <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
106 106 <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template>
107 107 <tb-device-profile-transport-configuration
... ... @@ -110,9 +110,9 @@
110 110 </tb-device-profile-transport-configuration>
111 111 </form>
112 112 </mat-step>
113   - <mat-step [stepControl]="alarmRulesFormGroup" *ngIf="createdProfile">
  113 + <mat-step [stepControl]="alarmRulesFormGroup" [optional]="true" *ngIf="createProfile">
114 114 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
115   - <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate:
  115 + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
116 116 {count: alarmRulesFormGroup.get('alarms').value ?
117 117 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
118 118 <tb-device-profile-alarms
... ... @@ -120,36 +120,50 @@
120 120 </tb-device-profile-alarms>
121 121 </form>
122 122 </mat-step>
123   - <mat-step [stepControl]="specificConfigFormGroup">
124   - <ng-template matStepLabel>{{ 'device.wizard.specific-configuration' | translate }}</ng-template>
125   - <form [formGroup]="specificConfigFormGroup" style="padding-bottom: 16px;">
  123 + <mat-step [stepControl]="credentialsFormGroup" [optional]="true">
  124 + <ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template>
  125 + <form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;">
  126 + <mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credential' | translate }}</mat-checkbox>
  127 + <tb-device-credentials
  128 + [fxShow]="credentialsFormGroup.get('setCredential').value"
  129 + formControlName="credential">
  130 + </tb-device-credentials>
  131 + </form>
  132 + </mat-step>
  133 + <mat-step [stepControl]="customerFormGroup" [optional]="true">
  134 + <ng-template matStepLabel>{{ 'customer.customer' | translate }}</ng-template>
  135 + <form [formGroup]="customerFormGroup" style="padding-bottom: 16px;">
126 136 <tb-entity-autocomplete
127 137 formControlName="customerId"
128 138 labelText="device.wizard.customer-to-assign-device"
129 139 [entityType]="entityType.CUSTOMER">
130 140 </tb-entity-autocomplete>
131   - <mat-checkbox formControlName="setCredential">{{ 'device.wizard.add-credential' | translate }}</mat-checkbox>
132   - <tb-device-credentials
133   - [fxShow]="specificConfigFormGroup.get('setCredential').value"
134   - formControlName="credential">
135   - </tb-device-credentials>
136 141 </form>
137 142 </mat-step>
138 143 </mat-horizontal-stepper>
139 144 </div>
140   - <div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center">
141   - <button mat-button *ngIf="selectedIndex > 0"
142   - [disabled]="(isLoading$ | async)"
143   - (click)="previousStep()">{{ 'action.back' | translate }}</button>
144   - <span *ngIf="selectedIndex == 0"></span>
145   - <div fxLayout="row wrap" fxLayoutGap="20px">
  145 + <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;">
  146 + <div fxFlex fxLayout="row" fxLayoutAlign="end">
  147 + <button mat-raised-button
  148 + *ngIf="showNext"
  149 + [disabled]="(isLoading$ | async)"
  150 + (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
  151 + </div>
  152 + <div fxFlex fxLayout="row">
146 153 <button mat-button
  154 + color="primary"
147 155 [disabled]="(isLoading$ | async)"
148 156 (click)="cancel()">{{ 'action.cancel' | translate }}</button>
149   - <button mat-raised-button
150   - [disabled]="(isLoading$ | async) || selectedForm.invalid"
151   - color="primary"
152   - (click)="nextStep()">{{ nextStepButtonLabel$ | async | translate }}</button>
  157 + <span fxFlex></span>
  158 + <div fxLayout="row wrap" fxLayoutGap="8px">
  159 + <button mat-raised-button *ngIf="selectedIndex > 0"
  160 + [disabled]="(isLoading$ | async)"
  161 + (click)="previousStep()">{{ 'action.back' | translate }}</button>
  162 + <button mat-raised-button
  163 + [disabled]="(isLoading$ | async)"
  164 + color="primary"
  165 + (click)="add()">{{ 'action.add' | translate }}</button>
  166 + </div>
153 167 </div>
154 168 </div>
155 169 </div>
... ...
... ... @@ -13,25 +13,51 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -:host {
17   - .mat-dialog-content {
18   - display: flex;
19   - flex-direction: column;
20   - overflow: hidden;
21 16
22   - .mat-stepper-horizontal {
23   - display: flex;
24   - flex-direction: column;
25   - overflow: hidden;
  17 +@import "../../../../../scss/constants";
  18 +
  19 +:host-context(.tb-fullscreen-dialog .mat-dialog-container) {
  20 + @media #{$mat-lt-sm} {
  21 + .mat-dialog-content {
  22 + max-height: 75vh;
26 23 }
27 24 }
28 25 }
29 26
30 27 :host ::ng-deep {
31 28 .mat-dialog-content {
  29 + display: flex;
  30 + flex-direction: column;
  31 + height: 100%;
  32 +
32 33 .mat-stepper-horizontal {
  34 + display: flex;
  35 + flex-direction: column;
  36 + height: 100%;
  37 + overflow: hidden;
  38 + @media #{$mat-lt-sm} {
  39 + .mat-step-label {
  40 + white-space: normal;
  41 + overflow: visible;
  42 + .mat-step-text-label {
  43 + overflow: visible;
  44 + }
  45 + }
  46 + }
33 47 .mat-horizontal-content-container {
34   - overflow: auto;
  48 + height: 450px;
  49 + max-height: 100%;
  50 + width: 100%;;
  51 + overflow-y: auto;
  52 + @media #{$mat-gt-sm} {
  53 + min-width: 800px;
  54 + }
  55 + }
  56 + .mat-horizontal-stepper-content[aria-expanded=true] {
  57 + height: 100%;
  58 + form {
  59 + height: 100%;
  60 + }
35 61 }
36 62 }
37 63 }
... ...
... ... @@ -26,7 +26,7 @@ import {
26 26 createDeviceProfileTransportConfiguration,
27 27 DeviceProfile,
28 28 DeviceProfileType,
29   - DeviceTransportType,
  29 + DeviceTransportType, deviceTransportTypeHintMap,
30 30 deviceTransportTypeTranslationMap
31 31 } from '@shared/models/device.models';
32 32 import { MatHorizontalStepper } from '@angular/material/stepper';
... ... @@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data';
35 35 import { EntityType } from '@shared/models/entity-type.models';
36 36 import { DeviceProfileService } from '@core/http/device-profile.service';
37 37 import { EntityId } from '@shared/models/id/entity-id';
38   -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs';
  38 +import { Observable, of, Subscription } from 'rxjs';
39 39 import { map, mergeMap, tap } from 'rxjs/operators';
40 40 import { DeviceService } from '@core/http/device.service';
41 41 import { ErrorStateMatcher } from '@angular/material/core';
42 42 import { StepperSelectionEvent } from '@angular/cdk/stepper';
  43 +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
  44 +import { MediaBreakpoints } from '@shared/models/constants';
43 45
44 46 @Component({
45 47 selector: 'tb-device-wizard',
... ... @@ -54,9 +56,10 @@ export class DeviceWizardDialogComponent extends
54 56
55 57 selectedIndex = 0;
56 58
57   - nextStepButtonLabel$ = new BehaviorSubject<string>('action.continue');
  59 + showNext = true;
58 60
59   - createdProfile = false;
  61 + createProfile = false;
  62 + createTransportConfiguration = false;
60 63
61 64 entityType = EntityType;
62 65
... ... @@ -64,15 +67,19 @@ export class DeviceWizardDialogComponent extends
64 67
65 68 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
66 69
67   - deviceWizardFormGroup: FormGroup;
  70 + deviceTransportTypeHints = deviceTransportTypeHintMap;
68 71
69   - profileConfigFormGroup: FormGroup;
  72 + deviceWizardFormGroup: FormGroup;
70 73
71 74 transportConfigFormGroup: FormGroup;
72 75
73 76 alarmRulesFormGroup: FormGroup;
74 77
75   - specificConfigFormGroup: FormGroup;
  78 + credentialsFormGroup: FormGroup;
  79 +
  80 + customerFormGroup: FormGroup;
  81 +
  82 + labelPosition = 'end';
76 83
77 84 private subscriptions: Subscription[] = [];
78 85
... ... @@ -83,6 +90,7 @@ export class DeviceWizardDialogComponent extends
83 90 public dialogRef: MatDialogRef<DeviceWizardDialogComponent, boolean>,
84 91 private deviceProfileService: DeviceProfileService,
85 92 private deviceService: DeviceService,
  93 + private breakpointObserver: BreakpointObserver,
86 94 private fb: FormBuilder) {
87 95 super(store, router, dialogRef);
88 96 this.deviceWizardFormGroup = this.fb.group({
... ... @@ -90,33 +98,32 @@ export class DeviceWizardDialogComponent extends
90 98 label: [''],
91 99 gateway: [false],
92 100 transportType: [DeviceTransportType.DEFAULT, Validators.required],
93   - description: ['']
94   - }
95   - );
96   -
97   - this.profileConfigFormGroup = this.fb.group({
98 101 addProfileType: [0],
99 102 deviceProfileId: [null, Validators.required],
100   - newDeviceProfileTitle: [{value: null, disabled: true}]
  103 + newDeviceProfileTitle: [{value: null, disabled: true}],
  104 + description: ['']
101 105 }
102 106 );
103 107
104   - this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe(
  108 + this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe(
105 109 (addProfileType: number) => {
106 110 if (addProfileType === 0) {
107   - this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]);
108   - this.profileConfigFormGroup.get('deviceProfileId').enable();
109   - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null);
110   - this.profileConfigFormGroup.get('newDeviceProfileTitle').disable();
111   - this.profileConfigFormGroup.updateValueAndValidity();
112   - this.createdProfile = false;
  111 + this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]);
  112 + this.deviceWizardFormGroup.get('deviceProfileId').enable();
  113 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null);
  114 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable();
  115 + this.deviceWizardFormGroup.updateValueAndValidity();
  116 + this.createProfile = false;
  117 + this.createTransportConfiguration = false;
113 118 } else {
114   - this.profileConfigFormGroup.get('deviceProfileId').setValidators(null);
115   - this.profileConfigFormGroup.get('deviceProfileId').disable();
116   - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);
117   - this.profileConfigFormGroup.get('newDeviceProfileTitle').enable();
118   - this.profileConfigFormGroup.updateValueAndValidity();
119   - this.createdProfile = true;
  119 + this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null);
  120 + this.deviceWizardFormGroup.get('deviceProfileId').disable();
  121 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);
  122 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable();
  123 + this.deviceWizardFormGroup.updateValueAndValidity();
  124 + this.createProfile = true;
  125 + this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value &&
  126 + DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value;
120 127 }
121 128 }
122 129 ));
... ... @@ -135,20 +142,37 @@ export class DeviceWizardDialogComponent extends
135 142 }
136 143 );
137 144
138   - this.specificConfigFormGroup = this.fb.group({
139   - customerId: [null],
  145 + this.credentialsFormGroup = this.fb.group({
140 146 setCredential: [false],
141 147 credential: [{value: null, disabled: true}]
142 148 }
143 149 );
144 150
145   - this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => {
  151 + this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => {
146 152 if (value) {
147   - this.specificConfigFormGroup.get('credential').enable();
  153 + this.credentialsFormGroup.get('credential').enable();
148 154 } else {
149   - this.specificConfigFormGroup.get('credential').disable();
  155 + this.credentialsFormGroup.get('credential').disable();
150 156 }
151 157 }));
  158 +
  159 + this.customerFormGroup = this.fb.group({
  160 + customerId: [null]
  161 + }
  162 + );
  163 +
  164 + this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom';
  165 +
  166 + this.subscriptions.push(this.breakpointObserver
  167 + .observe(MediaBreakpoints['gt-sm'])
  168 + .subscribe((state: BreakpointState) => {
  169 + if (state.matches) {
  170 + this.labelPosition = 'end';
  171 + } else {
  172 + this.labelPosition = 'bottom';
  173 + }
  174 + }
  175 + ));
152 176 }
153 177
154 178 ngOnDestroy() {
... ... @@ -171,26 +195,28 @@ export class DeviceWizardDialogComponent extends
171 195 }
172 196
173 197 nextStep(): void {
174   - if (this.selectedIndex < this.maxStepperIndex) {
175   - this.addDeviceWizardStepper.next();
176   - } else {
177   - this.add();
178   - }
  198 + this.addDeviceWizardStepper.next();
179 199 }
180 200
181   - get selectedForm(): FormGroup {
182   - const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex;
  201 + getFormLabel(index: number): string {
  202 + if (index > 0) {
  203 + if (!this.createProfile) {
  204 + index += 2;
  205 + } else if (!this.createTransportConfiguration) {
  206 + index += 1;
  207 + }
  208 + }
183 209 switch (index) {
184 210 case 0:
185   - return this.deviceWizardFormGroup;
  211 + return 'device.wizard.device-details';
186 212 case 1:
187   - return this.profileConfigFormGroup;
  213 + return 'device-profile.transport-configuration';
188 214 case 2:
189   - return this.transportConfigFormGroup;
  215 + return 'device-profile.alarm-rules';
190 216 case 3:
191   - return this.alarmRulesFormGroup;
  217 + return 'device.credentials';
192 218 case 4:
193   - return this.specificConfigFormGroup;
  219 + return 'customer.customer';
194 220 }
195 221 }
196 222
... ... @@ -201,23 +227,27 @@ export class DeviceWizardDialogComponent extends
201 227 private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void {
202 228 this.transportConfigFormGroup.patchValue(
203 229 {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)});
  230 + this.createTransportConfiguration = this.createProfile && deviceTransportType &&
  231 + DeviceTransportType.DEFAULT !== deviceTransportType;
204 232 }
205 233
206   - private add(): void {
207   - this.creatProfile().pipe(
208   - mergeMap(profileId => this.createdDevice(profileId)),
209   - mergeMap(device => this.saveCredential(device))
210   - ).subscribe(
211   - (created) => {
212   - this.dialogRef.close(created);
213   - }
214   - );
  234 + add(): void {
  235 + if (this.allValid()) {
  236 + this.createDeviceProfile().pipe(
  237 + mergeMap(profileId => this.createDevice(profileId)),
  238 + mergeMap(device => this.saveCredentials(device))
  239 + ).subscribe(
  240 + (created) => {
  241 + this.dialogRef.close(created);
  242 + }
  243 + );
  244 + }
215 245 }
216 246
217   - private creatProfile(): Observable<EntityId> {
218   - if (this.profileConfigFormGroup.get('addProfileType').value) {
  247 + private createDeviceProfile(): Observable<EntityId> {
  248 + if (this.deviceWizardFormGroup.get('addProfileType').value) {
219 249 const deviceProfile: DeviceProfile = {
220   - name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value,
  250 + name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value,
221 251 type: DeviceProfileType.DEFAULT,
222 252 transportType: this.deviceWizardFormGroup.get('transportType').value,
223 253 profileData: {
... ... @@ -229,11 +259,10 @@ export class DeviceWizardDialogComponent extends
229 259 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe(
230 260 map(profile => profile.id),
231 261 tap((profileId) => {
232   - this.profileConfigFormGroup.patchValue({
  262 + this.deviceWizardFormGroup.patchValue({
233 263 deviceProfileId: profileId,
234 264 addProfileType: 0
235 265 });
236   - this.addDeviceWizardStepper.selectedIndex = 2;
237 266 })
238 267 );
239 268 } else {
... ... @@ -241,7 +270,7 @@ export class DeviceWizardDialogComponent extends
241 270 }
242 271 }
243 272
244   - private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable<BaseData<HasId>> {
  273 + private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable<BaseData<HasId>> {
245 274 const device = {
246 275 name: this.deviceWizardFormGroup.get('name').value,
247 276 label: this.deviceWizardFormGroup.get('label').value,
... ... @@ -252,21 +281,21 @@ export class DeviceWizardDialogComponent extends
252 281 },
253 282 customerId: null
254 283 };
255   - if (this.specificConfigFormGroup.get('customerId').value) {
  284 + if (this.customerFormGroup.get('customerId').value) {
256 285 device.customerId = {
257 286 entityType: EntityType.CUSTOMER,
258   - id: this.specificConfigFormGroup.get('customerId').value
  287 + id: this.customerFormGroup.get('customerId').value
259 288 };
260 289 }
261 290 return this.data.entitiesTableConfig.saveEntity(device);
262 291 }
263 292
264   - private saveCredential(device: BaseData<HasId>): Observable<boolean> {
265   - if (this.specificConfigFormGroup.get('setCredential').value) {
  293 + private saveCredentials(device: BaseData<HasId>): Observable<boolean> {
  294 + if (this.credentialsFormGroup.get('setCredential').value) {
266 295 return this.deviceService.getDeviceCredentials(device.id.id).pipe(
267 296 mergeMap(
268 297 (deviceCredentials) => {
269   - const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential};
  298 + const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential};
270 299 return this.deviceService.saveDeviceCredentials(deviceCredentialsValue);
271 300 }
272 301 ),
... ... @@ -275,12 +304,28 @@ export class DeviceWizardDialogComponent extends
275 304 return of(true);
276 305 }
277 306
  307 + allValid(): boolean {
  308 + if (this.addDeviceWizardStepper.steps.find((item, index) => {
  309 + if (item.stepControl.invalid) {
  310 + item.interacted = true;
  311 + this.addDeviceWizardStepper.selectedIndex = index;
  312 + return true;
  313 + } else {
  314 + return false;
  315 + }
  316 + } )) {
  317 + return false;
  318 + } else {
  319 + return true;
  320 + }
  321 + }
  322 +
278 323 changeStep($event: StepperSelectionEvent): void {
279 324 this.selectedIndex = $event.selectedIndex;
280 325 if (this.selectedIndex === this.maxStepperIndex) {
281   - this.nextStepButtonLabel$.next('action.add');
  326 + this.showNext = false;
282 327 } else {
283   - this.nextStepButtonLabel$.next('action.continue');
  328 + this.showNext = true;
284 329 }
285 330 }
286 331 }
... ...
... ... @@ -296,7 +296,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
296 296 name: this.translate.instant('device.add-device-text'),
297 297 icon: 'insert_drive_file',
298 298 isEnabled: () => true,
299   - onAction: ($event) => this.config.table.addEntity($event)
  299 + onAction: ($event) => this.deviceWizard($event)
300 300 },
301 301 {
302 302 name: this.translate.instant('device.import'),
... ... @@ -304,12 +304,6 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
304 304 isEnabled: () => true,
305 305 onAction: ($event) => this.importDevices($event)
306 306 },
307   - {
308   - name: this.translate.instant('device.wizard.device-wizard'),
309   - icon: 'library_add',
310   - isEnabled: () => true,
311   - onAction: ($event) => this.deviceWizard($event)
312   - },
313 307 );
314 308 }
315 309 if (deviceScope === 'customer') {
... ...
... ... @@ -72,6 +72,14 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
72 72 ]
73 73 );
74 74
  75 +export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>(
  76 + [
  77 + [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'],
  78 + [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'],
  79 + [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint']
  80 + ]
  81 +);
  82 +
75 83 export const mqttTransportPayloadTypeTranslationMap = new Map<MqttTransportPayloadType, string>(
76 84 [
77 85 [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'],
... ...
... ... @@ -54,7 +54,8 @@
54 54 "share-via": "Share via {{provider}}",
55 55 "continue": "Continue",
56 56 "discard-changes": "Discard Changes",
57   - "download": "Download"
  57 + "download": "Download",
  58 + "next-with-label": "Next: {{label}}"
58 59 },
59 60 "aggregation": {
60 61 "aggregation": "Aggregation",
... ... @@ -760,8 +761,7 @@
760 761 "wizard": {
761 762 "device-wizard": "Device Wizard",
762 763 "device-details": "Device details",
763   - "profile-configuration": "Profile configuration",
764   - "new-device-profile": "New device profile",
  764 + "new-device-profile": "Create new device profile",
765 765 "existing-device-profile": "Select existing device profile",
766 766 "specific-configuration": "Specific configuration",
767 767 "customer-to-assign-device": "Customer to assign the device",
... ... @@ -784,6 +784,8 @@
784 784 "set-default": "Make device profile default",
785 785 "delete": "Delete device profile",
786 786 "copyId": "Copy device profile Id",
  787 + "new-device-profile-name": "Device profile name",
  788 + "new-device-profile-name-required": "Device profile name is required.",
787 789 "name": "Name",
788 790 "name-required": "Name is required.",
789 791 "type": "Profile type",
... ... @@ -792,8 +794,11 @@
792 794 "transport-type": "Transport type",
793 795 "transport-type-required": "Transport type is required.",
794 796 "transport-type-default": "Default",
  797 + "transport-type-default-hint": "Default transport type",
795 798 "transport-type-mqtt": "MQTT",
  799 + "transport-type-mqtt-hint": "MQTT transport type",
796 800 "transport-type-lwm2m": "LWM2M",
  801 + "transport-type-lwm2m-hint": "LWM2M transport type",
797 802 "description": "Description",
798 803 "default": "Default",
799 804 "profile-configuration": "Profile configuration",
... ... @@ -824,7 +829,8 @@
824 829 "not-valid-multi-character": "Invalid use of a multi-level wildcard character",
825 830 "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.",
826 831 "multi-level-wildcards-hint": "<code>[#]</code> can replace the topic filter itself and must be the last symbol of the topic. Ex.: <b>#</b> or <b>v1/devices/me/#</b>.",
827   - "alarm-rules": "Alarm rules ({{count}})",
  832 + "alarm-rules": "Alarm rules",
  833 + "alarm-rules-with-count": "Alarm rules ({{count}})",
828 834 "no-alarm-rules": "No alarm rules configured",
829 835 "add-alarm-rule": "Add alarm rule",
830 836 "edit-alarm-rule": "Edit alarm rule",
... ...