-
Offene Anfragen
-
{{ stats.pendingMemberships }}
+
+
+
+
Meine Zählpunkte
+
{{ stats.totalMeteringPoints }}
+
+
+
Aktive Mitgliedschaften
+
{{ stats.activeMemberships }}
+
+
+
Offene Anfragen
+
{{ stats.pendingMemberships }}
+
+
+ @if (meteringData(); as data) {
+ @if (data.meteringPoints.length > 0) {
+
+
+
Verbrauch & Erzeugung
+
+
+
+
+
+
+ @if (chartOptions()) {
+
+ } @else {
+
Keine Daten im ausgewählten Zeitraum vorhanden.
+ }
+
+ }
+ }
}
diff --git a/eeg_frontend/src/app/pages/dashboard-overview/dashboard-overview.ts b/eeg_frontend/src/app/pages/dashboard-overview/dashboard-overview.ts
index c6b50fe..f1958cb 100644
--- a/eeg_frontend/src/app/pages/dashboard-overview/dashboard-overview.ts
+++ b/eeg_frontend/src/app/pages/dashboard-overview/dashboard-overview.ts
@@ -1,10 +1,12 @@
import {Component, inject, OnInit, signal} from '@angular/core';
+import {NgxEchartsDirective} from 'ngx-echarts';
import {AuthService} from '../../services/auth';
-import {AdminStats, DashboardService, UserStats} from '../../services/dashboard';
+import {AdminStats, DashboardService, MeteringDataOverview, UserStats} from '../../services/dashboard';
@Component({
selector: 'app-dashboard-overview',
standalone: true,
+ imports: [NgxEchartsDirective],
templateUrl: './dashboard-overview.html'
})
export class DashboardOverview implements OnInit {
@@ -17,6 +19,10 @@ export class DashboardOverview implements OnInit {
isLoading = signal(true);
error = signal
(null);
+ meteringData = signal(null);
+ chartOptions = signal(null);
+ selectedDays = signal(30);
+
ngOnInit() {
if (this.role === 'ADMIN') {
this.dashboardService.getAdminStats().subscribe({
@@ -28,7 +34,11 @@ export class DashboardOverview implements OnInit {
});
} else {
this.dashboardService.getUserStats().subscribe({
- next: stats => { this.userStats.set(stats); this.isLoading.set(false); },
+ next: stats => {
+ this.userStats.set(stats);
+ this.isLoading.set(false);
+ this.loadMeteringData();
+ },
error: () => {
this.isLoading.set(false);
this.error.set('Fehler beim Laden der Statistiken.');
@@ -36,4 +46,72 @@ export class DashboardOverview implements OnInit {
});
}
}
+
+ setRange(days: number) {
+ this.selectedDays.set(days);
+ this.loadMeteringData();
+ }
+
+ private loadMeteringData() {
+ const to = new Date();
+ const from = new Date();
+ from.setDate(from.getDate() - this.selectedDays());
+
+ const fromStr = from.toISOString();
+ const toStr = to.toISOString();
+
+ this.dashboardService.getMeteringDataOverview(fromStr, toStr).subscribe({
+ next: (data) => {
+ this.meteringData.set(data);
+ this.buildChartOptions(data);
+ },
+ error: () => {}
+ });
+ }
+
+ private buildChartOptions(data: MeteringDataOverview) {
+ if (!data || data.timeSeries.length === 0) {
+ this.chartOptions.set(null);
+ return;
+ }
+
+ const dates = data.timeSeries.map(e => {
+ const d = new Date(e.timestamp);
+ return d.toLocaleDateString('de-AT', {day: '2-digit', month: '2-digit'});
+ });
+
+ const series = data.meteringPoints.map(mp => ({
+ name: `${mp.atNumber.slice(-8)} (${mp.type})`,
+ type: 'bar' as const,
+ stack: 'total',
+ data: data.timeSeries.map(e => e.values[mp.atNumber] || 0),
+ itemStyle: {color: mp.type === 'CONSUMER' ? '#3b82f6' : '#22c55e'}
+ }));
+
+ this.chartOptions.set({
+ tooltip: {
+ trigger: 'axis',
+ formatter: (params: any[]) => {
+ const label = params[0]?.axisValue || '';
+ let html = `${label}
`;
+ params.forEach((p: any) => {
+ html += `${p.marker} ${p.seriesName}: ${p.value?.toFixed(2)} kWh
`;
+ });
+ return html;
+ }
+ },
+ legend: {
+ data: series.map(s => s.name),
+ top: 0
+ },
+ grid: {left: '3%', right: '4%', bottom: '3%', containLabel: true},
+ xAxis: {
+ type: 'category',
+ data: dates,
+ axisLabel: {rotate: dates.length > 15 ? 45 : 0}
+ },
+ yAxis: {type: 'value', name: 'kWh'},
+ series
+ });
+ }
}
diff --git a/eeg_frontend/src/app/services/auth.ts b/eeg_frontend/src/app/services/auth.ts
index ab8965d..e7af2ce 100644
--- a/eeg_frontend/src/app/services/auth.ts
+++ b/eeg_frontend/src/app/services/auth.ts
@@ -42,4 +42,15 @@ export class AuthService {
localStorage.clear();
this.isLoggedInSubject.next(false);
}
+
+ getCurrentUserId(): string | null {
+ const token = localStorage.getItem('token');
+ if (!token) return null;
+ try {
+ const payload = JSON.parse(atob(token.split('.')[1]));
+ return payload.userId || null;
+ } catch {
+ return null;
+ }
+ }
}
diff --git a/eeg_frontend/src/app/services/dashboard.ts b/eeg_frontend/src/app/services/dashboard.ts
index 8243de9..9be8661 100644
--- a/eeg_frontend/src/app/services/dashboard.ts
+++ b/eeg_frontend/src/app/services/dashboard.ts
@@ -1,5 +1,5 @@
import {inject, Injectable} from '@angular/core';
-import {HttpClient} from '@angular/common/http';
+import {HttpClient, HttpParams} from '@angular/common/http';
import {Observable} from 'rxjs';
import {environment} from '../../environments/environment';
@@ -16,6 +16,23 @@ export interface UserStats {
pendingMemberships: number;
}
+export interface MeteringPointSummary {
+ id: string;
+ atNumber: string;
+ type: string;
+ totalKwh: number;
+}
+
+export interface TimeSeriesEntry {
+ timestamp: string;
+ values: { [atNumber: string]: number };
+}
+
+export interface MeteringDataOverview {
+ meteringPoints: MeteringPointSummary[];
+ timeSeries: TimeSeriesEntry[];
+}
+
@Injectable({providedIn: 'root'})
export class DashboardService {
private http = inject(HttpClient);
@@ -28,4 +45,11 @@ export class DashboardService {
getUserStats(): Observable {
return this.http.get(`${this.apiUrl}/user`);
}
+
+ getMeteringDataOverview(from: string, to: string): Observable {
+ const params = new HttpParams()
+ .set('from', from)
+ .set('to', to);
+ return this.http.get(`${this.apiUrl}/user/metering-data/overview`, {params});
+ }
}