SeriesHistory
This commit is contained in:
parent
3509d8ab41
commit
03ad1615d2
@ -0,0 +1,33 @@
|
|||||||
|
<div class="Section3">
|
||||||
|
<div class="SectionHeading">
|
||||||
|
<div class="SectionHeadingText">
|
||||||
|
{{ heading }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="SectionBody">
|
||||||
|
<div class="Section4">
|
||||||
|
<div class="SectionHeadingText">
|
||||||
|
Bezogen
|
||||||
|
</div>
|
||||||
|
<div class="SectionBody">
|
||||||
|
{{ historyEnergyPurchase?.valueString }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="Section4">
|
||||||
|
<div class="SectionHeadingText">
|
||||||
|
Eingespeist
|
||||||
|
</div>
|
||||||
|
<div class="SectionBody">
|
||||||
|
{{ historyEnergyDeliver?.valueString }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="Section4">
|
||||||
|
<div class="SectionHeadingText">
|
||||||
|
Erzeugt
|
||||||
|
</div>
|
||||||
|
<div class="SectionBody">
|
||||||
|
{{ historyEnergyProduce?.valueString }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
import {AfterViewInit, Component, Input} from '@angular/core';
|
||||||
|
import {History} from '../../../series/History';
|
||||||
|
import {Location} from '../../Location';
|
||||||
|
import {Series} from '../../../series/Series';
|
||||||
|
import {Interval, SeriesService} from '../../../series/series-service';
|
||||||
|
import {Next} from '../../../common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-series-history',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './series-history.html',
|
||||||
|
styleUrl: './series-history.less',
|
||||||
|
})
|
||||||
|
export class SeriesHistory implements AfterViewInit {
|
||||||
|
|
||||||
|
protected historyEnergyPurchase: History | null = null;
|
||||||
|
|
||||||
|
protected historyEnergyDeliver: History | null = null;
|
||||||
|
|
||||||
|
protected historyEnergyProduce: History | null = null;
|
||||||
|
|
||||||
|
protected readonly Interval = Interval;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
heading!: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
date!: Date;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
interval!: Interval;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
location!: Location;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly seriesService: SeriesService,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.history(this.location?.energyPurchase, history => this.historyEnergyPurchase = history);
|
||||||
|
this.history(this.location?.energyDeliver, history => this.historyEnergyDeliver = history);
|
||||||
|
this.history(this.location?.energyProduce, history => this.historyEnergyProduce = history);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly updateSeries = (fresh: Series): void => {
|
||||||
|
if (fresh.id === this.location?.energyPurchase?.id) {
|
||||||
|
this.history(this.location?.energyPurchase, history => this.historyEnergyPurchase = history);
|
||||||
|
}
|
||||||
|
if (fresh.id === this.location?.energyDeliver?.id) {
|
||||||
|
this.history(this.location?.energyDeliver, history => this.historyEnergyDeliver = history);
|
||||||
|
}
|
||||||
|
if (fresh.id === this.location?.energyProduce?.id) {
|
||||||
|
this.history(this.location?.energyProduce, history => this.historyEnergyProduce = history);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private history(series: Series | null | undefined, next: Next<History | null>) {
|
||||||
|
if (!series || !this.interval) {
|
||||||
|
next(null);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.seriesService.history(series, this.date, this.interval, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,9 @@
|
|||||||
@if (location) {
|
@if (location) {
|
||||||
|
|
||||||
|
<app-series-history [location]="location" [interval]="Interval.DAY" [date]="now" heading="Heute" #today></app-series-history>
|
||||||
|
|
||||||
|
<app-series-history [location]="location" [interval]="Interval.DAY" [date]="yesterday" heading="Gestern"></app-series-history>
|
||||||
|
|
||||||
<div class="Section">
|
<div class="Section">
|
||||||
<div class="SectionHeading">
|
<div class="SectionHeading">
|
||||||
<div class="SectionHeadingText">
|
<div class="SectionHeadingText">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||||
import {LocationService} from '../location-service';
|
import {LocationService} from '../location-service';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
import {Location} from '../Location';
|
import {Location} from '../Location';
|
||||||
@ -6,30 +6,45 @@ import {Text} from '../../shared/text/text';
|
|||||||
import {Number} from '../../shared/number/number';
|
import {Number} from '../../shared/number/number';
|
||||||
import {SeriesSelect} from '../../series/select/series-select';
|
import {SeriesSelect} from '../../series/select/series-select';
|
||||||
import {Series} from '../../series/Series';
|
import {Series} from '../../series/Series';
|
||||||
import {SeriesService} from '../../series/series-service';
|
import {Interval, SeriesService} from '../../series/series-service';
|
||||||
import {SeriesType} from '../../series/SeriesType';
|
import {SeriesType} from '../../series/SeriesType';
|
||||||
import {Subscription, timer} from 'rxjs';
|
import {Subscription, timer} from 'rxjs';
|
||||||
|
import {SeriesHistory} from './history/series-history';
|
||||||
|
|
||||||
|
function yesterday(now: any) {
|
||||||
|
const yesterday = new Date(now.getTime());
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
return yesterday;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-location-detail',
|
selector: 'app-location-detail',
|
||||||
imports: [
|
imports: [
|
||||||
Text,
|
Text,
|
||||||
Number,
|
Number,
|
||||||
SeriesSelect
|
SeriesSelect,
|
||||||
|
SeriesHistory
|
||||||
],
|
],
|
||||||
templateUrl: './location-detail.html',
|
templateUrl: './location-detail.html',
|
||||||
styleUrl: './location-detail.less',
|
styleUrl: './location-detail.less',
|
||||||
})
|
})
|
||||||
export class LocationDetail implements OnInit, OnDestroy {
|
export class LocationDetail implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@ViewChild("today")
|
||||||
|
protected today!: SeriesHistory;
|
||||||
|
|
||||||
|
protected readonly Interval = Interval;
|
||||||
|
|
||||||
|
protected location: Location | null = null;
|
||||||
|
|
||||||
private readonly subs: Subscription [] = [];
|
private readonly subs: Subscription [] = [];
|
||||||
|
|
||||||
private series: Series[] = [];
|
private series: Series[] = [];
|
||||||
|
|
||||||
protected location: Location | null = null;
|
|
||||||
|
|
||||||
protected now: Date = new Date();
|
protected now: Date = new Date();
|
||||||
|
|
||||||
|
protected yesterday: Date = yesterday(this.now);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly locationService: LocationService,
|
readonly locationService: LocationService,
|
||||||
readonly seriesService: SeriesService,
|
readonly seriesService: SeriesService,
|
||||||
@ -44,7 +59,10 @@ export class LocationDetail implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.seriesService.findAll(list => this.series = list);
|
this.seriesService.findAll(list => this.series = list);
|
||||||
this.subs.push(this.seriesService.subscribe(this.updateSeries));
|
this.subs.push(this.seriesService.subscribe(this.updateSeries));
|
||||||
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
this.subs.push(timer(1000, 1000).subscribe(() => {
|
||||||
|
this.now = new Date();
|
||||||
|
this.yesterday = yesterday(this.now);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@ -64,6 +82,7 @@ export class LocationDetail implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.series.push(fresh);
|
this.series.push(fresh);
|
||||||
}
|
}
|
||||||
|
this.today.updateSeries(fresh);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected readonly filterEnergy = (): Series[] => {
|
protected readonly filterEnergy = (): Series[] => {
|
||||||
|
|||||||
22
src/main/angular/src/app/series/History.ts
Normal file
22
src/main/angular/src/app/series/History.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {getValueString, Series} from "./Series";
|
||||||
|
import {or, validateNumber} from "../common";
|
||||||
|
|
||||||
|
export class History {
|
||||||
|
|
||||||
|
readonly valueString: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly series: Series,
|
||||||
|
readonly value: number | null,
|
||||||
|
) {
|
||||||
|
this.valueString = getValueString(value, series);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json: any): History {
|
||||||
|
return new History(
|
||||||
|
Series.fromJson(json.series),
|
||||||
|
or(json.value, validateNumber, null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -3,6 +3,10 @@ import {or, validateDate, validateEnum, validateNumber, validateString} from "..
|
|||||||
import {SeriesType} from './SeriesType';
|
import {SeriesType} from './SeriesType';
|
||||||
import {formatNumber} from '@angular/common';
|
import {formatNumber} from '@angular/common';
|
||||||
|
|
||||||
|
export function getValueString(value: number | null, series: Series): string {
|
||||||
|
return (value === null ? '-' : formatNumber(value, "de-DE", `0.${series.decimals}-${series.decimals}`)) + ' ' + series.unit;
|
||||||
|
}
|
||||||
|
|
||||||
export class Series {
|
export class Series {
|
||||||
|
|
||||||
readonly valueString: string;
|
readonly valueString: string;
|
||||||
@ -17,7 +21,7 @@ export class Series {
|
|||||||
readonly unit: string,
|
readonly unit: string,
|
||||||
readonly type: SeriesType,
|
readonly type: SeriesType,
|
||||||
) {
|
) {
|
||||||
this.valueString = (value === null ? '-' : formatNumber(value, "de-DE", `0.${decimals}-${decimals}`)) + ' ' + unit;
|
this.valueString = getValueString(value, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json: any): Series {
|
static fromJson(json: any): Series {
|
||||||
|
|||||||
@ -1,18 +1,40 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Inject, Injectable, LOCALE_ID} from '@angular/core';
|
||||||
import {ApiService, CrudService, Next, WebsocketService} from '../common';
|
import {ApiService, CrudService, Next, WebsocketService} from '../common';
|
||||||
import {Series} from './Series';
|
import {Series} from './Series';
|
||||||
|
import {History} from './History';
|
||||||
|
import {DatePipe} from '@angular/common';
|
||||||
|
|
||||||
|
export enum Interval {
|
||||||
|
FIVE = 'FIVE',
|
||||||
|
HOUR = 'HOUR',
|
||||||
|
DAY = 'DAY',
|
||||||
|
WEEK = 'WEEK',
|
||||||
|
MONTH = 'MONTH',
|
||||||
|
YEAR = 'YEAR',
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class SeriesService extends CrudService<Series> {
|
export class SeriesService extends CrudService<Series> {
|
||||||
|
|
||||||
constructor(api: ApiService, ws: WebsocketService) {
|
private readonly datePipe: DatePipe;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
api: ApiService,
|
||||||
|
ws: WebsocketService,
|
||||||
|
@Inject(LOCALE_ID) readonly locale: string,
|
||||||
|
) {
|
||||||
super(api, ws, ['Series'], Series.fromJson);
|
super(api, ws, ['Series'], Series.fromJson);
|
||||||
|
this.datePipe = new DatePipe(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll(next: Next<Series[]>) {
|
findAll(next: Next<Series[]>) {
|
||||||
this.getList(['findAll'], next);
|
this.getList(['findAll'], next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
history(series: Series, date: Date, interval: Interval, next: Next<History>) {
|
||||||
|
this.api.getSingle([...this.path, series.id, 'history', Math.floor(date.getTime() / 1000), interval], History.fromJson, next);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ div {
|
|||||||
|
|
||||||
.Section2 {
|
.Section2 {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
||||||
> .SectionHeading {
|
> .SectionHeading {
|
||||||
> .SectionHeadingText {
|
> .SectionHeadingText {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -55,3 +56,39 @@ div {
|
|||||||
padding-bottom: 0.5em;
|
padding-bottom: 0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Section3 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .SectionBody {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.Section4 {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> .SectionHeadingText {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .SectionBody {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
22
src/main/java/de/ph87/data/series/HistoryDto.java
Normal file
22
src/main/java/de/ph87/data/series/HistoryDto.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package de.ph87.data.series;
|
||||||
|
|
||||||
|
import de.ph87.data.series.point.AllSeriesPointResponse;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class HistoryDto {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final SeriesDto series;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final Double value;
|
||||||
|
|
||||||
|
public HistoryDto(final AllSeriesPointResponse.Entry entry) {
|
||||||
|
this.series = entry.getSeries();
|
||||||
|
this.value = entry.point == null ? null : entry.point.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package de.ph87.data.series;
|
package de.ph87.data.series;
|
||||||
|
|
||||||
|
import de.ph87.data.series.data.Interval;
|
||||||
import de.ph87.data.series.point.AllSeriesPointRequest;
|
import de.ph87.data.series.point.AllSeriesPointRequest;
|
||||||
import de.ph87.data.series.point.AllSeriesPointResponse;
|
import de.ph87.data.series.point.AllSeriesPointResponse;
|
||||||
import de.ph87.data.series.point.OneSeriesPointsRequest;
|
import de.ph87.data.series.point.OneSeriesPointsRequest;
|
||||||
@ -15,6 +16,9 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@CrossOrigin
|
@CrossOrigin
|
||||||
@ -64,11 +68,20 @@ public class SeriesController {
|
|||||||
return seriesService.modify(id, series -> series.setType(type));
|
return seriesService.modify(id, series -> series.setType(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@GetMapping("{id}/history/{epochSeconds}/{intervalName}")
|
||||||
|
public HistoryDto type(@PathVariable final long id, @PathVariable @NonNull final long epochSeconds, @PathVariable @NonNull final String intervalName) {
|
||||||
|
final Interval interval = Interval.valueOf(intervalName);
|
||||||
|
return seriesPointService.history(id, ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSeconds), ZoneId.systemDefault()), interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
public SeriesDto getById(@PathVariable final long id) {
|
public SeriesDto getById(@PathVariable final long id) {
|
||||||
return seriesRepository.getDtoById(id);
|
return seriesRepository.getDtoById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@GetMapping("findAll")
|
@GetMapping("findAll")
|
||||||
public List<SeriesDto> findAll() {
|
public List<SeriesDto> findAll() {
|
||||||
return seriesRepository.findAllDto();
|
return seriesRepository.findAllDto();
|
||||||
@ -80,6 +93,7 @@ public class SeriesController {
|
|||||||
return seriesPointService.oneSeriesPoints(request);
|
return seriesPointService.oneSeriesPoints(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@PostMapping("allSeriesPoint")
|
@PostMapping("allSeriesPoint")
|
||||||
public AllSeriesPointResponse allSeriesPoint(@NonNull @RequestBody final AllSeriesPointRequest request) {
|
public AllSeriesPointResponse allSeriesPoint(@NonNull @RequestBody final AllSeriesPointRequest request) {
|
||||||
return seriesPointService.allSeriesPoint(request);
|
return seriesPointService.allSeriesPoint(request);
|
||||||
|
|||||||
@ -1,17 +1,21 @@
|
|||||||
package de.ph87.data.series.point;
|
package de.ph87.data.series.point;
|
||||||
|
|
||||||
|
import de.ph87.data.series.HistoryDto;
|
||||||
import de.ph87.data.series.Series;
|
import de.ph87.data.series.Series;
|
||||||
import de.ph87.data.series.SeriesDto;
|
import de.ph87.data.series.SeriesDto;
|
||||||
import de.ph87.data.series.SeriesService;
|
import de.ph87.data.series.SeriesService;
|
||||||
|
import de.ph87.data.series.data.Interval;
|
||||||
import de.ph87.data.series.data.bool.BoolService;
|
import de.ph87.data.series.data.bool.BoolService;
|
||||||
import de.ph87.data.series.data.delta.DeltaService;
|
import de.ph87.data.series.data.delta.DeltaService;
|
||||||
import de.ph87.data.series.data.varying.VaryingService;
|
import de.ph87.data.series.data.varying.VaryingService;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -66,4 +70,31 @@ public class SeriesPointService {
|
|||||||
return points.stream().map(p -> p.times(factor)).toList();
|
return points.stream().map(p -> p.times(factor)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public HistoryDto history(final long id, @NonNull final ZonedDateTime date, @NonNull final Interval interval) {
|
||||||
|
final Series series = seriesService.getById(id);
|
||||||
|
final AllSeriesPointResponse.Entry entry = map(series, new Request(date, interval));
|
||||||
|
return new HistoryDto(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class Request implements ISeriesPointRequest {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final Interval interval;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final ZonedDateTime first;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final ZonedDateTime after;
|
||||||
|
|
||||||
|
public Request(final @NonNull ZonedDateTime date, final @NonNull Interval interval) {
|
||||||
|
this.interval = interval;
|
||||||
|
this.first = interval.align.apply(date);
|
||||||
|
this.after = this.first.plus(interval.amount, interval.unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user