import logging from dataclasses import field from typing import Optional from rio import Column, Component, event, TextStyle, Text, TextInput, ThemeContextSwitcher, Grid, \ PointerEventListener, PointerEvent, Rectangle, Color, TextInputChangeEvent, Spacer, Row, Switch, \ SwitchChangeEvent, EventHandler from src.ezgg_lan_manager import ConfigurationService, UserService, AccountingService, SeatingService, MailingService from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.NewTransactionForm import NewTransactionForm from src.ezgg_lan_manager.components.UserEditForm import UserEditForm from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError from src.ezgg_lan_manager.types.SessionStorage import SessionStorage from src.ezgg_lan_manager.types.Transaction import Transaction from src.ezgg_lan_manager.types.User import User logger = logging.getLogger(__name__.split(".")[-1]) class ClickableGridContent(Component): text: str = "" is_hovered: bool = False clicked_cb: EventHandler[str] = None async def on_mouse_enter(self, _: PointerEvent) -> None: self.is_hovered = True async def on_mouse_leave(self, _: PointerEvent) -> None: self.is_hovered = False async def on_mouse_click(self, _: PointerEvent) -> None: await self.call_event_handler(self.clicked_cb, self.text) def build(self) -> Component: return PointerEventListener( content=Rectangle( content=Text( self.text, style=TextStyle(fill=self.session.theme.success_color) if self.is_hovered else TextStyle( fill=self.session.theme.background_color), grow_x=True ), fill=Color.TRANSPARENT, cursor="pointer" ), on_pointer_enter=self.on_mouse_enter, on_pointer_leave=self.on_mouse_leave, on_press=self.on_mouse_click ) class ManageUsersPage(Component): selected_user: Optional[User] = None all_users: Optional[list] = None search_results: list[User] = field(default_factory=list) accounting_section_result_text: str = "" accounting_section_result_success: bool = True user_account_balance: str = "0.00 €" user_seat: str = "-" is_user_account_locked: bool = False @event.on_populate async def on_populate(self) -> None: await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Nutzer Verwaltung") self.all_users = await self.session[UserService].get_all_users() self.search_results = self.all_users async def on_user_clicked(self, user_name: str) -> None: self.selected_user = next(filter(lambda user: user.user_name == user_name, self.all_users)) user_account_balance_raw = await self.session[AccountingService].get_balance(self.selected_user.user_id) self.user_account_balance = AccountingService.make_euro_string_from_decimal(user_account_balance_raw) seat = await self.session[SeatingService].get_user_seat(self.selected_user.user_id) self.user_seat = seat.seat_id if seat else "-" self.is_user_account_locked = not self.selected_user.is_active async def on_search_parameters_changed(self, e: TextInputChangeEvent) -> None: self.search_results = list( filter(lambda user: (e.text.lower() in user.user_name.lower()) or e.text.lower() in str(user.user_id), self.all_users)) async def change_account_active(self, _: SwitchChangeEvent) -> None: self.selected_user.is_active = not self.is_user_account_locked await self.session[UserService].update_user(self.selected_user) async def on_new_transaction(self, transaction: Transaction) -> None: if not self.session[SessionStorage].is_team_member: # Better safe than sorry return logger.info(f"Got new transaction for user with ID '{transaction.user_id}' over " f"{'-' if transaction.is_debit else '+'}" f"{AccountingService.make_euro_string_from_decimal(transaction.value)} " f"with reference '{transaction.reference}'") if transaction.is_debit: try: await self.session[AccountingService].remove_balance( transaction.user_id, transaction.value, transaction.reference ) except InsufficientFundsError: self.accounting_section_result_text = "Guthaben nicht ausreichend!" self.accounting_section_result_success = False return else: new_total_balance = await self.session[AccountingService].add_balance( transaction.user_id, transaction.value, transaction.reference ) user = await self.session[UserService].get_user(transaction.user_id) await self.session[MailingService].send_email( "Dein Guthaben wurde aufgeladen", self.session[MailingService].generate_account_balance_added_mail_body(user, transaction.value, new_total_balance), user.user_mail ) self.accounting_section_result_text = f"Guthaben {'entfernt' if transaction.is_debit else 'hinzugefügt'}!" self.accounting_section_result_success = True def build(self) -> Component: return Column( MainViewContentBox( Column( Text( text="Nutzersuche", style=TextStyle( fill=self.session.theme.background_color, font_size=1.2 ), margin_top=2, margin_bottom=2, align_x=0.5 ), TextInput( label="Nutzername oder ID", margin=1, on_change=self.on_search_parameters_changed ), ThemeContextSwitcher( Grid( [ Text("Nutzername", margin_bottom=1, grow_x=True, style=TextStyle(font_size=1.1)), Text("Nutzer-ID", margin_bottom=1, style=TextStyle(font_size=1.1)) ], *[[ ClickableGridContent(text=user.user_name, clicked_cb=self.on_user_clicked), Text( str(user.user_id), justify="right" ) ] for user in self.search_results], row_spacing=0.2, margin=1 ), color="primary" ) ) ), MainViewContentBox( Column( Text( text="Konto & Sitzplatz", style=TextStyle( fill=self.session.theme.background_color, font_size=1.2 ), margin_top=2, margin_bottom=2, align_x=0.5 ), Row( Text( text="Kontostand:", style=TextStyle( fill=self.session.theme.background_color, font_size=1 ), margin_top=0.5, margin_bottom=1, margin_left=2 ), Text( text=self.bind().user_account_balance, style=TextStyle( fill=self.session.theme.neutral_color, font_size=1 ), margin_top=0.5, margin_bottom=1, margin_right=2, justify="right" ), ), Row( Text( text="Kontosperrung:", style=TextStyle( fill=self.session.theme.background_color, font_size=1 ), margin_top=0.5, margin_bottom=1, margin_left=2, grow_x=True ), ThemeContextSwitcher( content=Switch( is_on=self.bind().is_user_account_locked, margin_top=0.5, margin_bottom=1, margin_right=2, on_change=self.change_account_active ), color="primary" ), ), Row( Text( text="Sitzplatz:", style=TextStyle( fill=self.session.theme.background_color, font_size=1 ), margin_top=0.5, margin_bottom=1, margin_left=2 ), Text( text=self.bind().user_seat, style=TextStyle( fill=self.session.theme.neutral_color, font_size=1 ), margin_top=0.5, margin_bottom=1, margin_right=2, justify="right" ), ), Text( text="Geld hinzufügen/entfernen", style=TextStyle( fill=self.session.theme.background_color, font_size=1 ), margin_top=0.5, margin_bottom=1, align_x=0.5 ), NewTransactionForm(user=self.selected_user, new_transaction_cb=self.on_new_transaction), Text( text=self.bind().accounting_section_result_text, style=TextStyle( fill=self.session.theme.success_color if self.accounting_section_result_success else self.session.theme.danger_color ), margin_left=1, margin_bottom=1 ) ) ) if self.selected_user else Spacer(), MainViewContentBox( Column( Text( text="Allgemeines", style=TextStyle( fill=self.session.theme.background_color, font_size=1.2 ), margin_top=2, margin_bottom=2, align_x=0.5 ) if self.selected_user else Spacer(), UserEditForm( is_own_profile=False, user=self.selected_user ) if self.selected_user else Text( text="Bitte Nutzer auswählen...", style=TextStyle( fill=self.session.theme.background_color, font_size=1.2 ), margin_top=2, margin_bottom=2, align_x=0.5 ) ) ), align_y=0 )