Move to new server

This commit is contained in:
2024-10-12 13:38:41 +02:00
commit e2e855e9b9
113 changed files with 11268 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
<h2 mat-dialog-title>Kies een baan:</h2>
<mat-dialog-content>
<button type="button" class="btn {{ data.availableCourts.indexOf(i + 1) < 0 ? 'btn-secondary' : 'btn-primary' }} btn-lg m-3"
*ngFor="let item of [].constructor(data.totalCourts); let i = index"
[disabled]="data.availableCourts.indexOf(i + 1) < 0" [mat-dialog-close]="i + 1">
{{ i + 1 }}
</button>
<br>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onAnnulerenClick()">Annuleren</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,42 @@
import {Component, inject, Inject} from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogClose,
MatDialogContent, MatDialogRef,
MatDialogTitle
} from "@angular/material/dialog";
import {Match} from "../../model/match";
import {NgForOf} from "@angular/common";
import {MatButton} from "@angular/material/button";
@Component({
selector: 'app-court-selection',
standalone: true,
imports: [
MatDialogTitle,
MatDialogContent,
NgForOf,
MatButton,
MatDialogClose,
MatDialogActions
],
templateUrl: './court-selection.component.html',
styleUrl: './court-selection.component.scss'
})
export class CourtSelectionComponent {
court: number;
readonly dialogRef = inject(MatDialogRef<CourtSelectionComponent>);
constructor(@Inject(MAT_DIALOG_DATA) public data: {
match: Match,
availableCourts: number[],
totalCourts: number
}) {}
onAnnulerenClick() {
this.dialogRef.close();
}
}

View File

@@ -0,0 +1,22 @@
<mat-card>
<mat-card-content>
<form class="form-horizontal" [formGroup]="form">
<div class="row">
<mat-form-field appearance="outline">
<mat-label>Gebruikersnaam</mat-label>
<input matInput placeholder="Gebruikersnaam" formControlName="username" required>
</mat-form-field>
</div>
<div class="row">
<mat-form-field appearance="outline">
<mat-label>Wachtwoord</mat-label>
<input matInput placeholder="Wachtwoord" type="password" formControlName="password" required>
</mat-form-field>
</div>
<button class="w-100 mt-2" mat-button mat-flat-button color="primary"
(click)="login()" [disabled]="form.invalid">
Inloggen
</button>
</form>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,87 @@
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
import {MatError, MatFormField, MatLabel} from "@angular/material/form-field";
import {MatButton} from "@angular/material/button";
import {MatInput} from "@angular/material/input";
import {NgIf} from "@angular/common";
import {AuthenticationService} from "../../authentication/authentication.service";
import {UserService} from "../../authentication/user.service";
import {LoginCredentials} from "../../authentication/loginCredentials";
import {User} from "../../authentication/user";
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
standalone: true,
imports: [
MatCardTitle,
MatCardContent,
ReactiveFormsModule,
MatFormField,
MatButton,
RouterLink,
MatInput,
NgIf,
MatLabel,
MatError,
MatCard,
MatCardHeader
],
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
public form: FormGroup;
private returnUrl: string;
private ipAddress: string;
constructor(
private route: ActivatedRoute,
private fb: FormBuilder,
private authenticationService: AuthenticationService,
private router: Router,
private userPersistenceService: UserService,
private titleService: TitleService,
) {
this.initializeForm();
}
private initializeForm() {
this.form = this.fb.group({
username: ['', [Validators.required]],
password: ['', [Validators.required]],
});
}
ngOnInit() {
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
this.titleService.setTitle("Inloggen");
}
login() {
if (this.form.invalid) {
return;
}
let loginCredentials = new LoginCredentials();
loginCredentials = {
...loginCredentials,
...this.form.value,
};
this.authenticationService
.login(loginCredentials)
.subscribe(
{
next: (user: User) => {
this.userPersistenceService.setUser(user);
this.router.navigate([this.returnUrl]);
}
}
);
}
}

View File

@@ -0,0 +1,82 @@
<h2 mat-dialog-title>Uitslag invoeren</h2>
<mat-dialog-content>
<mat-grid-list cols="5" rowHeight="12:7">
<mat-grid-tile colspan="2">
<div class="w-100" style="font-size: 16px;">
<b>{{ data.group.name }} {{ data.round.name }}</b>
</div>
</mat-grid-tile>
<mat-grid-tile>
<button type="button" class="btn btn-primary btn-lg" (click)="set21(1, 1)">21</button>
</mat-grid-tile>
<mat-grid-tile>
<button type="button" class="btn btn-primary btn-lg" (click)="set21(1, 2)">21</button>
</mat-grid-tile>
<mat-grid-tile>
<button type="button" class="btn btn-primary btn-lg" (click)="set21(1, 3)">21</button>
</mat-grid-tile>
<mat-grid-tile colspan="2">
<div class="w-100" [ngClass]="{'winner': validateResult() == 1}">
{{ data.match.team1 | teamText }}
</div>
</mat-grid-tile>
<mat-grid-tile>
<mat-form-field appearance="outline" (change)="validateResult()">
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[0].score1">
</mat-form-field>
</mat-grid-tile>
<mat-grid-tile>
<mat-form-field appearance="outline" (change)="validateResult()">
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[1].score1">
</mat-form-field>
</mat-grid-tile>
<mat-grid-tile>
<mat-form-field appearance="outline" (change)="validateResult()">
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[2].score1">
</mat-form-field>
</mat-grid-tile>
<mat-grid-tile colspan="2">
<div class="w-100" [ngClass]="{'winner': validateResult() == -1}">
{{ data.match.team2 | teamText }}
</div>
</mat-grid-tile>
<mat-grid-tile>
<mat-form-field appearance="outline" (change)="validateResult()">
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[0].score2">
</mat-form-field>
</mat-grid-tile>
<mat-grid-tile>
<mat-form-field appearance="outline" (change)="validateResult()">
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[1].score2">
</mat-form-field>
</mat-grid-tile>
<mat-grid-tile>
<mat-form-field appearance="outline" (change)="validateResult()">
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[2].score2">
</mat-form-field>
</mat-grid-tile>
<mat-grid-tile>
</mat-grid-tile>
<mat-grid-tile>
</mat-grid-tile>
<mat-grid-tile>
<button type="button" class="btn btn-primary btn-lg" (click)="set21(2, 1)">21</button>
</mat-grid-tile>
<mat-grid-tile>
<button type="button" class="btn btn-primary btn-lg" (click)="set21(2, 2)">21</button>
</mat-grid-tile>
<mat-grid-tile>
<button type="button" class="btn btn-primary btn-lg" (click)="set21(2, 3)">21</button>
</mat-grid-tile>
</mat-grid-list>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onAnnulerenClick()">Annuleren</button>
<button mat-button [disabled]="validateResult() == 0" [mat-dialog-close]="result">Opslaan</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,12 @@
mat-grid-tile {
font-size: 20px;
}
mat-form-field {
width: 80px !important;
}
.winner {
color: green;
font-weight: bold;
}

View File

@@ -0,0 +1,147 @@
import {Component, inject, Inject} from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogClose,
MatDialogContent, MatDialogRef,
MatDialogTitle
} from "@angular/material/dialog";
import {MatButton, MatIconButton} from "@angular/material/button";
import {DatePipe, NgClass, NgForOf} from "@angular/common";
import {MatIcon} from "@angular/material/icon";
import {TeamPipe} from "../../pipes/team-pipe";
import {FullNamePipe} from "../../pipes/fullname-pipe";
import {Match} from "../../model/match";
import {MatFormField, MatInput} from "@angular/material/input";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {Result} from "../../model/result";
import {MatGridList, MatGridTile, MatGridTileText} from "@angular/material/grid-list";
import {Round} from "../../model/round";
import {Group} from "../../model/group";
import {Game} from "../../model/game";
@Component({
selector: 'app-match-result',
standalone: true,
imports: [
MatDialogContent,
MatDialogActions,
MatButton,
MatDialogClose,
MatDialogTitle,
DatePipe,
MatIcon,
NgForOf,
TeamPipe,
MatInput,
ReactiveFormsModule,
FormsModule,
MatFormField,
MatGridList,
MatGridTile,
MatGridTileText,
MatIconButton,
NgClass
],
providers: [
FullNamePipe,
TeamPipe
],
templateUrl: './match-result.component.html',
styleUrl: './match-result.component.scss'
})
export class MatchResultComponent {
result: Result = new Result();
constructor(@Inject(MAT_DIALOG_DATA) public data: {match: Match, group: Group, round: Round}) {
this.result.matchId = this.data.match.id;
if (data.match.games.length == 0) {
this.result.games.push(new Game());
this.result.games.push(new Game());
this.result.games.push(new Game());
} else {
for (let game of data.match.games) {
this.result.games.push(game);
}
if (data.match.games.length == 2) {
this.result.games.push(new Game());
}
}
}
readonly dialogRef = inject(MatDialogRef<MatchResultComponent>);
set21(team: number, game: number) {
if (team == 1) {
this.result.games[game - 1].score1 = 21;
}
if (team == 2) {
this.result.games[game - 1].score2 = 21;
}
}
onAnnulerenClick() {
this.dialogRef.close();
}
validateResult(): number {
let valid : boolean = true;
valid &&= this.gameValid(this.result.games[0].score1, this.result.games[0].score2);
valid &&= this.gameValid(this.result.games[1].score1, this.result.games[1].score2);
if (this.result.games[2].score1 != undefined && this.result.games[2].score2 != undefined) {
valid &&= this.gameValid(this.result.games[2].score1, this.result.games[2].score2);
}
return valid ? this.matchResult(this.result) : 0;
}
gameValid(score1: number, score2: number): boolean {
if (score1 == undefined) return false;
if (score2 == undefined) return false;
if (score1 < 0 || score1 > 30) return false;
if (score2 < 0 || score2 > 30) return false;
if (score1 == 21 && score2 <= 19) return true;
if (score1 <= 19 && score2 == 21) return true;
if (score1 == 30 && score2 == 29) return true;
if (score1 == 29 && score2 == 30) return true;
if (score1 >= 22 && score1 <= 30 && (score1 - score2) == 2) return true;
if (score2 >= 22 && score2 <= 30 && (score2 - score1) == 2) return true;
return false;
}
matchResult(result: Result): number {
let gameBalance = 0;
if (result.games[0].score1 < result.games[0].score2) {
gameBalance--;
} else {
gameBalance++;
}
if (result.games[1].score1 < result.games[1].score2) {
gameBalance--;
} else {
gameBalance++;
}
if (Math.abs(gameBalance) == 2 && (result.games[2].score1 != undefined || result.games[2].score2 != undefined)) {
return 0;
}
if (result.games[2].score1 != undefined && result.games[2].score2 != undefined) {
if (result.games[2].score1 < result.games[2].score2) {
gameBalance--;
} else {
gameBalance++;
}
}
return Math.sign(gameBalance);
}
}

View File

@@ -0,0 +1,63 @@
@if (round) {
<ng-container *ngFor="let match of round.matches">
<div class="nobreak">
<mat-card appearance="outlined">
<mat-card-content>
<h6>{{ tournament.name }}</h6>
<br>
<br>
<div class="row">
<div class="col-6">
<b>{{ match.team1 | teamText }}</b>
</div>
<div class="col-2">
<mat-form-field appearance="outline">
<input matInput>
</mat-form-field>
</div>
<div class="col-2">
<mat-form-field appearance="outline">
<input matInput>
</mat-form-field>
</div>
<div class="col-2">
<mat-form-field appearance="outline">
<input matInput>
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-6">
<b>{{ match.team2 | teamText }}</b>
</div>
<div class="col-2">
<mat-form-field appearance="outline">
<input matInput>
</mat-form-field>
</div>
<div class="col-2">
<mat-form-field appearance="outline">
<input matInput>
</mat-form-field>
</div>
<div class="col-2">
<mat-form-field appearance="outline">
<input matInput>
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-6">
<u>Graag de winnaar omcirkelen</u>
</div>
<div class="col-6 text-end">
{{ group.name }} {{ round.name }}
</div>
</div>
</mat-card-content>
</mat-card>
</div>
<br>
</ng-container>
}

View File

@@ -0,0 +1,9 @@
mat-form-field {
width: 80px !important;
}
@media print {
div.nobreak {
page-break-inside: avoid;
}
}

View File

@@ -0,0 +1,71 @@
import {Component, OnInit} from '@angular/core';
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {TournamentService} from "../../service/tournament.service";
import {ActivatedRoute, Router} from "@angular/router";
import {Tournament} from "../../model/tournament";
import {Round} from "../../model/round";
import {TeamPipe} from "../../pipes/team-pipe";
import {FullNamePipe} from "../../pipes/fullname-pipe";
import {Group} from "../../model/group";
import {NgForOf} from "@angular/common";
import {MatFormField} from "@angular/material/form-field";
import {MatInput} from "@angular/material/input";
import {ReactiveFormsModule} from "@angular/forms";
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-match-sheets',
standalone: true,
imports: [
MatCard,
MatCardHeader,
MatCardContent,
TeamPipe,
NgForOf,
MatFormField,
MatInput,
ReactiveFormsModule
],
providers: [
TeamPipe,
FullNamePipe
],
templateUrl: './match-sheets.component.html',
styleUrl: './match-sheets.component.scss'
})
export class MatchSheetsComponent implements OnInit {
tournament: Tournament;
group: Group;
round: Round;
constructor(
private tournamentService: TournamentService,
private route: ActivatedRoute,
private router: Router,
private titleService: TitleService
) {
}
ngOnInit() {
const tournamentId = this.route.snapshot.paramMap.get('id');
let roundId = Number(this.route.snapshot.paramMap.get('roundId'));
this.tournamentService.getById(Number(tournamentId)).subscribe(data => {
this.tournament = data;
for (let event of this.tournament.events) {
for (let group of event.groups) {
for (let round of group.rounds) {
if (round.id == roundId) {
this.group = group;
this.round = round;
this.titleService.setTitle(`Wedstrijdbriefjes ${this.group.name} ${this.round.name}`);
return;
}
}
}
}
});
}
}

View File

@@ -0,0 +1,82 @@
<form (ngSubmit)="savePlayer()">
<mat-card appearance="outlined">
<mat-card-content>
<div class="row">
<div class="col-md-5">
<mat-form-field appearance="fill">
<mat-label>Achternaam</mat-label>
<input matInput [(ngModel)]="player.lastName" name="lastName" required>
</mat-form-field>
</div>
<div class="col-md-2">
<mat-form-field appearance="fill">
<mat-label>Tussenvoegsel</mat-label>
<input matInput [(ngModel)]="player.middleName" name="middleName">
</mat-form-field>
</div>
<div class="col-md-2">
<mat-form-field appearance="fill">
<mat-label>Voornaam</mat-label>
<input matInput [(ngModel)]="player.firstName" name="firstName" required>
</mat-form-field>
</div>
<div class="col-md-3">
<mat-radio-group [(ngModel)]="player.sex" name="sex" required>
<mat-label>Geslacht</mat-label>
<mat-radio-button value="M">M</mat-radio-button>
<mat-radio-button value="V">V</mat-radio-button>
</mat-radio-group>
</div>
</div>
<div class="row">
<div class="col-md-3">
<mat-form-field appearance="fill">
<mat-label>Telefoon</mat-label>
<input matInput [(ngModel)]="player.phoneNumber" name="phoneNumber" required>
</mat-form-field>
</div>
<div class="col-md-9">
<mat-form-field appearance="fill">
<mat-label>Emailadres</mat-label>
<input matInput [(ngModel)]="player.email" name="email" required>
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-md-3">
<mat-form-field appearance="fill">
<mat-label>Geboortedatum</mat-label>
<input matInput mask="00-00-0000" [showMaskTyped]="true" [dropSpecialCharacters]="false" [(ngModel)]="player.birthday" type="text" name="birthday" required>
<mat-hint>dd-mm-jjjj</mat-hint>
</mat-form-field>
</div>
<div class="col-md-6">
<mat-form-field appearance="fill">
<mat-label>Club</mat-label>
<input matInput [(ngModel)]="player.club" name="club">
</mat-form-field>
</div>
<div class="col-md-3">
<mat-form-field appearance="fill">
<mat-label>Speelsterkte</mat-label>
<mat-select [(ngModel)]="player.strength" name="strength" required>
<mat-option *ngFor="let strengthOption of Strength | keyvalue" [value]="strengthOption.key">
{{ strengthOption.value }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</mat-card-content>
<mat-card-actions>
<button mat-button>
<mat-icon>save</mat-icon>
{{ isEditMode ? 'Bijwerken' : 'Opslaan' }}
</button>
<a mat-button routerLink="/players">
<mat-icon>cancel</mat-icon>
Annuleren
</a>
</mat-card-actions>
</mat-card>
</form>

View File

@@ -0,0 +1,6 @@
mat-form-field {
width: 100%;
}
a, button, mat-radio-button {
margin-right: 1em;
}

View File

@@ -0,0 +1,95 @@
import {Component, OnInit} from '@angular/core';
import {Player, Strength} from "../../model/player";
import {PlayerService} from "../../service/player.service";
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
import {FormGroup, FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
import {MatInput} from "@angular/material/input";
import {MatIcon} from "@angular/material/icon";
import {MatRadioButton, MatRadioGroup} from "@angular/material/radio";
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
import {MatDatepicker, MatDatepickerInput, MatDatepickerToggle} from "@angular/material/datepicker";
import {MatOption, MatSelect} from "@angular/material/select";
import {KeyValuePipe, NgForOf} from "@angular/common";
import {MatAnchor, MatButton} from "@angular/material/button";
import {TitleService} from "../../service/title.service";
import {MatSnackBar} from "@angular/material/snack-bar";
import {NgxMaskDirective, NgxMaskPipe} from "ngx-mask";
@Component({
selector: 'app-player-edit',
standalone: true,
imports: [
FormsModule,
RouterLink,
MatFormField,
MatInput,
MatIcon,
MatHint,
MatLabel,
MatRadioGroup,
MatRadioButton,
MatCard,
MatCardHeader,
MatCardContent,
MatCardActions,
MatDatepickerInput,
MatDatepickerToggle,
MatDatepicker,
MatSelect,
MatOption,
KeyValuePipe,
NgForOf,
MatButton,
MatAnchor,
ReactiveFormsModule,
NgxMaskDirective,
NgxMaskPipe
],
templateUrl: './player-edit.component.html',
styleUrl: './player-edit.component.scss'
})
export class PlayerEditComponent implements OnInit {
player: Player;
isEditMode: boolean = false;
constructor(
private playerService: PlayerService,
private route: ActivatedRoute,
private router: Router,
private titleService: TitleService,
private _snackBar: MatSnackBar,
) {
this.player = new Player();
}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
if (id) {
this.titleService.setTitle("Bewerk Speler");
this.isEditMode = true;
this.playerService.getById(Number(id)).subscribe(data => {
this.player = data;
});
} else {
this.titleService.setTitle("Nieuwe Speler");
}
}
savePlayer() {
if (this.isEditMode) {
this.playerService.update(this.player.id, this.player).subscribe(() => {
this.router.navigate(['/players']);
});
} else {
this.playerService.save(this.player).subscribe({
next: () => this.router.navigate(['/players']),
error: () => this._snackBar.open('Niet alle velden zijn correct gevuld.')
});
}
}
protected readonly Object = Object;
protected readonly Strength = Strength;
}

View File

@@ -0,0 +1,42 @@
<mat-card appearance="outlined">
<!--
<mat-card-header>
<h5>Spelers</h5>
</mat-card-header>
-->
<mat-card-content>
<table class="table table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Naam</th>
<th scope="col">M/V</th>
<th scope="col">Club</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let player of players">
<td class="align-middle">{{ player.id }}</td>
<td class="align-middle">{{ player | fullName }}</td>
<td class="align-middle">{{ player.sex }}</td>
<td class="align-middle">{{ player.club }}</td>
<td class="align-middle">
<a mat-button [routerLink]="['/players/edit', player.id]">
<mat-icon>edit</mat-icon>
Bewerk
</a>
<a mat-button [routerLink]="['/players', player.id, 'registrations']">
<mat-icon>app_registration</mat-icon>
Inschrijvingen
</a>
</td>
</tr>
</tbody>
</table>
<a mat-button routerLink="/players/add">
<mat-icon>person_add</mat-icon>
Nieuwe speler
</a>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,7 @@
a {
margin-right: 1em;
}
td, th {
background-color: transparent !important;
}

View File

@@ -0,0 +1,36 @@
import {Component, OnInit} from '@angular/core';
import {Player} from "../../model/player";
import {PlayerService} from "../../service/player.service";
import {NgFor} from "@angular/common";
import {RouterLink} from "@angular/router";
import {MatAnchor} from "@angular/material/button";
import {MatIcon} from "@angular/material/icon";
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {FullNamePipe} from "../../pipes/fullname-pipe";
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-player-list',
standalone: true,
imports: [NgFor, RouterLink, MatAnchor, MatIcon, MatCard, MatCardHeader, MatCardContent, FullNamePipe],
templateUrl: './player-list.component.html',
styleUrl: './player-list.component.scss'
})
export class PlayerListComponent implements OnInit {
players: Player[];
constructor(
private titleService: TitleService,
private playerService: PlayerService) {
}
ngOnInit() {
this.titleService.setTitle("Spelers");
this.playerService.getAll().subscribe(data => {
this.players = data;
});
}
protected readonly Player = Player;
}

View File

@@ -0,0 +1,50 @@
@if (player && tournamentRegistrations && allPlayers) {
<mat-card appearance="outlined">
<mat-card-content>
<mat-card *ngFor="let tournamentRegistration of getTournamentRegistrations()" appearance="outlined" class="mb-3">
<mat-card-header>
<h6>{{ tournamentRegistration.name }}</h6>
</mat-card-header>
<mat-card-content>
<ng-container *ngFor="let eventRegistration of tournamentRegistration.events">
<div class="row event-row">
<div class="col-md-2">
<mat-checkbox [disabled]="!tournamentRegistration.editable" [(ngModel)]="eventRegistration.registered" (change)="updateModelWhenEventChecked(eventRegistration, $event)" name="registered">
{{ EventRegistration.getType(eventRegistration.type) }}
</mat-checkbox>
</div>
<div class="col-md-4">
<ng-container *ngIf="eventRegistration.doublesEvent">
<mat-form-field appearance="fill">
<mat-label>Partner</mat-label>
<mat-select [value]="eventRegistration.partner" [disabled]="!tournamentRegistration.editable || !eventRegistration.registered" [(ngModel)]="eventRegistration.partner">
<mat-option>Geen</mat-option>
<mat-option *ngFor="let player of getRelevantPlayers(eventRegistration.type)" [value]="player.id">
{{ player | fullName }}
</mat-option>
</mat-select>
</mat-form-field>
</ng-container>
</div>
<div class="col-6"></div>
</div>
</ng-container>
</mat-card-content>
<mat-card-actions *ngIf="tournamentRegistration.editable">
<button mat-button (click)="saveRegistration(tournamentRegistration, $event)" [disabled]="waitingForBackend">
<mat-icon>save</mat-icon>
Opslaan
</button>
<a mat-button routerLink="/players">
<mat-icon>cancel</mat-icon>
Annuleren
</a>
</mat-card-actions>
</mat-card>
<button mat-button (click)="this.showAll = true" *ngIf="!this.showAll">
<mat-icon>search</mat-icon>
Toon oude toernooien
</button>
</mat-card-content>
</mat-card>
}

View File

@@ -0,0 +1,9 @@
.event-row {
height: 5em;
}
mat-form-field {
width: 100%;
}
a, button {
margin-right: 1em;
}

View File

@@ -0,0 +1,125 @@
import {Component, OnInit} from '@angular/core';
import {Player} from "../../model/player";
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
import {PlayerService} from "../../service/player.service";
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
import {MatFormField, MatLabel} from "@angular/material/form-field";
import {MatInput} from "@angular/material/input";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {RegistrationService} from "../../service/registration.service";
import {KeyValuePipe, NgFor, NgIf} from "@angular/common";
import {MatCheckbox, MatCheckboxChange} from "@angular/material/checkbox";
import {EventRegistration, TournamentRegistration} from "../../model/tournamentRegistration";
import {MatOption} from "@angular/material/core";
import {MatSelect} from "@angular/material/select";
import {MatIcon} from "@angular/material/icon";
import {MatAnchor, MatButton, MatFabButton} from "@angular/material/button";
import {MatSnackBar} from "@angular/material/snack-bar";
import {FullNamePipe} from "../../pipes/fullname-pipe";
import {Tournament} from "../../model/tournament";
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-player-registrations',
standalone: true,
imports: [
MatCard,
MatCardContent,
MatCardHeader,
MatFormField,
MatInput,
MatLabel,
NgFor,
ReactiveFormsModule,
FormsModule,
MatCheckbox,
NgIf,
MatCardActions,
RouterLink,
KeyValuePipe,
MatOption,
MatSelect,
MatIcon,
MatFabButton,
MatButton,
MatAnchor,
FullNamePipe
],
providers: [
FullNamePipe
],
templateUrl: './player-registrations.component.html',
styleUrl: './player-registrations.component.scss'
})
export class PlayerRegistrationsComponent implements OnInit {
player: Player;
tournamentRegistrations: TournamentRegistration[];
allPlayers: Player[];
showAll: boolean = false;
waitingForBackend: boolean = false;
constructor(
private _snackBar: MatSnackBar,
private playerService: PlayerService,
private registrationService: RegistrationService,
private route: ActivatedRoute,
private router: Router,
private titleService: TitleService,
private fullNamePipe: FullNamePipe
) {}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.playerService.getById(Number(id)).subscribe(data => {
this.player = data;
this.titleService.setTitle(`Inschrijvingen van ${this.fullNamePipe.transform(this.player)}`);
});
this.registrationService.getTournamentRegistrationsByPlayerId(Number(id)).subscribe(data => {
this.tournamentRegistrations = data;
})
this.playerService.getAll().subscribe(data => {
this.allPlayers = data;
});
}
saveRegistration(tournamentRegistration: TournamentRegistration, event: MouseEvent) {
this.waitingForBackend = true;
this.registrationService.saveTournamentRegistrations(tournamentRegistration, this.player.id).subscribe(data => {
this.waitingForBackend = false;
this._snackBar.open('Registratie opgeslagen.');
});
}
updateModelWhenEventChecked(eventRegistration: EventRegistration, event: MatCheckboxChange) {
if (event.checked) {
eventRegistration.player = this.player.id;
} else {
eventRegistration.player = undefined;
}
}
getRelevantPlayers(type: string): Player[] {
if ( (this.player.sex == 'M' && type == 'HD') ||
(this.player.sex == 'V' && type == 'GD')
) {
return this.allPlayers.filter(player => player.sex == 'M' && player.id != this.player.id);
} else {
return this.allPlayers.filter(player => player.sex == 'V' && player.id != this.player.id);
}
}
getTournamentRegistrations(): TournamentRegistration[] {
if (this.showAll) {
return this.tournamentRegistrations;
} else {
return this.tournamentRegistrations.filter(t => (t.status == 'UPCOMING' || t.status == 'ONGOING'));
}
}
protected readonly Player = Player;
protected readonly EventRegistration = EventRegistration;
protected readonly TournamentRegistration = TournamentRegistration;
}

View File

@@ -0,0 +1,92 @@
@if (round) {
<h6>{{ group.name }}, {{ round.name }}:</h6>
@if (round.status != 'FINISHED') {
<table class="table table-sm m-4 wide w-100">
<tbody>
<tr *ngFor="let match of round.matches">
<td class="align-middle" style="width: 45%;">{{ match.team1 | teamText }}</td>
<td class="align-middle w-sep">-</td>
<td class="align-middle" style="width: 45%;">{{ match.team2 | teamText }}</td>
<td class="align-middle w-sep"></td>
</tr>
@if (round.drawnOut) {
<tr>
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
</tr>
}
</tbody>
</table>
} @else {
<table class="table table-sm m-4 wide w-100">
<tbody>
<tr *ngFor="let match of round.matches">
<td class="align-middle" style="width: 30%;">
@if (event.doublesEvent) {
{{ match.team1.player1 | fullName }} /<br>{{ match.team1.player2 | fullName }}
} @else {
{{ match.team1.player1 | fullName }}
}
<td class="align-middle w-sep">-</td>
<td class="align-middle" style="width: 30%;">
@if (event.doublesEvent) {
{{ match.team2.player1 | fullName }} /<br>{{ match.team2.player2 | fullName }}
} @else {
{{ match.team2.player1 | fullName }}
}
</td>
<td class="align-middle" style="width: 35%;">
<div class="row result align-items-center">
<span *ngFor="let game of match.games" class="col-3">{{ game.score1 }}-{{ game.score2 }}</span>
@if (match.games.length == 2) {
<span class="col-3"></span>
}
</div>
</td>
</tr>
@if (round.drawnOut) {
<tr>
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
</tr>
}
</tbody>
</table>
}
@if (round.status == 'FINISHED' || prevRound) {
@if (group.status == 'FINISHED') {
<h6>Eindstand:</h6>
} @else if (round.status == 'FINISHED') {
<h6>Stand na {{ round.name }}:</h6>
} @else {
<h6>Stand na {{ prevRound?.name }}:</h6>
}
<table class="table table-sm w-100 m-4">
<thead>
<tr>
<th>#</th>
<th>
@if (event.doublesEvent) {
Team
} @else {
Speler
}
</th>
<th>Gespeeld</th>
<th>Punten/W</th>
<th>Games/W</th>
<th>#/W</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr *ngFor="let entry of round.standings.entries">
<td class="align-middle">{{ entry.position }}</td>
<td class="align-middle">{{ entry.team | teamText }}</td>
<td class="align-middle">{{ entry.played }}</td>
<td class="align-middle">{{ entry.points / entry.played | number: '1.0-2' }}</td>
<td class="align-middle">{{ (entry.gamesWon - entry.gamesLost) / entry.played | number: '1.0-2' }}</td>
<td class="align-middle">{{ (entry.pointsWon - entry.pointsLost) / entry.played | number: '1.0-2' }}</td>
</tr>
</tbody>
</table>
}
}

View File

@@ -0,0 +1,11 @@
td.w-team {
width: 30%;
}
td.w-sep {
width: 5%;
}
td.w-fill {
width: 35%;
}

View File

@@ -0,0 +1,79 @@
import {Component, OnInit} from '@angular/core';
import {Tournament} from "../../model/tournament";
import {Event} from "../../model/event";
import {Group} from "../../model/group";
import {Round} from "../../model/round";
import {TournamentService} from "../../service/tournament.service";
import {ActivatedRoute, Router} from "@angular/router";
import {DecimalPipe, NgForOf} from "@angular/common";
import {TeamPipe} from "../../pipes/team-pipe";
import {FullNamePipe} from "../../pipes/fullname-pipe";
import {MatButton, MatIconButton} from "@angular/material/button";
import {MatIcon} from "@angular/material/icon";
import {MatMenu, MatMenuItem} from "@angular/material/menu";
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-round-overview',
standalone: true,
imports: [
NgForOf,
TeamPipe,
DecimalPipe,
MatButton,
MatIcon,
MatIconButton,
MatMenu,
MatMenuItem,
FullNamePipe
],
providers: [
TeamPipe,
FullNamePipe
],
templateUrl: './round-overview.component.html',
styleUrl: './round-overview.component.scss'
})
export class RoundOverviewComponent implements OnInit {
tournament: Tournament;
event: Event;
group: Group;
round: Round;
prevRound: Round | undefined;
constructor(
private tournamentService: TournamentService,
private route: ActivatedRoute,
private router: Router,
private titleService: TitleService
) {
}
ngOnInit() {
this.titleService.setTitle("Rondeoverzicht");
const tournamentId = this.route.snapshot.paramMap.get('id');
let roundId = Number(this.route.snapshot.paramMap.get('roundId'));
this.tournamentService.getById(Number(tournamentId)).subscribe(data => {
this.tournament = data;
for (let event of this.tournament.events) {
for (let group of event.groups) {
let roundIndex = 0;
this.prevRound = undefined;
for (let round of group.rounds) {
roundIndex++;
if (round.id == roundId) {
this.event = event;
this.group = group;
this.round = round;
return;
}
this.prevRound = round;
}
}
}
});
}
}

View File

@@ -0,0 +1,45 @@
<mat-card appearance="outlined" *ngIf="tournament">
<mat-card-header>
<h5>Indeling voor {{ tournament.name }}</h5>
</mat-card-header>
<mat-card-content>
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
<mat-card-header>
<h6>Indeling {{ TournamentEvent.getType(event.type) }}</h6>
</mat-card-header>
<mat-card-content>
<mat-accordion multi="true">
<mat-expansion-panel *ngFor="let group of event.groups">
<mat-expansion-panel-header>
<mat-panel-title>
{{ group.name }}&nbsp;<span class="badge text-bg-success">{{ group.teams.length }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
<thead class="thead-dark">
<tr>
<th scope="col" class="w-20">Naam</th>
<th scope="col" class="w-20">Club</th>
<th scope="col" class="w-10">Speelsterkte</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Partner</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Club</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-10">Speelsterkte</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let team of group.teams">
<td class="align-middle">{{ team.player1 | fullName }}</td>
<td class="align-middle">{{ team.player1.club }}</td>
<td class="align-middle">{{ getStrength(team.player1.strength.valueOf()) }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2 | fullName }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2.club }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ getStrength(team.player2.strength.valueOf()) }}</td>
</tr>
</tbody>
</table>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content>
</mat-card>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,3 @@
td, th {
background-color: transparent !important;
}

View File

@@ -0,0 +1,65 @@
import {Component, OnInit} from '@angular/core';
import {Tournament} from "../../model/tournament";
import {TournamentService} from "../../service/tournament.service";
import {ActivatedRoute, Router} from "@angular/router";
import {TournamentDivision} from "../../model/tournamentDivision";
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {KeyValuePipe, NgForOf, NgIf} from "@angular/common";
import {
MatAccordion,
MatExpansionPanel,
MatExpansionPanelHeader,
MatExpansionPanelTitle
} from "@angular/material/expansion";
import {Event} from "../../model/event";
import {Player, Strength} from "../../model/player";
import {EventDivision} from "../../model/eventDivision";
import {FullNamePipe} from "../../pipes/fullname-pipe";
@Component({
selector: 'app-tournament-divide',
standalone: true,
imports: [
MatCard,
MatCardHeader,
NgIf,
MatCardContent,
MatExpansionPanel,
MatExpansionPanelTitle,
MatExpansionPanelHeader,
NgForOf,
KeyValuePipe,
FullNamePipe,
MatAccordion
],
templateUrl: './tournament-divide.component.html',
styleUrl: './tournament-divide.component.scss'
})
export class TournamentDivideComponent implements OnInit {
tournament?: Tournament;
constructor(
private tournamentService: TournamentService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.tournamentService.getById(Number(id)).subscribe(data => {
this.tournament = data;
});
// this.tournamentService.getDivision(Number(id)).subscribe(data => {
// this.tournamentDivision = data;
// });
}
protected readonly TournamentEvent = Event;
getStrength(strength: string) {
for (let [key, value] of Object.entries(Strength)) {
if (key == strength) return value;
}
return "";
}
}

View File

@@ -0,0 +1,40 @@
<mat-card appearance="outlined" *ngIf="tournament">
<mat-card-header>
<h5>Loting voor {{ tournament.name }}</h5>
</mat-card-header>
<mat-card-content>
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
<mat-card-header>
<h6>Loting {{ TournamentEvent.getType(event.type) }}</h6>
</mat-card-header>
<mat-card-content>
<mat-accordion multi="true">
<mat-expansion-panel *ngFor="let group of this.event.groups">
<mat-expansion-panel-header>
<mat-panel-title>
{{ group.name }}&nbsp;<span class="badge text-bg-success">{{ group.teams.length }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
<thead class="thead-dark">
<tr>
<th scope="col" class="w-25">Team 1</th>
<th scope="col" style="width: 5%">-</th>
<th scope="col" class="w-25">Team 2</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let match of group.rounds[0].matches">
<td class="align-middle">{{ match.team1 | teamText }}</td>
<td class="align-middle">-</td>
<td class="align-middle">{{ match.team2 | teamText }}</td>
</tr>
</tbody>
</table>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content>
</mat-card>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,3 @@
td, th {
background-color: transparent !important;
}

View File

@@ -0,0 +1,58 @@
import {Component, OnInit} from '@angular/core';
import {Tournament} from "../../model/tournament";
import {TournamentService} from "../../service/tournament.service";
import {ActivatedRoute, Router} from "@angular/router";
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {NgForOf, NgIf} from "@angular/common";
import {
MatAccordion,
MatExpansionPanel,
MatExpansionPanelHeader,
MatExpansionPanelTitle
} from "@angular/material/expansion";
import {Event} from "../../model/event";
import {TeamPipe} from "../../pipes/team-pipe";
import {FullNamePipe} from "../../pipes/fullname-pipe";
@Component({
selector: 'app-tournament-draw',
standalone: true,
imports: [
FullNamePipe,
MatCard,
NgIf,
MatCardContent,
MatCardHeader,
MatExpansionPanel,
MatExpansionPanelHeader,
MatExpansionPanelTitle,
NgForOf,
TeamPipe,
MatAccordion
],
providers: [
FullNamePipe,
TeamPipe
],
templateUrl: './tournament-draw.component.html',
styleUrl: './tournament-draw.component.scss'
})
export class TournamentDrawComponent implements OnInit {
tournament: Tournament;
constructor(
private tournamentService: TournamentService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.tournamentService.draw(Number(id)).subscribe(data => {
this.tournament = data;
})
}
protected readonly TournamentEvent = Event;
}

View File

@@ -0,0 +1,85 @@
@if (tournament) {
<form (ngSubmit)="saveTournament()">
<mat-card appearance="outlined">
<!--
<mat-card-header>
<h5>{{ isEditMode ? 'Bewerk toernooi' : 'Toevoegen toernooi' }}</h5>
</mat-card-header>
-->
<mat-card-content>
<div class="row">
<div class="col-md-6">
<mat-form-field appearance="fill">
<mat-label>Naam</mat-label>
<input matInput [(ngModel)]="tournament.name" name="name" required>
</mat-form-field>
</div>
<div class="col-md-3"></div>
</div>
<div class="row">
<div class="col-md-3">
<mat-form-field appearance="fill">
<mat-label>Datum</mat-label>
<input matInput mask="00-00-0000" [showMaskTyped]="true" [dropSpecialCharacters]="false" [(ngModel)]="tournament.date" name="date" required>
<mat-hint>dd-mm-jjjj</mat-hint>
</mat-form-field>
</div>
<div class="col-md-9"></div>
</div>
<div class="row">
<div class="col-md-4">
<label id="max-events-label">Max. aantal onderdelen per inschrijving</label>
<mat-radio-group [(ngModel)]="tournament.maxEvents" aria-labelledby="max-events-label" name="maxEvents" required>
<mat-radio-button [value]="2">2</mat-radio-button>
<mat-radio-button [value]="3">3</mat-radio-button>
</mat-radio-group>
</div>
</div>
<div class="row">
<div class="col-md-2">
<mat-form-field appearance="fill">
<mat-label>Kosten 1 onderdeel</mat-label>
<input matInput name="costsPerEvent1" type="number" min="0" step="0.5" [(ngModel)]="tournament.costsPerEvent[0]" required>
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-md-2">
<mat-form-field appearance="fill">
<mat-label>Kosten 2 onderdelen</mat-label>
<input matInput name="costsPerEvent2" type="number" min="0" step="0.5" [(ngModel)]="tournament.costsPerEvent[1]" required>
</mat-form-field>
</div>
</div>
@if (tournament.maxEvents == 3) {
<div class="row">
<div class="col-md-2">
<mat-form-field appearance="fill">
<mat-label>Kosten 3 onderdelen</mat-label>
<input matInput name="costsPerEvent3" type="number" min="0" step="0.5" [(ngModel)]="tournament.costsPerEvent[2]" required>
</mat-form-field>
</div>
</div>
}
<div class="row">
<div class="col-md-2">
<mat-form-field appearance="fill">
<mat-label>Aantal banen</mat-label>
<input matInput name="courts" type="number" min="1" [(ngModel)]="tournament.courts" required>
</mat-form-field>
</div>
</div>
</mat-card-content>
<mat-card-actions>
<button mat-button>
<mat-icon>save</mat-icon>
{{ isEditMode ? 'Bijwerken' : 'Opslaan' }}
</button>
<a mat-button routerLink="/tournaments">
<mat-icon>cancel</mat-icon>
Annuleren
</a>
</mat-card-actions>
</mat-card>
</form>
}

View File

@@ -0,0 +1,6 @@
mat-form-field {
width: 100%;
}
a, button {
margin-right: 1em;
}

View File

@@ -0,0 +1,97 @@
import {Component, OnInit} from '@angular/core';
import {FormsModule} from "@angular/forms";
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
import {Tournament} from "../../model/tournament";
import {TournamentService} from "../../service/tournament.service";
import {MatAnchor, MatButton} from "@angular/material/button";
import {MatIcon} from "@angular/material/icon";
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
import {MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
import {MatInput} from "@angular/material/input";
import {MatRadioButton, MatRadioGroup} from "@angular/material/radio";
import {CurrencyPipe, NgForOf, registerLocaleData} from "@angular/common";
import nl from "@angular/common/locales/nl";
import {TitleService} from "../../service/title.service";
import {NgxMaskDirective, NgxMaskPipe} from "ngx-mask";
registerLocaleData(nl);
@Component({
selector: 'app-tournament-edit',
standalone: true,
imports: [
FormsModule,
RouterLink,
MatAnchor,
MatButton,
MatIcon,
MatCard,
MatCardHeader,
MatCardContent,
MatFormField,
MatInput,
MatLabel,
MatCardActions,
MatRadioButton,
MatRadioGroup,
NgForOf,
CurrencyPipe,
MatHint,
NgxMaskDirective,
NgxMaskPipe
],
providers: [
CurrencyPipe
],
templateUrl: './tournament-edit.component.html',
styleUrl: './tournament-edit.component.scss'
})
export class TournamentEditComponent implements OnInit {
tournament: Tournament;
isEditMode: boolean = false;
constructor(
private tournamentService: TournamentService,
private currencyPipe: CurrencyPipe,
private route: ActivatedRoute,
private router: Router,
private titleService: TitleService
) {
this.tournament = new Tournament();
}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
if (id) {
this.titleService.setTitle("Bewerk Toernooi");
this.isEditMode = true;
this.tournamentService.getById(Number(id)).subscribe(data => {
this.tournament = data;
})
} else {
this.titleService.setTitle("Nieuw Toernooi");
}
}
saveTournament() {
if (this.isEditMode) {
this.tournamentService.update(this.tournament.id, this.tournament).subscribe(() => {
this.router.navigate(['/tournaments']);
});
} else {
this.tournamentService.save(this.tournament).subscribe(() => {
this.router.navigate(['/tournaments']);
});
}
}
protected readonly Array = Array;
setCostsPerEvent(number: number, $event: any) {
let value : string = String($event);
value = value.replace( /^\D+/g, ''); // replace all leading non-digits with nothing
this.tournament.costsPerEvent[number] = Number(value);
}
}

View File

@@ -0,0 +1,47 @@
<mat-card appearance="outlined">
<!--
<mat-card-header>
<h5>Toernooien</h5>
</mat-card-header>
-->
<mat-card-content>
<table class="table table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Naam</th>
<th scope="col">Datum</th>
<th scope="col">Status</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tournament of tournaments">
<td class="align-middle">{{ tournament.id }}</td>
<td class="align-middle"><a [routerLink]="['/tournaments', tournament.id, 'manage']">{{ tournament.name }}</a></td>
<td class="align-middle">{{ tournament.date }}</td>
<td class="align-middle">{{ Tournament.getStatus(tournament) }}</td>
<td class="align-middle">
<a mat-button [routerLink]="['/tournaments', tournament.id, 'edit']" *ngIf="Tournament.getStatus(tournament) != 'Afgerond'">
<mat-icon>edit</mat-icon>
Bewerk
</a>
<a mat-button [routerLink]="['/tournaments', tournament.id, 'registrations']">
<mat-icon>group</mat-icon>
Inschrijvingen
</a>
<a mat-button (click)="clearDraw(tournament)" *ngIf="Tournament.getStatus(tournament) == 'Geloot'">
<mat-icon>safety_divider</mat-icon>
Loting wissen
</a>
</td>
</tr>
</tbody>
</table>
<a mat-button routerLink="/tournaments/add">
<mat-icon>add</mat-icon>
Nieuw toernooi
</a>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,7 @@
a {
margin-right: 1em;
}
td, th {
background-color: transparent !important;
}

View File

@@ -0,0 +1,59 @@
import {AfterContentChecked, AfterContentInit, Component, EventEmitter, OnInit, Output} from '@angular/core';
import {NgFor, NgIf} from "@angular/common";
import {RouterLink} from "@angular/router";
import {Tournament} from "../../model/tournament";
import {TournamentService} from "../../service/tournament.service";
import {MatAnchor, MatButton, MatIconButton} from "@angular/material/button";
import {MatIcon} from "@angular/material/icon";
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {
MatCell,
MatCellDef,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef,
MatHeaderRow, MatRow,
MatTable, MatTableModule
} from "@angular/material/table";
import {MatMenuTrigger} from "@angular/material/menu";
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-tournament-list',
standalone: true,
imports: [
NgFor, RouterLink, NgIf, MatAnchor, MatIcon, MatCard, MatCardHeader, MatCardContent, MatButton, MatTable, MatColumnDef, MatHeaderCell, MatHeaderCellDef, MatCell, MatCellDef, MatHeaderRow, MatRow, MatTableModule, MatIconButton, MatMenuTrigger
],
templateUrl: './tournament-list.component.html',
styleUrl: './tournament-list.component.scss'
})
export class TournamentListComponent implements OnInit, AfterContentChecked {
tournaments: Tournament[];
constructor(
private tournamentService: TournamentService,
private titleService: TitleService
) {}
ngOnInit() {
this.tournamentService.getAll().subscribe(data => {
this.tournaments = data;
});
}
ngAfterContentChecked() {
this.titleService.setTitle("Toernooien");
}
protected readonly Tournament = Tournament;
clearDraw(tournament: Tournament) {
this.tournamentService.clearDraw(tournament.id).subscribe(data => {
this.tournamentService.getAll().subscribe(data => {
this.tournaments = data;
});
})
}
}

View File

@@ -0,0 +1,429 @@
@if (tournament) {
<mat-card appearance="outlined">
<!--
<mat-card-header>
<h5>{{ tournament.name }}</h5>
</mat-card-header>
-->
<mat-card-content>
<mat-tab-group animationDuration="0ms" disableRipple="true">
@if (tournament.status == 'UPCOMING') {
<mat-tab>
<ng-template mat-tab-label>
<mat-icon>playlist_add_check</mat-icon>
&nbsp;Validaties
<button mat-icon-button [matMenuTriggerFor]="upcomingTournamentMenu" class="menu-button m-3">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #upcomingTournamentMenu="matMenu">
<button mat-menu-item (click)="divideTournament()">
<mat-icon>play_arrow</mat-icon>
Deel toernooi in
</button>
</mat-menu>
</ng-template>
<app-tournament-validate></app-tournament-validate>
</mat-tab>
}
@if (tournament.status == 'DIVIDED') {
<mat-tab>
<ng-template mat-tab-label>
<mat-icon>safety_divider</mat-icon>
&nbsp;Indeling
<button mat-icon-button [matMenuTriggerFor]="dividedTournamentMenu" class="menu-button m-3">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #dividedTournamentMenu="matMenu">
<button mat-menu-item (click)="clearDivision()">
<mat-icon>highlight_remove</mat-icon>
Indeling wissen
</button>
<button mat-menu-item (click)="drawTournament()">
<mat-icon>safety_divider</mat-icon>
1e ronde loten
</button>
</mat-menu>
</ng-template>
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
<mat-card-header>
<h6>Indeling {{ TournamentEvent.getType(event.type) }}</h6>
</mat-card-header>
<mat-card-content>
<mat-accordion multi="true">
<mat-expansion-panel *ngFor="let group of event.groups">
<mat-expansion-panel-header>
<mat-panel-title>
{{ group.name }}&nbsp;<span class="badge text-bg-success">{{ group.teams.length }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
<thead class="thead-dark">
<tr>
<th scope="col" class="w-20">Naam</th>
<th scope="col" class="w-20">Club</th>
<th scope="col" class="w-10">Speelsterkte</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Partner</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Club</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-10">Speelsterkte</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let team of group.teams">
<td class="align-middle">{{ team.player1 | fullName }}</td>
<td class="align-middle">{{ team.player1.club }}</td>
<td class="align-middle">{{ getStrength(team.player1.strength.valueOf()) }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2 | fullName }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2?.club }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ getStrength(team.player2?.strength?.valueOf()) }}</td>
</tr>
</tbody>
</table>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content>
</mat-card>
</mat-tab>
}
@if (tournament.status == 'ONGOING') {
<mat-tab>
<ng-template mat-tab-label>
<mat-icon>play_arrow</mat-icon>
&nbsp;Actieve wedstrijden
&nbsp;
@if (this.activeMatches().length > 0) {
<span class="badge text-bg-success">{{ this.activeMatches().length }}</span>
}
</ng-template>
@if (this.activeMatches().length > 0) {
<table class="table table-hover w-100 m-4">
<thead>
<tr>
<th colspan="3">Wedstrijd</th>
<th>Onderdeel/Ronde</th>
<th>Start</th>
<th>Baan</th>
<th></th>
</tr>
</thead>
<tbody>
@for (activeMatch of this.activeMatches(); track activeMatch.match.id) {
<tr>
<td class="align-middle">{{ activeMatch.match.team1 | teamText }}</td>
<td class="align-middle">-</td>
<td class="align-middle">{{ activeMatch.match.team2 | teamText }}</td>
<td class="align-middle">{{ activeMatch.group.name }} {{ activeMatch.round.name }}</td>
<td class="align-middle">{{ activeMatch.match.startTime | date: 'HH:mm' }}</td>
<td class="align-middle">{{ activeMatch.match.court }}</td>
<td nowrap class="align-middle">
<button class="align-baseline" mat-button (click)="editResult(activeMatch.match, activeMatch.group, activeMatch.round)">
<mat-icon>edit</mat-icon>
Uitslag invoeren
</button>
<button mat-icon-button [matMenuTriggerFor]="activeMatchMenu" [matMenuTriggerData]="{ match: activeMatch.match }" class="menu-button">
<mat-icon>more_vert</mat-icon>
</button>
</td>
</tr>
}
</tbody>
</table>
<mat-menu #activeMatchMenu="matMenu">
<ng-template matMenuContent let-match="match">
<button mat-button (click)="stopMatch(match)">
<mat-icon>stop</mat-icon>
Wedstrijd stoppen
</button>
</ng-template>
</mat-menu>
} @else {
<h6 class="mt-3">Geen actieve wedstrijden</h6>
}
</mat-tab>
}
@if (tournament.status == 'ONGOING' || tournament.status == 'DRAWN') {
<mat-tab>
<ng-template mat-tab-label>
<mat-icon>list</mat-icon>
&nbsp;Onderdelen
</ng-template>
<mat-tab-group animationDuration="0ms" disableRipple="true">
<ng-container *ngFor="let event of tournament.events">
<ng-container *ngFor="let group of event.groups">
<mat-tab label="{{group.id}}">
<ng-template mat-tab-label>
<!--<mat-icon>list</mat-icon>&nbsp;-->
{{ group.name }}
&nbsp;
@if (getActiveMatchCountForGroup(group) > 0) {
<span class="badge text-bg-success">{{ getActiveMatchCountForGroup(group) }}</span>
}
@if (group.status != 'FINISHED' && groupOnlyHasFinishedRounds(group)) {
<button mat-icon-button [matMenuTriggerFor]="groupMenu" class="menu-button m-3">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #groupMenu="matMenu">
<button mat-menu-item (click)="finishGroup(group)">
<mat-icon>check</mat-icon>
Onderdeel afsluiten
</button>
</mat-menu>
}
</ng-template>
<mat-tab-group
animationDuration="0ms"
disableRipple="true"
[(selectedIndex)]="activeRoundTab"
(selectedTabChange)="onRoundTabChange($event)">
<ng-container *ngFor="let round of group.rounds; index as roundIndex">
<mat-tab label="{{round.id}}">
<ng-template mat-tab-label>
<mat-icon>{{ getRoundIcon(round.status) }}</mat-icon>
&nbsp;{{ round.name }}
<button mat-icon-button [matMenuTriggerFor]="activeRoundMenu" class="menu-button m-3">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #activeRoundMenu="matMenu">
@if (round.status == 'NOT_STARTED') {
<button mat-menu-item (click)="startRound(round)">
<mat-icon>play_arrow</mat-icon>
Ronde starten
</button>
<button mat-menu-item (click)="printMatchSheets(round)">
<mat-icon>print</mat-icon>
Wedstrijdbriefjes printen
</button>
}
<button mat-menu-item (click)="printRoundOverview(round)">
<mat-icon>print</mat-icon>
Rondeoverzicht printen
</button>
@if (round.status == 'IN_PROGRESS' && checkRoundComplete(round)) {
<button mat-menu-item (click)="finishRound(round)">
<mat-icon>check</mat-icon>
Ronde afsluiten
</button>
}
@if (group.status != 'FINISHED' && round.status == 'FINISHED' && (roundIndex + 1) == group.rounds.length) {
<button mat-menu-item (click)="newRound(group)">
<mat-icon>playlist_add</mat-icon>
Nieuwe ronde
</button>
}
</mat-menu>
</ng-template>
<h6 class="mt-3">Wedstrijden</h6>
@if (round.status == 'NOT_STARTED') {
<table class="table table-hover m-4 wide w-100">
<tbody>
<tr *ngFor="let match of round.matches">
<td class="align-middle w-team">{{ match.team1 | teamText }}</td>
<td class="align-middle w-sep">-</td>
<td class="align-middle w-team">{{ match.team2 | teamText }}</td>
<td class="align-middle w-fill"></td>
</tr>
@if (round.drawnOut) {
<tr>
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
</tr>
}
</tbody>
</table>
} @else if (round.status == 'IN_PROGRESS') {
<table class="table table-hover m-4 wide w-100">
<tbody>
<tr *ngFor="let match of round.matches">
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 1}">{{ match.team1 | teamText }}</td>
<td class="align-middle w-sep">-</td>
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 2}">{{ match.team2 | teamText }}</td>
<td class="align-middle w-fill">
@if (match.status == 'NOT_STARTED') {
<button mat-button (click)="startMatch(match)">
<mat-icon>play_arrow</mat-icon>
Wedstrijd starten
</button>
} @else if (match.status == 'IN_PROGRESS') {
<button mat-button (click)="editResult(match, group, round)">
<mat-icon>edit</mat-icon>
Uitslag invoeren
</button>
<button mat-button (click)="stopMatch(match)">
<mat-icon>stop</mat-icon>
Wedstrijd stoppen
</button>
} @else if (match.status == 'FINISHED') {
<div class="row result align-items-center">
<span *ngFor="let game of match.games" class="col-2">{{ game.score1 }}-{{ game.score2 }}</span>
@if (match.games.length == 2) {
<span class="col-2"></span>
}
<button mat-icon-button [matMenuTriggerFor]="finishedMatchMenu" class="menu-button m-3">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #finishedMatchMenu="matMenu">
<button mat-menu-item (click)="editResult(match, group, round)">
<mat-icon>edit</mat-icon>
Uitslag bewerken
</button>
</mat-menu>
</div>
}
</td>
</tr>
@if (round.drawnOut) {
<tr>
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
</tr>
}
</tbody>
</table>
} @else if (round.status == 'FINISHED') {
<table class="table table-hover m-4 wide {{ this.groupIsDoublesType(group) ? 'w-100' : 'w-100' }}">
<tbody>
<tr *ngFor="let match of round.matches">
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 1}">{{ match.team1 | teamText }}</td>
<td class="align-middle w-sep">-</td>
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 2}">{{ match.team2 | teamText }}</td>
<td class="align-middle w-fill">
<div class="row result align-items-center">
<span *ngFor="let game of match.games" class="col-2">{{ game.score1 }}-{{ game.score2 }}</span>
@if (match.games.length == 2) {
<span class="col-2"></span>
}
</div>
</td>
</tr>
@if (round.drawnOut) {
<tr>
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
</tr>
}
</tbody>
</table>
}
<h6 class="mt-3">Stand</h6>
<table class="table w-75 m-4">
<caption>Tussen haakjes de gemiddelden per gespeelde wedstrijd</caption>
<thead>
<tr>
<th>#</th>
<th>
@if (event.doublesEvent) {
Team
} @else {
Speler
}
</th>
<th>Gespeeld</th>
<th>Punten</th>
<th>Games</th>
<th>Wedstrijdpunten</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr *ngFor="let entry of getStandingsForRound(round, group).entries">
<td class="align-middle">{{ entry.position }}</td>
<td class="align-middle">{{ entry.team | teamText }}</td>
<td class="align-middle">{{ entry.played }}</td>
<td class="align-middle">
@if (entry.played > 0 ) {
{{ entry.points }} ({{ entry.points / entry.played | number: '1.0-2' }})
}
</td>
<td class="align-middle">
@if (entry.played > 0 ) {
{{ entry.gamesWon }}-{{ entry.gamesLost}} ({{ (entry.gamesWon - entry.gamesLost) / entry.played | number: '1.0-2' }})
}
</td>
<td class="align-middle">
@if (entry.played > 0 ) {
{{ entry.pointsWon }}-{{ entry.pointsLost }} ({{ (entry.pointsWon - entry.pointsLost) / entry.played | number: '1.0-2' }})
}
</td>
</tr>
</tbody>
</table>
</mat-tab>
</ng-container>
</mat-tab-group>
</mat-tab>
</ng-container>
</ng-container>
</mat-tab-group>
</mat-tab>
}
@if (tournament.status == 'ONGOING' || tournament.status == 'DRAWN') {
<mat-tab>
<ng-template mat-tab-label>
<mat-icon>settings</mat-icon>
&nbsp;Beheer
</ng-template>
<mat-tab-group animationDuration="0ms" disableRipple="true">
<mat-tab>
<ng-template mat-tab-label>
<mat-icon>group</mat-icon>
&nbsp;Spelerslijst
</ng-template>
<table class="table table-hover w-75 m-4">
<thead>
<tr>
<th>Naam</th>
<th>Onderdelen</th>
<th>Kosten</th>
<th>Betaald</th>
<th>Aanwezig</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tournamentPlayer of tournament.tournamentPlayers">
<td>{{ tournamentPlayer.name }}</td>
<td>
<ng-container *ngFor="let event of tournamentPlayer.events">
{{ event }}&nbsp;
</ng-container>
</td>
<td>
{{ tournament.costsPerEvent[tournamentPlayer.events.length - 1] | currency:'EUR':'symbol':'1.2-2':'nl' }}
</td>
<td>
<mat-slide-toggle [(ngModel)]="tournamentPlayer.paid" (change)="playerPaid($event, tournamentPlayer.playerId)">
@if (tournamentPlayer.paid) {
Betaald
} @else {
Nog niet betaald
}
</mat-slide-toggle>
</td>
<td>
<mat-slide-toggle [(ngModel)]="tournamentPlayer.present" (change)="playerPresent($event, tournamentPlayer.playerId)">
@if (tournamentPlayer.present) {
Aanwezig
} @else {
Nog niet aanwezig
}
</mat-slide-toggle>
</td>
</tr>
</tbody>
</table>
</mat-tab>
</mat-tab-group>
</mat-tab>
}
</mat-tab-group>
</mat-card-content>
</mat-card>
}

View File

@@ -0,0 +1,26 @@
td {
vertical-align: middle;
}
td, th {
background-color: transparent !important;
}
table.wide td, table.wide th {
height: 4em;
}
.winner {
color: green;
font-weight: bold;
}
td.w-team {
width: 30%;
}
td.w-sep {
width: 5%;
}
td.w-fill {
width: 35%;
}

View File

@@ -0,0 +1,355 @@
import {Component, inject, Input, OnInit} from '@angular/core';
import {
MatAccordion,
MatExpansionPanel,
MatExpansionPanelDescription,
MatExpansionPanelHeader,
MatExpansionPanelTitle
} from "@angular/material/expansion";
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {CurrencyPipe, DatePipe, DecimalPipe, NgClass, NgForOf, NgIf} from "@angular/common";
import {TeamPipe} from "../../pipes/team-pipe";
import {TournamentService} from "../../service/tournament.service";
import {ActivatedRoute, Router} from "@angular/router";
import {Tournament} from "../../model/tournament";
import {FullNamePipe} from "../../pipes/fullname-pipe";
import {MatList, MatListItem} from "@angular/material/list";
import {MatDivider} from "@angular/material/divider";
import {MatAnchor, MatButton, MatIconAnchor, MatIconButton, MatMiniFabAnchor} from "@angular/material/button";
import {MatIcon} from "@angular/material/icon";
import {Group} from "../../model/group";
import {Round} from "../../model/round";
import {MatTable} from "@angular/material/table";
import {StatusPipe} from "../../pipes/status-pipe";
import {MatMenu, MatMenuContent, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
import {Match} from "../../model/match";
import {FormsModule} from "@angular/forms";
import {MatTab, MatTabChangeEvent, MatTabGroup, MatTabLabel} from "@angular/material/tabs";
import {MatchResultComponent} from "../match-result/match-result.component";
import {MatDialog} from "@angular/material/dialog";
import {MatchResultPipe} from "../../pipes/match-result-pipe";
import {Event} from "../../model/event";
import {TournamentValidateComponent} from "../tournament-validate/tournament-validate.component";
import {Strength} from "../../model/player";
import {MatSlideToggle, MatSlideToggleChange} from "@angular/material/slide-toggle";
import {MatSnackBar} from "@angular/material/snack-bar";
import {CourtSelectionComponent} from "../court-selection/court-selection.component";
import {Standings} from "../../model/standings";
import {Title} from '@angular/platform-browser';
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-tournament-manage',
standalone: true,
imports: [
FullNamePipe,
MatAccordion,
MatCard,
MatCardContent,
MatCardHeader,
MatExpansionPanel,
MatExpansionPanelDescription,
MatExpansionPanelHeader,
MatExpansionPanelTitle,
NgForOf,
NgIf,
TeamPipe,
MatListItem,
MatDivider,
MatList,
MatAnchor,
MatIcon,
MatTable,
StatusPipe,
NgClass,
MatMenu,
MatMenuItem,
MatMenuTrigger,
FormsModule,
DatePipe,
MatTabGroup,
MatTab,
MatTabLabel,
MatButton,
MatchResultPipe,
MatIconAnchor,
MatMiniFabAnchor,
MatIconButton,
DecimalPipe,
TournamentValidateComponent,
MatSlideToggle,
CurrencyPipe,
MatMenuContent
],
providers: [
FullNamePipe,
TeamPipe,
MatchResultPipe
],
templateUrl: './tournament-manage.component.html',
styleUrl: './tournament-manage.component.scss'
})
export class TournamentManageComponent implements OnInit {
@Input() tournament: Tournament;
activeRoundTab: number = 0;
constructor(
private tournamentService: TournamentService,
private _snackBar: MatSnackBar,
private route: ActivatedRoute,
private router: Router,
private titleService: TitleService,
private title: Title
) {
}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.route.queryParams.subscribe(params => {
if (params['tab']) {
this.activeRoundTab = params['tab'];
}
})
this.tournamentService.getById(Number(id)).subscribe(data => {
this.tournament = data;
this.titleService.setTitle(this.tournament.name);
this.title.setTitle(this.tournament.name);
});
}
onRoundTabChange(event: MatTabChangeEvent) {
const index = event.index;
this.router.navigate(
['tournaments/' + this.tournament.id + '/manage'],
{
relativeTo: null, queryParams: {tab: index}
});
}
getRoundIcon(status: String) {
if (status == "FINISHED") {
return "check";
} else if (status == "IN_PROGRESS") {
return "play_arrow";
} else if (status == "NOT_STARTED") {
return "hourglass_top";
} else {
return "warning";
}
}
getStandingsForRound(round: Round, group: Group): Standings {
if (round.status == 'FINISHED') {
return round.standings;
} else {
return group.standings;
}
}
groupIsDoublesType(group: Group) {
return group.type == 'HD' || group.type == 'DD' || group.type == 'GD';
}
startRound(round: Round) {
this.tournamentService.startRound(this.tournament.id, round.id).subscribe(data => {
this.tournament = data;
});
}
finishRound(round: Round) {
this.tournamentService.finishRound(this.tournament.id, round.id).subscribe(data => {
this.tournament = data;
});
}
finishGroup(group: Group) {
this.tournamentService.finishGroup(this.tournament.id, group.id).subscribe(data => {
this.tournament = data;
});
}
divideTournament() {
this.tournamentService.divide(this.tournament.id).subscribe(data => {
this.tournament = data;
});
}
clearDivision() {
this.tournamentService.clearDivision(this.tournament.id).subscribe(data => {
this.tournament = data;
});
}
drawTournament() {
this.tournamentService.draw(this.tournament.id).subscribe(data => {
this.tournament = data;
});
}
startMatch(match: Match) {
const availableCourts = this.getAvailableCourts();
if (availableCourts.length == 0) {
alert('Geen banen beschikbaar!');
} else {
this.courtSelectionDialog.open(CourtSelectionComponent, {
data: {
match: match,
availableCourts: this.getAvailableCourts(),
totalCourts: this.tournament.courts
},
minWidth: '800px',
minHeight: '250px'
}).afterClosed().subscribe(result => {
if (result != undefined) {
this.tournamentService.startMatch(this.tournament.id, match.id, result).subscribe(data => {
this.tournament = data;
})
}
});
}
}
getAvailableCourts(): number[] {
const maxCourts = this.tournament.courts;
const activeCourts = this.activeMatches().map(activeMatch => activeMatch.match.court);
let i = 0, courts = Array(maxCourts);
while (i < maxCourts) courts[i++] = i;
return courts.filter(court => activeCourts.indexOf(court) < 0);
}
stopMatch(match: Match) {
this.tournamentService.stopMatch(this.tournament.id, match.id).subscribe(data => {
this.tournament = data;
})
}
newRound(group: Group) {
this.tournamentService.newRound(this.tournament.id, group.id).subscribe(data => {
this.tournament = data;
})
}
playerPaid($event: MatSlideToggleChange, playerId: number) {
this.tournamentService.playerPaid(this.tournament.id, playerId, $event.checked).subscribe(() => {
this._snackBar.open('Opgeslagen.');
});
}
playerPresent($event: MatSlideToggleChange, playerId: number) {
this.tournamentService.playerPresent(this.tournament.id, playerId, $event.checked).subscribe(() => {
this._snackBar.open('Opgeslagen.');
});
}
getStrength(strength: string | undefined) {
if (strength == undefined) return "";
for (let [key, value] of Object.entries(Strength)) {
if (key == strength) return value;
}
return "";
}
printMatchSheets(round: Round) {
window.open(`tournaments/${this.tournament.id}/rounds/${round.id}/matchsheets`, "_blank");
}
printRoundOverview(round: Round) {
window.open(`tournaments/${this.tournament.id}/rounds/${round.id}/overview`, "_blank");
}
activeMatches(): ActiveMatch[] {
let matches: ActiveMatch[] = [];
for (const event of this.tournament.events) {
for (const group of event.groups) {
for (const round of group.rounds) {
for (const match of round.matches) {
if (match.status == 'IN_PROGRESS') {
matches.push(new ActiveMatch(match, round, group));
}
}
}
}
}
return matches;
}
getActiveMatchCountForGroup(group: Group): number {
let active = 0;
for (const round of group.rounds) {
for (const match of round.matches) {
if (match.status == 'IN_PROGRESS') {
active++;
}
}
}
return active;
}
groupOnlyHasFinishedRounds(group: Group): boolean {
let allFinished = true;
for (const round of group.rounds) {
allFinished &&= round.status == 'FINISHED';
}
return allFinished;
}
matchResultDialog = inject(MatDialog);
courtSelectionDialog = inject(MatDialog);
editResult(match: Match, group: Group, round: Round) {
this.matchResultDialog.open(MatchResultComponent, {
data: {match: match, group: group, round: round},
minWidth: '800px'
}).afterClosed().subscribe(result => {
if (result != undefined) {
this.tournamentService.saveResult(this.tournament.id, result.matchId, result).subscribe(data => {
this.tournament = data;
})
}
});
}
checkWinner(match: Match): number {
if (match.games.length == 0) return 0;
if (match.games.length == 3) {
if (match.games[2].score1 > match.games[2].score2) {
return 1;
} else {
return 2;
}
}
if (match.games[1].score1 > match.games[1].score2) {
return 1;
} else {
return 2;
}
}
checkRoundComplete(round: Round) {
let complete: boolean = true;
for (let match of round.matches) {
complete &&= match.status == 'FINISHED';
}
return complete;
}
protected readonly TournamentEvent = Event;
}
class ActiveMatch {
constructor(match: Match, round: Round, group: Group) {
this.match = match;
this.round = round;
this.group = group;
}
match: Match;
round: Round;
group: Group;
}

View File

@@ -0,0 +1,32 @@
<mat-card appearance="outlined" *ngIf="tournament">
<mat-card-header>
<h5>Inschrijvingen voor {{ tournament.name }}ab</h5>
</mat-card-header>
<mat-card-content>
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
<mat-card-header>
<h6>{{ TournamentEvent.getType(event.type) }} ({{ event.registrations.length}} inschrijvingen)</h6>
</mat-card-header>
<mat-card-content>
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
<thead class="thead-dark">
<tr>
<th scope="col" class="w-25">Naam</th>
<th scope="col" class="w-25">Club</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Partner</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Club</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let registration of event.registrations">
<td class="align-middle">{{ registration.player | fullName }}</td>
<td class="align-middle">{{ registration.player.club }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner | fullName }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner?.club }}</td>
</tr>
</tbody>
</table>
</mat-card-content>
</mat-card>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,3 @@
td, th {
background-color: transparent !important;
}

View File

@@ -0,0 +1,46 @@
import {Component, OnInit} from '@angular/core';
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {Tournament} from "../../model/tournament";
import {TournamentService} from "../../service/tournament.service";
import {ActivatedRoute, Router} from "@angular/router";
import {NgForOf, NgIf} from "@angular/common";
import {Player} from "../../model/player";
import {Event} from "../../model/event";
import {FullNamePipe} from "../../pipes/fullname-pipe";
import {TitleService} from "../../service/title.service";
@Component({
selector: 'app-tournament-registrations',
standalone: true,
imports: [
MatCard,
MatCardHeader,
MatCardContent,
NgForOf,
NgIf,
FullNamePipe
],
templateUrl: './tournament-registrations.component.html',
styleUrl: './tournament-registrations.component.scss'
})
export class TournamentRegistrationsComponent implements OnInit {
tournament?: Tournament;
constructor(
private tournamentService: TournamentService,
private route: ActivatedRoute,
private router: Router,
private titleService: TitleService
) {}
ngOnInit() {
this.titleService.setTitle("Inschrijvingen");
const id = this.route.snapshot.paramMap.get('id');
this.tournamentService.getById(Number(id)).subscribe(data => {
this.tournament = data;
})
}
protected readonly TournamentEvent = Event;
}

View File

@@ -0,0 +1,69 @@
@if (tournamentValidation && tournament) {
<mat-card appearance="outlined" class="m-3">
<mat-card-header>
<h6>Toernooi</h6>
</mat-card-header>
<mat-card-content>
<mat-expansion-panel [disabled]="tournamentValidation.validations.length == 0">
<mat-expansion-panel-header>
<mat-panel-title>
Validaties&nbsp;<span class="badge {{ Validation.hasErrors(tournamentValidation.validations) ? 'text-bg-danger' : 'text-bg-success'}}">{{ tournamentValidation.validations.length }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<ul>
<li *ngFor="let validation of tournamentValidation.validations">
<mat-icon class="text-{{ getColorForSeverity(validation.severity) }}">{{ getIconForSeverity(validation.severity) }}</mat-icon>
{{ validation.message }}
</li>
</ul>
</mat-expansion-panel>
</mat-card-content>
</mat-card>
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
<mat-card-header>
<h6>{{ TournamentEvent.getType(event.type) }}</h6>
</mat-card-header>
<mat-card-content>
<mat-accordion multi="true">
<mat-expansion-panel [disabled]="event.registrations.length == 0">
<mat-expansion-panel-header>
<mat-panel-title>
Inschrijvingen&nbsp;<span class="badge {{ event.registrations.length == 0 ? 'text-bg-danger' : 'text-bg-success'}}">{{ event.registrations.length }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
<thead class="thead-dark">
<tr>
<th scope="col" class="w-25">Naam</th>
<th scope="col" class="w-25">Club</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Partner</th>
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Club</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let registration of event.registrations">
<td class="align-middle">{{ registration.player | fullName }}</td>
<td class="align-middle">{{ registration.player.club }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner | fullName }}</td>
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner?.club }}</td>
</tr>
</tbody>
</table>
</mat-expansion-panel>
<mat-expansion-panel [disabled]="getEventValidation(event.id)?.validations?.length == 0">
<mat-expansion-panel-header>
<mat-panel-title>
Validaties&nbsp;<span class="badge {{ Validation.hasErrors(getEventValidation(event.id)?.validations) ? 'text-bg-danger' : 'text-bg-success'}}">{{ getEventValidation(event.id)?.validations?.length }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<ul>
<li *ngFor="let validation of getEventValidation(event.id)?.validations">
<mat-icon class="text-{{ getColorForSeverity(validation.severity) }}">{{ getIconForSeverity(validation.severity) }}</mat-icon>
{{ validation.message }}
</li>
</ul>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content>
</mat-card>
}

View File

@@ -0,0 +1,3 @@
td, th {
background-color: transparent !important;
}

View File

@@ -0,0 +1,98 @@
import {Component, OnInit} from '@angular/core';
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
import {Tournament} from "../../model/tournament";
import {AsyncPipe, NgForOf, NgIf} from "@angular/common";
import {Event} from "../../model/event";
import {TournamentService} from "../../service/tournament.service";
import {ActivatedRoute, Router} from "@angular/router";
import {
MatAccordion,
MatExpansionPanel,
MatExpansionPanelHeader,
MatExpansionPanelTitle
} from "@angular/material/expansion";
import {Player} from "../../model/player";
import {EventValidation, TournamentValidation, Validation} from "../../model/tournamentValidation";
import {MatIcon} from "@angular/material/icon";
import {FullNamePipe} from "../../pipes/fullname-pipe";
@Component({
selector: 'app-tournament-validate',
standalone: true,
imports: [
MatCard,
MatCardHeader,
MatCardContent,
NgForOf,
MatExpansionPanel,
MatExpansionPanelTitle,
MatExpansionPanelHeader,
NgIf,
MatAccordion,
AsyncPipe,
MatIcon,
FullNamePipe
],
templateUrl: './tournament-validate.component.html',
styleUrl: './tournament-validate.component.scss'
})
export class TournamentValidateComponent implements OnInit {
tournament: Tournament;
tournamentValidation: TournamentValidation;
protected readonly TournamentEvent = Event;
protected readonly Player = Player;
protected readonly Validation = Validation;
constructor(
private tournamentService: TournamentService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.tournamentService.getById(Number(id)).subscribe(data => {
this.tournament = data;
});
this.tournamentService.getValidation(Number(id)).subscribe(data => {
this.tournamentValidation = data;
});
}
getEventValidation(id: number): EventValidation | null {
if (!this.tournamentValidation) {
return null;
} else {}
return this.tournamentValidation.eventValidations.filter(eventValidation => eventValidation.eventId == id)[0];
}
getIconForSeverity(severity: string) {
if (severity == 'INFO') {
return "info";
} else if (severity == 'WARN') {
return "warning";
} else {
return "dangerous";
}
}
getColorForSeverity(severity: string) {
if (severity == 'INFO') {
return "success";
} else if (severity == 'WARN') {
return "warning";
} else {
return "danger";
}
}
tournamentHasErrors(): boolean {
let hasErrors: boolean = Validation.hasErrors(this.tournamentValidation.validations);
for (let eventValidation of this.tournamentValidation.eventValidations) {
hasErrors &&= Validation.hasErrors(eventValidation.validations);
}
return hasErrors;
}
}