1
0
Fork 0
mirror of https://codeberg.org/beerbrawl/beerbrawl.git synced 2024-09-22 21:20:52 +02:00

Merge branch 'development' into 'master'

Development

See merge request 2024ss-se-pr-group/24ss-se-pr-qse-11!140
This commit is contained in:
Moritz Kepplinger 2024-06-26 22:45:58 +00:00
commit e6fb3bc2f1
20 changed files with 534 additions and 44 deletions

View file

@ -4,12 +4,14 @@ import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.UserDetailDto;
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.UserLoginDto;
import at.ac.tuwien.sepr.groupphase.backend.entity.Tournament;
import at.ac.tuwien.sepr.groupphase.backend.exception.UserAlreadyExistsException;
import at.ac.tuwien.sepr.groupphase.backend.service.TestDataService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentService;
import at.ac.tuwien.sepr.groupphase.backend.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@ -32,6 +34,7 @@ import java.net.URI;
import java.net.URISyntaxException;
@RestController
@AllArgsConstructor
@RequestMapping(value = UserEndpoint.BASE_ENDPOINT)
public class UserEndpoint {
public static final String BASE_ENDPOINT = "/api/v1/user";
@ -39,11 +42,8 @@ public class UserEndpoint {
private final UserService userService;
private final TournamentService tournamentService;
private final TestDataService testDataService;
public UserEndpoint(UserService userService, TournamentService tournamentService) {
this.userService = userService;
this.tournamentService = tournamentService;
}
/**
* Registers a new user in the system. If a user with the same username already exists,
@ -157,6 +157,22 @@ public class UserEndpoint {
return ResponseEntity.notFound().build();
}
/**
* Get Username and password for user.
* The target user is retrieved through the JWT
*r
*/
@GetMapping(value = "genTestData", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("isAuthenticated()")
@Operation(summary = "Get detailed information about user and their tournaments.", security = @SecurityRequirement(name = "apiKey"))
public void generateTestData(
Authentication authentication
) {
this.testDataService.generateTestDataForUser(authentication.getName());
}
private void logClientError(HttpStatus status, String message, Exception e) {
LOG.warn("{} {}: {}: {}", status.value(), message, e.getClass().getSimpleName(), e.getMessage());

View file

@ -27,6 +27,15 @@ public interface TournamentRepository extends JpaRepository<Tournament, Long> {
*/
List<Tournament> findAllByOrganizerIdOrderByNameAsc(Long organizerId);
/**
* Find all tournaments where the organizer's username matches the provided username.
*
* @param organizerUsername The username of the organizer
* @return List of tournaments organized by the provided username
*/
List<Tournament> findAllByOrganizerUsername(String organizerUsername);
/**
* Find all tournaments with their qualification matches eagerly loaded
* where the organizer's ID matches the provided ID, ordered by name (ascending).

View file

@ -0,0 +1,5 @@
package at.ac.tuwien.sepr.groupphase.backend.service;
public interface TestDataService {
void generateTestDataForUser(String personName);
}

View file

@ -0,0 +1,336 @@
package at.ac.tuwien.sepr.groupphase.backend.service.impl;
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentUpdateQualificationMatchDto;
import at.ac.tuwien.sepr.groupphase.backend.entity.BeerPongTable;
import at.ac.tuwien.sepr.groupphase.backend.entity.QualificationMatch;
import at.ac.tuwien.sepr.groupphase.backend.entity.Team;
import at.ac.tuwien.sepr.groupphase.backend.entity.Tournament;
import at.ac.tuwien.sepr.groupphase.backend.entity.domainservice.MatchDomainService;
import at.ac.tuwien.sepr.groupphase.backend.repository.BeerPongTableRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.QualificationMatchRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.QualificationParticipationRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.TeamRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.TournamentRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.UserRepository;
import at.ac.tuwien.sepr.groupphase.backend.service.TestDataService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentKoPhaseService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentQualificationService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentTeamService;
import at.ac.tuwien.sepr.groupphase.backend.util.BeerDateTime;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.stream.IntStream;
@AllArgsConstructor
@Service
public class TestDataServiceImpl implements TestDataService {
private final TournamentRepository tournamentRepository;
private final UserRepository userRepository;
private final TeamRepository teamRepository;
private final TournamentTeamService teamService;
private final TournamentService tournamentService;
private final BeerPongTableRepository beerPongTableRepository;
private final MatchDomainService matchDomainService;
private final TournamentQualificationService qualificationService;
private final TournamentKoPhaseService koPhaseService;
private final QualificationParticipationRepository qualificationParticipationRepository;
private final QualificationMatchRepository qualificationMatchRepository;
@Override
public void generateTestDataForUser(String username) {
var tournaments = this.tournamentRepository.findAllByOrganizerUsername(username);
tournamentRepository.deleteAllById(tournaments.stream().map(Tournament::getId).toList());
final var tournament1 = new Tournament(
"Semesterclosing Turnier",
LocalDateTime.of(LocalDate.of(2024, 6, 27), LocalTime.of(18, 0)),
32L,
"Willkommen zum Semesterclosing Beerpongturnier! Viel Spaß! Es gibt tolle Preise zu gewinnen!",
userRepository.findByUsername(username));
tournamentRepository.saveAllAndFlush(List.of(tournament1));
var teamNames = getTeamNames();
final var teams1 = IntStream.range(0, 32)
.mapToObj(i -> new Team(teamNames[i], tournament1))
.toList();
teamRepository.saveAllAndFlush(teams1);
var tableNames = List.of("Innen1", "Innen2", "Innen3", "Terasse");
var tables1 = IntStream.range(0, 4)
.mapToObj(i -> new BeerPongTable(tableNames.get(i), tournament1))
.toList();
beerPongTableRepository.saveAllAndFlush(tables1);
generateTournamentWithFinishedQualiPhaseAndDifferentScores(username);
}
protected void generateTournamentWithFinishedQualiPhaseAndDifferentScores(String username) {
var tournament = new Tournament(
"Ferienturnier", BeerDateTime.nowUtc().plusDays(1), 40L,
"Willkommen zum Ferienturnier! Viel Spaß! Es gibt tolle Preise zu gewinnen!",
userRepository.findByUsername(username));
tournamentService.create(tournament, username);
generate32Teams(tournament);
qualificationService.generateQualificationMatchesForTournament(tournament.getId(), username);
markAllTeamsAsReady(tournament);
var alwaysWinsId = teamRepository.findAllByTournamentId(tournament.getId()).get(0).getId();
var alwaysLoosesId = teamRepository.findAllByTournamentId(tournament.getId()).get(1).getId();
// all the team ids that have already won one match
var teamsWithOneWin = new LinkedList<Long>();
// we also track the teams in the specific
var teamsWith5Points = new LinkedList<Long>();
var teamsWithMoreThan5Points = new LinkedList<Long>();
var teamsWithLessThan5Points = new LinkedList<Long>();
final var qMatches = qualificationMatchRepository.findAllByTournamentId(tournament.getId());
for (var i = 0; i < qMatches.size() - 1; i++) {
var qm = qMatches.get(i);
markParticipantsAsDrinksCollected(tournament, qm);
var winnerInfo = determineWinner(
qm, alwaysWinsId, alwaysLoosesId,
teamsWithOneWin, teamsWith5Points,
teamsWithMoreThan5Points, teamsWithLessThan5Points
);
qualificationService.updateQualificationMatch(
tournament.getId(), qm.getId(),
new TournamentUpdateQualificationMatchDto(
new TournamentUpdateQualificationMatchDto.ScoreUpdateDto(
winnerInfo.winnerId, winnerInfo.winnerPoints
),
null
)
);
}
var tableNames = List.of("Tisch 1", "Tisch 2", "Tisch 3");
var tables1 = IntStream.range(0, 3)
.mapToObj(i -> new BeerPongTable(tableNames.get(i), tournament))
.toList();
beerPongTableRepository.saveAllAndFlush(tables1);
matchDomainService.scheduleQualiMatches(tournament.getId());
}
protected void generate32Teams(Tournament tournament) {
var teamNames = getTeamNames();
for (int i = 1; i <= 32; i++) {
var result = teamService.signupTeamForTournament(tournament.getId(),
tournament.getPublicAccessToken(), teamNames[i]);
if (result != Tournament.SignupTeamResult.SUCCESS) {
throw new IllegalStateException("Failed to sign up team for tournament");
}
}
}
protected void markAllTeamsAsReady(Tournament tournament) {
for (final var team : teamRepository.findAllByTournamentId(tournament.getId())) {
teamService.markTeamAsReady(tournament.getId(), team.getId());
}
}
protected void markParticipantsAsDrinksCollected(Tournament tournament, QualificationMatch qm) {
qm.getParticipations()
.stream().map(p -> p.getTeam().getId())
.forEach(p -> qualificationService.updateQualificationMatch(
tournament.getId(), qm.getId(),
new TournamentUpdateQualificationMatchDto(
null, new TournamentUpdateQualificationMatchDto.DrinksPickupDto(p)
)
));
}
protected WinnerInfo determineWinner(
QualificationMatch qm,
Long alwaysWinsId,
Long alwaysLoosesId,
LinkedList<Long> teamsWithOneWin,
LinkedList<Long> teamsWith5Points,
LinkedList<Long> teamsWithMoreThan5Points,
LinkedList<Long> teamsWithLessThan5Points
) {
var currentTeamIds = qm.getParticipations().stream().map(p -> p.getTeam().getId()).toList();
Long winnerId;
Long winnerPoints;
if (currentTeamIds.contains(alwaysWinsId)) {
// the match includes the "alwaysWins" team, so it wins
winnerId = alwaysWinsId;
winnerPoints = 10L;
} else if (currentTeamIds.contains(alwaysLoosesId)) {
// the match includes the "alwaysLooses" team, so it looses
winnerId = currentTeamIds.stream().filter(id -> !Objects.equals(id, alwaysLoosesId)).findFirst().get();
teamsWithOneWin.add(winnerId);
winnerPoints = getWinnerPoints(teamsWith5Points, teamsWithMoreThan5Points, teamsWithLessThan5Points, winnerId);
} else {
var teamWithOneWin = currentTeamIds.stream().filter(teamsWithOneWin::contains).findFirst();
if (teamWithOneWin.isPresent()) {
//the match includes a team that has already won one match, so the other team wins
winnerId = currentTeamIds.stream().filter(id -> !Objects.equals(id, teamWithOneWin.get())).findFirst().get();
} else {
// the match includes two teams that have not won a match yet, so we just pick the first one
winnerId = currentTeamIds.getFirst();
}
teamsWithOneWin.add(winnerId);
winnerPoints = getWinnerPoints(teamsWith5Points, teamsWithMoreThan5Points, teamsWithLessThan5Points, winnerId);
}
return new WinnerInfo(winnerId, winnerPoints);
}
private static class WinnerInfo {
Long winnerId;
Long winnerPoints;
WinnerInfo(Long winnerId, Long winnerPoints) {
this.winnerId = winnerId;
this.winnerPoints = winnerPoints;
}
}
protected Long getWinnerPoints(LinkedList<Long> teamsWith5Points, LinkedList<Long> teamsWithMoreThan5Points, LinkedList<Long> teamsWithLessThan5Points, Long winnerId) {
Long winnerPoints;
if (teamsWith5Points.size() < 4) {
// it joins the group with exactly 5 points
winnerPoints = 5L;
teamsWith5Points.add(winnerId);
} else {
if (teamsWithMoreThan5Points.size() < teamsWithLessThan5Points.size()) {
// it joins the group with more than 5 points
// random number from 1 to 4 (both inclusive)
winnerPoints = 1L + new Random().nextInt(4);
teamsWithMoreThan5Points.add(winnerId);
} else {
// it joins the group with less than 5 points
// random number from 6 to 9 (both inclusive)
winnerPoints = 6L + new Random().nextInt(4);
teamsWithLessThan5Points.add(winnerId);
}
}
return winnerPoints;
}
String[] getTeamNames() {
return new String[]{
"Pongmeister",
"Bierathleten",
"Becherstürmer",
"Hopfenhüpfer",
"PongProfis",
"Bierbuddies",
"Schaumjäger",
"Ponghelden",
"Becherritter",
"Bierwerfer",
"Bierbongers",
"Becherbullen",
"Braumeister",
"PongPioniere",
"Bieronauten",
"Bechermagier",
"Hopfenheroes",
"Bierflieger",
"Pongkönige",
"Braubrüder",
"Becherblitz",
"BierballKrieger",
"PingpongPrinzen",
"Bierkapitäne",
"Pongpiraten",
"Becherbarden",
"BierballBrigade",
"Schaumstürmer",
"Pongprofs",
"HopfenHüpfende",
"BierballBataillon",
"Pongpartisanen",
"Becherbomber",
"Braukrieger",
"BierballHexer",
"Pongpropheten",
"Becherbarone",
"Hopfenhaie",
"BierballFestung",
"Pongpäpste",
"Becherbären",
"Bierbaronen",
"Pongpanther",
"Becherbrigade",
"SchaumSchützen",
"BierballTruppe",
"Pongpatrioten",
"Bechergarde",
"Hopfenhexer",
"BierballBauern",
"Pongpiloten",
"Becherbazis",
"BrauBanditen",
"BierballBosse",
"Becherbrecher",
"Bierbären",
"Schaumkrieger",
"Pongplünderer",
"Becherbosse",
"Hopfenhelden",
"BierballKompanie",
"Pongprinzen",
"Becherblitzer",
"Braublitz",
"BierballBomber",
"Pongchamps",
"Becherbuddies",
"Bierbrüder",
"Schaumjäger",
"Pongpiloten",
"Bechermagier",
"Bierbarone",
"Hopfenhengste",
"BierballBande",
"Becherbarone",
"Schaumstürmer",
"Bierkapitäne",
"Pongköniginnen",
"Becherballer",
"Braukapitäne",
"BierballBerserker",
"Pongmeister",
"Becherhelden",
"Hopfenhüpfer",
"Bieronauten",
"Pongprofs",
"Becherwächter",
"Schaumritter",
"BierballBotsch",
"Ponghelden",
"Becherblitz",
"Braumeister",
"BierballZauberer",
"Pongchampions",
"Becherbomber",
"SchaumSchützen"
};
}
}

View file

@ -144,9 +144,9 @@ public class TournamentQualificationServiceImpl implements TournamentQualificati
.orElseThrow(() -> new NotFoundException("Match not found"));
}
private void updateQualificationMatchDrinksStatus(
QualificationMatch match,
TournamentUpdateQualificationMatchDto.DrinksPickupDto updateDto
protected void updateQualificationMatchDrinksStatus(
QualificationMatch match,
TournamentUpdateQualificationMatchDto.DrinksPickupDto updateDto
) {
final var participation = match.getParticipations()
.stream().filter(p -> p.getTeam().getId() == updateDto.teamId())

View file

@ -62,7 +62,7 @@ public class TournamentTeamServiceImpl implements TournamentTeamService {
throw new BadTournamentPublicAccessTokenException();
}
if (tournament.getRegistrationEnd().isBefore(BeerDateTime.nowUtc())) {
if (tournament.getRegistrationEnd().isBefore(BeerDateTime.nowUtc()) || !tournament.getQualificationMatches().isEmpty()) {
return SignupTeamResult.REGISTRATION_CLOSED;
}

View file

@ -3,7 +3,7 @@
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: v0
*
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
@ -13,7 +13,7 @@
import { Inject, Injectable, Optional } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams,
HttpResponse, HttpEvent, HttpParameterCodec, HttpContext
HttpResponse, HttpEvent, HttpParameterCodec, HttpContext
} from '@angular/common/http';
import { CustomHttpParameterCodec } from '../encoder';
import { Observable } from 'rxjs';
@ -95,7 +95,7 @@ export class UserEndpointService {
/**
* Delete user and all data belonging to them(Tournaments, Teams, etc) from the database.
* @param username
* @param username
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
@ -159,7 +159,7 @@ export class UserEndpointService {
/**
* Get detailed information about user and their tournaments.
* @param username
* @param username
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
@ -222,7 +222,66 @@ export class UserEndpointService {
}
/**
* @param userLoginDto
* Get detailed information about user and their tournaments.
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public generateTestData(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any>;
public generateTestData(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<any>>;
public generateTestData(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<any>>;
public generateTestData(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any> {
let localVarHeaders = this.defaultHeaders;
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}
let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}
let localVarTransferCache: boolean | undefined = options && options.transferCache;
if (localVarTransferCache === undefined) {
localVarTransferCache = true;
}
let responseType_: 'text' | 'json' | 'blob' = 'json';
if (localVarHttpHeaderAcceptSelected) {
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
responseType_ = 'json';
} else {
responseType_ = 'blob';
}
}
let localVarPath = `/api/v1/user/genTestData`;
return this.httpClient.request<any>('get', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
transferCache: localVarTransferCache,
reportProgress: reportProgress
}
);
}
/**
* @param userLoginDto
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
@ -295,8 +354,8 @@ export class UserEndpointService {
}
/**
* @param username
* @param userLoginDto
* @param username
* @param userLoginDto
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/

View file

@ -2,7 +2,14 @@
class="qualification-line-container"
[class.currently-playing-light]="match().startTime && !match().endTime && !viewOnly()"
[class.currently-playing-dark]="match().startTime && !match().endTime && viewOnly()"
[class.admin-qualification-line-container]="!viewOnly()"
[class.public-qualification-line-container]="viewOnly()"
>
@if (!viewOnly()) {
<div [class.table-light]="match().table?.name">
{{ match().table?.name }}
</div>
}
<div
class="qualification-team"
[class.text-winner]="participant1()?.isWinner === true"
@ -20,6 +27,7 @@
class="team-action-button"
data-cy="team1-action-button"
mat-icon-button
[disabled]="disableMatchEdit()"
aria-label="Mark team drinks as picked up for this match"
(click)="markTeamDrinksPickedUp.emit(participant1()!)"
[matTooltip]="'Mark team drinks as picked up for this match'"
@ -32,13 +40,17 @@
}
</div>
<div class="center-result-column">
@if (match().table && !match()?.winnerPoints) {
<p class="beerpong-table-name-floating">{{ match().table?.name }}</p>
@if (match().table && !match()?.winnerPoints && viewOnly()) {
<p [class.table-dark]="match().table?.name">{{ match().table?.name }}</p>
}
<div [matTooltip]="resultTooltip" aria-label="Enter match result">
@if (match()?.winnerPoints) {
@if (!viewOnly()) {
<button mat-button [disabled]="!matchHasStarted" (click)="openMatchResultDialog()">
<button
mat-button
[disabled]="!matchHasStarted || disableMatchEdit()"
(click)="openMatchResultDialog()"
>
{{ matchResultString }}
</button>
} @else {
@ -48,7 +60,7 @@
@if (!viewOnly()) {
<button
mat-icon-button
[disabled]="!matchHasStarted"
[disabled]="!matchHasStarted || disableMatchEdit()"
(click)="openMatchResultDialog()"
data-cy="enter-match-results-btn"
>
@ -57,8 +69,6 @@
} @else {
@if (!match().table) {
vs.
} @else {
&nbsp;
}
}
}
@ -81,6 +91,7 @@
class="team-action-button"
data-cy="team2-action-button"
mat-icon-button
[disabled]="disableMatchEdit()"
aria-label="Mark team drinks as picked up for this match"
(click)="markTeamDrinksPickedUp.emit(participant2()!)"
[matTooltip]="'Mark team drinks as picked up for this match'"

View file

@ -4,12 +4,19 @@
.qualification-line-container {
height: 3.2rem;
display: grid;
grid-template-columns: 3fr 2fr 3fr 1fr;
justify-items: center;
align-items: center;
border-bottom: 1px solid var(--mat-table-row-item-outline-color);
}
.admin-qualification-line-container {
grid-template-columns: 1fr 3fr 2fr 3fr 1fr;
}
.public-qualification-line-container {
grid-template-columns: 3fr 2fr 3fr 1fr;
}
.text-winner {
text-decoration: underline;
font-weight: bold;
@ -22,7 +29,7 @@
.team-action-button {
position: absolute;
top: -1.5rem;
right: -2.4rem;
right: -2.2rem;
scale: 0.7;
color: black;
}
@ -38,13 +45,6 @@
align-items: center;
}
.beerpong-table-name-floating {
font-weight: bold;
margin: 0px;
// put this on top of the result display, I found no clean way to do this
margin-bottom: -20px;
}
.currently-playing-icon {
position: absolute;
top: -0.8rem;
@ -88,3 +88,26 @@
.currently-playing-dark {
background-color: var(--color-background-dark-success);
}
.table-light {
font-size: 0.8rem;
margin: 0.5rem;
padding: 0.5rem;
background-color: var(--color-background-light-success);
text-align: center;
border-radius: 0.3rem;
cursor: default;
user-select: none;
box-shadow: 0 0 0.4rem 0.1rem var(--color-background-dark-success);
}
.table-dark {
font-weight: bold;
margin: 0.5rem;
padding: 0.5rem;
background-color: var(--color-background-dark-success);
text-align: center;
border-radius: 0.3rem;
cursor: default;
user-select: none;
}

View file

@ -26,6 +26,7 @@ export class QualificationMatchLineComponent {
tournamentId = input<number>();
teams = input<Map<number, TeamDto>>();
viewOnly = input<boolean>(false);
disableMatchEdit = input<boolean>(false);
match = model<TournamentQualificationMatchDto>({});
onMatchUpdate = output<TournamentQualificationMatchDto>();

View file

@ -7,6 +7,7 @@
[tournamentId]="tournamentId()"
[teams]="teamDtos()"
[viewOnly]="viewOnly()"
[disableMatchEdit]="disableMatchEdit()"
></app-qualification-match-line>
}
@if (qualificationMatches().length === 0 && showEmptyMessage()) {

View file

@ -29,6 +29,7 @@ export class QualificationMatchesComponent {
tournamentId = input.required<number>();
viewOnly = input<boolean>(false);
teamDtos = input<Map<number, TeamDto>>(new Map());
disableMatchEdit = input<boolean>(false);
renderedRows = viewChildren<QueryList<ElementRef<HTMLTableRowElement>>>(
'matchLineElement',
// @ts-ignore - this is a bug in the typescript definitions

View file

@ -33,6 +33,9 @@
@if (nameFormControl.errors?.maxParticipantsReached) {
<mat-error>No more spots left!</mat-error>
}
@if (nameFormControl.errors?.registrationClosed) {
<mat-error> Registration is closed! </mat-error>
}
</mat-form-field>
<button mat-raised-button type="submit">Sign up!</button>
</form>

View file

@ -80,6 +80,9 @@ export class TeamSignupComponent {
case TournamentSignupTeamResponseDto.SignupTeamResultEnum.MaxParticipantsReached:
this.nameFormControl.setErrors({ maxParticipantsReached: true });
return of();
case TournamentSignupTeamResponseDto.SignupTeamResultEnum.RegistrationClosed:
this.nameFormControl.setErrors({ registrationClosed: true });
return of();
default:
return [];
}

View file

@ -28,14 +28,13 @@
</div>
<div class="form-actions" mat-dialog-actions>
<button
mat-flat-button
mat-button
data-cy="cancel-btn"
color="warn"
aria-label="Cancel entering match results"
mat-dialog-close
>
Cancel
<mat-icon>cancel</mat-icon>
</button>
<button
mat-flat-button
@ -46,7 +45,6 @@
type="submit"
>
Update match results
<mat-icon>check</mat-icon>
</button>
</div>
</form>

View file

@ -109,13 +109,12 @@
<mat-card-actions>
<a
[disabled]="this.tournamentForm.disabled"
mat-flat-button
mat-button
[routerLink]="'..'"
color="warn"
style="margin-right: var(--spacing2)"
>
Cancel
<mat-icon>cancel</mat-icon>
</a>
<button
@ -126,7 +125,6 @@
id="edit-tournament"
>
Edit
<mat-icon>check</mat-icon>
</button>
</mat-card-actions>
</form>

View file

@ -7,16 +7,10 @@
Public live page
<mat-icon>share</mat-icon>
</button>
<div
[matTooltip]="
!qualificationMatchesFinished()
? 'All qualification matches must be finished before starting the knockout phase.'
: ''
"
>
<div [matTooltip]="getStartKoPhaseButtonTooltip()">
<button
mat-flat-button
[disabled]="!qualificationMatchesFinished()"
[disabled]="!qualificationMatchesFinished() || koPhaseStarted()"
(click)="startKoPhase()"
color="primary"
>
@ -31,6 +25,7 @@
[qualificationMatches]="qualificationMatches()"
[tournamentId]="tournamentId()"
[teamDtos]="teamDtos()"
[disableMatchEdit]="koPhaseStarted()"
(generateQualificationMatches)="generateQualificationMatches()"
(onMatchUpdate)="onMatchUpdate($event)"
(markTeamDrinksPickedUp)="markTeamDrinksPickedUp($event)"

View file

@ -75,6 +75,9 @@ export class TournamentQualificationPhaseComponent implements OnInit, OnDestroy
this.qualificationMatches().length > 0
);
});
koPhaseStarted = computed(() => {
return this.tournament()?.allKoMatches !== undefined && this.tournament()!.allKoMatches > 0;
});
tournament = signal<TournamentOverviewDto | undefined>(undefined);
infoElement = viewChild<ElementRef>('qualifiedInfo');
@ -343,4 +346,16 @@ export class TournamentQualificationPhaseComponent implements OnInit, OnDestroy
title: 'Public link to qualification phase',
});
}
getStartKoPhaseButtonTooltip(): string {
if (this.koPhaseStarted()) {
return 'KO phase already started';
}
if (!this.qualificationMatchesFinished()) {
return 'Qualification matches not finished';
}
return 'Start KO phase';
}
}

View file

@ -21,6 +21,7 @@
<button mat-flat-button class="delete-button" (click)="onDelete()">
<mat-icon>delete</mat-icon> Delete your Account
</button>
<button mat-button class="delete-button" (click)="genTestData()">Generate Testdata</button>
</mat-card-actions>
</mat-card>
</div>

View file

@ -7,6 +7,7 @@ import { UpdateUserComponent } from '../update-user/update-user.component';
import { HttpErrorResponse } from '@angular/common/http';
import { UserEndpointService, UserDetailDto } from '@api';
import { ConfirmationService } from '../../services/confirmation.service';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-user-detail',
@ -90,4 +91,18 @@ export class UserDetailComponent implements OnInit {
duration: 5000,
});
}
async genTestData() {
await firstValueFrom(this.userService.generateTestData()).then(
() => {
this.snackBar.open('Successfully generated test data', 'Close', {
duration: 5000,
});
},
(error: HttpErrorResponse) => {
console.error('Error generating test data', error);
this.defaultServiceErrorHandling(error);
},
);
}
}