refactored big AppComponent into Dashboard and parts

This commit is contained in:
Patrick Haßel 2025-02-28 09:04:16 +01:00
parent 9d12986720
commit 2882184a82
20 changed files with 328 additions and 283 deletions

View File

@ -25,3 +25,27 @@
@productionBack: #2d4255; @productionBack: #2d4255;
@selfBack: #2b4e2b; @selfBack: #2b4e2b;
@deliveryBack: #753475; @deliveryBack: #753475;
.consumption {
color: @consumption;
}
.purchase {
color: @purchase;
}
.production {
color: @production;
}
.self {
color: @self;
}
.delivery {
color: @delivery;
}
.zero {
filter: opacity(20%);
}

View File

@ -0,0 +1,50 @@
@import "./colors.less";
.numberTable {
margin-bottom: 2em;
.arrowLeft {
float: left;
}
.arrowRight {
float: right;
}
.title {
clear: left;
font-weight: bold;
text-align: center;
}
.option {
clear: left;
text-align: center;
}
.content {
.entry {
clear: left;
.name {
float: left;
}
.value {
float: right;
}
.percent {
float: right;
padding-top: 0.5em;
font-size: 60%;
width: 4.5em;
text-align: right;
}
}
}
}

View File

@ -1,89 +1 @@
<div class="section">
<div class="title">
Leistung
</div>
<div class="content">
<div class="entry consumption" [class.zero]="powerConsumption?.zero">
<div class="name">Bedarf</div>
<div class="percent">&nbsp;</div>
<div class="value">{{ powerConsumption?.formatted }}</div>
</div>
<div class="entry purchase" [class.zero]="powerPurchase?.zero">
<div class="name">Bezug</div>
<div class="percent">{{ powerPurchasePercent?.formatted }}</div>
<div class="value">{{ powerPurchase?.formatted }}</div>
</div>
<div class="entry production" [class.zero]="powerProduction?.zero">
<div class="name">Produktion</div>
<div class="percent">{{ powerProducedPercent?.formatted }}</div>
<div class="value">{{ powerProduction?.formatted }}</div>
</div>
<div class="entry self" [class.zero]="powerSelf?.zero">
<div class="name">Eigenbedarf</div>
<div class="percent">{{ powerSelfPercent?.formatted }}</div>
<div class="value">{{ powerSelf?.formatted }}</div>
</div>
<div class="entry delivery" [class.zero]="powerDelivery?.zero">
<div class="name">Einspeisung</div>
<div class="percent">{{ powerDeliveryPercent?.formatted }}</div>
<div class="value">{{ powerDelivery?.formatted }}</div>
</div>
</div>
<app-percent-bar [produktion]="powerProduction" [self]="powerSelf" [purchase]="powerPurchase" [delivery]="powerDelivery"></app-percent-bar>
</div>
<div class="section">
<div class="title">
Energie
</div>
<div class="option">
<button class="back" (click)="shiftAlignment(+1)">&larr;</button>
{{ alignment.display }} {{ offset > 0 ? -offset : '' }}
<button class="next" (click)="shiftAlignment(-1)">&rarr;</button>
</div>
<div class="option">
<button class="back" (click)="shiftOffset(+1)">&larr;</button>
{{ alignment.offsetTitle(offset, locale) }}
<button class="next" (click)="shiftOffset(-1)" [disabled]="offset === 0">&rarr;</button>
</div>
<div class="content">
<div class="entry consumption" [class.zero]="aggregations.energyConsumed?.zero">
<div class="name">Bedarf</div>
<div class="percent">&nbsp;</div>
<div class="value">{{ aggregations.energyConsumed?.formatted }}</div>
</div>
<div class="entry purchase" [class.zero]="aggregations.energyPurchased?.delta?.zero">
<div class="name">Bezug</div>
<div class="percent">{{ aggregations.energyPurchasedPercent?.formatted }}</div>
<div class="value">{{ aggregations.energyPurchased?.delta?.formatted }}</div>
</div>
<div class="entry production" [class.zero]="aggregations.energyProduced?.delta?.zero">
<div class="name">Produktion</div>
<div class="percent">{{ aggregations.energyProducedPercent?.formatted }}</div>
<div class="value">{{ aggregations.energyProduced?.delta?.formatted }}</div>
</div>
<div class="entry self" [class.zero]="aggregations.energySelf?.zero">
<div class="name">Eigenbedarf</div>
<div class="percent">{{ aggregations.energySelfPercent?.formatted }}</div>
<div class="value">{{ aggregations.energySelf?.formatted }}</div>
</div>
<div class="entry delivery" [class.zero]="aggregations.energyDelivered?.delta?.zero">
<div class="name">Einspeisung</div>
<div class="percent">{{ aggregations.energyDeliveredPercent?.formatted }}</div>
<div class="value">{{ aggregations.energyDelivered?.delta?.formatted }}</div>
</div>
</div>
<app-percent-bar [produktion]="aggregations.energyProduced?.delta" [self]="aggregations.energySelf" [purchase]="aggregations.energyPurchased?.delta" [delivery]="aggregations.energyDelivered?.delta"></app-percent-bar>
</div>
<router-outlet/> <router-outlet/>

View File

@ -1,86 +0,0 @@
@import "../../config.less";
.section {
margin-bottom: 2em;
.back {
float: left;
}
.next {
float: right;
}
.title {
clear: left;
font-weight: bold;
text-align: center;
}
.option {
clear: left;
text-align: center;
}
.content {
.entry {
clear: left;
.name {
float: left;
}
.value {
float: right;
}
.percent {
float: right;
padding-top: 0.5em;
font-size: 60%;
width: 4.5em;
text-align: right;
}
}
}
}
.live {
font-size: 40%;
color: green;
vertical-align: top;
}
.archive {
font-size: 40%;
color: darkred;
vertical-align: top;
}
.consumption {
color: @consumption;
}
.purchase {
color: @purchase;
}
.production {
color: @production;
}
.self {
color: @self;
}
.delivery {
color: @delivery;
}
.zero {
filter: opacity(20%);
}

View File

@ -1,113 +1,12 @@
import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core'; import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router'; import {RouterOutlet} from '@angular/router';
import {Alignment} from './series/Alignment';
import {AggregationWrapperDto} from './series/AggregationWrapperDto';
import {SeriesService} from './series/series.service';
import {Subscription} from 'rxjs';
import {Value} from './value/Value';
import {PercentBarComponent} from './percent-bar/percent-bar.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [RouterOutlet, PercentBarComponent], imports: [RouterOutlet],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.less' styleUrl: './app.component.less'
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent {
protected aggregations: AggregationWrapperDto = AggregationWrapperDto.EMPTY;
protected alignment: Alignment = Alignment.DAY;
protected offset: number = 0;
protected interval: any;
private readonly subs: Subscription[] = [];
get powerProduction(): Value | undefined {
return this.seriesService.powerProduced.series?.lastValue;
}
get powerBalance(): Value | undefined {
return this.seriesService.powerBalance.series?.lastValue;
}
get powerPurchase(): Value | undefined {
return this.powerBalance?.notNegative();
}
get powerDelivery(): Value | undefined {
return this.powerBalance?.negate()?.notNegative();
}
get powerSelf(): Value | undefined {
return this.powerProduction?.minus(this.powerDelivery)?.notNegative();
}
get powerConsumption(): Value | undefined {
return this.powerPurchase?.plus(this.powerProduction)?.minus(this.powerDelivery);
}
get powerProducedPercent(): Value | undefined {
return this.powerProduction?.percent(this.powerConsumption);
}
get powerSelfPercent(): Value | undefined {
return this.powerSelf?.percent(this.powerProduction);
}
get powerPurchasePercent(): Value | undefined {
return this.powerPurchase?.percent(this.powerConsumption);
}
get powerDeliveryPercent(): Value | undefined {
return this.powerDelivery?.percent(this.powerProduction);
}
constructor(
@Inject(LOCALE_ID) public locale: string,
protected readonly seriesService: SeriesService,
) {
//
}
ngOnInit(): void {
this.fetch();
this.subs.push(this.seriesService.subscribeAny());
}
ngOnDestroy(): void {
this.intervalStop();
this.subs.forEach(sub => sub.unsubscribe());
}
shiftOffset(delta: number) {
this.offset = Math.max(0, this.offset + delta);
this.fetch();
}
shiftAlignment(delta: number) {
this.alignment = this.alignment.plus(delta)
this.fetch();
}
private fetch() {
if (this.offset === 0) {
if (!this.interval) {
this.interval = setInterval(() => this.fetch(), 5000);
}
} else {
this.intervalStop();
}
this.seriesService.aggregations(this.alignment, this.offset, aggregations => this.aggregations = aggregations);
}
private intervalStop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
} }

View File

@ -1,3 +1,7 @@
import {Routes} from '@angular/router'; import {Routes} from '@angular/router';
import {DashboardComponent} from './dashboard/dashboard.component';
export const routes: Routes = []; export const routes: Routes = [
{path: 'Dashboard', component: DashboardComponent},
{path: '**', redirectTo: 'Dashboard'},
];

View File

@ -4,7 +4,7 @@ import {map, Subscription} from 'rxjs';
import {StompService} from '@stomp/ng2-stompjs'; import {StompService} from '@stomp/ng2-stompjs';
import {FromJson, Next} from './types'; import {FromJson, Next} from './types';
const DEV_TO_PROD = false; const DEV_TO_PROD = true;
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'

View File

@ -0,0 +1,3 @@
<app-electro-power></app-electro-power>
<app-electro-energy></app-electro-energy>

View File

@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import {ElectroEnergyComponent} from "../electro/energy/electro-energy.component";
import {ElectroPowerComponent} from "../electro/power/electro-power.component";
@Component({
selector: 'app-dashboard',
imports: [
ElectroEnergyComponent,
ElectroPowerComponent
],
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.less'
})
export class DashboardComponent {
}

View File

@ -0,0 +1,49 @@
<div class="numberTable">
<div class="title">
Energie
</div>
<div class="option">
<button class="arrowLeft" (click)="shiftAlignment(+1)">&larr;</button>
{{ alignment.display }} {{ offset > 0 ? -offset : '' }}
<button class="arrowRight" (click)="shiftAlignment(-1)">&rarr;</button>
</div>
<div class="option">
<button class="arrowLeft" (click)="shiftOffset(+1)">&larr;</button>
{{ alignment.offsetTitle(offset, locale) }}
<button class="arrowRight" (click)="shiftOffset(-1)" [disabled]="offset === 0">&rarr;</button>
</div>
<div class="content">
<div class="entry consumption" [class.zero]="aggregations.energyConsumed?.zero">
<div class="name">Bedarf</div>
<div class="percent">&nbsp;</div>
<div class="value">{{ aggregations.energyConsumed?.formatted }}</div>
</div>
<div class="entry purchase" [class.zero]="aggregations.energyPurchased?.delta?.zero">
<div class="name">Bezug</div>
<div class="percent">{{ aggregations.energyPurchasedPercent?.formatted }}</div>
<div class="value">{{ aggregations.energyPurchased?.delta?.formatted }}</div>
</div>
<div class="entry production" [class.zero]="aggregations.energyProduced?.delta?.zero">
<div class="name">Produktion</div>
<div class="percent">{{ aggregations.energyProducedPercent?.formatted }}</div>
<div class="value">{{ aggregations.energyProduced?.delta?.formatted }}</div>
</div>
<div class="entry self" [class.zero]="aggregations.energySelf?.zero">
<div class="name">Eigenbedarf</div>
<div class="percent">{{ aggregations.energySelfPercent?.formatted }}</div>
<div class="value">{{ aggregations.energySelf?.formatted }}</div>
</div>
<div class="entry delivery" [class.zero]="aggregations.energyDelivered?.delta?.zero">
<div class="name">Einspeisung</div>
<div class="percent">{{ aggregations.energyDeliveredPercent?.formatted }}</div>
<div class="value">{{ aggregations.energyDelivered?.delta?.formatted }}</div>
</div>
</div>
<app-percent-bar [produktion]="aggregations.energyProduced?.delta" [self]="aggregations.energySelf" [purchase]="aggregations.energyPurchased?.delta" [delivery]="aggregations.energyDelivered?.delta"></app-percent-bar>
</div>

View File

@ -0,0 +1 @@
@import "../../../../colors.less";

View File

@ -0,0 +1,73 @@
import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core';
import {PercentBarComponent} from '../../shared/percent-bar/percent-bar.component';
import {AggregationWrapperDto} from '../../series/AggregationWrapperDto';
import {Alignment} from '../../series/Alignment';
import {Subscription} from 'rxjs';
import {SeriesService} from '../../series/series.service';
@Component({
selector: 'app-electro-energy',
imports: [
PercentBarComponent
],
templateUrl: './electro-energy.component.html',
styleUrl: './electro-energy.component.less'
})
export class ElectroEnergyComponent implements OnInit, OnDestroy {
protected aggregations: AggregationWrapperDto = AggregationWrapperDto.EMPTY;
protected alignment: Alignment = Alignment.DAY;
protected offset: number = 0;
protected interval: any;
private readonly subs: Subscription[] = [];
constructor(
@Inject(LOCALE_ID) public locale: string,
protected readonly seriesService: SeriesService,
) {
//
}
ngOnInit(): void {
this.fetch();
this.subs.push(this.seriesService.subscribeAny());
}
ngOnDestroy(): void {
this.intervalStop();
this.subs.forEach(sub => sub.unsubscribe());
}
shiftOffset(delta: number) {
this.offset = Math.max(0, this.offset + delta);
this.fetch();
}
shiftAlignment(delta: number) {
this.alignment = this.alignment.plus(delta)
this.fetch();
}
private fetch() {
if (this.offset === 0) {
if (!this.interval) {
this.interval = setInterval(() => this.fetch(), 5000);
}
} else {
this.intervalStop();
}
this.seriesService.aggregations(this.alignment, this.offset, aggregations => this.aggregations = aggregations);
}
private intervalStop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
}

View File

@ -0,0 +1,37 @@
<div class="numberTable">
<div class="title">
Leistung
</div>
<div class="content">
<div class="entry consumption" [class.zero]="powerConsumption?.zero">
<div class="name">Bedarf</div>
<div class="percent">&nbsp;</div>
<div class="value">{{ powerConsumption?.formatted }}</div>
</div>
<div class="entry purchase" [class.zero]="powerPurchase?.zero">
<div class="name">Bezug</div>
<div class="percent">{{ powerPurchasePercent?.formatted }}</div>
<div class="value">{{ powerPurchase?.formatted }}</div>
</div>
<div class="entry production" [class.zero]="powerProduction?.zero">
<div class="name">Produktion</div>
<div class="percent">{{ powerProducedPercent?.formatted }}</div>
<div class="value">{{ powerProduction?.formatted }}</div>
</div>
<div class="entry self" [class.zero]="powerSelf?.zero">
<div class="name">Eigenbedarf</div>
<div class="percent">{{ powerSelfPercent?.formatted }}</div>
<div class="value">{{ powerSelf?.formatted }}</div>
</div>
<div class="entry delivery" [class.zero]="powerDelivery?.zero">
<div class="name">Einspeisung</div>
<div class="percent">{{ powerDeliveryPercent?.formatted }}</div>
<div class="value">{{ powerDelivery?.formatted }}</div>
</div>
</div>
<app-percent-bar [produktion]="powerProduction" [self]="powerSelf" [purchase]="powerPurchase" [delivery]="powerDelivery"></app-percent-bar>
</div>

View File

@ -0,0 +1 @@
@import "../../../../colors.less";

View File

@ -0,0 +1,61 @@
import {Component} from '@angular/core';
import {PercentBarComponent} from "../../shared/percent-bar/percent-bar.component";
import {Value} from '../../value/Value';
import {SeriesService} from '../../series/series.service';
@Component({
selector: 'app-electro-power',
imports: [
PercentBarComponent
],
templateUrl: './electro-power.component.html',
styleUrl: './electro-power.component.less'
})
export class ElectroPowerComponent {
constructor(
readonly seriesService: SeriesService,
) {
}
get powerProduction(): Value | undefined {
return this.seriesService.powerProduced.series?.lastValue;
}
get powerBalance(): Value | undefined {
return this.seriesService.powerBalance.series?.lastValue;
}
get powerPurchase(): Value | undefined {
return this.powerBalance?.notNegative();
}
get powerDelivery(): Value | undefined {
return this.powerBalance?.negate()?.notNegative();
}
get powerSelf(): Value | undefined {
return this.powerProduction?.minus(this.powerDelivery)?.notNegative();
}
get powerConsumption(): Value | undefined {
return this.powerPurchase?.plus(this.powerProduction)?.minus(this.powerDelivery);
}
get powerProducedPercent(): Value | undefined {
return this.powerProduction?.percent(this.powerConsumption);
}
get powerSelfPercent(): Value | undefined {
return this.powerSelf?.percent(this.powerProduction);
}
get powerPurchasePercent(): Value | undefined {
return this.powerPurchase?.percent(this.powerConsumption);
}
get powerDeliveryPercent(): Value | undefined {
return this.powerDelivery?.percent(this.powerProduction);
}
}

View File

@ -1,4 +1,4 @@
@import "../../../config.less"; @import "../../../../colors.less";
.bar { .bar {
position: relative; position: relative;

View File

@ -1,5 +1,5 @@
import {Component, Input} from '@angular/core'; import {Component, Input} from '@angular/core';
import {Value} from '../value/Value'; import {Value} from '../../value/Value';
import {NgIf} from '@angular/common'; import {NgIf} from '@angular/common';
@Component({ @Component({

View File

@ -1,4 +1,5 @@
@import "../config.less"; @import "../colors.less";
@import "../numberTable.less";
body { body {
font-family: sans-serif; font-family: sans-serif;