Skip to content

Commit b651829

Browse files
Feature/iot 118 move device (#176)
* Added change button for application on devices. Dialog is created, and application, device modell ,organization selectors are up and running. Data targets and payload decoders is a work in progress. * Added default values, and made datatarget/payload decoders work. * Added correct translation, and check for null values in data targets. * added padding to dialog, and reload when dialog dismissed. * PR changes --------- Co-authored-by: Frederik Christ Vestergaard <[email protected]>
1 parent 6d8cd1f commit b651829

File tree

14 files changed

+433
-29
lines changed

14 files changed

+433
-29
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<div class="iot-device-change-application-dialog">
2+
<h1 mat-dialog-title>{{ "IOTDEVICE.CHANGE-APPLICATION.TITLE" | translate }}</h1>
3+
<div mat-dialog-content>
4+
<label class="form-label" for="organizationSelect">{{
5+
"IOTDEVICE.CHANGE-APPLICATION.CHOOSE-ORGANIZATION" | translate
6+
}}</label>
7+
<mat-select
8+
(selectionChange)="onOrganizationChange()"
9+
[(value)]="iotDeviceUpdate.organizationId"
10+
[compareWith]="compare"
11+
class="form-control"
12+
id="organizationSelect"
13+
panelClass="overflow-x-hidden"
14+
>
15+
<mat-option *ngFor="let organization of organizations | async" [value]="organization.id">
16+
{{ organization.name }}
17+
</mat-option>
18+
</mat-select>
19+
<label class="form-label" for="organizationSelect">{{
20+
"IOTDEVICE.CHANGE-APPLICATION.CHOOSE-APPLICATION" | translate
21+
}}</label>
22+
<mat-select
23+
(selectionChange)="onApplicationChange()"
24+
[(value)]="iotDeviceUpdate.applicationId"
25+
[compareWith]="compare"
26+
class="form-control"
27+
id="applicationSelect"
28+
panelClass="overflow-x-hidden"
29+
>
30+
<mat-option *ngFor="let application of filteredApplications | async" [value]="application.id">
31+
{{ application.name }}
32+
</mat-option>
33+
</mat-select>
34+
<label class="form-label" for="organizationSelect">{{
35+
"IOTDEVICE.CHANGE-APPLICATION.CHOOSE-DEVICE-MODEL" | translate
36+
}}</label>
37+
<mat-select
38+
[(value)]="iotDeviceUpdate.deviceModelId"
39+
[compareWith]="compare"
40+
class="form-control"
41+
id="deviceModelSelect"
42+
panelClass="overflow-x-hidden"
43+
>
44+
<mat-option [value]="0">
45+
{{ "QUESTION.DEVICE-MODEL-SELECT-NON" | translate }}
46+
</mat-option>
47+
<mat-option *ngFor="let deviceModel of deviceModels" [value]="deviceModel.id">
48+
{{ deviceModel.body.name }}
49+
</mat-option>
50+
</mat-select>
51+
</div>
52+
53+
<div class="container row" class="mt-3" mat-dialog-content>
54+
<a (click)="addRow()" class="btn btn-secondary my-2 mb-3 mt-3">{{
55+
"IOTDEVICE.CHANGE-APPLICATION.CHOOSE-DATA-TARGET" | translate
56+
}}</a>
57+
<ng-container *ngIf="payloadDecoders && iotDeviceUpdate.dataTargetToPayloadDecoderIds?.length > 0">
58+
<table class="table table-striped table-bordered">
59+
<tbody>
60+
<tr
61+
*ngFor="let element of iotDeviceUpdate.dataTargetToPayloadDecoderIds; let i = index"
62+
[attr.data-index]="i"
63+
>
64+
<td>
65+
<div class="row">
66+
<mat-form-field appearance="fill">
67+
<mat-label>{{ "IOTDEVICE.CHANGE-APPLICATION.ADD-DATA-TARGET" | translate }}</mat-label>
68+
<mat-select [(value)]="element.dataTargetId" name="dataTarget">
69+
<mat-option *ngFor="let dataTarget of dataTargets" [value]="dataTarget.id">{{
70+
dataTarget.name
71+
}}
72+
</mat-option>
73+
</mat-select>
74+
</mat-form-field>
75+
</div>
76+
</td>
77+
<td>
78+
<div class="row">
79+
<mat-form-field appearance="fill">
80+
<mat-label>{{ "QUESTION.DATATARGET.SELECT-PAYLOADDECODER" | translate }}</mat-label>
81+
<mat-select [(value)]="element.payloadDecoderId" matNativeControl name="payloadDecoder">
82+
<mat-option [value]="0">
83+
{{ "QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED" | translate }}
84+
</mat-option>
85+
<mat-option *ngFor="let payloadDecoder of payloadDecoders" [value]="payloadDecoder.id">
86+
{{ payloadDecoder.name }}
87+
</mat-option>
88+
</mat-select>
89+
</mat-form-field>
90+
</div>
91+
</td>
92+
<td>
93+
<a (click)="deleteRow(i)">
94+
<div class="text-center m-2">
95+
<fa-icon [icon]="faTimesCircle"></fa-icon>
96+
<p>{{ "DATATARGET.DELETE" | translate }}</p>
97+
</div>
98+
</a>
99+
</td>
100+
</tr>
101+
</tbody>
102+
</table>
103+
</ng-container>
104+
</div>
105+
106+
<div class="d-flex flex-row mat-dialog-actions" mat-dialog-actions>
107+
<button (click)="onSubmit()" class="btn btn-primary">
108+
{{ "GEN.SAVE" | translate }}
109+
</button>
110+
<button [mat-dialog-close]="false" class="btn btn-secondary ml-2" mat-dialog-close>
111+
{{ "GEN.CANCEL" | translate }}
112+
</button>
113+
</div>
114+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.iot-device-change-application-dialog {
2+
width: 50vw;
3+
}
4+
5+
.mat-dialog-actions {
6+
margin-left: 13px;
7+
margin-bottom: 13px;
8+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
2+
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
3+
import { MatSnackBar } from "@angular/material/snack-bar";
4+
import { Organisation } from "@app/admin/organisation/organisation.model";
5+
import { OrganisationService } from "@app/admin/organisation/organisation.service";
6+
import { DeviceModelService } from "@app/device-model/device-model.service";
7+
import { DeviceModel } from "@app/device-model/device.model";
8+
import { Application } from "@applications/application.model";
9+
import { ApplicationService } from "@applications/application.service";
10+
import { TranslateService } from "@ngx-translate/core";
11+
import { IoTDeviceApplicationDialogModel } from "@shared/models/dialog.model";
12+
import { ReplaySubject } from "rxjs";
13+
import { IotDevice, UpdateIoTDeviceApplication } from "../iot-device.model";
14+
import { IoTDeviceService } from "../iot-device.service";
15+
import { PayloadDecoder, PayloadDecoderMappedResponse } from "@payload-decoder/payload-decoder.model";
16+
import { PayloadDecoderService } from "@payload-decoder/payload-decoder.service";
17+
import { DatatargetService } from "@applications/datatarget/datatarget.service";
18+
import { Datatarget } from "@applications/datatarget/datatarget.model";
19+
import { PayloadDeviceDatatargetService } from "@payload-decoder/payload-device-datatarget.service";
20+
import { faTimesCircle } from "@fortawesome/free-solid-svg-icons";
21+
22+
@Component({
23+
selector: "app-iot-device-change-application-dialog",
24+
templateUrl: "./iot-device-change-application-dialog.component.html",
25+
styleUrls: ["./iot-device-change-application-dialog.component.scss"],
26+
})
27+
export class IoTDeviceChangeApplicationDialogComponent implements OnInit, OnDestroy {
28+
public iotDevice: IotDevice;
29+
public iotDeviceUpdate: UpdateIoTDeviceApplication;
30+
public organizations: ReplaySubject<Organisation[]> = new ReplaySubject<Organisation[]>(1);
31+
public applications: Application[];
32+
public filteredApplications: ReplaySubject<Application[]> = new ReplaySubject<Application[]>(1);
33+
public deviceModels: DeviceModel[];
34+
public devices: IotDevice[] = [];
35+
public payloadDecoders: PayloadDecoder[] = [];
36+
public dataTargets: Datatarget[] = [];
37+
faTimesCircle = faTimesCircle;
38+
private subscriptions = [];
39+
40+
constructor(
41+
private iotDeviceService: IoTDeviceService,
42+
private applicationService: ApplicationService,
43+
public translate: TranslateService,
44+
private organizationService: OrganisationService,
45+
private deviceModelService: DeviceModelService,
46+
private payloadDecoderService: PayloadDecoderService,
47+
private dateTargetService: DatatargetService,
48+
private payloadDeviceDatatargetService: PayloadDeviceDatatargetService,
49+
private snackBar: MatSnackBar,
50+
private dialog: MatDialogRef<IoTDeviceChangeApplicationDialogComponent>,
51+
@Inject(MAT_DIALOG_DATA) public dialogModel: IoTDeviceApplicationDialogModel
52+
) {}
53+
54+
ngOnInit(): void {
55+
this.translate.use("da");
56+
this.iotDeviceUpdate = {
57+
deviceModelId: this.dialogModel.deviceId,
58+
applicationId: 0,
59+
organizationId: 0,
60+
dataTargetToPayloadDecoderIds: [],
61+
};
62+
63+
this.getIoTDeviceAndDefaultData(this.dialogModel.deviceId);
64+
}
65+
66+
ngOnDestroy(): void {
67+
this.subscriptions.forEach(s => s?.unsubscribe());
68+
}
69+
70+
getIoTDeviceAndDefaultData(id: number): void {
71+
const iotDevicesSubscription = this.iotDeviceService.getIoTDevice(id).subscribe((iotDevice: IotDevice) => {
72+
this.iotDevice = iotDevice;
73+
this.getOrganizations();
74+
this.getApplications();
75+
this.getPayloadDecoders();
76+
77+
this.getDataTargets(this.iotDevice.application.id);
78+
this.getDeviceModels(this.iotDevice.application.belongsTo.id);
79+
80+
this.iotDeviceUpdate.deviceModelId = this.iotDevice.deviceModel.id;
81+
this.iotDeviceUpdate.applicationId = this.iotDevice.application.id;
82+
this.iotDeviceUpdate.organizationId = this.iotDevice.application.belongsTo.id;
83+
84+
this.getIoTDeviceCurrentDataTargetsAndPayloadDecoders(this.dialogModel.deviceId);
85+
});
86+
87+
this.subscriptions.push(iotDevicesSubscription);
88+
}
89+
90+
getIoTDeviceCurrentDataTargetsAndPayloadDecoders(id: number): void {
91+
const payloadDeviceDatatargetSubscription = this.payloadDeviceDatatargetService
92+
.getByIoTDevice(id)
93+
.subscribe(dataTargetsAndPayloadDecoders => {
94+
const dataTargetsAndPayloadIds = dataTargetsAndPayloadDecoders.data.map(val => {
95+
return { dataTargetId: val.dataTarget.id, payloadDecoderId: val.payloadDecoder?.id };
96+
});
97+
this.iotDeviceUpdate.dataTargetToPayloadDecoderIds.push(...dataTargetsAndPayloadIds);
98+
});
99+
100+
this.subscriptions.push(payloadDeviceDatatargetSubscription);
101+
}
102+
103+
getOrganizations() {
104+
const organizationsSubscription = this.organizationService.getMultipleWithApplicationAdmin().subscribe(res => {
105+
this.organizations.next(res.data.slice());
106+
});
107+
108+
this.subscriptions.push(organizationsSubscription);
109+
}
110+
111+
getApplications(): void {
112+
const applicationsSubscription = this.applicationService
113+
.getApplications(1000, 0, "asc", "id")
114+
.subscribe(applicationData => {
115+
this.applications = applicationData.data;
116+
this.filteredApplications.next(
117+
this.applications.filter(app => app.belongsTo.id === this.iotDevice.application.belongsTo.id)
118+
);
119+
});
120+
121+
this.subscriptions.push(applicationsSubscription);
122+
}
123+
124+
getPayloadDecoders() {
125+
const payloadDecoderSubscription = this.payloadDecoderService
126+
.getMultiple(1000, 0, "id", "ASC")
127+
.subscribe((response: PayloadDecoderMappedResponse) => {
128+
this.payloadDecoders = response.data.sort((a, b) => a.name.localeCompare(b.name, "en", { numeric: true }));
129+
});
130+
131+
this.subscriptions.push(payloadDecoderSubscription);
132+
}
133+
134+
getDataTargets(applicationId: number) {
135+
const dataTargetSubscription = this.dateTargetService
136+
.getByApplicationId(1000, 0, applicationId)
137+
.subscribe(response => {
138+
this.dataTargets = response.data;
139+
});
140+
141+
this.subscriptions.push(dataTargetSubscription);
142+
}
143+
144+
getDeviceModels(organizationId: number) {
145+
const deviceModelSubscription = this.deviceModelService
146+
.getMultiple(1000, 0, "asc", "id", organizationId)
147+
.subscribe(res => {
148+
this.deviceModels = res.data.sort((a, b) => a.body.name.localeCompare(b.body.name, "en", { numeric: true }));
149+
});
150+
151+
this.subscriptions.push(deviceModelSubscription);
152+
}
153+
154+
public addRow() {
155+
this.iotDeviceUpdate.dataTargetToPayloadDecoderIds.push({
156+
dataTargetId: null,
157+
payloadDecoderId: null,
158+
});
159+
}
160+
161+
public deleteRow(index) {
162+
this.iotDeviceUpdate.dataTargetToPayloadDecoderIds.splice(index, 1);
163+
}
164+
165+
onOrganizationChange() {
166+
this.filteredApplications.next(
167+
this.applications.filter(app => app.belongsTo.id === this.iotDeviceUpdate.organizationId)
168+
);
169+
this.iotDeviceUpdate.applicationId = 0;
170+
this.iotDeviceUpdate.deviceModelId = 0;
171+
this.iotDeviceUpdate.dataTargetToPayloadDecoderIds = [];
172+
this.dataTargets = [];
173+
this.getDeviceModels(this.iotDeviceUpdate.organizationId);
174+
}
175+
176+
onApplicationChange() {
177+
this.getDataTargets(this.iotDeviceUpdate.applicationId);
178+
this.iotDeviceUpdate.dataTargetToPayloadDecoderIds = [];
179+
}
180+
181+
public compare(o1: any, o2: any): boolean {
182+
return o1 === o2;
183+
}
184+
185+
onSubmit() {
186+
if (
187+
!this.iotDeviceUpdate.applicationId ||
188+
!this.iotDeviceUpdate.organizationId ||
189+
this.iotDeviceUpdate.deviceModelId == null ||
190+
this.iotDeviceUpdate.dataTargetToPayloadDecoderIds.some(val => !val.dataTargetId)
191+
)
192+
return;
193+
194+
const iotDevicesSubscription = this.iotDeviceService
195+
.changeIoTDeviceApplication(this.iotDevice.id, this.iotDeviceUpdate)
196+
.subscribe(savedIoTDevice => {
197+
this.snackBar.open(
198+
this.translate.instant("IOTDEVICE.CHANGE-APPLICATION.SNACKBAR-SAVED", {
199+
deviceName: savedIoTDevice.name,
200+
applicationName: savedIoTDevice.application.name,
201+
}),
202+
this.translate.instant("DIALOG.OK"),
203+
{
204+
duration: 10000,
205+
}
206+
);
207+
this.dialog.close(true);
208+
this.snackBar._openedSnackBarRef.afterDismissed().subscribe(() => location.reload());
209+
});
210+
211+
this.subscriptions.push(iotDevicesSubscription);
212+
}
213+
}

0 commit comments

Comments
 (0)