ezgg-lan-manager/src/ezgg_lan_manager/pages/ManageUsersPage.py
2025-07-30 13:33:55 +02:00

292 lines
12 KiB
Python

import logging
from dataclasses import field
from typing import Optional
from rio import Column, Component, event, TextStyle, Text, TextInput, ThemeContextSwitcher, Grid, \
PointerEventListener, PointerEvent, Rectangle, CursorStyle, 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=CursorStyle.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
)