267 lines
9.2 KiB
TypeScript
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;
|
|
}
|