add seat booking

This commit is contained in:
David Rodenkirchen 2024-09-06 14:12:33 +02:00
parent 871d8d6a3d
commit 3c3fd878c5
4 changed files with 183 additions and 17 deletions

View File

@ -1,18 +1,21 @@
from typing import Optional from typing import Optional, Callable
from rio import Component, Column, Text, TextStyle, Button, Spacer from rio import Component, Column, Text, TextStyle, Button, Spacer
from src.ez_lan_manager import AccountingService
class SeatingPlanInfoBox(Component): class SeatingPlanInfoBox(Component):
show: bool
purchase_cb: Callable
is_booking_blocked: bool
seat_id: Optional[str] = None seat_id: Optional[str] = None
seat_occupant: Optional[str] = None seat_occupant: Optional[str] = None
seat_price: int = 0 seat_price: int = 0
is_blocked: bool = False is_blocked: bool = False
user_has_seat: bool = False
def build(self) -> Component: def build(self) -> Component:
if not self.show:
return Spacer()
if self.is_blocked: if self.is_blocked:
return Column(Text(f"Sitzplatz gesperrt", margin=1, style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.4), wrap=True, justify="center"), min_height=10) return Column(Text(f"Sitzplatz gesperrt", margin=1, style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.4), wrap=True, justify="center"), min_height=10)
if self.seat_id is None and self.seat_occupant is None: if self.seat_id is None and self.seat_occupant is None:
@ -25,7 +28,7 @@ class SeatingPlanInfoBox(Component):
Text(f"Dieser Sitzplatz ({self.seat_id}) ist frei", margin=1, style=TextStyle(fill=self.session.theme.neutral_color), wrap=True, justify="center"), Text(f"Dieser Sitzplatz ({self.seat_id}) ist frei", margin=1, style=TextStyle(fill=self.session.theme.neutral_color), wrap=True, justify="center"),
Button( Button(
Text( Text(
f"Buchen ({AccountingService.make_euro_string_from_int(self.seat_price)})", f"Buchen",
margin=1, margin=1,
style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.1), style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.1),
wrap=True, wrap=True,
@ -36,7 +39,8 @@ class SeatingPlanInfoBox(Component):
color="secondary", color="secondary",
margin=1, margin=1,
grow_y=False, grow_y=False,
is_sensitive=not self.user_has_seat is_sensitive=not self.is_booking_blocked,
on_press=self.purchase_cb
), ),
min_height=10 min_height=10
) )

View File

@ -0,0 +1,96 @@
from typing import Optional, Callable
from rio import Component, Column, Text, TextStyle, Button, Spacer, Row, ProgressCircle
class SeatingPurchaseBox(Component):
show: bool
seat_id: str
is_loading: bool
confirm_cb: Callable
cancel_cb: Callable
error_msg: Optional[str] = None
success_msg: Optional[str] = None
def build(self) -> Component:
if not self.show:
return Spacer()
if self.is_loading:
return Column(
ProgressCircle(
color="secondary",
align_x=0.5,
margin_top=2,
margin_bottom=2
),
min_height=10
)
if self.success_msg:
return Column(
Text(f"{self.success_msg}", margin=1, style=TextStyle(fill=self.session.theme.success_color, font_size=1.1),
wrap=True, justify="center"),
Row(
Button(
Text("Zurück",
margin=1,
style=TextStyle(fill=self.session.theme.success_color, font_size=1.1),
wrap=True,
justify="center"
),
shape="rounded",
style="plain",
on_press=self.cancel_cb
)
),
min_height=10
)
if self.error_msg:
return Column(
Text(f"{self.error_msg}", margin=1, style=TextStyle(fill=self.session.theme.danger_color, font_size=1.1),
wrap=True, justify="center"),
Row(
Button(
Text("Zurück",
margin=1,
style=TextStyle(fill=self.session.theme.danger_color, font_size=1.1),
wrap=True,
justify="center"
),
shape="rounded",
style="plain",
on_press=self.cancel_cb
)
),
min_height=10
)
return Column(
Text(f"Sitzplatz {self.seat_id} verbindlich buchen?", margin=1, style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.4), wrap=True, justify="center"),
Row(
Button(
Text("Nein",
margin=1,
style=TextStyle(fill=self.session.theme.danger_color, font_size=1.1),
wrap=True,
justify="center"
),
shape="rounded",
style="plain",
on_press=self.cancel_cb
),
Button(
Text("Ja",
margin=1,
style=TextStyle(fill=self.session.theme.success_color, font_size=1.1),
wrap=True,
justify="center"
),
shape="rounded",
style="minor",
on_press=self.confirm_cb
)
),
min_height=10
)

View File

@ -58,7 +58,7 @@ class UserInfoAndLoginBox(Component):
@staticmethod @staticmethod
def get_greeting() -> str: def get_greeting() -> str:
return choice(["Guten Tacho", "Tuten Gag", "Servus", "Moinjour", "Hallöchen Popöchen", "Heyho", "Moinsen"]) return choice(["Guten Tacho", "Tuten Gag", "Servus", "Moinjour", "Hallöchen", "Heyho", "Moinsen"])
# @FixMe: If the user logs out and then tries to log back in, it does not work # @FixMe: If the user logs out and then tries to log back in, it does not work
# If the user navigates to another page and then tries again. It works. # If the user navigates to another page and then tries again. It works.

View File

@ -1,3 +1,5 @@
import logging
from asyncio import sleep
from typing import Optional from typing import Optional
from rio import Text, Column, TextStyle, Component, event, PressEvent, ProgressCircle, Row, Image, Button, Spacer from rio import Text, Column, TextStyle, Component, event, PressEvent, ProgressCircle, Row, Image, Button, Spacer
@ -6,12 +8,16 @@ from src.ez_lan_manager import ConfigurationService, SeatingService, TicketingSe
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.SeatingPlan import SeatingPlan, SeatingPlanLegend from src.ez_lan_manager.components.SeatingPlan import SeatingPlan, SeatingPlanLegend
from src.ez_lan_manager.components.SeatingPlanInfoBox import SeatingPlanInfoBox from src.ez_lan_manager.components.SeatingPlanInfoBox import SeatingPlanInfoBox
from src.ez_lan_manager.components.SeatingPurchaseBox import SeatingPurchaseBox
from src.ez_lan_manager.pages import BasePage from src.ez_lan_manager.pages import BasePage
from src.ez_lan_manager.services.SeatingService import NoTicketError, SeatNotFoundError, WrongCategoryError, SeatAlreadyTakenError
from src.ez_lan_manager.types.Seat import Seat from src.ez_lan_manager.types.Seat import Seat
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User from src.ez_lan_manager.types.User import User
logger = logging.getLogger(__name__.split(".")[-1])
class SeatingPlanPage(Component): class SeatingPlanPage(Component):
seating_info: Optional[list[Seat]] = None seating_info: Optional[list[Seat]] = None
current_seat_id: Optional[str] = None current_seat_id: Optional[str] = None
@ -19,22 +25,30 @@ class SeatingPlanPage(Component):
current_seat_price: int = 0 current_seat_price: int = 0
current_seat_is_blocked: bool = False current_seat_is_blocked: bool = False
user: Optional[User] = None user: Optional[User] = None
user_has_seat: bool = False show_info_box: bool = True
show_purchase_box: bool = False
purchase_box_loading: bool = False
purchase_box_success_msg: Optional[str] = None
purchase_box_error_msg: Optional[str] = None
is_booking_blocked: bool = False
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Sitzplan") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Sitzplan")
self.seating_info = await self.session[SeatingService].get_seating() self.seating_info = await self.session[SeatingService].get_seating()
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id) self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
user_has_seat = False if not self.user:
for seat in self.seating_info: self.is_booking_blocked = True
if not seat.user or not self.user: else:
continue for seat in self.seating_info:
if seat.user.user_id == self.user.user_id: if not seat.user or not self.user:
user_has_seat = True continue
self.user_has_seat = user_has_seat if seat.user.user_id == self.user.user_id:
self.is_booking_blocked = True
async def on_seat_clicked(self, seat_id: str, _: PressEvent) -> None: async def on_seat_clicked(self, seat_id: str, _: PressEvent) -> None:
self.show_info_box = True
self.show_purchase_box = False
seat = next(filter(lambda s: s.seat_id == seat_id, self.seating_info), None) seat = next(filter(lambda s: s.seat_id == seat_id, self.seating_info), None)
if not seat: if not seat:
return return
@ -48,6 +62,47 @@ class SeatingPlanPage(Component):
else: else:
self.current_seat_occupant = None self.current_seat_occupant = None
def set_error(self, msg: str) -> None:
self.purchase_box_error_msg = msg
self.purchase_box_success_msg = None
def set_success(self, msg: str) -> None:
self.purchase_box_error_msg = None
self.purchase_box_success_msg = msg
async def on_purchase_clicked(self) -> None:
self.show_info_box = False
self.show_purchase_box = True
async def on_purchase_confirmed(self) -> None:
self.purchase_box_loading = True
await self.force_refresh()
await sleep(0.5)
try:
await self.session[SeatingService].seat_user(self.user.user_id, self.current_seat_id)
except (NoTicketError, WrongCategoryError):
self.set_error("Du besitzt kein gültiges Ticket für diesen Platz")
except SeatNotFoundError:
self.set_error("Der angegebene Sitzplatz existiert nicht")
except SeatAlreadyTakenError:
self.set_error("Dieser Platz ist bereits vergeben")
except Exception as e:
self.set_error("Ein unbekannter Fehler ist aufgetreten")
logger.error(e)
else:
self.set_success("Platz erfolgreich gebucht!")
self.purchase_box_loading = False
await self.on_populate()
async def on_purchase_cancelled(self) -> None:
self.purchase_box_loading = False
self.show_info_box = True
self.show_purchase_box = False
self.purchase_box_error_msg = None
self.purchase_box_success_msg = None
def build(self) -> Component: def build(self) -> Component:
if not self.seating_info: if not self.seating_info:
return BasePage( return BasePage(
@ -66,8 +121,19 @@ class SeatingPlanPage(Component):
return BasePage( return BasePage(
content=Column( content=Column(
MainViewContentBox( MainViewContentBox(
SeatingPlanInfoBox(seat_id=self.current_seat_id, seat_occupant=self.current_seat_occupant, seat_price=self.current_seat_price, Column(
is_blocked=self.current_seat_is_blocked, user_has_seat=self.user_has_seat) SeatingPlanInfoBox(seat_id=self.current_seat_id, seat_occupant=self.current_seat_occupant, seat_price=self.current_seat_price,
is_blocked=self.current_seat_is_blocked, is_booking_blocked=self.is_booking_blocked, show=self.show_info_box, purchase_cb=self.on_purchase_clicked),
SeatingPurchaseBox(
show=self.show_purchase_box,
seat_id=self.current_seat_id,
is_loading=self.purchase_box_loading,
confirm_cb=self.on_purchase_confirmed,
cancel_cb=self.on_purchase_cancelled,
error_msg=self.purchase_box_error_msg,
success_msg=self.purchase_box_success_msg
)
)
), ),
MainViewContentBox( MainViewContentBox(
SeatingPlan(seat_clicked_cb=self.on_seat_clicked, seating_info=self.seating_info) if self.seating_info else SeatingPlan(seat_clicked_cb=self.on_seat_clicked, seating_info=self.seating_info) if self.seating_info else