From 3509d8ab418552d6db2b4d879f60b68bb5f267f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Wed, 29 Oct 2025 12:02:09 +0100 Subject: [PATCH] location-detail --- .../app/location/detail/location-detail.html | 130 ++++++++++++++++-- .../app/location/detail/location-detail.ts | 31 ++++- src/main/angular/src/app/series/Series.ts | 26 +++- src/main/angular/src/app/series/SeriesType.ts | 5 + .../src/app/series/select/series-select.html | 18 +-- .../src/app/series/select/series-select.ts | 35 +++-- src/main/angular/src/styles.less | 34 ++++- .../de/ph87/data/series/SeriesService.java | 5 + 8 files changed, 240 insertions(+), 44 deletions(-) create mode 100644 src/main/angular/src/app/series/SeriesType.ts diff --git a/src/main/angular/src/app/location/detail/location-detail.html b/src/main/angular/src/app/location/detail/location-detail.html index fc0a571..03f35e0 100644 --- a/src/main/angular/src/app/location/detail/location-detail.html +++ b/src/main/angular/src/app/location/detail/location-detail.html @@ -1,11 +1,123 @@ @if (location) { - - - - - - - - - + +
+
+
+ Ort +
+
+
+
+
+
+ Name: +
+
+
+ +
+
+
+
+
+ Breitegrad: +
+
+
+ +
+
+
+
+
+ Längengrad: +
+
+
+ +
+
+
+
+ +
+
+
+ Energie +
+
+
+
+
+
+ Bezug: +
+
+
+ +
+
+
+
+
+ Einspeisung: +
+
+
+ +
+
+
+
+
+ Erzeugung: +
+
+
+ +
+
+
+
+ +
+
+
+ Leistung +
+
+
+
+
+
+ Bezug: +
+
+
+ +
+
+
+
+
+ Einspeisung: +
+
+
+ +
+
+
+
+
+ Erzeugung: +
+
+
+ +
+
+
+
+ } diff --git a/src/main/angular/src/app/location/detail/location-detail.ts b/src/main/angular/src/app/location/detail/location-detail.ts index b22eca3..9efeb5a 100644 --- a/src/main/angular/src/app/location/detail/location-detail.ts +++ b/src/main/angular/src/app/location/detail/location-detail.ts @@ -1,12 +1,14 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; import {LocationService} from '../location-service'; import {ActivatedRoute} from '@angular/router'; import {Location} from '../Location'; import {Text} from '../../shared/text/text'; import {Number} from '../../shared/number/number'; -import {SeriesSelect, SeriesType} from '../../series/select/series-select'; +import {SeriesSelect} from '../../series/select/series-select'; import {Series} from '../../series/Series'; import {SeriesService} from '../../series/series-service'; +import {SeriesType} from '../../series/SeriesType'; +import {Subscription, timer} from 'rxjs'; @Component({ selector: 'app-location-detail', @@ -18,11 +20,15 @@ import {SeriesService} from '../../series/series-service'; templateUrl: './location-detail.html', styleUrl: './location-detail.less', }) -export class LocationDetail implements OnInit { +export class LocationDetail implements OnInit, OnDestroy { + + private readonly subs: Subscription [] = []; + + private series: Series[] = []; protected location: Location | null = null; - private series: Series[] = []; + protected now: Date = new Date(); constructor( readonly locationService: LocationService, @@ -37,14 +43,29 @@ export class LocationDetail implements OnInit { this.locationService.getById(params['id'], location => this.location = location); }); this.seriesService.findAll(list => this.series = list); + this.subs.push(this.seriesService.subscribe(this.updateSeries)); + this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date())); } - protected readonly update = (location: Location): void => { + ngOnDestroy(): void { + this.subs.forEach(sub => sub.unsubscribe()); + } + + protected readonly updateLocation = (location: Location): void => { if (this.location?.id === location.id) { this.location = location; } }; + protected readonly updateSeries = (fresh: Series): void => { + const index = this.series.findIndex(series => series.id === fresh.id); + if (index >= 0) { + this.series.splice(index, 1, fresh); + } else { + this.series.push(fresh); + } + }; + protected readonly filterEnergy = (): Series[] => { return this.series.filter(series => series.type === SeriesType.DELTA && series.unit === 'kWh'); }; diff --git a/src/main/angular/src/app/series/Series.ts b/src/main/angular/src/app/series/Series.ts index 0d8c309..ca886e5 100644 --- a/src/main/angular/src/app/series/Series.ts +++ b/src/main/angular/src/app/series/Series.ts @@ -1,24 +1,44 @@ -import {validateEnum, validateNumber, validateString} from "../common"; -import {SeriesType} from "./select/series-select"; +import {or, validateDate, validateEnum, validateNumber, validateString} from "../common"; + +import {SeriesType} from './SeriesType'; +import {formatNumber} from '@angular/common'; export class Series { + readonly valueString: string; + constructor( readonly id: number, readonly name: string, + readonly decimals: number, + readonly seconds: number, + readonly value: number | null, + readonly last: Date | null, readonly unit: string, readonly type: SeriesType, ) { - // + this.valueString = (value === null ? '-' : formatNumber(value, "de-DE", `0.${decimals}-${decimals}`)) + ' ' + unit; } static fromJson(json: any): Series { return new Series( validateNumber(json.id), validateString(json.name), + validateNumber(json.decimals), + validateNumber(json.seconds), + or(json.value, validateNumber, null), + or(json.last, validateDate, null), validateString(json.unit), validateEnum(json.type, SeriesType), ); } + isOld(now: Date): boolean { + if (this.last === null) { + return true; + } + const ageSeconds = (now.getTime() - this.last.getTime()) / 1000; + return ageSeconds > this.seconds * 2.1; + } + } diff --git a/src/main/angular/src/app/series/SeriesType.ts b/src/main/angular/src/app/series/SeriesType.ts new file mode 100644 index 0000000..7fabbdc --- /dev/null +++ b/src/main/angular/src/app/series/SeriesType.ts @@ -0,0 +1,5 @@ +export enum SeriesType { + BOOL = 'BOOL', + DELTA = 'DELTA', + VARYING = 'VARYING', +} diff --git a/src/main/angular/src/app/series/select/series-select.html b/src/main/angular/src/app/series/select/series-select.html index 592c64a..f5e896d 100644 --- a/src/main/angular/src/app/series/select/series-select.html +++ b/src/main/angular/src/app/series/select/series-select.html @@ -4,15 +4,17 @@ (mouseenter)="showPen = true" (mouseleave)="showPen = false" > - - @for (series of list; track series.id) { - + @for (series of series; track series.id) { + } @if (showPen) { diff --git a/src/main/angular/src/app/series/select/series-select.ts b/src/main/angular/src/app/series/select/series-select.ts index 147d1c8..b6cfb2c 100644 --- a/src/main/angular/src/app/series/select/series-select.ts +++ b/src/main/angular/src/app/series/select/series-select.ts @@ -1,4 +1,4 @@ -import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core'; +import {Component, EventEmitter, Input, Output} from '@angular/core'; import {FaIconComponent} from '@fortawesome/angular-fontawesome'; import {FormsModule} from '@angular/forms'; import {NgClass} from '@angular/common'; @@ -6,12 +6,6 @@ import {faPen} from '@fortawesome/free-solid-svg-icons'; import {Series} from '../Series'; import {or} from '../../common'; -export enum SeriesType { - BOOL = 'BOOL', - DELTA = 'DELTA', - VARYING = 'VARYING', -} - @Component({ selector: 'app-series-select', imports: [ @@ -24,16 +18,18 @@ export enum SeriesType { }) export class SeriesSelect { - @ViewChild('input') - protected readonly input!: ElementRef; + protected readonly faPen = faPen; private _initial: Series | null = null; @Input() - protected allowEmpty: boolean = true; + now!: Date; @Input() - list: Series[] = []; + series!: Series[]; + + @Input() + allowEmpty: boolean = true; @Output() readonly onChange = new EventEmitter(); @@ -42,6 +38,8 @@ export class SeriesSelect { protected model: number | null = null; + protected readonly Series = Series; + @Input() set initial(value: Series | null) { this._initial = value; @@ -56,12 +54,6 @@ export class SeriesSelect { return this._initial; } - protected apply() { - if (this.model !== this.initial) { - this.onChange.emit(this.model); - } - } - protected classes(): {} { return { "unchanged": this.model === this.initial, @@ -70,5 +62,12 @@ export class SeriesSelect { }; } - protected readonly faPen = faPen; + protected changed(id: number | null) { + if (this.allowEmpty || id !== null) { + this.onChange.emit(id); + } else { + this.reset(); + } + } + } diff --git a/src/main/angular/src/styles.less b/src/main/angular/src/styles.less index 0b0f230..d109b93 100644 --- a/src/main/angular/src/styles.less +++ b/src/main/angular/src/styles.less @@ -3,7 +3,7 @@ body { height: 100%; margin: 0; font-family: sans-serif; - font-size: 5vw; + font-size: 4vw; } div { @@ -23,3 +23,35 @@ div { .NoUserSelect { user-select: none; } + +.Section { + border: 1px solid gray; + margin: 1em 0.5em 0.5em; + padding: 0.5em; + overflow: visible; + + > .SectionHeading { + display: flex; + margin-top: -1.25em; + + > .SectionHeadingText { + font-weight: bold; + background-color: white; + } + } +} + +.Section2 { + overflow: visible; + > .SectionHeading { + > .SectionHeadingText { + font-style: italic; + } + } +} + +.Section2:not(:last-child) { + border-bottom: 1px solid gray; + padding-bottom: 0.5em; + margin-bottom: 0.5em; +} diff --git a/src/main/java/de/ph87/data/series/SeriesService.java b/src/main/java/de/ph87/data/series/SeriesService.java index 316465c..f219803 100644 --- a/src/main/java/de/ph87/data/series/SeriesService.java +++ b/src/main/java/de/ph87/data/series/SeriesService.java @@ -4,6 +4,7 @@ import de.ph87.data.common.CrudAction; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +21,8 @@ public class SeriesService { private final SeriesRepository seriesRepository; + private final ApplicationEventPublisher applicationEventPublisher; + @NonNull @Transactional public SeriesDto create() { @@ -60,6 +63,7 @@ public class SeriesService { private SeriesDto publish(@NonNull final Series series, @NonNull final CrudAction action) { final SeriesDto dto = new SeriesDto(series); log.info("{} {}: {}", Series.class.getSimpleName(), action, dto); + applicationEventPublisher.publishEvent(dto); return dto; } @@ -68,6 +72,7 @@ public class SeriesService { public Series update(final long seriesId, @NonNull final ZonedDateTime date, final double value) { final Series series = getById(seriesId); series.update(date, value); + applicationEventPublisher.publishEvent(new SeriesDto(series)); return series; }