diff --git a/src/main/angular/src/app/api/Area/Area.ts b/src/main/angular/src/app/api/Area/Area.ts
new file mode 100644
index 0000000..6577d6f
--- /dev/null
+++ b/src/main/angular/src/app/api/Area/Area.ts
@@ -0,0 +1,25 @@
+import {validateString} from "../common/validators";
+
+export class Area {
+
+ constructor(
+ readonly uuid: string,
+ readonly slug: string,
+ readonly name: string,
+ ) {
+ //
+ }
+
+ static fromJson(json: any): Area {
+ return new Area(
+ validateString(json.uuid),
+ validateString(json.slug),
+ validateString(json.name),
+ );
+ }
+
+ static compareByName(a: Area, b: Area): number {
+ return a.name.localeCompare(b.name);
+ }
+
+}
diff --git a/src/main/angular/src/app/api/Device/Device.ts b/src/main/angular/src/app/api/Device/Device.ts
index 1defc1a..63bd259 100644
--- a/src/main/angular/src/app/api/Device/Device.ts
+++ b/src/main/angular/src/app/api/Device/Device.ts
@@ -1,9 +1,11 @@
import {Property} from "../Property/Property";
import {orNull, validateString} from "../common/validators";
+import {Area} from '../Area/Area';
export class Device {
constructor(
+ readonly area: Area,
readonly uuid: string,
readonly name: string,
readonly slug: string,
@@ -15,6 +17,7 @@ export class Device {
static fromJson(json: any): Device {
return new Device(
+ Area.fromJson(json.area),
validateString(json.uuid),
validateString(json.name),
validateString(json.slug),
@@ -23,6 +26,20 @@ export class Device {
);
}
+ get nameOrArea(): string {
+ if (this.name === '') {
+ return this.area.name;
+ }
+ return this.name;
+ }
+
+ get nameWithArea(): string {
+ if (this.name === '') {
+ return this.area.name;
+ }
+ return this.area.name + ' ' + this.name;
+ }
+
static trackBy(index: number, device: Device) {
return device.uuid;
}
@@ -31,4 +48,12 @@ export class Device {
return a.uuid === b.uuid;
}
+ static compareByAreaThenName(a: Device, b: Device): number {
+ const area = Area.compareByName(a.area, b.area);
+ if (area !== 0) {
+ return area;
+ }
+ return a.name.localeCompare(b.name);
+ }
+
}
diff --git a/src/main/angular/src/app/api/Group/Group.ts b/src/main/angular/src/app/api/Group/Group.ts
index 43a2828..eab309b 100644
--- a/src/main/angular/src/app/api/Group/Group.ts
+++ b/src/main/angular/src/app/api/Group/Group.ts
@@ -29,5 +29,9 @@ export class Group {
return group.address;
}
+ static compareByName(a: Group, b: Group): number {
+ return a.name.localeCompare(b.name);
+ }
+
}
diff --git a/src/main/angular/src/app/api/Shutter/Shutter.ts b/src/main/angular/src/app/api/Shutter/Shutter.ts
index 1197e4e..9a2946e 100644
--- a/src/main/angular/src/app/api/Shutter/Shutter.ts
+++ b/src/main/angular/src/app/api/Shutter/Shutter.ts
@@ -1,9 +1,12 @@
import {Property} from "../Property/Property";
import {orNull, validateString} from "../common/validators";
+import {Area} from '../Area/Area';
+
export class Shutter {
constructor(
+ readonly area: Area,
readonly uuid: string,
readonly name: string,
readonly slug: string,
@@ -15,6 +18,7 @@ export class Shutter {
static fromJson(json: any): Shutter {
return new Shutter(
+ Area.fromJson(json.area),
validateString(json.uuid),
validateString(json.name),
validateString(json.slug),
@@ -23,6 +27,20 @@ export class Shutter {
);
}
+ get nameOrArea(): string {
+ if (this.name === '') {
+ return this.area.name;
+ }
+ return this.name;
+ }
+
+ get nameWithArea(): string {
+ if (this.name === '') {
+ return this.area.name;
+ }
+ return this.area.name + ' ' + this.name;
+ }
+
static trackBy(index: number, shutter: Shutter) {
return shutter.uuid;
}
@@ -31,4 +49,12 @@ export class Shutter {
return a.uuid === b.uuid;
}
+ static compareByAreaThenName(a: Shutter, b: Shutter): number {
+ const area = Area.compareByName(a.area, b.area);
+ if (area !== 0) {
+ return area;
+ }
+ return a.name.localeCompare(b.name);
+ }
+
}
diff --git a/src/main/angular/src/app/api/Tunable/Tunable.ts b/src/main/angular/src/app/api/Tunable/Tunable.ts
index 268d2cd..f5a48cd 100644
--- a/src/main/angular/src/app/api/Tunable/Tunable.ts
+++ b/src/main/angular/src/app/api/Tunable/Tunable.ts
@@ -1,9 +1,11 @@
import {Property} from "../Property/Property";
import {orNull, validateString} from "../common/validators";
+import {Area} from '../Area/Area';
export class Tunable {
constructor(
+ readonly area: Area,
readonly uuid: string,
readonly name: string,
readonly slug: string,
@@ -19,6 +21,7 @@ export class Tunable {
static fromJson(json: any): Tunable {
return new Tunable(
+ Area.fromJson(json.area),
validateString(json.uuid),
validateString(json.name),
validateString(json.slug),
@@ -31,6 +34,20 @@ export class Tunable {
);
}
+ get nameOrArea(): string {
+ if (this.name === '') {
+ return this.area.name;
+ }
+ return this.name;
+ }
+
+ get nameWithArea(): string {
+ if (this.name === '') {
+ return this.area.name;
+ }
+ return this.area.name + ' ' + this.name;
+ }
+
static trackBy(index: number, tunable: Tunable) {
return tunable.uuid;
}
@@ -39,4 +56,12 @@ export class Tunable {
return a.uuid === b.uuid;
}
+ static compareByAreaThenName(a: Tunable, b: Tunable): number {
+ const area = Area.compareByName(a.area, b.area);
+ if (area !== 0) {
+ return area;
+ }
+ return a.name.localeCompare(b.name);
+ }
+
}
diff --git a/src/main/angular/src/app/shared/device-list/device-list.component.html b/src/main/angular/src/app/shared/device-list/device-list.component.html
index ea4b1b1..d018b0f 100644
--- a/src/main/angular/src/app/shared/device-list/device-list.component.html
+++ b/src/main/angular/src/app/shared/device-list/device-list.component.html
@@ -1,11 +1,11 @@
-
+
- {{ device.name }}
+ {{ device.nameWithArea }}
diff --git a/src/main/angular/src/app/shared/device-list/device-list.component.ts b/src/main/angular/src/app/shared/device-list/device-list.component.ts
index 0fe00af..2e452f7 100644
--- a/src/main/angular/src/app/shared/device-list/device-list.component.ts
+++ b/src/main/angular/src/app/shared/device-list/device-list.component.ts
@@ -49,4 +49,8 @@ export class DeviceListComponent implements OnInit, OnDestroy {
};
}
+ sorted(): Device[] {
+ return this.list.sort(Device.compareByAreaThenName);
+ }
+
}
diff --git a/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.html b/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.html
index aacb0db..2bb9b8d 100644
--- a/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.html
+++ b/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.html
@@ -1,6 +1,6 @@
-
+
diff --git a/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.ts b/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.ts
index f220798..ff5a601 100644
--- a/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.ts
+++ b/src/main/angular/src/app/shared/knx-group-list/knx-group-list.component.ts
@@ -42,4 +42,8 @@ export class KnxGroupListComponent implements OnInit, OnDestroy {
this.subs.forEach(sub => sub.unsubscribe());
}
+ sorted(): Group[] {
+ return this.groupList.sort(Group.compareByName);
+ }
+
}
diff --git a/src/main/angular/src/app/shared/shutter-list/shutter-list.component.html b/src/main/angular/src/app/shared/shutter-list/shutter-list.component.html
index afca00c..211de5e 100644
--- a/src/main/angular/src/app/shared/shutter-list/shutter-list.component.html
+++ b/src/main/angular/src/app/shared/shutter-list/shutter-list.component.html
@@ -1,11 +1,11 @@
-
+
- {{ shutter.name }}
+ {{ shutter.nameWithArea }}
diff --git a/src/main/angular/src/app/shared/shutter-list/shutter-list.component.ts b/src/main/angular/src/app/shared/shutter-list/shutter-list.component.ts
index a5a4bd4..29e0c40 100644
--- a/src/main/angular/src/app/shared/shutter-list/shutter-list.component.ts
+++ b/src/main/angular/src/app/shared/shutter-list/shutter-list.component.ts
@@ -43,4 +43,8 @@ export class ShutterListComponent implements OnInit, OnDestroy {
this.subs.forEach(sub => sub.unsubscribe());
}
+ sorted(): Shutter[] {
+ return this.list.sort(Shutter.compareByAreaThenName);
+ }
+
}
diff --git a/src/main/angular/src/app/shared/tunable-list/tunable-list.component.html b/src/main/angular/src/app/shared/tunable-list/tunable-list.component.html
index 62cb35d..e4afa1c 100644
--- a/src/main/angular/src/app/shared/tunable-list/tunable-list.component.html
+++ b/src/main/angular/src/app/shared/tunable-list/tunable-list.component.html
@@ -1,11 +1,11 @@
-
+
- {{ tunable.name }}
+ {{ tunable.nameWithArea }}
diff --git a/src/main/angular/src/app/shared/tunable-list/tunable-list.component.ts b/src/main/angular/src/app/shared/tunable-list/tunable-list.component.ts
index 28ce9e6..ad2f504 100644
--- a/src/main/angular/src/app/shared/tunable-list/tunable-list.component.ts
+++ b/src/main/angular/src/app/shared/tunable-list/tunable-list.component.ts
@@ -52,4 +52,8 @@ export class TunableListComponent implements OnInit, OnDestroy {
};
}
+ sorted(): Tunable[] {
+ return this.list.sort(Tunable.compareByAreaThenName);
+ }
+
}
diff --git a/src/main/java/de/ph87/home/area/Area.java b/src/main/java/de/ph87/home/area/Area.java
new file mode 100644
index 0000000..8c397eb
--- /dev/null
+++ b/src/main/java/de/ph87/home/area/Area.java
@@ -0,0 +1,36 @@
+package de.ph87.home.area;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.ToString;
+
+import java.util.UUID;
+
+@Entity
+@Getter
+@ToString
+@NoArgsConstructor
+public class Area {
+
+ @Id
+ @NonNull
+ private String uuid = UUID.randomUUID().toString();
+
+ @NonNull
+ @Column(nullable = false)
+ private String name;
+
+ @NonNull
+ @Column(nullable = false, unique = true)
+ private String slug;
+
+ public Area(@NonNull final String name, @NonNull final String slug) {
+ this.name = name;
+ this.slug = slug;
+ }
+
+}
diff --git a/src/main/java/de/ph87/home/area/AreaController.java b/src/main/java/de/ph87/home/area/AreaController.java
new file mode 100644
index 0000000..7d2d362
--- /dev/null
+++ b/src/main/java/de/ph87/home/area/AreaController.java
@@ -0,0 +1,44 @@
+package de.ph87.home.area;
+
+import de.ph87.home.property.PropertyTypeMismatch;
+import jakarta.annotation.Nullable;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import tuwien.auto.calimero.KNXFormatException;
+
+import java.util.List;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("Area")
+public class AreaController {
+
+ private final AreaService areaService;
+
+ @NonNull
+ @GetMapping("getByUuid/{id}")
+ @ExceptionHandler(KNXFormatException.class)
+ private AreaDto getByUuid(@PathVariable final String id, @NonNull final HttpServletRequest request) {
+ log.debug("getByUuid: path={}", request.getServletPath());
+ return areaService.getByUuidDto(id);
+ }
+
+ @NonNull
+ @RequestMapping(value = "list", method = {RequestMethod.GET, RequestMethod.POST})
+ private List
list(@RequestBody(required = false) @Nullable final AreaFilter filter, @NonNull final HttpServletRequest request) throws PropertyTypeMismatch {
+ log.debug("list: path={} filter={}", request.getServletPath(), filter);
+ return areaService.list(filter);
+ }
+
+ @NonNull
+ @GetMapping("get/{uuidOrSlug}")
+ private AreaDto get(@PathVariable @NonNull final String uuidOrSlug, @NonNull final HttpServletRequest request) {
+ log.debug("get: path={}", request.getServletPath());
+ return areaService.getByUuidOrSlugDto(uuidOrSlug);
+ }
+
+}
diff --git a/src/main/java/de/ph87/home/area/AreaDto.java b/src/main/java/de/ph87/home/area/AreaDto.java
new file mode 100644
index 0000000..8d3c337
--- /dev/null
+++ b/src/main/java/de/ph87/home/area/AreaDto.java
@@ -0,0 +1,32 @@
+package de.ph87.home.area;
+
+import de.ph87.home.web.IWebSocketMessage;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.ToString;
+
+import java.util.List;
+
+@Getter
+@ToString
+public class AreaDto implements IWebSocketMessage {
+
+ @ToString.Exclude
+ private final List