first commit

This commit is contained in:
Bastian Wagner
2026-07-31 21:02:47 +02:00
commit 6bea4f766a
512 changed files with 64459 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
@if (loading()) {
<div class="team-select-loading">
<mat-spinner diameter="32" />
</div>
} @else if (players().length === 0) {
<div class="team-select-empty">
<p>Du bist noch keinem Team zugeordnet.</p>
</div>
} @else {
<div class="team-select-page">
<h1>Team auswählen</h1>
<mat-nav-list>
@for (player of players(); track player.id) {
<a mat-list-item [routerLink]="['/team', player.team?.id, 'overview']">
<span matListItemTitle>{{ player.team?.name }}</span>
<span matListItemLine>{{ player.firstName }} {{ player.lastName }}</span>
</a>
}
</mat-nav-list>
</div>
}

View File

@@ -0,0 +1,13 @@
.team-select-loading,
.team-select-empty {
display: flex;
justify-content: center;
align-items: center;
min-height: 60dvh;
padding: 1rem;
text-align: center;
}
.team-select-page {
padding: 1rem;
}

View File

@@ -0,0 +1,95 @@
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter, Router } from '@angular/router';
import { TeamSelect } from './team-select';
import { environment } from '../../../environments/environment';
import { AuthStore } from '../../core/auth/auth-store';
import { Player } from '../../models/player.model';
describe('TeamSelect', () => {
let httpMock: HttpTestingController;
let router: Router;
let authStore: AuthStore;
beforeEach(async () => {
localStorage.clear();
await TestBed.configureTestingModule({
imports: [TeamSelect],
providers: [provideHttpClient(), provideHttpClientTesting(), provideRouter([])],
}).compileComponents();
httpMock = TestBed.inject(HttpTestingController);
router = TestBed.inject(Router);
authStore = TestBed.inject(AuthStore);
authStore.setSession('token', { id: 42, email: 'a@b.de', firstName: 'A', lastName: 'B' });
});
afterEach(() => {
httpMock.verify();
});
it('renders one entry per team when the user has several', async () => {
const players: Player[] = [
{
id: 1,
firstName: 'A',
lastName: 'B',
balance: 0,
active: true,
team: { id: 5, name: 'Team A', alias: 'a', balance: 0 },
},
{
id: 2,
firstName: 'A',
lastName: 'B',
balance: 0,
active: true,
team: { id: 6, name: 'Team B', alias: 'b', balance: 0 },
},
];
const fixture = TestBed.createComponent(TeamSelect);
fixture.detectChanges();
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush(players);
await fixture.whenStable();
fixture.detectChanges();
const items = fixture.nativeElement.querySelectorAll('a[mat-list-item]');
expect(items.length).toBe(2);
});
it('redirects automatically when the user has exactly one team', async () => {
const players: Player[] = [
{
id: 1,
firstName: 'A',
lastName: 'B',
balance: 0,
active: true,
team: { id: 5, name: 'Team A', alias: 'a', balance: 0 },
},
];
const navigateSpy = vi.spyOn(router, 'navigate');
const fixture = TestBed.createComponent(TeamSelect);
fixture.detectChanges();
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush(players);
await fixture.whenStable();
expect(navigateSpy).toHaveBeenCalledWith(['/team', 5, 'overview'], { replaceUrl: true });
});
it('shows an empty state when the user has no teams', async () => {
const fixture = TestBed.createComponent(TeamSelect);
fixture.detectChanges();
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush([]);
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Du bist noch keinem Team zugeordnet.');
});
});

View File

@@ -0,0 +1,37 @@
import { Component, effect, inject } from '@angular/core';
import { Router, RouterLink } from '@angular/router';
import { MatListModule } from '@angular/material/list';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { AuthStore } from '../../core/auth/auth-store';
import { MyTeamsStore } from '../../core/team/my-teams-store';
@Component({
selector: 'app-team-select',
imports: [RouterLink, MatListModule, MatProgressSpinnerModule],
templateUrl: './team-select.html',
styleUrl: './team-select.scss',
})
export class TeamSelect {
private readonly authStore = inject(AuthStore);
private readonly myTeamsStore = inject(MyTeamsStore);
private readonly router = inject(Router);
protected readonly players = this.myTeamsStore.players;
protected readonly loading = this.myTeamsStore.loading;
constructor() {
const userId = this.authStore.currentUser()?.id;
if (userId) {
this.myTeamsStore.ensureLoaded(userId);
}
effect(() => {
const players = this.myTeamsStore.players();
if (!this.myTeamsStore.loading() && players.length === 1 && players[0].team) {
void this.router.navigate(['/team', players[0].team.id, 'overview'], {
replaceUrl: true,
});
}
});
}
}