pre-release-changes #14

Merged
Typhus merged 17 commits from pre-release-changes into main 2025-03-25 23:07:43 +00:00
21 changed files with 490 additions and 164 deletions

View File

@ -1 +1 @@
0.0.1 0.1.0

View File

@ -19,12 +19,6 @@
username="" username=""
password="" password=""
[seating]
# SeatID -> Category
A01 = "NORMAL"
A02 = "NORMAL"
C01 = "LUXUS"
[tickets] [tickets]
[tickets."NORMAL"] [tickets."NORMAL"]
total_tickets=30 total_tickets=30

View File

@ -0,0 +1,63 @@
INSERT INTO `catering_menu_items` VALUES
(7,'Schnitzel Wiener Art','mit Pommes',10.50,'MAIN_COURSE',0),
(8,'Jäger Schnitzel mit Champignonrahm Sauce','mit Pommes',11.50,'MAIN_COURSE',0),
(9,'Tortellini in Käsesauce mit Fleischfüllung','',10.50,'MAIN_COURSE',0),
(10,'Tortellini in Käsesauce ohne Fleischfüllung','Vegetarisch',10.50,'MAIN_COURSE',0),
(11,'Käse Schinken Wrap','',5.00,'SNACK',1),
(12,'Puten Paprika Wrap','',7.00,'SNACK',0),
(13,'Tomate Mozzarella Wrap','',6.00,'SNACK',0),
(14,'Portion Pommes','',4.00,'SNACK',0),
(15,'Rinds-Currywurst','',4.50,'SNACK',0),
(16,'Rinds-Currywurst mit Pommes','',6.50,'SNACK',0),
(17,'Nudelsalat','',4.50,'SNACK',0),
(18,'Nudelsalat mit Bockwurst','',6.00,'SNACK',0),
(19,'Kartoffelsalat','',4.50,'SNACK',0),
(20,'Kartoffelsalat mit Bockwurst','',6.00,'SNACK',0),
(21,'Sandwichtoast - Schinken','',1.80,'SNACK',0),
(22,'Sandwichtoast - Käse','',1.80,'SNACK',0),
(23,'Sandwichtoast - Schinken/Käse','',2.10,'SNACK',0),
(24,'Sandwichtoast - Salami','',1.80,'SNACK',0),
(25,'Sandwichtoast - Salami/Käse','',2.10,'SNACK',0),
(26,'Chips - Western Style','',1.30,'SNACK',0),
(27,'Nachos - Salted','',1.30,'SNACK',0),
(28,'Panna Cotta mit Erdbeersauce','',7.00,'DESSERT',0),
(29,'Panna Cotta mit Blaubeersauce','',7.00,'DESSERT',0),
(30,'Mousse au Chocolat','',7.00,'DESSERT',0),
(31,'Fruit Loops','',1.50,'BREAKFAST',0),
(32,'Smacks','',1.50,'BREAKFAST',0),
(33,'Knuspermüsli','Schoko',2.00,'BREAKFAST',0),
(34,'Cini Minis','',1.50,'BREAKFAST',0),
(35,'Brötchen - Schinken','mit Margarine',1.20,'BREAKFAST',0),
(36,'Brötchen - Käse','mit Margarine',1.20,'BREAKFAST',0),
(37,'Brötchen - Schinken/Käse','mit Margarine',1.40,'BREAKFAST',0),
(38,'Brötchen - Salami','mit Margarine',1.20,'BREAKFAST',0),
(39,'Brötchen - Salami/Käse','mit Margarine',1.40,'BREAKFAST',0),
(40,'Brötchen - Nutella','mit Margarine',1.20,'BREAKFAST',0),
(41,'Wasser - Still','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(42,'Wasser - Medium','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(43,'Wasser - Spritzig','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(44,'Coca-Cola','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(45,'Coca-Cola Zero','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(46,'Fanta','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(47,'Sprite','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(48,'Spezi','von Paulaner, 0,5L Flasche',1.50,'BEVERAGE_NON_ALCOHOLIC',0),
(49,'Red Bull','',2.00,'BEVERAGE_NON_ALCOHOLIC',0),
(50,'Energy','Hausmarke',1.50,'BEVERAGE_NON_ALCOHOLIC',0),
(51,'Pils','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',0),
(52,'Radler','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',0),
(53,'Diesel','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',0),
(54,'Apfelwein Pur','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',0),
(55,'Apfelwein Sauer','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',0),
(56,'Apfelwein Cola','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',0),
(57,'Vodka Energy','',4.00,'BEVERAGE_COCKTAIL',0),
(58,'Vodka O-Saft','',4.00,'BEVERAGE_COCKTAIL',0),
(59,'Whiskey Cola','mit Bourbon',4.00,'BEVERAGE_COCKTAIL',0),
(60,'Jägermeister Energy','',4.00,'BEVERAGE_COCKTAIL',0),
(61,'Sex on the Beach','',5.50,'BEVERAGE_COCKTAIL',0),
(62,'Long Island Ice Tea','',5.50,'BEVERAGE_COCKTAIL',0),
(63,'Caipirinha','',5.50,'BEVERAGE_COCKTAIL',0),
(64,'Jägermeister','',2.00,'BEVERAGE_SHOT',0),
(65,'Tequila','',2.00,'BEVERAGE_SHOT',0),
(66,'PfEZzi','Getunter Pfefferminz-Schnaps',1.99,'BEVERAGE_SHOT',0),
(67,'Zigaretten','Elixyr',8.00,'NON_FOOD',0),
(68,'Mentholfilter','passend für Elixyr',1.20,'NON_FOOD',0);

View File

@ -62,7 +62,7 @@ if __name__ == "__main__":
ComponentPage( ComponentPage(
name="Overview", name="Overview",
url_segment="overview", url_segment="overview",
build=lambda: pages.PlaceholderPage(placeholder_name="LAN Übersicht"), build=pages.OverviewPage,
), ),
ComponentPage( ComponentPage(
name="BuyTicket", name="BuyTicket",

View File

@ -25,7 +25,7 @@ def init_services() -> tuple[AccountingService, CateringService, ConfigurationSe
news_service = NewsService(db_service) news_service = NewsService(db_service)
mailing_service = MailingService(configuration_service) mailing_service = MailingService(configuration_service)
ticketing_service = TicketingService(configuration_service.get_ticket_info(), db_service, accounting_service) ticketing_service = TicketingService(configuration_service.get_ticket_info(), db_service, accounting_service)
seating_service = SeatingService(configuration_service.get_seating_configuration(), configuration_service.get_lan_info(), db_service, ticketing_service) seating_service = SeatingService(configuration_service.get_lan_info(), db_service, ticketing_service)
catering_service = CateringService(db_service, accounting_service, user_service) catering_service = CateringService(db_service, accounting_service, user_service)
local_data_service = LocalDataService() local_data_service = LocalDataService()

View File

@ -54,7 +54,7 @@ class DesktopNavigation(Component):
DesktopNavigationButton("FAQ", "./faq"), DesktopNavigationButton("FAQ", "./faq"),
DesktopNavigationButton("Regeln & AGB", "./rules-gtc"), DesktopNavigationButton("Regeln & AGB", "./rules-gtc"),
Spacer(min_height=1), Spacer(min_height=1),
DesktopNavigationButton("Discord", "#", open_new_tab=True), # Temporarily disabled: https://discord.gg/8gTjg34yyH DesktopNavigationButton("Discord", "https://discord.gg/8gTjg34yyH", open_new_tab=True),
DesktopNavigationButton("Die EZ GG e.V.", "https://ezgg-ev.de/about", open_new_tab=True), DesktopNavigationButton("Die EZ GG e.V.", "https://ezgg-ev.de/about", open_new_tab=True),
DesktopNavigationButton("Kontakt", "./contact"), DesktopNavigationButton("Kontakt", "./contact"),
DesktopNavigationButton("Impressum & DSGVO", "./imprint"), DesktopNavigationButton("Impressum & DSGVO", "./imprint"),

View File

@ -1,21 +1,21 @@
from typing import Callable from typing import Callable
from rio import Component, Rectangle, Grid, Column, Row, Text, TextStyle, Color from rio import Component, Rectangle, Grid, Column, Row, Text, TextStyle, Color, PointerEventListener
from src.ez_lan_manager.components.SeatingPlanPixels import SeatPixel, WallPixel, InvisiblePixel, TextPixel from src.ez_lan_manager.components.SeatingPlanPixels import SeatPixel, WallPixel, InvisiblePixel, TextPixel
from src.ez_lan_manager.types.Seat import Seat from src.ez_lan_manager.types.Seat import Seat
MAX_GRID_WIDTH_PIXELS = 34 MAX_GRID_WIDTH_PIXELS = 60
MAX_GRID_HEIGHT_PIXELS = 45 MAX_GRID_HEIGHT_PIXELS = 60
class SeatingPlanLegend(Component): class SeatingPlanLegend(Component):
def build(self) -> Component: def build(self) -> Component:
return Column( return Column(
Text("Legende", style=TextStyle(fill=self.session.theme.neutral_color), justify="center", margin=1), Text("Legende", style=TextStyle(fill=self.session.theme.neutral_color), justify="center", margin=1),
Row( # Row( # Disabled for upcoming LAN
Text("L = Luxus Platz", justify="center", style=TextStyle(fill=self.session.theme.neutral_color)), # Text("L = Luxus Platz", justify="center", style=TextStyle(fill=self.session.theme.neutral_color)),
Text("N = Normaler Platz", justify="center", style=TextStyle(fill=self.session.theme.neutral_color)), # Text("N = Normaler Platz", justify="center", style=TextStyle(fill=self.session.theme.neutral_color)),
), # ),
Row( Row(
Rectangle( Rectangle(
content=Column( content=Column(
@ -71,9 +71,11 @@ class SeatingPlanLegend(Component):
class SeatingPlan(Component): class SeatingPlan(Component):
seat_clicked_cb: Callable seat_clicked_cb: Callable
seating_info: list[Seat] seating_info: list[Seat]
info_clicked_cb: Callable
def get_seat(self, seat_id: str) -> Seat: def get_seat(self, seat_id: str) -> Seat:
return next(filter(lambda seat: seat.seat_id == seat_id, self.seating_info)) seat = next(filter(lambda seat_: seat_.seat_id == seat_id, self.seating_info), None)
return seat if seat else Seat(seat_id="Z99", is_blocked=True, category="LUXUS", user=None)
""" """
This seating plan is for the community center "Bottenhorn" This seating plan is for the community center "Bottenhorn"
@ -81,106 +83,171 @@ class SeatingPlan(Component):
def build(self) -> Component: def build(self) -> Component:
grid = Grid() grid = Grid()
# Outlines # Outlines
for column_id in range(0, MAX_GRID_WIDTH_PIXELS): for x in range(0, MAX_GRID_WIDTH_PIXELS):
grid.add(InvisiblePixel(), row=0, column=column_id) grid.add(WallPixel(), row=0, column=x)
for y in range(0, 13):
grid.add(WallPixel(), row=y, column=0) for y in range(0, MAX_GRID_HEIGHT_PIXELS):
for y in range(13, 19):
grid.add(InvisiblePixel(), row=y, column=0)
for y in range(19, MAX_GRID_HEIGHT_PIXELS):
grid.add(WallPixel(), row=y, column=0) grid.add(WallPixel(), row=y, column=0)
for x in range(0, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=MAX_GRID_HEIGHT_PIXELS, column=x)
for x in range(0, 31):
grid.add(WallPixel(), row=15, column=x)
for x in range(41, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=32, column=x)
for x in range(31, 34):
grid.add(WallPixel(), row=32, column=x)
grid.add(WallPixel(), row=19, column=x)
for x in range(42, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=11, column=x)
grid.add(WallPixel(), row=22, column=x)
for x in range(22, 30):
grid.add(WallPixel(), row=5, column=x)
grid.add(WallPixel(), row=10, column=x)
for y in range(5, 11):
grid.add(WallPixel(), row=y, column=21)
grid.add(WallPixel(), row=y, column=30)
for y in range(40, MAX_GRID_HEIGHT_PIXELS):
grid.add(WallPixel(), row=y, column=30)
for y in range(32, 36):
grid.add(WallPixel(), row=y, column=30)
for y in range(19, 33):
grid.add(WallPixel(), row=y, column=34)
for y in range(16, 20):
grid.add(WallPixel(), row=y, column=30)
for y in range(0, 5):
grid.add(WallPixel(), row=y, column=41)
for y in range(9, 15):
grid.add(WallPixel(), row=y, column=41)
for y in range(19, 33):
grid.add(WallPixel(), row=y, column=41)
# Block A # Block A
block_a_margin_left = 12 grid.add(SeatPixel("A01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A01")), row=57, column=2, width=3, height=2)
block_a_margin_top = 1 grid.add(SeatPixel("A02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A02")), row=57, column=5, width=3, height=2)
(grid grid.add(SeatPixel("A03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A03")), row=57, column=8, width=3, height=2)
.add(SeatPixel("A01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A01")), row=block_a_margin_top, column=block_a_margin_left, width=2, height=3) grid.add(SeatPixel("A04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A04")), row=57, column=11, width=3, height=2)
.add(SeatPixel("A02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A02")), row=block_a_margin_top + 4, column=block_a_margin_left, width=2, height=3) grid.add(SeatPixel("A05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A05")), row=57, column=14, width=3, height=2)
.add(SeatPixel("A03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A03")), row=block_a_margin_top + 8, column=block_a_margin_left, width=2, height=3) grid.add(SeatPixel("A06", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A06")), row=57, column=17, width=3, height=2)
.add(SeatPixel("A10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A10")), row=block_a_margin_top, column=block_a_margin_left + 3, width=2, height=3)
.add(SeatPixel("A11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A11")), row=block_a_margin_top + 4, column=block_a_margin_left + 3, width=2, height=3) grid.add(SeatPixel("A10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A10")), row=55, column=2, width=3, height=2)
.add(SeatPixel("A12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A12")), row=block_a_margin_top + 8, column=block_a_margin_left + 3, width=2, height=3) grid.add(SeatPixel("A11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A11")), row=55, column=5, width=3, height=2)
) grid.add(SeatPixel("A12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A12")), row=55, column=8, width=3, height=2)
grid.add(SeatPixel("A13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A13")), row=55, column=11, width=3, height=2)
grid.add(SeatPixel("A14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A14")), row=55, column=14, width=3, height=2)
grid.add(SeatPixel("A15", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A15")), row=55, column=17, width=3, height=2)
# Block B # Block B
block_b_margin_left = 20 grid.add(SeatPixel("B01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B01")), row=50, column=2, width=3, height=2)
block_b_margin_top = 1 grid.add(SeatPixel("B02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B02")), row=50, column=5, width=3, height=2)
(grid grid.add(SeatPixel("B03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B03")), row=50, column=8, width=3, height=2)
.add(SeatPixel("B01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B01")), row=block_b_margin_top, column=block_b_margin_left, width=2, height=3) grid.add(SeatPixel("B04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B04")), row=50, column=11, width=3, height=2)
.add(SeatPixel("B02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B02")), row=block_b_margin_top + 4, column=block_b_margin_left, width=2, height=3) grid.add(SeatPixel("B05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B05")), row=50, column=14, width=3, height=2)
.add(SeatPixel("B03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B03")), row=block_b_margin_top + 8, column=block_b_margin_left, width=2, height=3)
.add(SeatPixel("B10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B10")), row=block_b_margin_top, column=block_b_margin_left + 3, width=2, height=3) grid.add(SeatPixel("B10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B10")), row=48, column=2, width=3, height=2)
.add(SeatPixel("B11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B11")), row=block_b_margin_top + 4, column=block_b_margin_left + 3, width=2, height=3) grid.add(SeatPixel("B11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B11")), row=48, column=5, width=3, height=2)
.add(SeatPixel("B12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B12")), row=block_b_margin_top + 8, column=block_b_margin_left + 3, width=2, height=3) grid.add(SeatPixel("B12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B12")), row=48, column=8, width=3, height=2)
) grid.add(SeatPixel("B13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B13")), row=48, column=11, width=3, height=2)
grid.add(SeatPixel("B14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B14")), row=48, column=14, width=3, height=2)
# Block C # Block C
block_c_margin_left = 28 grid.add(SeatPixel("C01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C01")), row=43, column=2, width=3, height=2)
block_c_margin_top = 1 grid.add(SeatPixel("C02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C02")), row=43, column=5, width=3, height=2)
(grid grid.add(SeatPixel("C03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C03")), row=43, column=8, width=3, height=2)
.add(SeatPixel("C01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C01")), row=block_c_margin_top, column=block_c_margin_left, width=2, height=3) grid.add(SeatPixel("C04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C04")), row=43, column=11, width=3, height=2)
.add(SeatPixel("C02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C02")), row=block_c_margin_top + 4, column=block_c_margin_left, width=2, height=3) grid.add(SeatPixel("C05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C05")), row=43, column=14, width=3, height=2)
.add(SeatPixel("C03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C03")), row=block_c_margin_top + 8, column=block_c_margin_left, width=2, height=3)
.add(SeatPixel("C10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C10")), row=block_c_margin_top, column=block_c_margin_left + 3, width=2, height=3) grid.add(SeatPixel("C10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C10")), row=41, column=2, width=3, height=2)
.add(SeatPixel("C11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C11")), row=block_c_margin_top + 4, column=block_c_margin_left + 3, width=2, height=3) grid.add(SeatPixel("C11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C11")), row=41, column=5, width=3, height=2)
.add(SeatPixel("C12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C12")), row=block_c_margin_top + 8, column=block_c_margin_left + 3, width=2, height=3) grid.add(SeatPixel("C12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C12")), row=41, column=8, width=3, height=2)
) grid.add(SeatPixel("C13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C13")), row=41, column=11, width=3, height=2)
grid.add(SeatPixel("C14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C14")), row=41, column=14, width=3, height=2)
# Block D # Block D
block_d_margin_left = 20 grid.add(SeatPixel("D01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D01")), row=34, column=2, width=3, height=2)
block_d_margin_top = 20 grid.add(SeatPixel("D02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D02")), row=34, column=5, width=3, height=2)
(grid grid.add(SeatPixel("D03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D03")), row=34, column=8, width=3, height=2)
.add(SeatPixel("D01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D01")), row=block_d_margin_top, column=block_d_margin_left, width=2, height=3) grid.add(SeatPixel("D04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D04")), row=34, column=11, width=3, height=2)
.add(SeatPixel("D02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D02")), row=block_d_margin_top + 4, column=block_d_margin_left, width=2, height=3) grid.add(SeatPixel("D05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D05")), row=34, column=14, width=3, height=2)
.add(SeatPixel("D03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D03")), row=block_d_margin_top + 8, column=block_d_margin_left, width=2, height=3) grid.add(SeatPixel("D06", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D06")), row=34, column=17, width=3, height=2)
.add(SeatPixel("D10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D10")), row=block_d_margin_top, column=block_d_margin_left + 3, width=2, height=3)
.add(SeatPixel("D11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D11")), row=block_d_margin_top + 4, column=block_d_margin_left + 3, width=2, height=3) grid.add(SeatPixel("D10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D10")), row=32, column=2, width=3, height=2)
.add(SeatPixel("D12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D12")), row=block_d_margin_top + 8, column=block_d_margin_left + 3, width=2, height=3) grid.add(SeatPixel("D11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D11")), row=32, column=5, width=3, height=2)
) grid.add(SeatPixel("D12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D12")), row=32, column=8, width=3, height=2)
grid.add(SeatPixel("D13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D13")), row=32, column=11, width=3, height=2)
grid.add(SeatPixel("D14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D14")), row=32, column=14, width=3, height=2)
grid.add(SeatPixel("D15", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D15")), row=32, column=17, width=3, height=2)
# Block E # Block E
block_e_margin_left = 28 grid.add(SeatPixel("E01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E01")), row=27, column=2, width=3, height=2)
block_e_margin_top = 20 grid.add(SeatPixel("E02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E02")), row=27, column=5, width=3, height=2)
(grid grid.add(SeatPixel("E03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E03")), row=27, column=8, width=3, height=2)
.add(SeatPixel("E01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E01")), row=block_e_margin_top, column=block_e_margin_left, width=2, height=3) grid.add(SeatPixel("E04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E04")), row=27, column=11, width=3, height=2)
.add(SeatPixel("E02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E02")), row=block_e_margin_top + 4, column=block_e_margin_left, width=2, height=3) grid.add(SeatPixel("E05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E05")), row=27, column=14, width=3, height=2)
.add(SeatPixel("E03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E03")), row=block_e_margin_top + 8, column=block_e_margin_left, width=2, height=3) grid.add(SeatPixel("E06", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E06")), row=27, column=17, width=3, height=2)
.add(SeatPixel("E10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E10")), row=block_e_margin_top, column=block_e_margin_left + 3, width=2, height=3)
.add(SeatPixel("E11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E11")), row=block_e_margin_top + 4, column=block_e_margin_left + 3, width=2, height=3)
.add(SeatPixel("E12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E12")), row=block_e_margin_top + 8, column=block_e_margin_left + 3, width=2, height=3)
)
# Middle Wall grid.add(SeatPixel("E10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E10")), row=25, column=2, width=3, height=2)
for y in range(0, 13): grid.add(SeatPixel("E11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E11")), row=25, column=5, width=3, height=2)
grid.add(WallPixel(), row=y, column=10) grid.add(SeatPixel("E12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E12")), row=25, column=8, width=3, height=2)
for y in range(19, MAX_GRID_HEIGHT_PIXELS): grid.add(SeatPixel("E13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E13")), row=25, column=11, width=3, height=2)
grid.add(WallPixel(), row=y, column=10) grid.add(SeatPixel("E14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E14")), row=25, column=14, width=3, height=2)
grid.add(SeatPixel("E15", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E15")), row=25, column=17, width=3, height=2)
# Stage # Stage
for x in range(11, MAX_GRID_WIDTH_PIXELS): grid.add(PointerEventListener(
grid.add(WallPixel(), row=35, column=x) TextPixel(text="Bühne"),
grid.add(TextPixel(text="Bühne"), row=36, column=11, width=24, height=9) on_press=lambda _: self.info_clicked_cb("Hier darf ab Freitag 20 Uhr ebenfalls geschlafen werden.")
), row=16, column=1, width=29, height=4)
# Drinks # Drinks
grid.add(TextPixel(text="G\ne\nt\nr\nä\nn\nk\ne"), row=21, column=11, width=3, height=11) grid.add(PointerEventListener(
TextPixel(text="G\ne\nt\nr\nä\nn\nk\ne"),
on_press=lambda _: self.info_clicked_cb("Ich mag Bier, B - I - R")
), row=20, column=30, width=4, height=12)
# Main Entrance
grid.add(PointerEventListener(
TextPixel(text="H\na\nl\nl\ne\nn\n\ne\ni\nn\ng\na\nn\ng"),
on_press=lambda _: self.info_clicked_cb("Hallo, ich bin ein Haupteingang")
), row=33, column=56, width=4, height=27)
# Sleeping # Sleeping
grid.add(TextPixel(icon_name="material/bed"), row=1, column=1, width=4, height=11) grid.add(PointerEventListener(
TextPixel(icon_name="material/bed"),
on_press=lambda _: self.info_clicked_cb("In diesem Raum kann geschlafen werden.\nAchtung: Hier werden nicht alle Teilnehmer Platz finden.")
), row=1, column=1, width=20, height=14)
# Toilet # Toilet
grid.add(TextPixel(icon_name="material/floor", no_outline=True), row=1, column=7, width=3, height=2) grid.add(PointerEventListener(
grid.add(TextPixel(icon_name="material/north", no_outline=True), row=3, column=7, width=3, height=2) TextPixel(icon_name="material/wc"),
grid.add(TextPixel(icon_name="material/wc"), row=5, column=7, width=3, height=2) on_press=lambda _: self.info_clicked_cb("Damen Toilette")
), row=1, column=42, width=19, height=10)
grid.add(PointerEventListener(
TextPixel(icon_name="material/wc"),
on_press=lambda _: self.info_clicked_cb("Herren Toilette")
), row=12, column=42, width=19, height=10)
# Entry/Helpdesk # Entry/Helpdesk
grid.add(TextPixel(text="Einlass\n &Orga"), row=19, column=3, width=7, height=5) grid.add(PointerEventListener(
TextPixel(text="Einlass\n &Orga"),
on_press=lambda _: self.info_clicked_cb("Für alle Anliegen findest du hier rund um die Uhr jemanden vom Team.")
), row=40, column=22, width=8, height=12)
# Wall below Entry/Helpdesk
for y in range(24, MAX_GRID_HEIGHT_PIXELS):
grid.add(WallPixel(), row=y, column=3)
# Entry Arrow
grid.add(TextPixel(icon_name="material/east", no_outline=True), row=15, column=1, width=2, height=2)
return Rectangle( return Rectangle(
content=grid, content=grid,

View File

@ -1,7 +1,11 @@
from decimal import Decimal from decimal import Decimal
from functools import partial
from typing import Optional, Callable from typing import Optional, Callable
from rio import Component, Column, Text, TextStyle, Button, Spacer from rio import Component, Column, Text, TextStyle, Button, Spacer, event
from src.ez_lan_manager import TicketingService
from src.ez_lan_manager.types.SessionStorage import SessionStorage
class SeatingPlanInfoBox(Component): class SeatingPlanInfoBox(Component):
@ -12,8 +16,30 @@ class SeatingPlanInfoBox(Component):
seat_occupant: Optional[str] = None seat_occupant: Optional[str] = None
seat_price: Decimal = Decimal("0") seat_price: Decimal = Decimal("0")
is_blocked: bool = False is_blocked: bool = False
has_user_ticket: bool = False
booking_button_text: str = ""
override_text: str = "" # If this is set, all other functionality is disabled and the text is shown
@event.on_populate
async def check_ticket(self) -> None:
if self.session[SessionStorage].user_id:
user_ticket = await self.session[TicketingService].get_user_ticket(self.session[SessionStorage].user_id)
self.has_user_ticket = not (user_ticket is None)
self.booking_button_text = "Buchen" if self.has_user_ticket else "Ticket kaufen"
self.force_refresh()
async def purchase_clicked(self):
if self.has_user_ticket:
await self.purchase_cb()
else:
self.session.navigate_to("./buy_ticket")
def build(self) -> Component: def build(self) -> Component:
if self.override_text:
return Column(Text(self.override_text, margin=1,
style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.4), overflow="wrap",
justify="center"), min_height=10)
if not self.show: if not self.show:
return Spacer() return Spacer()
if self.is_blocked: if self.is_blocked:
@ -36,7 +62,7 @@ class SeatingPlanInfoBox(Component):
style=TextStyle(fill=self.session.theme.neutral_color), overflow="wrap", justify="center"), style=TextStyle(fill=self.session.theme.neutral_color), overflow="wrap", justify="center"),
Button( Button(
Text( Text(
f"Buchen", text=self.booking_button_text,
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),
overflow="wrap", overflow="wrap",
@ -48,7 +74,10 @@ class SeatingPlanInfoBox(Component):
margin=1, margin=1,
grow_y=False, grow_y=False,
is_sensitive=not self.is_booking_blocked, is_sensitive=not self.is_booking_blocked,
on_press=self.purchase_cb on_press=self.purchase_clicked
), ) if self.session[SessionStorage].user_id else Text(f"Du musst eingeloggt sein um einen Sitzplatz zu buchen",
margin=1,
style=TextStyle(fill=self.session.theme.neutral_color),
overflow="wrap", justify="center"),
min_height=10 min_height=10
) )

View File

@ -1,6 +1,6 @@
from functools import partial from functools import partial
from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, PointerEventListener, Column from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, PointerEventListener, Column, Row
from typing import Optional, Callable from typing import Optional, Callable
from src.ez_lan_manager.types.Seat import Seat from src.ez_lan_manager.types.Seat import Seat
@ -22,13 +22,13 @@ class SeatPixel(Component):
def build(self) -> Component: def build(self) -> Component:
return PointerEventListener( return PointerEventListener(
content=Rectangle( content=Rectangle(
content=Column( content=Row(
Text(f"{self.seat_id}", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False), Text(f"{self.seat_id}", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5, selectable=False)
Text(f"{self.seat.category[0]}", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5, selectable=False, overflow="wrap")
), ),
min_width=1, min_width=1,
min_height=1, min_height=1,
fill=self.determine_color(), fill=self.determine_color(),
stroke_width = 0.1,
hover_stroke_width = 0.1, hover_stroke_width = 0.1,
grow_x=True, grow_x=True,
grow_y=True, grow_y=True,

View File

@ -2,7 +2,7 @@ from asyncio import sleep, create_task
from decimal import Decimal from decimal import Decimal
import rio import rio
from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer, Popup, Table from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer, Popup, Table, event
from src.ez_lan_manager.components.CateringCartItem import CateringCartItem from src.ez_lan_manager.components.CateringCartItem import CateringCartItem
from src.ez_lan_manager.components.CateringOrderItem import CateringOrderItem from src.ez_lan_manager.components.CateringOrderItem import CateringOrderItem
@ -21,6 +21,11 @@ class ShoppingCartAndOrders(Component):
popup_is_shown: bool = False popup_is_shown: bool = False
popup_is_error: bool = True popup_is_error: bool = True
@event.periodic(5)
async def periodic_refresh_of_orders(self) -> None:
if not self.show_cart and not self.popup_is_shown:
self.orders = await self.session[CateringService].get_orders_for_user(self.session[SessionStorage].user_id)
async def switch(self) -> None: async def switch(self) -> None:
self.show_cart = not self.show_cart self.show_cart = not self.show_cart
self.orders = await self.session[CateringService].get_orders_for_user(self.session[SessionStorage].user_id) self.orders = await self.session[CateringService].get_orders_for_user(self.session[SessionStorage].user_id)
@ -85,7 +90,8 @@ class ShoppingCartAndOrders(Component):
show_popup_task = create_task(self.show_popup("Guthaben nicht ausreichend", True)) show_popup_task = create_task(self.show_popup("Guthaben nicht ausreichend", True))
else: else:
show_popup_task = create_task(self.show_popup("Unbekannter Fehler", True)) show_popup_task = create_task(self.show_popup("Unbekannter Fehler", True))
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, []) else:
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
self.order_button_loading = False self.order_button_loading = False
if not show_popup_task: if not show_popup_task:
show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False)) show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False))
@ -124,7 +130,7 @@ class ShoppingCartAndOrders(Component):
dialog = await self.session.show_custom_dialog( dialog = await self.session.show_custom_dialog(
build=build_dialog_content, build=build_dialog_content,
modal=True, modal=True,
user_closeable=True, user_closable=True,
) )
await dialog.wait_for_close() await dialog.wait_for_close()

View File

@ -73,6 +73,8 @@ class UserInfoBox(Component):
async def update(self) -> None: async def update(self) -> None:
if not self.user: if not self.user:
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)
if not self.user:
return
self.user_balance = await self.session[AccountingService].get_balance(self.user.user_id) self.user_balance = await self.session[AccountingService].get_balance(self.user.user_id)
self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id) self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)
self.user_seat = await self.session[SeatingService].get_user_seat(self.user.user_id) self.user_seat = await self.session[SeatingService].get_user_seat(self.user.user_id)

View File

@ -1,3 +1,4 @@
from decimal import Decimal
from functools import partial from functools import partial
from typing import Optional from typing import Optional
@ -12,7 +13,7 @@ from src.ez_lan_manager.types.User import User
class AccountPage(Component): class AccountPage(Component):
user: Optional[User] = None user: Optional[User] = None
balance: Optional[int] = None balance: Optional[Decimal] = None
transaction_history: list[Transaction] = list() transaction_history: list[Transaction] = list()
banking_info_revealer_open: bool = False banking_info_revealer_open: bool = False
paypal_info_revealer_open: bool = False paypal_info_revealer_open: bool = False

View File

@ -10,6 +10,8 @@ from src.ez_lan_manager.components.DesktopNavigation import DesktopNavigation
class BasePage(Component): class BasePage(Component):
color = "secondary" color = "secondary"
corner_radius = (0, 0.5, 0, 0) corner_radius = (0, 0.5, 0, 0)
footer_size = 53.1
@event.periodic(60) @event.periodic(60)
async def check_db_conn(self) -> None: async def check_db_conn(self) -> None:
is_healthy = await self.session[DatabaseService].is_healthy() is_healthy = await self.session[DatabaseService].is_healthy()
@ -20,6 +22,16 @@ class BasePage(Component):
async def on_window_size_change(self): async def on_window_size_change(self):
self.force_refresh() self.force_refresh()
@event.on_page_change
def check_needed_size(self):
# ToDo: Low-Prio: Change layout, so the footer is always as wide as needed.
# This is a workaround, bc the seating page needs more width
if "/seating" in self.session.active_page_url.__str__():
self.footer_size = 78.2
else:
self.footer_size = 53.1
self.force_refresh()
def build(self) -> Component: def build(self) -> Component:
content = Card( content = Card(
PageView(), PageView(),
@ -47,7 +59,7 @@ class BasePage(Component):
grow_x=False, grow_x=False,
grow_y=False, grow_y=False,
min_height=1.2, min_height=1.2,
min_width=53.1, min_width=self.footer_size,
margin_bottom=3 margin_bottom=3
), ),
Spacer(grow_x=True, grow_y=False), Spacer(grow_x=True, grow_y=False),

View File

@ -4,7 +4,6 @@ from typing import Optional
from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineTextInput, Row, Button from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineTextInput, Row, Button
from src.ez_lan_manager import ConfigurationService, UserService, MailingService from src.ez_lan_manager import ConfigurationService, UserService, MailingService
from src.ez_lan_manager.components.AnimatedText import AnimatedText
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
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
@ -14,9 +13,15 @@ class ContactPage(Component):
# Workaround: Can not reassign this value without rio triggering refresh # Workaround: Can not reassign this value without rio triggering refresh
# Using list to bypass this behavior # Using list to bypass this behavior
last_message_sent: list[datetime] = [datetime(day=1, month=1, year=2000)] last_message_sent: list[datetime] = [datetime(day=1, month=1, year=2000)]
display_printing: list[bool] = [False]
user: Optional[User] = None user: Optional[User] = None
e_mail: str = ""
subject: str = ""
message: str = ""
submit_button_is_loading: bool = False
response_message: str = ""
is_success: bool = True
@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} - Kontakt") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Kontakt")
@ -24,73 +29,67 @@ class ContactPage(Component):
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)
else: else:
self.user = None self.user = None
self.e_mail = "" if not self.user else self.user.user_mail
async def on_send_pressed(self) -> None: async def on_send_pressed(self) -> None:
error_msg = "" error_msg = ""
self.submit_button.is_loading = True self.submit_button_is_loading = True
self.submit_button.force_refresh()
now = datetime.now() now = datetime.now()
if not self.email_input.text: if not self.e_mail:
error_msg = "E-Mail darf nicht leer sein!" error_msg = "E-Mail darf nicht leer sein!"
elif not self.subject_input.text: elif not self.subject:
error_msg = "Betreff darf nicht leer sein!" error_msg = "Betreff darf nicht leer sein!"
elif not self.message_input.text: elif not self.message:
error_msg = "Nachricht darf nicht leer sein!" error_msg = "Nachricht darf nicht leer sein!"
elif (now - self.last_message_sent[0]) < timedelta(minutes=1): elif (now - self.last_message_sent[0]) < timedelta(minutes=1):
error_msg = "Immer mit der Ruhe!" error_msg = "Immer mit der Ruhe!"
if error_msg: if error_msg:
self.submit_button.is_loading = False self.submit_button_is_loading = False
await self.animated_text.display_text(False, error_msg) self.is_success = False
self.response_message = error_msg
return return
mail_recipient = self.session[ConfigurationService].get_lan_info().organizer_mail mail_recipient = self.session[ConfigurationService].get_lan_info().organizer_mail
msg = (f"Kontaktformular vom {now.strftime('%d.%m.%Y %H:%M')}:\n\n" msg = (f"Kontaktformular vom {now.strftime('%d.%m.%Y %H:%M')}:\n\n"
f"Betreff: {self.subject_input.text}\n" f"Betreff: {self.subject}\n"
f"Absender: {self.email_input.text}\n\n" f"Absender: {self.e_mail}\n\n"
f"Inhalt:\n" f"Inhalt:\n"
f"{self.message_input.text}\n") f"{self.message}\n")
await self.session[MailingService].send_email("Kontaktformular-Mitteilung", msg, mail_recipient) await self.session[MailingService].send_email("Kontaktformular-Mitteilung", msg, mail_recipient)
self.last_message_sent[0] = datetime.now() self.last_message_sent[0] = datetime.now()
self.submit_button.is_loading = False self.submit_button_is_loading = False
await self.animated_text.display_text(True, "Nachricht erfolgreich gesendet!") self.is_success = True
self.response_message = "Nachricht erfolgreich gesendet!"
def build(self) -> Component: def build(self) -> Component:
self.animated_text = AnimatedText( email_input = TextInput(
margin_top=2,
margin_bottom=1,
align_x=0.1
)
self.email_input = TextInput(
label="E-Mail Adresse", label="E-Mail Adresse",
text="" if not self.user else self.user.user_mail, text=self.bind().e_mail,
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True grow_x=True
) )
self.subject_input = TextInput( subject_input = TextInput(
label="Betreff", label="Betreff",
text="", text=self.bind().subject,
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True grow_x=True
) )
self.message_input = MultiLineTextInput( message_input = MultiLineTextInput(
label="Deine Nachricht an uns", label="Deine Nachricht an uns",
text="", text=self.bind().message,
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
min_height=5 min_height=5
) )
self.submit_button = Button( submit_button = Button(
content=Text( content=Text(
"Absenden", "Absenden",
style=TextStyle(fill=self.session.theme.success_color, font_size=0.9), style=TextStyle(fill=self.session.theme.success_color, font_size=0.9),
@ -102,7 +101,8 @@ class ContactPage(Component):
shape="rectangle", shape="rectangle",
style="major", style="major",
color="primary", color="primary",
on_press=self.on_send_pressed on_press=self.on_send_pressed,
is_loading=self.bind().submit_button_is_loading
) )
return Column( return Column(
MainViewContentBox( MainViewContentBox(
@ -117,12 +117,21 @@ class ContactPage(Component):
margin_bottom=1, margin_bottom=1,
align_x=0.5 align_x=0.5
), ),
self.email_input, email_input,
self.subject_input, subject_input,
self.message_input, message_input,
Row( Row(
self.animated_text, Text(
self.submit_button, text=self.bind().response_message,
style=TextStyle(
fill=self.session.theme.success_color if self.is_success else self.session.theme.danger_color,
font_size=0.9
),
margin_top=2,
margin_bottom=1,
align_x=0.1
),
submit_button,
) )
) )
), ),

View File

@ -0,0 +1,141 @@
from rio import Column, Component, event, Text, Spacer, Row, Link
from src.ez_lan_manager import ConfigurationService, TicketingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
class OverviewPage(Component):
@event.on_populate
async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Übersicht")
def build(self) -> Component:
lan_info = self.session[ConfigurationService].get_lan_info()
return Column(
MainViewContentBox(
Column(
Text(lan_info.name, font_size=2, justify="center", fill=self.session.theme.neutral_color, margin_top=0.5),
Text(f"Edition {lan_info.iteration}", font_size=0.9, justify="center", fill=self.session.theme.neutral_color, margin_bottom=1.5)
)
),
MainViewContentBox(
Column(
Text("Allgemeines", font_size=2, justify="center", fill=self.session.theme.neutral_color, margin_top=0.5, margin_bottom=1),
Column(
Row(
Text("Wann?", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text(f"{lan_info.date_from.strftime("%d.%m.")} bis {lan_info.date_till.strftime("%d.%m.%Y")}", fill=self.session.theme.neutral_color, margin_right=1),
margin_bottom=0.3
),
Row(
Text("Wo?", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Link(Text(f"DGH Donsbach", fill=self.session.theme.secondary_color, margin_right=1), target_url="https://maps.app.goo.gl/3Zyue776A22jdoxz5", open_in_new_tab=True),
margin_bottom=0.3
),
Row(
Text("Einlass", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text(lan_info.date_from.strftime("Freitag %H:%M Uhr"), fill=self.session.theme.neutral_color, margin_right=1),
margin_bottom=0.3
),
Row(
Text("Ende", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text(lan_info.date_till.strftime("Sonntag %H:%M Uhr"), fill=self.session.theme.neutral_color, margin_right=1),
margin_bottom=0.3
),
Row(
Text("Anmeldung", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text("Geöffnet", fill=self.session.theme.success_color, margin_right=1),
margin_bottom=0.3
),
Row(
Text("Teilnehmer", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text(str(self.session[TicketingService].get_total_tickets()), fill=self.session.theme.neutral_color, margin_right=1),
margin_bottom=0.3
)
,
Row(
Text("Ticket Preise", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Link(Text(f"Preisliste", fill=self.session.theme.secondary_color, margin_right=1), target_url="./buy_ticket", open_in_new_tab=False),
margin_bottom=0.3
)
)
)
),
MainViewContentBox(
Column(
Text("Technisches", font_size=2, justify="center", fill=self.session.theme.neutral_color, margin_top=0.5, margin_bottom=1),
Column(
Row(
Text("Internet", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text(f"60/20 Mbit/s (down/up)", fill=self.session.theme.neutral_color, margin_right=1),
margin_bottom=0.3
),
Row(
Text("Routing", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text(f"Flaches Netz", fill=self.session.theme.neutral_color, margin_right=1),
margin_bottom=0.3
),
Row(
Text("WLAN", fill=self.session.theme.neutral_color, margin_left=1),
Spacer(),
Text(f"vorhanden", fill=self.session.theme.neutral_color, margin_right=1),
margin_bottom=0.3
)
)
)
),
MainViewContentBox(
Column(
Text("Sonstiges", font_size=2, justify="center", fill=self.session.theme.neutral_color, margin_top=0.5, margin_bottom=1),
Column(
Row(
Text("Schlafen", fill=self.session.theme.neutral_color, margin_left=1, justify="center"),
margin_bottom=0.3
),
Row(
Text("Es steht ein Schlafsaal zur Verfügung. Nach der Eröffnung steht auch die Bühne als Schlafbereich zur Verfügung.", font_size=0.7,
fill=self.session.theme.neutral_color, margin_left=1, overflow="wrap"),
margin_bottom=0.3
),
Row(
Text("Essen & Trinken", fill=self.session.theme.neutral_color, margin_left=1, justify="center"),
margin_bottom=0.3
),
Row(
Text("Wir sorgen für euer leibliches Wohl, ihr dürft aber auch eure eigenen Speißen und Getränke mitbringen.", font_size=0.7, fill=self.session.theme.neutral_color, margin_left=1, overflow="wrap"),
margin_bottom=0.3
),
Row(
Text("Parken", fill=self.session.theme.neutral_color, margin_left=1, justify="center"),
margin_bottom=0.3
),
Row(
Text("Vor der Halle sind ausreichend Parkplätze vorhanden.", font_size=0.7, fill=self.session.theme.neutral_color, margin_left=1, overflow="wrap"),
margin_bottom=0.3
)
)
)
),
MainViewContentBox(
Column(
Text("Turniere & Ablauf", font_size=2, justify="center", fill=self.session.theme.neutral_color, margin_top=0.5, margin_bottom=1),
Column(
Row(
Text("Zum aktuellen Zeitpunkt steht noch nicht fest welche Turniere gespielt werden. Wir planen diverse Online- und Offline Turniere mit Preisen durchzuführen. Weitere Informationen gibt es, sobald sie kommen, auf der NEWS- und Turnier-Seite.", font_size=0.7,
fill=self.session.theme.neutral_color, margin_left=1, overflow="wrap"),
margin_bottom=0.3
)
)
)
),
Spacer()
)

View File

@ -13,7 +13,7 @@ logger = logging.getLogger(__name__.split(".")[-1])
class RegisterPage(Component): class RegisterPage(Component):
def on_pw_change(self, _: TextInputChangeEvent) -> None: def on_pw_focus_loss(self, _: TextInputChangeEvent) -> None:
if not (self.pw_1.text == self.pw_2.text) or len(self.pw_1.text) < MINIMUM_PASSWORD_LENGTH: if not (self.pw_1.text == self.pw_2.text) or len(self.pw_1.text) < MINIMUM_PASSWORD_LENGTH:
self.pw_1.is_valid = False self.pw_1.is_valid = False
self.pw_2.is_valid = False self.pw_2.is_valid = False
@ -21,14 +21,14 @@ class RegisterPage(Component):
self.pw_1.is_valid = True self.pw_1.is_valid = True
self.pw_2.is_valid = True self.pw_2.is_valid = True
def on_email_changed(self, change_event: TextInputChangeEvent) -> None: def on_email_focus_loss(self, change_event: TextInputChangeEvent) -> None:
try: try:
validate_email(change_event.text, check_deliverability=False) validate_email(change_event.text, check_deliverability=False)
self.email_input.is_valid = True self.email_input.is_valid = True
except EmailNotValidError: except EmailNotValidError:
self.email_input.is_valid = False self.email_input.is_valid = False
def on_user_name_input_change(self, _: TextInputChangeEvent) -> None: def on_user_name_focus_loss(self, _: TextInputChangeEvent) -> None:
current_text = self.user_name_input.text current_text = self.user_name_input.text
if len(current_text) > UserService.MAX_USERNAME_LENGTH: if len(current_text) > UserService.MAX_USERNAME_LENGTH:
self.user_name_input.text = current_text[:UserService.MAX_USERNAME_LENGTH] self.user_name_input.text = current_text[:UserService.MAX_USERNAME_LENGTH]
@ -100,7 +100,7 @@ class RegisterPage(Component):
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
on_change=self.on_user_name_input_change on_lose_focus=self.on_user_name_focus_loss
) )
self.email_input = TextInput( self.email_input = TextInput(
label="E-Mail Adresse", label="E-Mail Adresse",
@ -109,7 +109,7 @@ class RegisterPage(Component):
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
on_change=self.on_email_changed on_lose_focus=self.on_email_focus_loss
) )
self.pw_1 = TextInput( self.pw_1 = TextInput(
label="Passwort", label="Passwort",
@ -119,7 +119,7 @@ class RegisterPage(Component):
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
is_secret=True, is_secret=True,
on_change=self.on_pw_change on_lose_focus=self.on_pw_focus_loss
) )
self.pw_2 = TextInput( self.pw_2 = TextInput(
label="Passwort wiederholen", label="Passwort wiederholen",
@ -129,7 +129,7 @@ class RegisterPage(Component):
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
is_secret=True, is_secret=True,
on_change=self.on_pw_change on_lose_focus=self.on_pw_focus_loss
) )
self.submit_button = Button( self.submit_button = Button(
content=Text( content=Text(

View File

@ -30,6 +30,7 @@ class SeatingPlanPage(Component):
purchase_box_loading: bool = False purchase_box_loading: bool = False
purchase_box_success_msg: Optional[str] = None purchase_box_success_msg: Optional[str] = None
purchase_box_error_msg: Optional[str] = None purchase_box_error_msg: Optional[str] = None
seating_info_text = ""
is_booking_blocked: bool = False is_booking_blocked: bool = False
@event.on_populate @event.on_populate
@ -47,6 +48,7 @@ class SeatingPlanPage(Component):
self.is_booking_blocked = True 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.seating_info_text = ""
self.show_info_box = True self.show_info_box = True
self.show_purchase_box = False 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)
@ -62,6 +64,12 @@ class SeatingPlanPage(Component):
else: else:
self.current_seat_occupant = None self.current_seat_occupant = None
async def on_info_clicked(self, text: str) -> None:
self.show_info_box = True
self.show_purchase_box = False
self.current_seat_id = None
self.seating_info_text = text
def set_error(self, msg: str) -> None: def set_error(self, msg: str) -> None:
self.purchase_box_error_msg = msg self.purchase_box_error_msg = msg
self.purchase_box_success_msg = None self.purchase_box_success_msg = None
@ -119,7 +127,7 @@ class SeatingPlanPage(Component):
Column( Column(
SeatingPlanInfoBox(seat_id=self.current_seat_id, seat_occupant=self.current_seat_occupant, seat_price=self.current_seat_price, 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, 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), purchase_cb=self.on_purchase_clicked, override_text=self.seating_info_text),
SeatingPurchaseBox( SeatingPurchaseBox(
show=self.show_purchase_box, show=self.show_purchase_box,
seat_id=self.current_seat_id, seat_id=self.current_seat_id,
@ -132,7 +140,7 @@ class SeatingPlanPage(Component):
) )
), ),
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, info_clicked_cb=self.on_info_clicked) if self.seating_info else
Column(ProgressCircle(color=self.session.theme.secondary_color, margin=3), Column(ProgressCircle(color=self.session.theme.secondary_color, margin=3),
Text("Sitzplan wird geladen", style=TextStyle(fill=self.session.theme.neutral_color), align_x=0.5, margin=1)) Text("Sitzplan wird geladen", style=TextStyle(fill=self.session.theme.neutral_color), align_x=0.5, margin=1))
), ),

View File

@ -19,3 +19,4 @@ from .ManageNewsPage import ManageNewsPage
from .ManageUsersPage import ManageUsersPage from .ManageUsersPage import ManageUsersPage
from .ManageCateringPage import ManageCateringPage from .ManageCateringPage import ManageCateringPage
from .ManageTournamentsPage import ManageTournamentsPage from .ManageTournamentsPage import ManageTournamentsPage
from .OverviewPage import OverviewPage

View File

@ -2,6 +2,7 @@ import logging
from collections.abc import Callable from collections.abc import Callable
from datetime import datetime from datetime import datetime
from decimal import Decimal, ROUND_DOWN from decimal import Decimal, ROUND_DOWN
from typing import Optional
from src.ez_lan_manager.services.DatabaseService import DatabaseService from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.types.Transaction import Transaction from src.ez_lan_manager.types.Transaction import Transaction
@ -65,9 +66,11 @@ class AccountingService:
return await self._db_service.get_all_transactions_for_user(user_id) return await self._db_service.get_all_transactions_for_user(user_id)
@staticmethod @staticmethod
def make_euro_string_from_decimal(euros: Decimal) -> str: def make_euro_string_from_decimal(euros: Optional[Decimal]) -> str:
""" """
Internally, all money values are euros as decimal. Only when showing them to the user we generate a string. Internally, all money values are euros as decimal. Only when showing them to the user we generate a string.
""" """
if euros is None:
return "0.00 €"
rounded_decimal = str(euros.quantize(Decimal(".01"), rounding=ROUND_DOWN)) rounded_decimal = str(euros.quantize(Decimal(".01"), rounding=ROUND_DOWN))
return f"{rounded_decimal}" return f"{rounded_decimal}"

View File

@ -71,15 +71,6 @@ class ConfigurationService:
logger.fatal("Error loading LAN Info, exiting...") logger.fatal("Error loading LAN Info, exiting...")
sys.exit(1) sys.exit(1)
def get_seating_configuration(self) -> SeatingConfiguration:
try:
return SeatingConfiguration(
seats=self._config["seating"]
)
except KeyError:
logger.fatal("Error loading seating configuration, exiting...")
sys.exit(1)
def get_ticket_info(self) -> tuple[TicketInfo, ...]: def get_ticket_info(self) -> tuple[TicketInfo, ...]:
try: try:
return tuple([TicketInfo( return tuple([TicketInfo(

View File

@ -22,8 +22,7 @@ class SeatAlreadyTakenError(Exception):
pass pass
class SeatingService: class SeatingService:
def __init__(self, seating_configuration: SeatingConfiguration, lan_info: LanInfo, db_service: DatabaseService, ticketing_service: TicketingService) -> None: def __init__(self, lan_info: LanInfo, db_service: DatabaseService, ticketing_service: TicketingService) -> None:
self._seating_configuration = seating_configuration
self._lan_info = lan_info self._lan_info = lan_info
self._db_service = db_service self._db_service = db_service
self._ticketing_service = ticketing_service self._ticketing_service = ticketing_service