Backend: - Add MembershipResponse and PendingMembershipResponse DTOs - Add GET /memberships/me and GET /admin/memberships/pending endpoints - Add findByUserIdWithDetails and findPendingWithDetails queries - Add getOwnMemberships and getPendingMemberships service methods Frontend: - Add membership service for API calls - Add join-community page for users to browse and join communities - Add my-memberships page for users to view their memberships - Add admin-memberships page for admins to approve/reject requests - Add routes and navigation items Tests: - Add MembershipServiceTest with 13 unit tests - All 20 tests passing (UserServiceTest + MembershipServiceTest)
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import {Component, inject, OnInit, signal} from '@angular/core';
|
|
import {FormsModule} from '@angular/forms';
|
|
import {MeteringPointService, MeteringPoint} from '../../services/metering-point';
|
|
import {MembershipService, EligibleCommunity} from '../../services/membership';
|
|
import {ToastService} from '../../services/toast';
|
|
|
|
@Component({
|
|
selector: 'app-join-community',
|
|
standalone: true,
|
|
imports: [FormsModule],
|
|
templateUrl: './join-community.html'
|
|
})
|
|
export class JoinCommunityComponent implements OnInit {
|
|
private meteringPointService = inject(MeteringPointService);
|
|
private membershipService = inject(MembershipService);
|
|
private toastService = inject(ToastService);
|
|
|
|
meteringPoints = signal<MeteringPoint[]>([]);
|
|
selectedMeteringPoint = signal<MeteringPoint | null>(null);
|
|
eligibleCommunities = signal<EligibleCommunity[]>([]);
|
|
isLoading = signal(true);
|
|
isLoadingCommunities = signal(false);
|
|
isSubmitting = signal(false);
|
|
selectedPriority = signal(1);
|
|
|
|
ngOnInit() {
|
|
this.loadMeteringPoints();
|
|
}
|
|
|
|
loadMeteringPoints() {
|
|
this.meteringPointService.getMyMeteringPoints().subscribe({
|
|
next: (points: MeteringPoint[]) => {
|
|
// Only show ACTIVE metering points
|
|
this.meteringPoints.set(points.filter((p: MeteringPoint) => p.makoState === 'ACTIVE'));
|
|
this.isLoading.set(false);
|
|
},
|
|
error: () => this.isLoading.set(false)
|
|
});
|
|
}
|
|
|
|
onMeteringPointChange(event: Event) {
|
|
const target = event.target as HTMLSelectElement;
|
|
const pointId = target.value;
|
|
|
|
if (!pointId) {
|
|
this.selectedMeteringPoint.set(null);
|
|
this.eligibleCommunities.set([]);
|
|
return;
|
|
}
|
|
|
|
const point = this.meteringPoints().find(p => p.id === pointId);
|
|
this.selectedMeteringPoint.set(point || null);
|
|
|
|
if (point) {
|
|
this.loadEligibleCommunities(point.id);
|
|
}
|
|
}
|
|
|
|
loadEligibleCommunities(meteringPointId: string) {
|
|
this.isLoadingCommunities.set(true);
|
|
this.eligibleCommunities.set([]);
|
|
|
|
this.membershipService.getEligibleCommunities(meteringPointId).subscribe({
|
|
next: communities => {
|
|
this.eligibleCommunities.set(communities);
|
|
this.isLoadingCommunities.set(false);
|
|
},
|
|
error: () => {
|
|
this.isLoadingCommunities.set(false);
|
|
this.toastService.error('Fehler beim Laden der passenden Communities.');
|
|
}
|
|
});
|
|
}
|
|
|
|
requestMembership(community: EligibleCommunity) {
|
|
if (this.isSubmitting() || !this.selectedMeteringPoint()) return;
|
|
|
|
this.isSubmitting.set(true);
|
|
|
|
this.membershipService.requestMembership({
|
|
meteringPointId: this.selectedMeteringPoint()!.id,
|
|
energyCommunityId: community.id,
|
|
priorityLevel: this.selectedPriority()
|
|
}).subscribe({
|
|
next: () => {
|
|
this.isSubmitting.set(false);
|
|
this.toastService.success(`Beitrittsantrag an "${community.name}" erfolgreich gestellt.`);
|
|
this.eligibleCommunities.set([]);
|
|
this.selectedMeteringPoint.set(null);
|
|
},
|
|
error: (err) => {
|
|
this.isSubmitting.set(false);
|
|
this.toastService.error(err.error?.message || 'Fehler beim Stellen des Beitrittsantrags.');
|
|
}
|
|
});
|
|
}
|
|
}
|