From 1e87b2e1676322be9c4dab90485a8c879428d2d8 Mon Sep 17 00:00:00 2001 From: GAM Date: Sun, 7 Mar 2021 19:03:23 +0100 Subject: [PATCH] table --- frontend/views/results/results-view.css | 2 +- frontend/views/table/table-view.css | 3 + ...MatchdayView.java => CalculatedMatch.java} | 16 +- .../application/data/bean/PlayerForTable.java | 129 +++++++++ .../data/service/MatchService.java | 18 +- .../data/service/PlayerService.java | 150 +++++++++- .../SeasonAndMatchdayNavigationView.java | 199 +++++++++++++ .../application/views/main/MainView.java | 3 + .../views/results/ResultsView.java | 268 ++++++------------ .../application/views/table/TableView.java | 146 ++++++++++ 10 files changed, 726 insertions(+), 208 deletions(-) create mode 100644 frontend/views/table/table-view.css rename src/main/java/com/example/application/data/bean/{MatchForMatchdayView.java => CalculatedMatch.java} (72%) create mode 100644 src/main/java/com/example/application/data/bean/PlayerForTable.java create mode 100644 src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigationView.java create mode 100644 src/main/java/com/example/application/views/table/TableView.java diff --git a/frontend/views/results/results-view.css b/frontend/views/results/results-view.css index e5e0704..638f609 100644 --- a/frontend/views/results/results-view.css +++ b/frontend/views/results/results-view.css @@ -12,7 +12,7 @@ font-size: x-large; } -.matchday_grid { +.my_grid { --_lumo-grid-border-width: 0; } diff --git a/frontend/views/table/table-view.css b/frontend/views/table/table-view.css new file mode 100644 index 0000000..303cebc --- /dev/null +++ b/frontend/views/table/table-view.css @@ -0,0 +1,3 @@ +.about-view { + display: block; +} \ No newline at end of file diff --git a/src/main/java/com/example/application/data/bean/MatchForMatchdayView.java b/src/main/java/com/example/application/data/bean/CalculatedMatch.java similarity index 72% rename from src/main/java/com/example/application/data/bean/MatchForMatchdayView.java rename to src/main/java/com/example/application/data/bean/CalculatedMatch.java index cbf8824..02e4b38 100644 --- a/src/main/java/com/example/application/data/bean/MatchForMatchdayView.java +++ b/src/main/java/com/example/application/data/bean/CalculatedMatch.java @@ -3,14 +3,14 @@ package com.example.application.data.bean; import com.example.application.data.entity.Match; import com.example.application.data.entity.Player; -public class MatchForMatchdayView { +public class CalculatedMatch { private Match match; private Player player1; private Player player2; - private Integer score1; - private Integer score2; + private Double score1; + private Double score2; - public MatchForMatchdayView(Match match, Player player1, Player player2, Integer score1, Integer score2) { + public CalculatedMatch(Match match, Player player1, Player player2, Double score1, Double score2) { this.match = match; this.player1 = player1; this.player2 = player2; @@ -42,19 +42,19 @@ public class MatchForMatchdayView { this.player2 = player2; } - public Integer getScore1() { + public Double getScore1() { return score1; } - public void setScore1(Integer score1) { + public void setScore1(Double score1) { this.score1 = score1; } - public Integer getScore2() { + public Double getScore2() { return score2; } - public void setScore2(Integer score2) { + public void setScore2(Double score2) { this.score2 = score2; } } diff --git a/src/main/java/com/example/application/data/bean/PlayerForTable.java b/src/main/java/com/example/application/data/bean/PlayerForTable.java new file mode 100644 index 0000000..0bf25fb --- /dev/null +++ b/src/main/java/com/example/application/data/bean/PlayerForTable.java @@ -0,0 +1,129 @@ +package com.example.application.data.bean; + +import com.example.application.data.entity.Player; + +public class PlayerForTable { + private Player player; + + private Integer matchPoints; + + private Integer amountOfMatches; + private Integer amountOfMatchesWon; + private Integer amountOfMatchesDrawn; + private Integer amountOfMatchesLost; + + private Double gamePointsForSelf; + private Double gamePointsForOpponents; + private Double gamePointDiff; + + private Integer place; + private Integer placeDiffToLastMatchday; + + public PlayerForTable(Player player, + Integer matchPoints, + Integer amountOfMatches, + Integer amountOfMatchesWon, + Integer amountOfMatchesDrawn, + Integer amountOfMatchesLost, + Double gamePointsForSelf, + Double gamePointsForOpponents, + Double gamePointDiff) { + this.player = player; + this.matchPoints = matchPoints; + this.amountOfMatches = amountOfMatches; + this.amountOfMatchesWon = amountOfMatchesWon; + this.amountOfMatchesDrawn = amountOfMatchesDrawn; + this.amountOfMatchesLost = amountOfMatchesLost; + this.gamePointsForSelf = gamePointsForSelf; + this.gamePointsForOpponents = gamePointsForOpponents; + this.gamePointDiff = gamePointDiff; + } + + public Player getPlayer() { + return player; + } + + public void setPlayer(Player player) { + this.player = player; + } + + public Integer getMatchPoints() { + return matchPoints; + } + + public void setMatchPoints(Integer matchPoints) { + this.matchPoints = matchPoints; + } + + public Integer getAmountOfMatches() { + return amountOfMatches; + } + + public void setAmountOfMatches(Integer amountOfMatches) { + this.amountOfMatches = amountOfMatches; + } + + public Integer getAmountOfMatchesWon() { + return amountOfMatchesWon; + } + + public void setAmountOfMatchesWon(Integer amountOfMatchesWon) { + this.amountOfMatchesWon = amountOfMatchesWon; + } + + public Integer getAmountOfMatchesDrawn() { + return amountOfMatchesDrawn; + } + + public void setAmountOfMatchesDrawn(Integer amountOfMatchesDrawn) { + this.amountOfMatchesDrawn = amountOfMatchesDrawn; + } + + public Integer getAmountOfMatchesLost() { + return amountOfMatchesLost; + } + + public void setAmountOfMatchesLost(Integer amountOfMatchesLost) { + this.amountOfMatchesLost = amountOfMatchesLost; + } + + public Double getGamePointsForSelf() { + return gamePointsForSelf; + } + + public void setGamePointsForSelf(Double gamePointsForSelf) { + this.gamePointsForSelf = gamePointsForSelf; + } + + public Double getGamePointsForOpponents() { + return gamePointsForOpponents; + } + + public void setGamePointsForOpponents(Double gamePointsForOpponents) { + this.gamePointsForOpponents = gamePointsForOpponents; + } + + public Double getGamePointDiff() { + return gamePointDiff; + } + + public void setGamePointDiff(Double gamePointDiff) { + this.gamePointDiff = gamePointDiff; + } + + public Integer getPlace() { + return place; + } + + public void setPlace(Integer place) { + this.place = place; + } + + public Integer getPlaceDiffToLastMatchday() { + return placeDiffToLastMatchday; + } + + public void setPlaceDiffToLastMatchday(Integer placeDiffToLastMatchday) { + this.placeDiffToLastMatchday = placeDiffToLastMatchday; + } +} diff --git a/src/main/java/com/example/application/data/service/MatchService.java b/src/main/java/com/example/application/data/service/MatchService.java index 0961387..7ec5b33 100644 --- a/src/main/java/com/example/application/data/service/MatchService.java +++ b/src/main/java/com/example/application/data/service/MatchService.java @@ -1,6 +1,6 @@ package com.example.application.data.service; -import com.example.application.data.bean.MatchForMatchdayView; +import com.example.application.data.bean.CalculatedMatch; import com.example.application.data.entity.Game; import com.example.application.data.entity.Match; import com.example.application.data.entity.Matchday; @@ -25,25 +25,25 @@ public class MatchService extends CrudService { return repository; } - public List getMatchesForMatchdayView(Matchday matchday) { + public List getCalculatedMatches(Matchday matchday) { return repository.findAll().stream() .filter(match -> match.getMatchday().equals(matchday)) - .map(match -> new MatchForMatchdayView(match, match.getPlayer1(), match.getPlayer2(), getScore1(match), getScore2(match))) + .map(match -> new CalculatedMatch(match, match.getPlayer1(), match.getPlayer2(), getScore1(match), getScore2(match))) .collect(Collectors.toList()); } - private static Integer getScore1(Match match) { - int score = 0; + private static double getScore1(Match match) { + double score = 0; for (Game game : match.getGames()) { - score += ((game.getPlayer1IsWhite() ? game.getResult() : -game.getResult()) + 1) / 2; + score += (double) ((game.getPlayer1IsWhite() ? game.getResult() : -game.getResult()) + 1) / 2; } return score; } - private static Integer getScore2(Match match) { - int score = 0; + private static double getScore2(Match match) { + double score = 0; for (Game game : match.getGames()) { - score += ((game.getPlayer1IsWhite() ? -game.getResult() : game.getResult()) + 1) / 2; + score += (double) ((game.getPlayer1IsWhite() ? -game.getResult() : game.getResult()) + 1) / 2; } return score; } diff --git a/src/main/java/com/example/application/data/service/PlayerService.java b/src/main/java/com/example/application/data/service/PlayerService.java index 54abd48..a0cc0ee 100644 --- a/src/main/java/com/example/application/data/service/PlayerService.java +++ b/src/main/java/com/example/application/data/service/PlayerService.java @@ -1,17 +1,30 @@ package com.example.application.data.service; +import com.example.application.data.bean.CalculatedMatch; +import com.example.application.data.bean.PlayerForTable; +import com.example.application.data.entity.Matchday; import com.example.application.data.entity.Player; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.vaadin.artur.helpers.CrudService; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + @Service public class PlayerService extends CrudService { private final PlayerRepository repository; - public PlayerService(@Autowired PlayerRepository repository) { + private final MatchdayService matchdayService; + private final MatchService matchService; + + public PlayerService(@Autowired PlayerRepository repository, @Autowired MatchdayService matchdayService, @Autowired MatchService matchService) { this.repository = repository; + this.matchdayService = matchdayService; + this.matchService = matchService; } @Override @@ -19,4 +32,139 @@ public class PlayerService extends CrudService { return repository; } + public List getPlayersForTable(Matchday matchday) { + return new PlayerForTableProvider(matchday).getPlayersForTableSorted(); + } + + private class PlayerForTableProvider { + List matchdays = new ArrayList<>(); + List calculatedMatches = new ArrayList<>(); + List players = repository.findAll(); + + public PlayerForTableProvider(Matchday matchday) { + matchdays.addAll(matchdayService.getMatchdaysForSeason(matchday.getSeason()).stream() + .filter(matchdayToFilter -> matchdayToFilter.getNumber() <= matchday.getNumber()) + .collect(Collectors.toList())); + + calculatedMatches.addAll(matchdays.stream() + .flatMap((Function>) matchdayToMap -> matchService.getCalculatedMatches(matchdayToMap).stream()) + .collect(Collectors.toList())); + } + + private List getPlayersForTableSorted() { + List playerForTableList = players.stream() + .map(this::getPlayerForTable) + .sorted(Comparator.comparingDouble(PlayerForTable::getGamePointsForSelf).reversed()) + .sorted(Comparator.comparingDouble(PlayerForTable::getGamePointDiff).reversed()) + .sorted(Comparator.comparingInt(PlayerForTable::getMatchPoints).reversed()) + .collect(Collectors.toList()); + int offset = 0; + PlayerForTable lastPlayer; + PlayerForTable currentPlayer; + for (int i = 0; i < playerForTableList.size(); i++) { + currentPlayer = playerForTableList.get(i); + if (i>0) { + lastPlayer = playerForTableList.get(i-1); + if (Objects.equals(currentPlayer.getMatchPoints(), lastPlayer.getMatchPoints()) + && Objects.equals(currentPlayer.getGamePointsForSelf(), lastPlayer.getGamePointsForSelf()) + && Objects.equals(currentPlayer.getGamePointsForOpponents(), lastPlayer.getGamePointsForOpponents())) { + offset +=1; + } + else { + offset = 0; + } + } + currentPlayer.setPlace(i + 1 - offset); + } + // TODO: add diff to last matchday + return playerForTableList; + } + + private PlayerForTable getPlayerForTable(Player player) { + List matchesAsPlayer1 = getMatchesAsPlayer1(player); + List matchesAsPlayer2 = getMatchesAsPlayer2(player); + + Map> map = getWinStateLists(matchesAsPlayer1, matchesAsPlayer2); + + int amountOfMatchesWon = map.get(WinState.WON).size(); + int amountOfMatchesDrawn = map.get(WinState.DRAWN).size(); + int amountOfMatchesLost = map.get(WinState.LOST).size(); + + int amountOfMatches = amountOfMatchesWon + amountOfMatchesDrawn + amountOfMatchesLost; + + int matchPoints = amountOfMatchesWon * 2 + amountOfMatchesDrawn; + + double gamePointsForSelf = 0; + gamePointsForSelf += matchesAsPlayer1.stream().mapToDouble(CalculatedMatch::getScore1).sum(); + gamePointsForSelf += matchesAsPlayer2.stream().mapToDouble(CalculatedMatch::getScore2).sum(); + + double gamePointsForOpponents = 0; + gamePointsForOpponents += matchesAsPlayer1.stream().mapToDouble(CalculatedMatch::getScore2).sum(); + gamePointsForOpponents += matchesAsPlayer2.stream().mapToDouble(CalculatedMatch::getScore1).sum(); + + double gamePointDiff = gamePointsForSelf - gamePointsForOpponents; + + return new PlayerForTable(player, + matchPoints, + amountOfMatches, + amountOfMatchesWon, + amountOfMatchesDrawn, + amountOfMatchesLost, + gamePointsForSelf, + gamePointsForOpponents, + gamePointDiff); + } + + private List getMatchesAsPlayer1(Player player) { + return calculatedMatches.stream() + .filter(match -> match.getPlayer1().equals(player)) + .collect(Collectors.toList()); + } + + private List getMatchesAsPlayer2(Player player) { + return calculatedMatches.stream() + .filter(match -> match.getPlayer2().equals(player)) + .collect(Collectors.toList()); + } + + private Map> getWinStateLists(List matchesAsPlayer1, List matchesAsPlayer2) { + Map> map = new HashMap<>(); + map.put(WinState.WON, new ArrayList<>()); + map.put(WinState.DRAWN, new ArrayList<>()); + map.put(WinState.LOST, new ArrayList<>()); + + for (CalculatedMatch match : matchesAsPlayer1) { + if (match.getScore1() > match.getScore2()) { + map.get(WinState.WON).add(match); + continue; + } + if (match.getScore1() < match.getScore2()) { + map.get(WinState.LOST).add(match); + continue; + } + if (match.getScore1() > 0) { + map.get(WinState.DRAWN).add(match); + } + } + + for (CalculatedMatch match : matchesAsPlayer2) { + if (match.getScore2() > match.getScore1()) { + map.get(WinState.WON).add(match); + continue; + } + if (match.getScore2() < match.getScore1()) { + map.get(WinState.LOST).add(match); + continue; + } + if (match.getScore2() > 0) { + map.get(WinState.DRAWN).add(match); + } + } + return map; + } + } + + private enum WinState { + WON, DRAWN, LOST + } } diff --git a/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigationView.java b/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigationView.java new file mode 100644 index 0000000..e53b720 --- /dev/null +++ b/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigationView.java @@ -0,0 +1,199 @@ +package com.example.application.views.abstractnavigation; + +import com.example.application.data.entity.Matchday; +import com.example.application.data.entity.Season; +import com.example.application.data.service.MatchdayService; +import com.example.application.data.service.SeasonService; +import com.vaadin.flow.component.AbstractField; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasValue; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Label; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.select.Select; +import com.vaadin.flow.router.BeforeEvent; +import com.vaadin.flow.router.HasUrlParameter; +import com.vaadin.flow.router.WildcardParameter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public abstract class SeasonAndMatchdayNavigationView extends Div implements HasUrlParameter { + // TODO: show dropdown menus also for invalid URLs (with content that fits the situation) + + protected final SeasonService seasonService; + protected final MatchdayService matchdayService; + + protected String seasonParam; + protected String matchdayParam; + + private final Label invalidUrlLabel = new Label(); + + protected final VerticalLayout outer = new VerticalLayout(); + protected final HorizontalLayout selectionLayout = new HorizontalLayout(); + + protected final List seasonList = new ArrayList<>(); + protected final Select seasonSelect = new Select<>(); + + protected final List matchdayList = new ArrayList<>(); + protected final Select matchdaySelect = new Select<>(); + protected Component contentLayout; + + public SeasonAndMatchdayNavigationView(@Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService) { + this.seasonService = seasonService; + this.matchdayService = matchdayService; + + addClassName("results-view"); + + configureOuterLayout(); + configureSelectionLayout(); + configureContentLayout(); + } + + protected abstract String route(); + + protected abstract void configureContentLayout(); + + protected abstract void configureContent(); + + private void configureOuterLayout() { + add(outer); + outer.setAlignItems(FlexComponent.Alignment.CENTER); + } + + private void configureSelectionLayout() { + selectionLayout.setWidthFull(); + selectionLayout.setAlignItems(FlexComponent.Alignment.CENTER); + selectionLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END); + selectionLayout.add(new Label("Season:"), seasonSelect, new Label("Matchday:"), matchdaySelect); + + seasonSelect.addValueChangeListener(seasonSelectValueChangeListener()); + matchdaySelect.addValueChangeListener(matchdaySelectValueChangeListener()); + + // provide data + fillSeasonSelectWithData(); + } + + private HasValue.ValueChangeListener, Matchday>> matchdaySelectValueChangeListener() { + return matchdayChangeEvent -> getUI().ifPresent(ui -> { + Season season = seasonSelect.getValue(); + Matchday matchday = matchdayChangeEvent.getValue(); + if (season != null && matchday != null) { + String seasonParam = season.toString(); + String matchdayParam = matchday.toString(); + navigate(ui, seasonParam, matchdayParam); + } + }); + } + + private HasValue.ValueChangeListener, Season>> seasonSelectValueChangeListener() { + return seasonChangeEvent -> getUI().ifPresent(ui -> { + Season newSeason = seasonChangeEvent.getValue(); + if (newSeason != null) { + String seasonParam = newSeason.toString(); + String matchdayParam = null; + + Matchday matchdayInNewSeason = null; + Matchday matchdayInOldSeason = matchdaySelect.getValue(); + if (matchdayInOldSeason != null) { + matchdayParam = matchdayInOldSeason.toString(); + matchdayInNewSeason = getMatchdayFromParam(matchdayParam, newSeason); + } + matchdayParam = matchdayInNewSeason == null ? "1" : matchdayParam; + + navigate(ui, seasonParam, matchdayParam); + } + }); + } + + private void fillSeasonSelectWithData() { + seasonList.clear(); + seasonList.addAll(seasonService.getAllSeasonsSorted()); + seasonSelect.setItems(seasonList); + } + + private void fillMatchdaySelectWithData() { + matchdayList.clear(); + matchdayList.addAll(matchdayService.getMatchdaysForSeason(seasonSelect.getValue())); + matchdaySelect.setItems(matchdayList); + } + + protected boolean isMatchDayParamValid(@NonNull String matchdayParam) { + return matchdayList.stream().anyMatch(matchday -> matchdayParam.equals(matchday.toString())); + } + + protected void navigate(UI ui, String seasonParam, String matchdayParam) { + ui.navigate(String.format("%s/%s/%s/", route(), seasonParam, matchdayParam)); + } + + @Override + public void setParameter(BeforeEvent beforeEvent, @WildcardParameter String param) { + outer.removeAll(); + + if (!param.matches("^[0-9]*/[0-9]*/?$")) { + invalidUrlLabel.setText("Invalid URL! Please provide params in the form season/matchday/"); + outer.add(invalidUrlLabel); + return; + } + + String[] params = param.split("/"); + seasonParam = params[0]; + matchdayParam = params[1]; + + Season season = getSeasonFromParam(seasonParam); + if (season == null) { + invalidUrlLabel.setText(String.format("Invalid URL! Season \"%s\" does not exist in the database!", seasonParam)); + outer.add(invalidUrlLabel); + return; + } + + seasonSelect.setValue(season); + fillMatchdaySelectWithData(); + + Matchday matchday = getMatchdayFromParam(matchdayParam); + if (matchday == null) { + invalidUrlLabel.setText(String.format("Invalid URL! Matchday \"%s\" in Season \"%s\" does not exist in the database!", matchdayParam, seasonParam)); + outer.add(invalidUrlLabel); + return; + } + + matchdaySelect.setValue(matchday); + + outer.add(selectionLayout); + outer.add(contentLayout); + + configureContent(); + } + + @Nullable + private Season getSeasonFromParam(@NonNull String seasonParam) { + for (Season season : seasonList) { + if (seasonParam.equals(season.toString())) { + return season; + } + } + return null; + } + + @Nullable + private Matchday getMatchdayFromParam(@NonNull String matchdayParam) { + return getMatchdayFromParam(matchdayParam, null); + } + + @Nullable + private Matchday getMatchdayFromParam(@NonNull String matchdayParam, @Nullable Season season) { + List matchdayList = season == null ? this.matchdayList : matchdayService.getMatchdaysForSeason(season); + for (Matchday matchday : matchdayList) { + if (matchdayParam.equals(matchday.toString())) { + return matchday; + } + } + return null; + } +} diff --git a/src/main/java/com/example/application/views/main/MainView.java b/src/main/java/com/example/application/views/main/MainView.java index 69b9626..ccd479c 100644 --- a/src/main/java/com/example/application/views/main/MainView.java +++ b/src/main/java/com/example/application/views/main/MainView.java @@ -1,6 +1,7 @@ package com.example.application.views.main; import com.example.application.views.results.ResultsView; +import com.example.application.views.table.TableView; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.applayout.AppLayout; @@ -35,6 +36,7 @@ import java.util.Optional; @JsModule("./styles/shared-styles.js") @Theme(value = Lumo.class, variant = Lumo.DARK) public class MainView extends AppLayout { + // TODO: Add Localization private final Tabs menu; private H1 viewTitle; @@ -95,6 +97,7 @@ public class MainView extends AppLayout { // createTab("Address Form", AddressFormView.class), // createTab("Credit Card Form", CreditCardFormView.class), // createTab("Map", MapView.class), + createTab("Table", TableView.class), createTab("Results", ResultsView.class)}; } diff --git a/src/main/java/com/example/application/views/results/ResultsView.java b/src/main/java/com/example/application/views/results/ResultsView.java index 5ac914b..62dd94e 100644 --- a/src/main/java/com/example/application/views/results/ResultsView.java +++ b/src/main/java/com/example/application/views/results/ResultsView.java @@ -1,151 +1,106 @@ package com.example.application.views.results; -import com.example.application.data.bean.MatchForMatchdayView; -import com.example.application.data.entity.Matchday; +import com.example.application.data.bean.CalculatedMatch; import com.example.application.data.entity.Player; -import com.example.application.data.entity.Season; import com.example.application.data.service.MatchService; import com.example.application.data.service.MatchdayService; import com.example.application.data.service.SeasonService; +import com.example.application.views.abstractnavigation.SeasonAndMatchdayNavigationView; import com.example.application.views.main.MainView; import com.vaadin.flow.component.*; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.grid.ColumnTextAlign; import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Label; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.component.select.Select; import com.vaadin.flow.function.ValueProvider; import com.vaadin.flow.router.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import java.util.ArrayList; -import java.util.List; +import javax.swing.text.NumberFormatter; @CssImport("./views/results/results-view.css") @Route(value = "results", layout = MainView.class) -@RouteAlias(value = "", layout = MainView.class) @PageTitle("Results") -public class ResultsView extends Div implements HasUrlParameter { +public class ResultsView extends SeasonAndMatchdayNavigationView { - private final SeasonService seasonService; - private final MatchdayService matchdayService; private final MatchService matchService; - private String seasonParam; - private String matchdayParam; + private VerticalLayout matchdayLayout; + private Label matchdayHeader; + private Grid grid; - private final Label invalidUrlLabel = new Label(); - private final Label matchdayViewHeader = new Label(); - - private final VerticalLayout outer = new VerticalLayout(); - private final HorizontalLayout selectionLayout = new HorizontalLayout(); - private final HorizontalLayout outerMatchdaylayout = new HorizontalLayout(); - private final VerticalLayout innerMatchdayLayout = new VerticalLayout(); - - private final List seasonList = new ArrayList<>(); - private final Select seasonSelect = new Select<>(); - - private final List matchdayList = new ArrayList<>(); - private final Select matchdaySelect = new Select<>(); - - private final Button prevButton = new Button(new Icon(VaadinIcon.ARROW_LEFT)); - private final Button nextButton = new Button(new Icon(VaadinIcon.ARROW_RIGHT)); - - private final Grid grid = new Grid<>(); + private Button prevButton; + private Button nextButton; public ResultsView(@Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService, @Autowired MatchService matchService) { - this.seasonService = seasonService; - this.matchdayService = matchdayService; + super(seasonService, matchdayService); this.matchService = matchService; - addClassName("results-view"); - - configureOuterLayout(); - configureSelectionLayout(); - configureMatchdayViewLayout(); } - private void configureOuterLayout() { - add(outer); - outer.setAlignItems(FlexComponent.Alignment.CENTER); + @Override + protected String route() { + return "results"; } - private void configureSelectionLayout() { - // pretty looks - selectionLayout.setWidthFull(); - selectionLayout.setAlignItems(FlexComponent.Alignment.CENTER); - selectionLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END); - selectionLayout.add(new Label("Season:"), seasonSelect, new Label("Matchday:"), matchdaySelect); - - seasonSelect.addValueChangeListener(seasonSelectValueChangeListener()); - matchdaySelect.addValueChangeListener(matchdaySelectValueChangeListener()); + //////////// + // LAYOUT // + //////////// - // provide data - fillSeasonSelectWithData(); + private VerticalLayout getMatchdayLayout() { + if (matchdayLayout == null) { + matchdayLayout = new VerticalLayout(); + } + return matchdayLayout; } - private HasValue.ValueChangeListener, Matchday>> matchdaySelectValueChangeListener() { - return matchdayChangeEvent -> getUI().ifPresent(ui -> { - Season season = seasonSelect.getValue(); - Matchday matchday = matchdayChangeEvent.getValue(); - if (season != null && matchday != null) { - String seasonParam = season.toString(); - String matchdayParam = matchday.toString(); - navigate(ui, seasonParam, matchdayParam); - } - }); + private Label getMatchdayHeader() { + if (matchdayHeader == null) { + matchdayHeader = new Label(); + } + return matchdayHeader; } - private HasValue.ValueChangeListener, Season>> seasonSelectValueChangeListener() { - return seasonChangeEvent -> getUI().ifPresent(ui -> { - Season newSeason = seasonChangeEvent.getValue(); - if (newSeason != null) { - String seasonParam = newSeason.toString(); - String matchdayParam = null; - - Matchday matchdayInNewSeason = null; - Matchday matchdayInOldSeason = matchdaySelect.getValue(); - if (matchdayInOldSeason != null) { - matchdayParam = matchdayInOldSeason.toString(); - matchdayInNewSeason = getMatchdayFromParam(matchdayParam, newSeason); - } - matchdayParam = matchdayInNewSeason == null ? "1" : matchdayParam; - - navigate(ui, seasonParam, matchdayParam); - } - }); + private Grid getGrid() { + if (grid == null) { + grid = new Grid<>(); + } + return grid; } - private void fillSeasonSelectWithData() { - seasonList.clear(); - seasonList.addAll(seasonService.getAllSeasonsSorted()); - seasonSelect.setItems(seasonList); + private Button getPrevButton() { + if (prevButton == null) { + prevButton = new Button(new Icon(VaadinIcon.ARROW_LEFT)); + } + return prevButton; } - private void fillMatchdaySelectWithData() { - matchdayList.clear(); - matchdayList.addAll(matchdayService.getMatchdaysForSeason(seasonSelect.getValue())); - matchdaySelect.setItems(matchdayList); + private Button getNextButton() { + if (nextButton == null) { + nextButton = new Button(new Icon(VaadinIcon.ARROW_RIGHT)); + } + return nextButton; } - private void configureMatchdayViewLayout() { - outerMatchdaylayout.add(prevButton, innerMatchdayLayout, nextButton); + @Override + protected void configureContentLayout() { + contentLayout = new HorizontalLayout(getPrevButton(), getMatchdayLayout(), getNextButton()); + configureMatchdayLayout(); + } - matchdayViewHeader.addClassName("big_header"); + private void configureMatchdayLayout() { + getMatchdayHeader().addClassName("big_header"); - innerMatchdayLayout.setPadding(false); - innerMatchdayLayout.add(matchdayViewHeader, grid); - innerMatchdayLayout.setAlignItems(FlexComponent.Alignment.CENTER); - innerMatchdayLayout.addClassName("inner_matchday_layout"); + getMatchdayLayout().setPadding(false); + getMatchdayLayout().add(getMatchdayHeader(), getGrid()); + getMatchdayLayout().setAlignItems(FlexComponent.Alignment.CENTER); + getMatchdayLayout().addClassName("inner_matchday_layout"); Label headerPlayer1 = new Label("Player 1"); headerPlayer1.addClassName("column_header"); @@ -154,129 +109,64 @@ public class ResultsView extends Div implements HasUrlParameter { Label headerResult = new Label("Result"); headerResult.addClassName("column_header"); - grid.addColumn((ValueProvider) MatchForMatchdayView::getPlayer1) + getGrid().addColumn((ValueProvider) CalculatedMatch::getPlayer1) .setHeader(headerPlayer1) .setTextAlign(ColumnTextAlign.CENTER) .setWidth("13em"); - grid.addColumn((ValueProvider) match -> "vs.") + getGrid().addColumn((ValueProvider) match -> "vs.") .setHeader("vs.") .setTextAlign(ColumnTextAlign.CENTER) .setWidth("3em"); - grid.addColumn((ValueProvider) MatchForMatchdayView::getPlayer2) + getGrid().addColumn((ValueProvider) CalculatedMatch::getPlayer2) .setHeader(headerPlayer2) .setTextAlign(ColumnTextAlign.CENTER) .setWidth("13em"); - grid.addColumn((ValueProvider) match -> { - String result = match.getScore1().toString() + " : " + match.getScore2().toString(); + getGrid().addColumn((ValueProvider) match -> { + + String result = match.getScore1().toString().replace(".0", "") + + " : " + + match.getScore2().toString().replace(".0", ""); return result.equals("0 : 0") ? "" : result; }) .setHeader(headerResult) .setTextAlign(ColumnTextAlign.CENTER) .setWidth("6em"); - grid.setWidth("36em"); - grid.setHeightByRows(true); + getGrid().setWidth("36em"); + getGrid().setHeightByRows(true); - grid.addClassName("matchday_grid"); + getGrid().addClassName("my_grid"); } - private String getPrevMatchdayParam() { - return String.valueOf(Integer.parseInt(matchdayParam) - 1); - } - - private String getNextMatchdayParam() { - return String.valueOf(Integer.parseInt(matchdayParam) + 1); - } - - private boolean isMatchDayParamValid(@NonNull String matchdayParam) { - return matchdayList.stream().anyMatch(matchday -> matchdayParam.equals(matchday.toString())); - } - - private ComponentEventListener> getButtonClickListener(Button button, String matchdayParam) { - return buttonClickEvent -> getUI().ifPresent(ui -> navigate(ui, seasonParam, matchdayParam)); - } - - private void navigate(UI ui, String seasonParam, String matchdayParam) { - ui.navigate(String.format("results/%s/%s/", seasonParam, matchdayParam)); - } - - private void configureMatchdayView() { - matchdayViewHeader.setText(String.format("Matchday %s", matchdaySelect.getValue().toString())); - grid.setItems(matchService.getMatchesForMatchdayView(matchdaySelect.getValue())); - } + ///////////// + // CONTENT // + ///////////// @Override - public void setParameter(BeforeEvent beforeEvent, @WildcardParameter String param) { - outer.removeAll(); - - if (!param.matches("^[0-9]*/[0-9]*/?$")) { - invalidUrlLabel.setText("Invalid URL! Please provide params in the form season/matchday/"); - outer.add(invalidUrlLabel); - return; - } - - String[] params = param.split("/"); - seasonParam = params[0]; - matchdayParam = params[1]; - - Season season = getSeasonFromParam(seasonParam); - if (season == null) { - invalidUrlLabel.setText(String.format("Invalid URL! Season \"%s\" does not exist in the database!", seasonParam)); - outer.add(invalidUrlLabel); - return; - } - - seasonSelect.setValue(season); - fillMatchdaySelectWithData(); - - Matchday matchday = getMatchdayFromParam(matchdayParam); - if (matchday == null) { - invalidUrlLabel.setText(String.format("Invalid URL! Matchday \"%s\" in Season \"%s\" does not exist in the database!", matchdayParam, seasonParam)); - outer.add(invalidUrlLabel); - return; - } - - matchdaySelect.setValue(matchday); - configureMatchdayView(); - - outer.add(selectionLayout); - outer.add(outerMatchdaylayout); - + protected void configureContent() { + getMatchdayHeader().setText(String.format("Matchday %s", matchdaySelect.getValue().toString())); + getGrid().setItems(matchService.getCalculatedMatches(matchdaySelect.getValue())); configureButtons(); } private void configureButtons() { - prevButton.setEnabled(isMatchDayParamValid(getPrevMatchdayParam())); - prevButton.addClickListener(getButtonClickListener(prevButton, getPrevMatchdayParam())); + getPrevButton().setEnabled(isMatchDayParamValid(getPrevMatchdayParam())); + getPrevButton().addClickListener(getButtonClickListener(getPrevMatchdayParam())); - nextButton.setEnabled(isMatchDayParamValid(getNextMatchdayParam())); - nextButton.addClickListener(getButtonClickListener(nextButton, getNextMatchdayParam())); + getNextButton().setEnabled(isMatchDayParamValid(getNextMatchdayParam())); + getNextButton().addClickListener(getButtonClickListener(getNextMatchdayParam())); } - @Nullable - private Season getSeasonFromParam(@NonNull String seasonParam) { - for (Season season : seasonList) { - if (seasonParam.equals(season.toString())) { - return season; - } - } - return null; + private ComponentEventListener> getButtonClickListener(String matchdayParam) { + return buttonClickEvent -> getUI().ifPresent(ui -> navigate(ui, seasonParam, matchdayParam)); } - @Nullable - private Matchday getMatchdayFromParam(@NonNull String matchdayParam) { - return getMatchdayFromParam(matchdayParam, null); + private String getPrevMatchdayParam() { + return String.valueOf(Integer.parseInt(matchdayParam) - 1); } - @Nullable - private Matchday getMatchdayFromParam(@NonNull String matchdayParam, @Nullable Season season) { - List matchdayList = season == null ? this.matchdayList : matchdayService.getMatchdaysForSeason(season); - for (Matchday matchday : matchdayList) { - if (matchdayParam.equals(matchday.toString())) { - return matchday; - } - } - return null; + private String getNextMatchdayParam() { + return String.valueOf(Integer.parseInt(matchdayParam) + 1); } } diff --git a/src/main/java/com/example/application/views/table/TableView.java b/src/main/java/com/example/application/views/table/TableView.java new file mode 100644 index 0000000..2bf28aa --- /dev/null +++ b/src/main/java/com/example/application/views/table/TableView.java @@ -0,0 +1,146 @@ +package com.example.application.views.table; + +import com.example.application.data.bean.PlayerForTable; +import com.example.application.data.entity.Player; +import com.example.application.data.service.MatchdayService; +import com.example.application.data.service.PlayerService; +import com.example.application.data.service.SeasonService; +import com.example.application.views.abstractnavigation.SeasonAndMatchdayNavigationView; +import com.example.application.views.main.MainView; +import com.vaadin.flow.component.dependency.CssImport; +import com.vaadin.flow.component.grid.ColumnTextAlign; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.Label; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.function.ValueProvider; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouteAlias; +import org.springframework.beans.factory.annotation.Autowired; + +@CssImport("./views/table/table-view.css") +@Route(value = "table", layout = MainView.class) +@RouteAlias(value = "", layout = MainView.class) +@PageTitle("Table") +public class TableView extends SeasonAndMatchdayNavigationView { + + private final PlayerService playerService; + + private Grid grid; + + public TableView(@Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService, @Autowired PlayerService playerService) { + super(seasonService, matchdayService); + this.playerService = playerService; + addClassName("table-view"); + } + + @Override + protected String route() { + return "table"; + } + + //////////// + // LAYOUT // + //////////// + + private Grid getGrid() { + if (grid == null) { + grid = new Grid<>(); + } + return grid; + } + + @Override + protected void configureContentLayout() { + contentLayout = new HorizontalLayout(getGrid()); + + Label headerPlace = new Label("Place"); + headerPlace.addClassName("column_header"); + + Label headerPlayer = new Label("Player"); + headerPlayer.addClassName("column_header"); + + Label headerMatchesPlayed = new Label("Played"); + + Label headerMatchPoints = new Label("Points"); + headerMatchPoints.addClassName("column_header"); + + Label headerWon = new Label("W"); + + Label headerDrawn = new Label("D"); + + Label headerLost = new Label("L"); + + Label headerGames = new Label("Games"); + + Label headerDiff = new Label("Diff"); + headerDiff.addClassName("column_header"); + + getGrid().addColumn((ValueProvider) PlayerForTable::getPlace) + .setHeader(headerPlace) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("5em"); + + getGrid().addColumn((ValueProvider) PlayerForTable::getPlayer) + .setHeader(headerPlayer) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("13em"); + + getGrid().addColumn((ValueProvider) PlayerForTable::getAmountOfMatches) + .setHeader(headerMatchesPlayed) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("5em"); + + getGrid().addColumn((ValueProvider) PlayerForTable::getMatchPoints) + .setHeader(headerMatchPoints) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("6em"); + + getGrid().addColumn((ValueProvider) PlayerForTable::getAmountOfMatchesWon) + .setHeader(headerWon) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("3em"); + + getGrid().addColumn((ValueProvider) PlayerForTable::getAmountOfMatchesDrawn) + .setHeader(headerDrawn) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("3em"); + + getGrid().addColumn((ValueProvider) PlayerForTable::getAmountOfMatchesLost) + .setHeader(headerLost) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("3em"); + + getGrid().addColumn((ValueProvider) playerForTable -> { + //noinspection CodeBlock2Expr + return playerForTable.getGamePointsForSelf().toString().replace(".0", "") // TODO: make this reusable + + " : " + + playerForTable.getGamePointsForOpponents().toString().replace(".0", ""); + }) + .setHeader(headerGames) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("6em"); + + getGrid().addColumn((ValueProvider) playerForTable -> { + Double diff = playerForTable.getGamePointDiff(); + String diffString = playerForTable.getGamePointDiff().toString().replace(".0", ""); + return diff > 0 ? "+" + diffString : diffString; + }) + .setHeader(headerDiff) + .setTextAlign(ColumnTextAlign.CENTER) + .setWidth("3em"); + + getGrid().setWidth("49em"); + getGrid().setHeightByRows(true); + getGrid().addClassName("my_grid"); + } + + ///////////// + // CONTENT // + ///////////// + + @Override + protected void configureContent() { + getGrid().setItems(playerService.getPlayersForTable(matchdaySelect.getValue())); + } +}