From e386d89c6f10e111ff3de13f5c8a9c9fa13604d9 Mon Sep 17 00:00:00 2001 From: David Rodenkirchen Date: Tue, 3 Feb 2026 22:58:24 +0100 Subject: [PATCH] add managing UI --- .../pages/ManageTournamentsPage.py | 117 +++++++++++++++--- .../services/TournamentService.py | 14 +++ src/ezgg_lan_manager/types/Tournament.py | 4 + 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/src/ezgg_lan_manager/pages/ManageTournamentsPage.py b/src/ezgg_lan_manager/pages/ManageTournamentsPage.py index 9755ef6..7071727 100644 --- a/src/ezgg_lan_manager/pages/ManageTournamentsPage.py +++ b/src/ezgg_lan_manager/pages/ManageTournamentsPage.py @@ -1,32 +1,117 @@ import logging +from datetime import datetime +from typing import Optional -from rio import Column, Component, event, TextStyle, Text, Spacer +from from_root import from_root +from rio import Column, Component, event, TextStyle, Text, Spacer, Row, Image, Tooltip, IconButton, Popup, Rectangle, Dropdown, ThemeContextSwitcher, Button -from src.ezgg_lan_manager import ConfigurationService +from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserService from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox +from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text +from src.ezgg_lan_manager.types.Participant import Participant +from src.ezgg_lan_manager.types.Tournament import Tournament +from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus logger = logging.getLogger(__name__.split(".")[-1]) class ManageTournamentsPage(Component): + tournaments: list[Tournament] = [] + remove_participant_popup_open: bool = False + cancel_options: dict[str, Optional[Participant]] = {"": None} + tournament_id_selected_for_participant_removal: Optional[int] = None + participant_selected_for_removal: Optional[Participant] = None + @event.on_populate async def on_populate(self) -> None: + self.tournaments = await self.session[TournamentService].get_tournaments() await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turnier Verwaltung") + async def on_start_pressed(self, tournament_id: int) -> None: + logger.info(f"Starting tournament with ID {tournament_id}") + await self.session[TournamentService].start_tournament(tournament_id) + + async def on_cancel_pressed(self, tournament_id: int) -> None: + logger.info(f"Canceling tournament with ID {tournament_id}") + await self.session[TournamentService].cancel_tournament(tournament_id) + + async def on_remove_participant_pressed(self, tournament_id: int) -> None: + tournament = await self.session[TournamentService].get_tournament_by_id(tournament_id) + if tournament is None: + return + users = await self.session[UserService].get_all_users() + try: + self.cancel_options = {next(filter(lambda u: u.user_id == p.id, users)).user_name: p for p in tournament.participants} + except StopIteration as e: + logger.error(f"Error trying to find user for participant: {e}") + self.tournament_id_selected_for_participant_removal = tournament_id + self.remove_participant_popup_open = True + + async def on_remove_participant_confirm_pressed(self) -> None: + if self.participant_selected_for_removal is not None and self.tournament_id_selected_for_participant_removal is not None: + logger.info(f"Removing participant with ID {self.participant_selected_for_removal.id} from tournament with ID {self.tournament_id_selected_for_participant_removal}") + await self.session[TournamentService].unregister_user_from_tournament(self.participant_selected_for_removal.id, self.tournament_id_selected_for_participant_removal) + await self.on_remove_participant_cancel_pressed() + + async def on_remove_participant_cancel_pressed(self) -> None: + self.tournament_id_selected_for_participant_removal = None + self.participant_selected_for_removal = None + self.remove_participant_popup_open = False + def build(self) -> Component: - return Column( - MainViewContentBox( - Column( - Text( - text="Turnier Verwaltung", - style=TextStyle( - fill=self.session.theme.background_color, - font_size=1.2 - ), - margin_top=2, - margin_bottom=2, - align_x=0.5 - ) + tournament_rows = [] + for tournament in self.tournaments: + start_time_color = self.session.theme.background_color + if tournament.start_time < datetime.now() and tournament.status == TournamentStatus.OPEN: + start_time_color = self.session.theme.warning_color + + tournament_rows.append( + Row( + Image(image=from_root(f"src/ezgg_lan_manager/assets/img/games/{tournament.game_title.image_name}"), min_width=1.5, margin_right=1), + Text(tournament.name, style=TextStyle(fill=self.session.theme.background_color, font_size=0.8), justify="left", margin_right=1.5), + Text(f"{weekday_to_display_text(tournament.start_time.weekday())[:2]}.{tournament.start_time.strftime('%H:%M')} Uhr", style=TextStyle(fill=start_time_color, font_size=0.8), justify="left", margin_right=1), + Spacer(), + Tooltip(anchor=IconButton("material/play_arrow", min_size=2, margin_right=1, on_press=lambda: self.on_start_pressed(tournament.id)), tip="Starten"), + Tooltip(anchor=IconButton("material/cancel_schedule_send", min_size=2, margin_right=1, on_press=lambda: self.on_cancel_pressed(tournament.id)), tip="Absagen"), + Tooltip(anchor=IconButton("material/person_cancel", min_size=2, margin_right=1, on_press=lambda: self.on_remove_participant_pressed(tournament.id)), tip="Spieler entfernen"), + margin=1 ) + ) + + return Column( + Popup( + anchor=MainViewContentBox( + Column( + Text( + text="Turnier Verwaltung", + style=TextStyle( + fill=self.session.theme.background_color, + font_size=1.2 + ), + margin_top=2, + margin_bottom=2, + align_x=0.5 + ), + *tournament_rows + ) + ), + content=Rectangle( + content=Row( + ThemeContextSwitcher( + content=Dropdown(options=self.cancel_options, min_width=20, selected_value=self.bind().participant_selected_for_removal), color=self.session.theme.hud_color + ), + Button(content="REMOVE", shape="rectangle", grow_x=False, on_press=self.on_remove_participant_confirm_pressed), + Button(content="CANCEL", shape="rectangle", grow_x=False, on_press=self.on_remove_participant_cancel_pressed), + margin=0.5 + ), + min_width=30, + min_height=4, + fill=self.session.theme.primary_color, + margin_top=3.5, + stroke_width=0.3, + stroke_color=self.session.theme.neutral_color, + ), + is_open=self.remove_participant_popup_open, + color="none" ), Spacer() - ) + ) diff --git a/src/ezgg_lan_manager/services/TournamentService.py b/src/ezgg_lan_manager/services/TournamentService.py index ad4d28f..1a3f08b 100644 --- a/src/ezgg_lan_manager/services/TournamentService.py +++ b/src/ezgg_lan_manager/services/TournamentService.py @@ -56,3 +56,17 @@ class TournamentService: all_users = await self._db_service.get_all_users() participant_ids = [p.id for p in participants] return list(filter(lambda u: u.user_id in participant_ids, all_users)) + + async def start_tournament(self, tournament_id: int): + tournament = await self.get_tournament_by_id(tournament_id) + if tournament: + tournament.start() + # ToDo: Write matches/round to database + self._cache_dirty = True + + async def cancel_tournament(self, tournament_id: int): + tournament = await self.get_tournament_by_id(tournament_id) + if tournament: + tournament.cancel() + # ToDo: Update to database + self._cache_dirty = True diff --git a/src/ezgg_lan_manager/types/Tournament.py b/src/ezgg_lan_manager/types/Tournament.py index 076e06f..ea6ead1 100644 --- a/src/ezgg_lan_manager/types/Tournament.py +++ b/src/ezgg_lan_manager/types/Tournament.py @@ -93,8 +93,12 @@ class Tournament: def remove_participant(self, participant: Participant) -> None: if participant.id not in (p.id for p in self._participants): raise TournamentError(f"Participant with ID {participant.id} not registered for tournament") + # ToDo: Check if tournament already started => correctly resolve matches with now missing participant self._participants.remove(participant) + def cancel(self): + self.status = TournamentStatus.CANCELED + def match_has_ended_callback(self, match: Match) -> None: if self._matches is None: return