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) {
|
||||
|
||||
<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="SectionHeading">
|
||||
<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 {ActivatedRoute} from '@angular/router';
|
||||
import {Location} from '../Location';
|
||||
@ -6,30 +6,45 @@ import {Text} from '../../shared/text/text';
|
||||
import {Number} from '../../shared/number/number';
|
||||
import {SeriesSelect} from '../../series/select/series-select';
|
||||
import {Series} from '../../series/Series';
|
||||
import {SeriesService} from '../../series/series-service';
|
||||
import {Interval, SeriesService} from '../../series/series-service';
|
||||
import {SeriesType} from '../../series/SeriesType';
|
||||
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({
|
||||
selector: 'app-location-detail',
|
||||
imports: [
|
||||
Text,
|
||||
Number,
|
||||
SeriesSelect
|
||||
SeriesSelect,
|
||||
SeriesHistory
|
||||
],
|
||||
templateUrl: './location-detail.html',
|
||||
styleUrl: './location-detail.less',
|
||||
})
|
||||
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 series: Series[] = [];
|
||||
|
||||
protected location: Location | null = null;
|
||||
|
||||
protected now: Date = new Date();
|
||||
|
||||
protected yesterday: Date = yesterday(this.now);
|
||||
|
||||
constructor(
|
||||
readonly locationService: LocationService,
|
||||
readonly seriesService: SeriesService,
|
||||
@ -44,7 +59,10 @@ export class LocationDetail implements OnInit, OnDestroy {
|
||||
});
|
||||
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()));
|
||||
this.subs.push(timer(1000, 1000).subscribe(() => {
|
||||
this.now = new Date();
|
||||
this.yesterday = yesterday(this.now);
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -64,6 +82,7 @@ export class LocationDetail implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.series.push(fresh);
|
||||
}
|
||||
this.today.updateSeries(fresh);
|
||||
};
|
||||
|
||||
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 {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 {
|
||||
|
||||
readonly valueString: string;
|
||||
@ -17,7 +21,7 @@ export class Series {
|
||||
readonly unit: string,
|
||||
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 {
|
||||
|
||||
@ -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 {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({
|
||||
providedIn: 'root'
|
||||
})
|
||||
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);
|
||||
this.datePipe = new DatePipe(locale);
|
||||
}
|
||||
|
||||
findAll(next: Next<Series[]>) {
|
||||
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 {
|
||||
overflow: visible;
|
||||
|
||||
> .SectionHeading {
|
||||
> .SectionHeadingText {
|
||||
font-style: italic;
|
||||
@ -55,3 +56,39 @@ div {
|
||||
padding-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;
|
||||
|
||||
import de.ph87.data.series.data.Interval;
|
||||
import de.ph87.data.series.point.AllSeriesPointRequest;
|
||||
import de.ph87.data.series.point.AllSeriesPointResponse;
|
||||
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.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@CrossOrigin
|
||||
@ -64,11 +68,20 @@ public class SeriesController {
|
||||
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}")
|
||||
public SeriesDto getById(@PathVariable final long id) {
|
||||
return seriesRepository.getDtoById(id);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@GetMapping("findAll")
|
||||
public List<SeriesDto> findAll() {
|
||||
return seriesRepository.findAllDto();
|
||||
@ -80,6 +93,7 @@ public class SeriesController {
|
||||
return seriesPointService.oneSeriesPoints(request);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@PostMapping("allSeriesPoint")
|
||||
public AllSeriesPointResponse allSeriesPoint(@NonNull @RequestBody final AllSeriesPointRequest request) {
|
||||
return seriesPointService.allSeriesPoint(request);
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
package de.ph87.data.series.point;
|
||||
|
||||
import de.ph87.data.series.HistoryDto;
|
||||
import de.ph87.data.series.Series;
|
||||
import de.ph87.data.series.SeriesDto;
|
||||
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.delta.DeltaService;
|
||||
import de.ph87.data.series.data.varying.VaryingService;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@ -66,4 +70,31 @@ public class SeriesPointService {
|
||||
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