eeg_portal/eeg_frontend/src/app/pages/producer-invites/producer-invites.ts

267 lines
9.2 KiB
TypeScript

import {Component, OnInit, inject} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { DashboardService, AdminStats } from '../../services/dashboard';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../environments/environment';
import { AuthService } from '../../services/auth';
interface CommunityMember {
userId: string;
firstName: string;
lastName: string;
email: string;
}
interface TariffInvite {
id: string;
producerUserId: string;
consumerUserId: string;
energyCommunityId: string;
code: string;
note: string;
status: string;
expiresAt: string;
acceptedAt: string;
createdAt: string;
}
interface EnergyCommunity {
id: string;
name: string;
}
@Component({
selector: 'app-producer-invites',
standalone: true,
imports: [
CommonModule,
FormsModule,
MatCardModule,
MatButtonModule,
MatInputModule,
MatFormFieldModule,
MatSelectModule,
MatIconModule,
MatTableModule,
MatSnackBarModule,
MatProgressSpinnerModule
],
template: `
<div class="space-y-6">
<h1 class="text-2xl font-bold text-slate-800">Meine Einladungen</h1>
<!-- Neue Einladung erstellen -->
<mat-card>
<mat-card-header>
<mat-card-title>Neue Einladung erstellen</mat-card-title>
<mat-card-subtitle>Laden Sie einen Consumer ein, einen Spezial-Tarif zu erhalten</mat-card-subtitle>
</mat-card-header>
<mat-card-content class="mt-4">
@if (communities.length === 0) {
<p class="text-slate-500">Sie sind noch keiner Energiegemeinschaft beigetreten.</p>
} @else {
<div class="space-y-4">
<mat-form-field appearance="outline" class="w-full">
<mat-label>Energiegemeinschaft</mat-label>
<mat-select [(ngModel)]="selectedCommunityId" (selectionChange)="loadMembers()">
@for (community of communities; track community.id) {
<mat-option [value]="community.id">{{ community.name }}</mat-option>
}
</mat-select>
</mat-form-field>
@if (selectedCommunityId) {
<mat-form-field appearance="outline" class="w-full">
<mat-label>Consumer auswählen</mat-label>
<mat-select [(ngModel)]="selectedConsumerId">
@for (member of members; track member.userId) {
<mat-option [value]="member.userId">{{ member.firstName }} {{ member.lastName }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="w-full">
<mat-label>Notiz (optional)</mat-label>
<textarea matInput [(ngModel)]="inviteNote" rows="3"
placeholder="z.B. Ich biete Ihnen einen Spezial-Tarif von 15 Cent/kWh an..."></textarea>
</mat-form-field>
<button mat-raised-button color="primary" (click)="createInvite()"
[disabled]="!selectedConsumerId || isLoading">
@if (isLoading) {
<mat-spinner diameter="20"></mat-spinner>
} @else {
<ng-container>
<mat-icon>send</mat-icon>
Einladung senden
</ng-container>
}
</button>
}
</div>
}
</mat-card-content>
</mat-card>
<!-- Gesendete Einladungen -->
<mat-card>
<mat-card-header>
<mat-card-title>Gesendete Einladungen</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-4">
@if (invites.length === 0) {
<p class="text-slate-500">Noch keine Einladungen gesendet.</p>
} @else {
<table mat-table [dataSource]="invites" class="w-full">
<ng-container matColumnDef="consumer">
<th mat-header-cell *matHeaderCellDef>Consumer</th>
<td mat-cell *matCellDef="let invite">{{ invite.consumerUserId }}</td>
</ng-container>
<ng-container matColumnDef="code">
<th mat-header-cell *matHeaderCellDef>Code</th>
<td mat-cell *matCellDef="let invite">
<code class="bg-slate-100 px-2 py-1 rounded">{{ invite.code }}</code>
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>Status</th>
<td mat-cell *matCellDef="let invite">
<span [class]="getStatusClass(invite.status)">{{ getStatusLabel(invite.status) }}</span>
</td>
</ng-container>
<ng-container matColumnDef="expiresAt">
<th mat-header-cell *matHeaderCellDef>Gültig bis</th>
<td mat-cell *matCellDef="let invite">{{ invite.expiresAt | date:'dd.MM.yyyy HH:mm' }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
}
</mat-card-content>
</mat-card>
</div>
`
})
export class ProducerInvitesComponent implements OnInit {
private http = inject(HttpClient);
private snackBar = inject(MatSnackBar);
private authService = inject(AuthService);
private apiUrl = `${environment.apiUrl}/api`;
communities: EnergyCommunity[] = [];
members: CommunityMember[] = [];
invites: TariffInvite[] = [];
selectedCommunityId: string = '';
selectedConsumerId: string = '';
inviteNote: string = '';
isLoading = false;
displayedColumns = ['consumer', 'code', 'status', 'expiresAt'];
ngOnInit() {
this.loadCommunities();
}
loadCommunities() {
this.http.get<EnergyCommunity[]>(`${this.apiUrl}/community/admin/communities`)
.subscribe({
next: (communities) => this.communities = communities,
error: () => this.snackBar.open('Fehler beim Laden der Energiegemeinschaften', 'Schließen', { duration: 3000 })
});
}
loadMembers() {
if (!this.selectedCommunityId) return;
this.http.get<CommunityMember[]>(`${this.apiUrl}/community/admin/communities/${this.selectedCommunityId}/members`)
.subscribe({
next: (members) => {
const currentUserId = this.authService.getCurrentUserId();
this.members = members.filter(m => m.userId !== currentUserId);
},
error: () => this.snackBar.open('Fehler beim Laden der Mitglieder', 'Schließen', { duration: 3000 })
});
this.loadInvites();
}
loadInvites() {
if (!this.selectedCommunityId) return;
this.http.get<TariffInvite[]>(`${this.apiUrl}/tariffs/invites/community/${this.selectedCommunityId}`)
.subscribe({
next: (invites) => this.invites = invites,
error: () => this.snackBar.open('Fehler beim Laden der Einladungen', 'Schließen', { duration: 3000 })
});
}
createInvite() {
if (!this.selectedConsumerId || !this.selectedCommunityId) return;
this.isLoading = true;
const request = {
consumerUserId: this.selectedConsumerId,
energyCommunityId: this.selectedCommunityId,
note: this.inviteNote || null
};
this.http.post<TariffInviteRequest>(`${this.apiUrl}/tariffs/invites`, request)
.subscribe({
next: (invite) => {
this.isLoading = false;
this.snackBar.open('Einladung erfolgreich gesendet!', 'Schließen', { duration: 3000 });
this.inviteNote = '';
this.selectedConsumerId = '';
this.loadInvites();
},
error: (err) => {
this.isLoading = false;
const message = err.error?.message || 'Fehler beim Erstellen der Einladung';
this.snackBar.open(message, 'Schließen', { duration: 5000 });
}
});
}
getStatusClass(status: string): string {
switch (status) {
case 'PENDING': return 'text-yellow-600 bg-yellow-100 px-2 py-1 rounded';
case 'ACCEPTED': return 'text-green-600 bg-green-100 px-2 py-1 rounded';
case 'EXPIRED': return 'text-red-600 bg-red-100 px-2 py-1 rounded';
default: return 'text-slate-600 bg-slate-100 px-2 py-1 rounded';
}
}
getStatusLabel(status: string): string {
switch (status) {
case 'PENDING': return 'Ausstehend';
case 'ACCEPTED': return 'Angenommen';
case 'EXPIRED': return 'Abgelaufen';
default: return status;
}
}
}
interface TariffInviteRequest {
consumerUserId: string;
energyCommunityId: string;
note: string | null;
}