Compare commits

...

31 Commits

Author SHA1 Message Date
David Rodenkirchen
b505191156 add hover tooltips for seating plan 2025-11-12 23:36:33 +01:00
David Rodenkirchen
43559fcf30 Rename EZ-LAN to EZGG-LAN 2025-07-30 22:38:57 +02:00
David Rodenkirchen
af8cf19dc8 send mail to user when account balances is added to 2025-07-30 13:33:55 +02:00
David Rodenkirchen
29caadaca2 rename lan 2025-07-26 14:16:09 +02:00
6e598b577f Merge pull request 'upgrade rio to 0.11.2rc6' (#21) from feature/upgrade-rio-rc into main
Reviewed-on: Vereins-IT/ez-lan-manager#21
2025-07-13 21:37:48 +00:00
David Rodenkirchen
1091179492 upgrade rio to 0.11.2rc6 2025-07-13 23:37:17 +02:00
David Rodenkirchen
530efcd286 make portrait mode optional 2025-05-13 07:10:24 +02:00
David Rodenkirchen
4018502a63 fix seating plan 2025-04-03 22:54:20 +02:00
David Rodenkirchen
3f06015cb8 add 502 Fallback HTML 2025-04-03 17:59:58 +02:00
David Rodenkirchen
336bd0e108 Introduce receipt printing 2025-04-03 06:53:58 +02:00
David Rodenkirchen
fa90e16fdf register fix 2025-03-27 08:01:51 +01:00
David Rodenkirchen
b987623d9d sql update 2025-03-27 07:54:52 +01:00
David Rodenkirchen
f58a7872ef periodically refresh order status 2025-03-26 00:07:04 +01:00
David Rodenkirchen
23e903a207 add additional info to seating plan 2025-03-25 23:59:10 +01:00
David Rodenkirchen
8aee3af9d7 dont empty cart if funds insufficient 2025-03-25 23:34:55 +01:00
David Rodenkirchen
dd8b79c254 fix seating plan info box not showing correct button 2025-03-25 23:28:32 +01:00
David Rodenkirchen
2f6e2b15aa minor fixes 2025-03-25 23:19:25 +01:00
David Rodenkirchen
b4da6b9729 fix bug where contact page did not respond 2025-03-25 23:04:39 +01:00
David Rodenkirchen
a430c81624 make account balance string formatting more rigid 2025-03-25 21:10:39 +01:00
David Rodenkirchen
1d21fbae5a improve seating plan booking button 2025-03-25 21:05:17 +01:00
David Rodenkirchen
db8ada283b make UserInfoBox rigid against missing userid 2025-03-25 20:19:26 +01:00
David Rodenkirchen
d3aadcf697 add overview page 2025-03-24 10:37:26 +01:00
David Rodenkirchen
bbb4e8f1d1 remove seating from configuration 2025-03-24 08:31:23 +01:00
David Rodenkirchen
51b07baa36 add SQL for catering menu items 2025-03-23 23:57:23 +01:00
David Rodenkirchen
ea9a805483 add conditional footer width workaround 2025-03-23 23:57:08 +01:00
David Rodenkirchen
d43dd96fbb Change Seating Plan to Donsbach 2025-03-23 23:56:49 +01:00
David Rodenkirchen
a61ee8e775 enable Discord Link 2025-03-23 23:56:27 +01:00
David Rodenkirchen
328bea32d7 bump version to 0.1.0 2025-03-23 23:56:05 +01:00
David Rodenkirchen
49d89feda3 remove buggy input checks 2025-03-23 21:48:44 +01:00
David Rodenkirchen
26994d4536 dockerize 2025-03-23 17:20:14 +01:00
David Rodenkirchen
ee6f35b771 add development instructions to README 2025-03-11 22:26:32 +01:00
88 changed files with 1198 additions and 559 deletions

22
502.html Normal file
View File

@ -0,0 +1,22 @@
<html>
<head>
<title>EZGG LAN Manager - Wartungsmodus</title>
<style>
body { text-align: center; padding: 150px; }
h1 { font-size: 50px; }
body { font: 20px Helvetica, sans-serif; color: #333; }
article { display: block; text-align: left; width: 650px; margin: 0 auto; }
a { color: #dc8100; text-decoration: none; }
a:hover { color: #333; text-decoration: none; }
</style>
</head>
<body>
<article>
<h1>Wir sind bald wieder da!</h1>
<div>
<p>Wir f&uuml;hren zurzeit Wartungsarbeiten durch und sind in k&uuml;rze wieder f&uuml;r euch da.</p>
<p>&mdash; Euer EZGG LAN Team</p>
</div>
</article>
</body>
</html>

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM python:3.12-bookworm
RUN apt-get update
RUN apt install dumb-init
COPY requirements.txt .
RUN pip install -r requirements.txt
EXPOSE 8000
EXPOSE 8001
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

View File

@ -1,13 +1,45 @@
# EZ LAN Manager
# EZGG LAN Manager
## Overview
This repository contains the code for the EZ LAN Manager.
This repository contains the code for the EZGG LAN Manager.
## How to install [prod]
## Development Setup
TBD
### Prerequisites
## How to install [dev]
- Working Installation of MySQL 5 or latest MariaDB Server (`mariadb-server` for Debian-based Linux, `XAMPP` for Windows)
- Python 3.9 or higher
- PyCharm or similar IDE (optional)
TBD
### Step 1: Preparing Database
To prepare the database, apply the SQL file located in `sql/create_database.sql` to your database server. This is easily accomplished with the MYSQL Workbench, but it can be also done by pipeing the file into the mariadb-server executable.
Optionally, you can now execute the script `create_demo_database_content.py`, found in `src/ezgg_lan_manager/helpers`. Be aware that it can be buggy sometimes, especially if you overwrite existing data.
### Step 2: Preparing configuration
Use the example configuration at `config/config.example.toml` to create a `config.toml` at the base of the repository. Most of the parameters do not matter to get the development setup done, but the database credentials need to be correct.
### Step 3: Install dependecies
Use `pip install -r requirements.txt` to install the requirements. The usage of a venv is recommended.
### Step 4: Running the application
Run the application by executing the file `EzggLanManager.py` found at `src/ezgg_lan_manager`. Check the STDOUT for information regarding the port on which the application is now served.
## Docker Deployment
To get the docker compose setup running, you need to manually complete the following steps:
1. Create a valid `config.toml` in the project root, so it gets copied over into the container.
2. Create the database user:
```sql
CREATE USER 'ezgg_lan_manager'@'%' IDENTIFIED BY 'PASSWORD';
GRANT ALL PRIVILEGES ON ezgg_lan_manager.* TO 'ezgg_lan_manager'@'%';
FLUSH PRIVILEGES;
```
3. Make sure to **NOT** use the default passwords!
4. Apply the `create_database.sql` when starting the MariaDB container for the first time.

View File

@ -1 +1 @@
0.0.1
0.1.0

View File

@ -1,5 +1,5 @@
[lan]
name="EZ LAN"
name="EZGG LAN"
iteration="0.5"
date_from="2024-10-30 15:00:00"
date_till="2024-11-01 12:00:00"
@ -10,7 +10,7 @@
db_password="demo_password"
db_host="127.0.0.1"
db_port=3306
db_name="ez_lan_manager"
db_name="ezgg_lan_manager"
[mailing]
smtp_server=""
@ -19,12 +19,6 @@
username=""
password=""
[seating]
# SeatID -> Category
A01 = "NORMAL"
A02 = "NORMAL"
C01 = "LUXUS"
[tickets]
[tickets."NORMAL"]
total_tickets=30
@ -40,5 +34,11 @@
additional_info="Berechtigt zur Nutzung eines verbesserten Platzes. Dieser ist mit einer höheren Internet-Bandbreite und einem Sitzkissen ausgestattet."
is_default=false
[receipt_printing]
host="127.0.0.1"
port="5000"
order_print_endpoint="print_order"
password="Alkohol1"
[misc]
dev_mode_active=true # Supresses E-Mail sending

36
docker-compose.yml Normal file
View File

@ -0,0 +1,36 @@
services:
lan_manager:
build: .
depends_on:
db:
condition: service_healthy
environment:
PYTHONPATH: /opt/ezgg-lan-manager
ports:
- "8000:8000"
- "8001:8001"
volumes:
- ./:/opt/ezgg-lan-manager
entrypoint: ["/bin/sh", "-c", "cd /opt/ezgg-lan-manager/src && python3 /opt/ezgg-lan-manager/src/EzggLanManager.py"]
db:
image: mariadb:latest
environment:
MARIADB_ROOT_PASSWORD: Alkohol1
MARIADB_DATABASE: ezgg_lan_manager
MARIADB_USER: ezgg_lan_manager
MARIADB_PASSWORD: Alkohol1
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 5
ports:
- "127.0.0.1:3306:3306"
volumes:
- database:/var/lib/mysql
- ./sql/create_database.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
database:

Binary file not shown.

View File

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

View File

@ -1,8 +1,8 @@
CREATE DATABASE IF NOT EXISTS `ez_lan_manager` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
USE `ez_lan_manager`;
CREATE DATABASE IF NOT EXISTS `ezgg_lan_manager` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
USE `ezgg_lan_manager`;
-- MySQL dump 10.13 Distrib 5.7.24, for Linux (x86_64)
--
-- Host: 127.0.0.1 Database: ez_lan_manager
-- Host: 127.0.0.1 Database: ezgg_lan_manager
-- ------------------------------------------------------
-- Server version 5.5.5-10.11.8-MariaDB-0ubuntu0.24.04.1

61
sql/seats_ez_lan_1.sql Normal file
View File

@ -0,0 +1,61 @@
INSERT INTO `seats` (`seat_id`, `is_blocked`, `seat_category`) VALUES
('A01', '0', 'NORMAL'),
('A02', '0', 'NORMAL'),
('A03', '0', 'NORMAL'),
('A04', '0', 'NORMAL'),
('A05', '0', 'NORMAL'),
('A06', '0', 'NORMAL'),
('A10', '0', 'NORMAL'),
('A11', '0', 'NORMAL'),
('A12', '0', 'NORMAL'),
('A13', '0', 'NORMAL'),
('A14', '0', 'NORMAL'),
('A15', '0', 'NORMAL'),
('B01', '0', 'NORMAL'),
('B02', '0', 'NORMAL'),
('B03', '0', 'NORMAL'),
('B04', '0', 'NORMAL'),
('B05', '0', 'NORMAL'),
('B10', '0', 'NORMAL'),
('B11', '0', 'NORMAL'),
('B12', '0', 'NORMAL'),
('B13', '0', 'NORMAL'),
('B14', '0', 'NORMAL'),
('C01', '0', 'NORMAL'),
('C02', '0', 'NORMAL'),
('C03', '0', 'NORMAL'),
('C04', '0', 'NORMAL'),
('C05', '0', 'NORMAL'),
('C10', '0', 'NORMAL'),
('C11', '0', 'NORMAL'),
('C12', '0', 'NORMAL'),
('C13', '0', 'NORMAL'),
('C14', '0', 'NORMAL'),
('D01', '0', 'NORMAL'),
('D02', '0', 'NORMAL'),
('D03', '0', 'NORMAL'),
('D04', '0', 'NORMAL'),
('D05', '0', 'NORMAL'),
('D06', '0', 'NORMAL'),
('D10', '0', 'NORMAL'),
('D11', '0', 'NORMAL'),
('D12', '0', 'NORMAL'),
('D13', '0', 'NORMAL'),
('D14', '0', 'NORMAL'),
('D15', '0', 'NORMAL'),
('E01', '0', 'NORMAL'),
('E02', '0', 'NORMAL'),
('E03', '0', 'NORMAL'),
('E04', '0', 'NORMAL'),
('E05', '0', 'NORMAL'),
('E06', '0', 'NORMAL'),
('E10', '0', 'NORMAL'),
('E11', '0', 'NORMAL'),
('E12', '0', 'NORMAL'),
('E13', '0', 'NORMAL'),
('E14', '0', 'NORMAL'),
('E15', '0', 'NORMAL');

View File

@ -8,13 +8,13 @@ from pathlib import Path
from rio import App, Theme, Color, Font, ComponentPage, Session
from from_root import from_root
from src.ez_lan_manager import pages, init_services
from src.ez_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard, team_guard
from src.ez_lan_manager.services.DatabaseService import NoDatabaseConnectionError
from src.ez_lan_manager.services.LocalDataService import LocalData
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager import pages, init_services
from src.ezgg_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard, team_guard
from src.ezgg_lan_manager.services.DatabaseService import NoDatabaseConnectionError
from src.ezgg_lan_manager.services.LocalDataService import LocalData
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
logger = logging.getLogger("EzLanManager")
logger = logging.getLogger("EzggLanManager")
if __name__ == "__main__":
theme = Theme.from_colors(
@ -28,7 +28,7 @@ if __name__ == "__main__":
corner_radius_small=0,
corner_radius_medium=0,
corner_radius_large=0,
font=Font(from_root("src/ez_lan_manager/assets/fonts/joystix.otf"))
font=Font(from_root("src/ezgg_lan_manager/assets/fonts/joystix.otf"))
)
default_attachments = [LocalData()]
default_attachments.extend(init_services())
@ -46,7 +46,7 @@ if __name__ == "__main__":
sys.exit(1)
app = App(
name="EZ LAN Manager",
name="EZGG LAN Manager",
build=pages.BasePage,
pages=[
ComponentPage(
@ -62,7 +62,7 @@ if __name__ == "__main__":
ComponentPage(
name="Overview",
url_segment="overview",
build=lambda: pages.PlaceholderPage(placeholder_name="LAN Übersicht"),
build=pages.OverviewPage,
),
ComponentPage(
name="BuyTicket",
@ -168,13 +168,13 @@ if __name__ == "__main__":
default_attachments=default_attachments,
on_session_start=on_session_start,
on_app_start=on_app_start,
icon=from_root("src/ez_lan_manager/assets/img/favicon.png"),
icon=from_root("src/ezgg_lan_manager/assets/img/favicon.png"),
meta_tags={
"robots": "INDEX,FOLLOW",
"description": f"Info und Verwaltungs-Seite der LAN Party '{lan_info.name} - {lan_info.iteration}'.",
"og:description": f"Info und Verwaltungs-Seite der LAN Party '{lan_info.name} - {lan_info.iteration}'.",
"keywords": "Gaming, Clan, Guild, Verein, Club, Einfach, Zocken, Gesellschaft, Videospiele, "
"Videogames, LAN, Party, EZ, LAN, Manager",
"Videogames, LAN, Party, EZ, EZGG, LAN, Manager",
"author": "David Rodenkirchen",
"publisher": "EZ GG e.V.",
"copyright": "EZ GG e.V.",
@ -186,4 +186,7 @@ if __name__ == "__main__":
}
)
sys.exit(app.run_as_web_server())
sys.exit(app.run_as_web_server(
host="0.0.0.0",
port=8000,
))

View File

@ -1,32 +0,0 @@
import logging
from from_root import from_root
from src.ez_lan_manager.services import *
from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.CateringService import CateringService
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.services.LocalDataService import LocalDataService
from src.ez_lan_manager.services.MailingService import MailingService
from src.ez_lan_manager.services.NewsService import NewsService
from src.ez_lan_manager.services.SeatingService import SeatingService
from src.ez_lan_manager.services.TicketingService import TicketingService
from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.types import *
# Inits services in the correct order
def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService]:
logging.basicConfig(level=logging.DEBUG)
configuration_service = ConfigurationService(from_root("config.toml"))
db_service = DatabaseService(configuration_service.get_database_configuration())
user_service = UserService(db_service)
accounting_service = AccountingService(db_service)
news_service = NewsService(db_service)
mailing_service = MailingService(configuration_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)
catering_service = CateringService(db_service, accounting_service, user_service)
local_data_service = LocalDataService()
return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service, local_data_service

View File

@ -1,194 +0,0 @@
from typing import Callable
from rio import Component, Rectangle, Grid, Column, Row, Text, TextStyle, Color
from src.ez_lan_manager.components.SeatingPlanPixels import SeatPixel, WallPixel, InvisiblePixel, TextPixel
from src.ez_lan_manager.types.Seat import Seat
MAX_GRID_WIDTH_PIXELS = 34
MAX_GRID_HEIGHT_PIXELS = 45
class SeatingPlanLegend(Component):
def build(self) -> Component:
return Column(
Text("Legende", style=TextStyle(fill=self.session.theme.neutral_color), justify="center", margin=1),
Row(
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)),
),
Row(
Rectangle(
content=Column(
Text(f"Freier Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
selectable=False, overflow="wrap")
),
min_width=1,
min_height=1,
fill=self.session.theme.success_color,
grow_x=False,
grow_y=False,
hover_fill=self.session.theme.success_color,
transition_time=0.4,
ripple=True
),
Rectangle(
content=Column(
Text(f"Belegter Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
selectable=False, overflow="wrap")
),
min_width=1,
min_height=1,
fill=self.session.theme.danger_color,
grow_x=False,
grow_y=False,
hover_fill=self.session.theme.danger_color,
transition_time=0.4,
ripple=True
),
Rectangle(
content=Column(
Text(f"Eigener Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
selectable=False, overflow="wrap")
),
min_width=1,
min_height=1,
fill=Color.from_hex("800080"),
grow_x=False,
grow_y=False,
hover_fill=Color.from_hex("800080"),
transition_time=0.4,
ripple=True
),
margin=1,
spacing=1
)
)
class SeatingPlan(Component):
seat_clicked_cb: Callable
seating_info: list[Seat]
def get_seat(self, seat_id: str) -> Seat:
return next(filter(lambda seat: seat.seat_id == seat_id, self.seating_info))
"""
This seating plan is for the community center "Bottenhorn"
"""
def build(self) -> Component:
grid = Grid()
# Outlines
for column_id in range(0, MAX_GRID_WIDTH_PIXELS):
grid.add(InvisiblePixel(), row=0, column=column_id)
for y in range(0, 13):
grid.add(WallPixel(), row=y, column=0)
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)
# Block A
block_a_margin_left = 12
block_a_margin_top = 1
(grid
.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)
.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)
.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)
.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)
.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)
)
# Block B
block_b_margin_left = 20
block_b_margin_top = 1
(grid
.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)
.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)
.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)
.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)
.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)
)
# Block C
block_c_margin_left = 28
block_c_margin_top = 1
(grid
.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)
.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)
.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)
.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)
.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)
)
# Block D
block_d_margin_left = 20
block_d_margin_top = 20
(grid
.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)
.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)
.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)
.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)
.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)
)
# Block E
block_e_margin_left = 28
block_e_margin_top = 20
(grid
.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)
.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)
.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)
.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
for y in range(0, 13):
grid.add(WallPixel(), row=y, column=10)
for y in range(19, MAX_GRID_HEIGHT_PIXELS):
grid.add(WallPixel(), row=y, column=10)
# Stage
for x in range(11, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=35, column=x)
grid.add(TextPixel(text="Bühne"), row=36, column=11, width=24, height=9)
# Drinks
grid.add(TextPixel(text="G\ne\nt\nr\nä\nn\nk\ne"), row=21, column=11, width=3, height=11)
# Sleeping
grid.add(TextPixel(icon_name="material/bed"), row=1, column=1, width=4, height=11)
# Toilet
grid.add(TextPixel(icon_name="material/floor", no_outline=True), row=1, column=7, width=3, height=2)
grid.add(TextPixel(icon_name="material/north", no_outline=True), row=3, column=7, width=3, height=2)
grid.add(TextPixel(icon_name="material/wc"), row=5, column=7, width=3, height=2)
# Entry/Helpdesk
grid.add(TextPixel(text="Einlass\n &Orga"), row=19, column=3, width=7, height=5)
# 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(
content=grid,
grow_x=True,
grow_y=True,
stroke_color=self.session.theme.neutral_color,
stroke_width=0.1,
fill=self.session.theme.primary_color,
margin=0.5
)

View File

@ -0,0 +1,35 @@
import logging
from from_root import from_root
from src.ezgg_lan_manager.services import *
from src.ezgg_lan_manager.services.AccountingService import AccountingService
from src.ezgg_lan_manager.services.CateringService import CateringService
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.services.LocalDataService import LocalDataService
from src.ezgg_lan_manager.services.MailingService import MailingService
from src.ezgg_lan_manager.services.NewsService import NewsService
from src.ezgg_lan_manager.services.ReceiptPrintingService import ReceiptPrintingService
from src.ezgg_lan_manager.services.SeatingService import SeatingService
from src.ezgg_lan_manager.services.TicketingService import TicketingService
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types import *
# Inits services in the correct order
def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService, ReceiptPrintingService]:
logging.basicConfig(level=logging.DEBUG)
configuration_service = ConfigurationService(from_root("config.toml"))
db_service = DatabaseService(configuration_service.get_database_configuration())
user_service = UserService(db_service)
accounting_service = AccountingService(db_service)
news_service = NewsService(db_service)
mailing_service = MailingService(configuration_service)
ticketing_service = TicketingService(configuration_service.get_ticket_info(), db_service, accounting_service)
seating_service = SeatingService(configuration_service.get_lan_info(), db_service, ticketing_service)
receipt_printing_service = ReceiptPrintingService(seating_service, configuration_service.get_receipt_printing_configuration(), configuration_service.DEV_MODE_ACTIVE)
catering_service = CateringService(db_service, accounting_service, user_service, receipt_printing_service)
local_data_service = LocalDataService()
return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service, local_data_service, receipt_printing_service

View File

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -4,7 +4,7 @@ from decimal import Decimal
import rio
from rio import Component, Row, Text, IconButton, TextStyle
from src.ez_lan_manager import AccountingService
from src.ezgg_lan_manager import AccountingService
MAX_LEN = 24

View File

@ -3,9 +3,9 @@ from typing import Optional, Callable
from rio import Component, Row, Card, Column, Text, TextStyle, Spacer, PointerEventListener, Button
from src.ez_lan_manager.services.CateringService import CateringService
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
from src.ez_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.services.CateringService import CateringService
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
from src.ezgg_lan_manager.types.Seat import Seat
class CateringManagementOrderDisplayStatusButton(Component):
status: CateringOrderStatus

View File

@ -3,7 +3,7 @@ from typing import Callable
from rio import Component, Row, Text, TextStyle, Color, Rectangle, CursorStyle
from rio.components.pointer_event_listener import PointerEvent, PointerEventListener
from src.ez_lan_manager.types.CateringOrder import CateringOrderStatus, CateringOrder
from src.ezgg_lan_manager.types.CateringOrder import CateringOrderStatus, CateringOrder
MAX_LEN = 24

View File

@ -4,7 +4,7 @@ from typing import Callable
import rio
from rio import Component, Row, Text, IconButton, TextStyle, Column, Spacer, Card, Color
from src.ez_lan_manager import AccountingService
from src.ezgg_lan_manager import AccountingService
MAX_LEN = 24

View File

@ -3,12 +3,12 @@ from typing import Optional, Callable
from rio import *
from src.ez_lan_manager import ConfigurationService, UserService, LocalDataService
from src.ez_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton
from src.ez_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox
from src.ez_lan_manager.services.LocalDataService import LocalData
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager import ConfigurationService, UserService, LocalDataService
from src.ezgg_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton
from src.ezgg_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox
from src.ezgg_lan_manager.services.LocalDataService import LocalData
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User
class DesktopNavigation(Component):
@ -54,7 +54,7 @@ class DesktopNavigation(Component):
DesktopNavigationButton("FAQ", "./faq"),
DesktopNavigationButton("Regeln & AGB", "./rules-gtc"),
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("Kontakt", "./contact"),
DesktopNavigationButton("Impressum & DSGVO", "./imprint"),
@ -79,7 +79,7 @@ class DesktopNavigation(Component):
return Card(
Column(
Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)),
Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.9)),
Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2),
user_info_and_login_box,
*nav_to_use,

View File

@ -1,15 +1,14 @@
from rio import Component, TextStyle, Color, TextInput, Button, Text, Rectangle, Column, Row, Spacer, \
EventHandler
from src.ez_lan_manager.services.LocalDataService import LocalDataService, LocalData
from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.services.LocalDataService import LocalDataService, LocalData
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User
class LoginBox(Component):
status_change_cb: EventHandler = None
TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
user_name_input_text: str = ""
password_input_text: str = ""
user_name_input_is_valid = True
@ -57,7 +56,7 @@ class LoginBox(Component):
is_valid=self.password_input_is_valid
)
login_button = Button(
Text("LOGIN", style=self.TEXT_STYLE, justify="center"),
Text("LOGIN", fill=Color.from_hex("02dac5"), style=TextStyle(font_size=0.9), justify="center"),
shape="rectangle",
style="minor",
color="secondary",
@ -65,14 +64,14 @@ class LoginBox(Component):
on_press=self._on_login_pressed
)
register_button = Button(
Text("REG", style=self.TEXT_STYLE, justify="center"),
Text("REG", fill=Color.from_hex("02dac5"), style=TextStyle(font_size=0.9), justify="center"),
shape="rectangle",
style="minor",
color="secondary",
on_press=lambda: self.session.navigate_to("./register")
)
forgot_password_button = Button(
Text("LST PWD", style=self.TEXT_STYLE, justify="center"),
Text("LST PWD", fill=Color.from_hex("02dac5"), style=TextStyle(font_size=0.9), justify="center"),
shape="rectangle",
style="minor",
color="secondary",
@ -95,7 +94,7 @@ class LoginBox(Component):
),
margin_bottom=0.5
),
Text(text="Dieses Konto\nist gesperrt", style=TextStyle(fill=self.session.theme.danger_color, font_size=0.9 if self.is_account_locked else 0), align_x=0.5),
Text(text="Dieses Konto\nist gesperrt", fill=self.session.theme.danger_color, style=TextStyle(font_size=0.9 if self.is_account_locked else 0), align_x=0.5),
spacing=0.4
),
fill=Color.TRANSPARENT,

View File

@ -4,8 +4,8 @@ from typing import Optional
from rio import Component, Column, NumberInput, ThemeContextSwitcher, TextInput, Row, Button, EventHandler
from src.ez_lan_manager.types.Transaction import Transaction
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.types.Transaction import Transaction
from src.ezgg_lan_manager.types.User import User
class NewTransactionForm(Component):
@ -46,7 +46,6 @@ class NewTransactionForm(Component):
label="Betrag",
suffix_text="",
decimals=2,
thousands_separator=".",
margin=1,
margin_bottom=0
),

View File

@ -21,8 +21,8 @@ class NewsPost(Component):
grow_x=True,
margin=2,
margin_bottom=0,
fill=self.session.theme.background_color,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.3
),
overflow="ellipsize"
@ -31,8 +31,8 @@ class NewsPost(Component):
self.date,
margin=2,
align_x=1,
fill=self.session.theme.background_color,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.6
),
overflow="wrap"
@ -44,8 +44,8 @@ class NewsPost(Component):
margin=2,
margin_top=0,
margin_bottom=0,
fill=self.session.theme.background_color,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.8
),
overflow="ellipsize"
@ -53,9 +53,7 @@ class NewsPost(Component):
Text(
self.text,
margin=2,
style=TextStyle(
fill=self.session.theme.background_color
),
fill=self.session.theme.background_color,
overflow="wrap"
),
Text(
@ -65,8 +63,8 @@ class NewsPost(Component):
margin=2,
margin_top=0,
margin_bottom=1,
fill=self.session.theme.background_color,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.5,
italic=True
),

View File

@ -0,0 +1,274 @@
from typing import Callable
from rio import Component, Rectangle, Grid, Column, Row, Text, TextStyle, Color, PointerEventListener, Spacer
from src.ezgg_lan_manager.components.SeatingPlanPixels import SeatPixel, WallPixel, InvisiblePixel, TextPixel
from src.ezgg_lan_manager.types.Seat import Seat
MAX_GRID_WIDTH_PIXELS = 60
MAX_GRID_HEIGHT_PIXELS = 60
class SeatingPlanLegend(Component):
def build(self) -> Component:
return Column(
Text("Legende", style=TextStyle(fill=self.session.theme.neutral_color), justify="center", margin=1),
Row(
Spacer(),
Rectangle(
content=Text("Normaler Platz", style=TextStyle(fill=self.session.theme.neutral_color, font_size=0.7), margin=0.2, justify="center"),
fill=Color.TRANSPARENT,
stroke_width=0.2,
stroke_color=Color.from_hex("003300"),
min_width=20,
margin_right=1
),
Rectangle(
content=Text("Deluxe Platz", style=TextStyle(fill=self.session.theme.neutral_color, font_size=0.7), margin=0.2, justify="center"),
fill=Color.TRANSPARENT,
stroke_width=0.2,
stroke_color=Color.from_hex("66ff99"),
min_width=20
),
Spacer()
),
Row(
Rectangle(
content=Column(
Text(f"Freier Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
selectable=False, overflow="wrap")
),
min_width=1,
min_height=1,
fill=self.session.theme.success_color,
grow_x=False,
grow_y=False,
hover_fill=self.session.theme.success_color,
transition_time=0.4,
ripple=True
),
Rectangle(
content=Column(
Text(f"Belegter Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
selectable=False, overflow="wrap")
),
min_width=1,
min_height=1,
fill=self.session.theme.danger_color,
grow_x=False,
grow_y=False,
hover_fill=self.session.theme.danger_color,
transition_time=0.4,
ripple=True
),
Rectangle(
content=Column(
Text(f"Eigener Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
selectable=False, overflow="wrap")
),
min_width=1,
min_height=1,
fill=Color.from_hex("800080"),
grow_x=False,
grow_y=False,
hover_fill=Color.from_hex("800080"),
transition_time=0.4,
ripple=True
),
margin=1,
spacing=1
)
)
class SeatingPlan(Component):
seat_clicked_cb: Callable
seating_info: list[Seat]
info_clicked_cb: Callable
def get_seat(self, seat_id: str) -> Seat:
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 "Donsbach"
"""
def build(self) -> Component:
grid = Grid()
# Outlines
for x in range(0, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=0, column=x)
for y in range(0, MAX_GRID_HEIGHT_PIXELS):
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
grid.add(SeatPixel("A01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A01"), seat_orientation="bottom"), row=57, column=1, width=5, height=2)
grid.add(SeatPixel("A02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A02"), seat_orientation="bottom"), row=57, column=6, width=5, height=2)
grid.add(SeatPixel("A03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A03"), seat_orientation="bottom"), row=57, column=11, width=5, height=2)
grid.add(SeatPixel("A04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A04"), seat_orientation="bottom"), row=57, column=16, width=5, height=2)
grid.add(SeatPixel("A05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A05"), seat_orientation="bottom"), row=57, column=21, width=5, height=2)
grid.add(SeatPixel("A10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A10"), seat_orientation="top"), row=55, column=1, width=5, height=2)
grid.add(SeatPixel("A11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A11"), seat_orientation="top"), row=55, column=6, width=5, height=2)
grid.add(SeatPixel("A12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A12"), seat_orientation="top"), row=55, column=11, width=5, height=2)
grid.add(SeatPixel("A13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A13"), seat_orientation="top"), row=55, column=16, width=5, height=2)
grid.add(SeatPixel("A14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A14"), seat_orientation="top"), row=55, column=21, width=5, height=2)
# Block B
grid.add(SeatPixel("B01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B01"), seat_orientation="bottom"), row=50, column=1, width=3, height=2)
grid.add(SeatPixel("B02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B02"), seat_orientation="bottom"), row=50, column=4, width=3, height=2)
grid.add(SeatPixel("B03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B03"), seat_orientation="bottom"), row=50, column=7, width=3, height=2)
grid.add(SeatPixel("B04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B04"), seat_orientation="bottom"), row=50, column=10, width=3, height=2)
grid.add(SeatPixel("B05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B05"), seat_orientation="bottom"), row=50, column=13, width=3, height=2)
grid.add(SeatPixel("B06", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B06"), seat_orientation="bottom"), row=50, column=16, width=3, height=2)
grid.add(SeatPixel("B10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B10"), seat_orientation="top"), row=48, column=1, width=3, height=2)
grid.add(SeatPixel("B11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B11"), seat_orientation="top"), row=48, column=4, width=3, height=2)
grid.add(SeatPixel("B12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B12"), seat_orientation="top"), row=48, column=7, width=3, height=2)
grid.add(SeatPixel("B13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B13"), seat_orientation="top"), row=48, column=10, width=3, height=2)
grid.add(SeatPixel("B14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B14"), seat_orientation="top"), row=48, column=13, width=3, height=2)
grid.add(SeatPixel("B15", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B15"), seat_orientation="top"), row=48, column=16, width=3, height=2)
# Block C
grid.add(SeatPixel("C01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C01"), seat_orientation="bottom"), row=43, column=1, width=3, height=2)
grid.add(SeatPixel("C02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C02"), seat_orientation="bottom"), row=43, column=4, width=3, height=2)
grid.add(SeatPixel("C03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C03"), seat_orientation="bottom"), row=43, column=7, width=3, height=2)
grid.add(SeatPixel("C04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C04"), seat_orientation="bottom"), row=43, column=10, width=3, height=2)
grid.add(SeatPixel("C05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C05"), seat_orientation="bottom"), row=43, column=13, width=3, height=2)
grid.add(SeatPixel("C06", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C06"), seat_orientation="bottom"), row=43, column=16, width=3, height=2)
grid.add(SeatPixel("C10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C10"), seat_orientation="top"), row=41, column=1, width=3, height=2)
grid.add(SeatPixel("C11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C11"), seat_orientation="top"), row=41, column=4, width=3, height=2)
grid.add(SeatPixel("C12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C12"), seat_orientation="top"), row=41, column=7, width=3, height=2)
grid.add(SeatPixel("C13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C13"), seat_orientation="top"), row=41, column=10, width=3, height=2)
grid.add(SeatPixel("C14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C14"), seat_orientation="top"), row=41, column=13, width=3, height=2)
grid.add(SeatPixel("C15", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C15"), seat_orientation="top"), row=41, column=16, width=3, height=2)
# Block D
grid.add(SeatPixel("D01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D01"), seat_orientation="bottom"), row=34, column=1, width=5, height=2)
grid.add(SeatPixel("D02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D02"), seat_orientation="bottom"), row=34, column=6, width=5, height=2)
grid.add(SeatPixel("D03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D03"), seat_orientation="bottom"), row=34, column=11, width=5, height=2)
grid.add(SeatPixel("D04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D04"), seat_orientation="bottom"), row=34, column=16, width=5, height=2)
grid.add(SeatPixel("D05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D05"), seat_orientation="bottom"), row=34, column=21, width=5, height=2)
grid.add(SeatPixel("D10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D10"), seat_orientation="top"), row=32, column=1, width=5, height=2)
grid.add(SeatPixel("D11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D11"), seat_orientation="top"), row=32, column=6, width=5, height=2)
grid.add(SeatPixel("D12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D12"), seat_orientation="top"), row=32, column=11, width=5, height=2)
grid.add(SeatPixel("D13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D13"), seat_orientation="top"), row=32, column=16, width=5, height=2)
grid.add(SeatPixel("D14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D14"), seat_orientation="top"), row=32, column=21, width=5, height=2)
# Block E
grid.add(SeatPixel("E01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E01"), seat_orientation="bottom"), row=27, column=1, width=5, height=2)
grid.add(SeatPixel("E02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E02"), seat_orientation="bottom"), row=27, column=6, width=5, height=2)
grid.add(SeatPixel("E03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E03"), seat_orientation="bottom"), row=27, column=11, width=5, height=2)
grid.add(SeatPixel("E04", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E04"), seat_orientation="bottom"), row=27, column=16, width=5, height=2)
grid.add(SeatPixel("E05", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E05"), seat_orientation="bottom"), row=27, column=21, width=5, height=2)
grid.add(SeatPixel("E10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E10"), seat_orientation="top"), row=25, column=1, width=5, height=2)
grid.add(SeatPixel("E11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E11"), seat_orientation="top"), row=25, column=6, width=5, height=2)
grid.add(SeatPixel("E12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E12"), seat_orientation="top"), row=25, column=11, width=5, height=2)
grid.add(SeatPixel("E13", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E13"), seat_orientation="top"), row=25, column=16, width=5, height=2)
grid.add(SeatPixel("E14", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E14"), seat_orientation="top"), row=25, column=21, width=5, height=2)
# Stage
grid.add(PointerEventListener(
TextPixel(text="Bühne"),
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
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
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
grid.add(PointerEventListener(
TextPixel(icon_name="material/wc"),
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
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)
return Rectangle(
content=grid,
grow_x=True,
grow_y=True,
stroke_color=self.session.theme.neutral_color,
stroke_width=0.1,
fill=self.session.theme.primary_color,
margin=0.5
)

View File

@ -1,7 +1,11 @@
from decimal import Decimal
from functools import partial
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.ezgg_lan_manager import TicketingService
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class SeatingPlanInfoBox(Component):
@ -12,8 +16,30 @@ class SeatingPlanInfoBox(Component):
seat_occupant: Optional[str] = None
seat_price: Decimal = Decimal("0")
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:
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:
return Spacer()
if self.is_blocked:
@ -36,7 +62,7 @@ class SeatingPlanInfoBox(Component):
style=TextStyle(fill=self.session.theme.neutral_color), overflow="wrap", justify="center"),
Button(
Text(
f"Buchen",
text=self.booking_button_text,
margin=1,
style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.1),
overflow="wrap",
@ -48,7 +74,10 @@ class SeatingPlanInfoBox(Component):
margin=1,
grow_y=False,
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
)

View File

@ -1,16 +1,17 @@
from functools import partial
from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, PointerEventListener, Column
from typing import Optional, Callable
from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, PointerEventListener, Column, Row, PointerEvent, Tooltip
from typing import Optional, Callable, Literal
from src.ez_lan_manager.types.Seat import Seat
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class SeatPixel(Component):
seat_id: str
on_press_cb: Callable
seat: Seat
seat_orientation: Literal["top", "bottom"]
def determine_color(self) -> Color:
if self.seat.user is not None and self.seat.user.user_id == self.session[SessionStorage].user_id:
@ -20,25 +21,38 @@ class SeatPixel(Component):
return self.session.theme.success_color
def build(self) -> Component:
return PointerEventListener(
content=Rectangle(
content=Column(
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.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_height=1,
fill=self.determine_color(),
hover_stroke_width = 0.1,
grow_x=True,
grow_y=True,
hover_fill=self.session.theme.hud_color,
transition_time=0.4,
ripple=True
),
on_press=partial(self.on_press_cb, self.seat_id)
text = Text(f"{self.seat_id}", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5, selectable=False)
rec = Rectangle(
content=Row(text),
min_width=1,
min_height=1,
fill=self.determine_color(),
stroke_width=0.1,
hover_stroke_width=0.1,
stroke_color=Color.from_hex("003300") if self.seat.category == "NORMAL" else Color.from_hex("66ff99"),
grow_x=True,
grow_y=True,
hover_fill=self.session.theme.hud_color,
transition_time=0.4,
ripple=True
)
if self.seat.user or self.seat.is_blocked:
return PointerEventListener(
content=Tooltip(
anchor=rec,
tip=self.seat.user.user_name if self.seat.user else "Gesperrt",
position=self.seat_orientation,
),
on_press=partial(self.on_press_cb, self.seat_id),
)
else:
return PointerEventListener(
content=rec,
on_press=partial(self.on_press_cb, self.seat_id),
)
class TextPixel(Component):
text: Optional[str] = None
icon_name: Optional[str] = None
@ -58,13 +72,14 @@ class TextPixel(Component):
fill=self.session.theme.primary_color,
stroke_width=0.0 if self.no_outline else 0.1,
stroke_color=self.session.theme.neutral_color,
hover_stroke_width = None if self.no_outline else 0.1,
hover_stroke_width=None if self.no_outline else 0.1,
grow_x=True,
grow_y=True,
hover_fill=None,
ripple=True
)
class WallPixel(Component):
def build(self) -> Component:
return Rectangle(
@ -75,6 +90,7 @@ class WallPixel(Component):
grow_y=True,
)
class DebugPixel(Component):
def build(self) -> Component:
return Rectangle(
@ -82,14 +98,15 @@ class DebugPixel(Component):
min_width=1,
min_height=1,
fill=self.session.theme.success_color,
hover_stroke_color = self.session.theme.hud_color,
hover_stroke_width = 0.1,
hover_stroke_color=self.session.theme.hud_color,
hover_stroke_width=0.1,
grow_x=True,
grow_y=True,
hover_fill=self.session.theme.secondary_color,
transition_time=0.1
)
class InvisiblePixel(Component):
def build(self) -> Component:
return Rectangle(
@ -100,4 +117,4 @@ class InvisiblePixel(Component):
hover_stroke_width=0.0,
grow_x=True,
grow_y=True
)
)

View File

@ -2,14 +2,14 @@ from asyncio import sleep, create_task
from decimal import Decimal
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.CateringOrderItem import CateringOrderItem
from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.CateringService import CateringService, CateringError, CateringErrorType
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringMenuItemsWithAmount
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.components.CateringCartItem import CateringCartItem
from src.ezgg_lan_manager.components.CateringOrderItem import CateringOrderItem
from src.ezgg_lan_manager.services.AccountingService import AccountingService
from src.ezgg_lan_manager.services.CateringService import CateringService, CateringError, CateringErrorType
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringMenuItemsWithAmount
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
POPUP_CLOSE_TIMEOUT_SECONDS = 3
@ -21,6 +21,11 @@ class ShoppingCartAndOrders(Component):
popup_is_shown: bool = False
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:
self.show_cart = not self.show_cart
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))
else:
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
if not show_popup_task:
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(
build=build_dialog_content,
modal=True,
user_closeable=True,
user_closable=True,
)
await dialog.wait_for_close()

View File

@ -5,9 +5,9 @@ from decimal import Decimal
import rio
from rio import Component, Card, Column, Text, Row, Button, TextStyle, ProgressBar, event, Spacer
from src.ez_lan_manager import TicketingService
from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager import TicketingService
from src.ezgg_lan_manager.services.AccountingService import AccountingService
from src.ezgg_lan_manager.types.Ticket import Ticket
class TicketBuyCard(Component):

View File

@ -7,10 +7,10 @@ from from_root import from_root
from rio import Component, Column, Button, Color, TextStyle, Text, TextInput, Row, Image, event, Spacer, DateInput, \
TextInputChangeEvent, NoFileSelectedError
from src.ez_lan_manager.services.UserService import UserService, NameNotAllowedError
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.services.UserService import UserService, NameNotAllowedError
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User
class UserEditForm(Component):
@ -122,7 +122,7 @@ class UserEditForm(Component):
def build(self) -> Component:
pfp_image_container = Image(
from_root("src/ez_lan_manager/assets/img/anon_pfp.png") if self.profile_picture is None else self.profile_picture,
from_root("src/ezgg_lan_manager/assets/img/anon_pfp.png") if self.profile_picture is None else self.profile_picture,
align_x=0.5,
min_width=10,
min_height=10,

View File

@ -1,9 +1,9 @@
import logging
from rio import Component
from src.ez_lan_manager.components.LoginBox import LoginBox
from src.ez_lan_manager.components.UserInfoBox import UserInfoBox
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.components.LoginBox import LoginBox
from src.ezgg_lan_manager.components.UserInfoBox import UserInfoBox
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
logger = logging.getLogger(__name__.split(".")[-1])

View File

@ -4,16 +4,16 @@ from decimal import Decimal
from rio import Component, TextStyle, Color, Button, Text, Rectangle, Column, Row, Spacer, Link, event, EventHandler
from src.ez_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton
from src.ez_lan_manager.services.LocalDataService import LocalData, LocalDataService
from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.TicketingService import TicketingService
from src.ez_lan_manager.services.SeatingService import SeatingService
from src.ez_lan_manager.types.Seat import Seat
from src.ez_lan_manager.types.Ticket import Ticket
from src.ez_lan_manager.types.User import User
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton
from src.ezgg_lan_manager.services.LocalDataService import LocalData, LocalDataService
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.services.AccountingService import AccountingService
from src.ezgg_lan_manager.services.TicketingService import TicketingService
from src.ezgg_lan_manager.services.SeatingService import SeatingService
from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class StatusButton(Component):
@ -73,6 +73,8 @@ class UserInfoBox(Component):
async def update(self) -> None:
if not self.user:
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_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)

View File

@ -2,8 +2,8 @@ from typing import Optional
from rio import URL, GuardEvent
from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
# Guards pages against access from users that are NOT logged in

View File

@ -5,9 +5,9 @@ from decimal import Decimal
import sys
from src.ez_lan_manager import init_services
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItemCategory
from src.ez_lan_manager.types.News import News
from src.ezgg_lan_manager import init_services
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItemCategory
from src.ezgg_lan_manager.types.News import News
DEMO_USERS = [
{"user_name": "manfred", "user_mail": "manfred@demomail.com", "password_clear_text": "manfred"}, # Gast
@ -185,9 +185,9 @@ async def run() -> None:
await news_service.add_news(News(
news_id=None,
title="Der EZ LAN Manager",
title="Der EZGG LAN Manager",
subtitle="Eine Software des EZ GG e.V.",
content="Dies ist eine WIP-Version des EZ LAN Managers. Diese Software soll uns helfen in Zukunft die LAN "
content="Dies ist eine WIP-Version des EZGG LAN Managers. Diese Software soll uns helfen in Zukunft die LAN "
"Parties des EZ GG e.V.'s zu organisieren. Wer Fehler findet darf sie behalten. (Oder er meldet "
"sie)",
author=user,

View File

@ -1,18 +1,19 @@
from decimal import Decimal
from functools import partial
from typing import Optional
from rio import Column, Component, event, Text, TextStyle, Button, Color, Revealer, Row, ProgressCircle, Link
from src.ez_lan_manager import ConfigurationService, UserService, AccountingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.Transaction import Transaction
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager import ConfigurationService, UserService, AccountingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
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
class AccountPage(Component):
user: Optional[User] = None
balance: Optional[int] = None
balance: Optional[Decimal] = None
transaction_history: list[Transaction] = list()
banking_info_revealer_open: bool = False
paypal_info_revealer_open: bool = False

View File

@ -2,14 +2,17 @@ from __future__ import annotations
from typing import * # type: ignore
from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text, PageView
from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text, PageView, Button
from src.ez_lan_manager import ConfigurationService, DatabaseService
from src.ez_lan_manager.components.DesktopNavigation import DesktopNavigation
from src.ezgg_lan_manager import ConfigurationService, DatabaseService
from src.ezgg_lan_manager.components.DesktopNavigation import DesktopNavigation
class BasePage(Component):
color = "secondary"
corner_radius = (0, 0.5, 0, 0)
footer_size = 53.1
force_portrait_mode = False
@event.periodic(60)
async def check_db_conn(self) -> None:
is_healthy = await self.session[DatabaseService].is_healthy()
@ -20,6 +23,20 @@ class BasePage(Component):
async def on_window_size_change(self):
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 enforce_portrait_mode(self) -> None:
self.force_portrait_mode = True
self.force_refresh()
def build(self) -> Component:
content = Card(
PageView(),
@ -27,7 +44,7 @@ class BasePage(Component):
min_width=38,
corner_radius=(0, 0.5, 0, 0)
)
if self.session.window_width > 28:
if self.session.window_width > 28 or self.force_portrait_mode:
return Container(
content=Column(
Column(
@ -41,13 +58,13 @@ class BasePage(Component):
Row(
Spacer(grow_x=True, grow_y=False),
Card(
content=Text(f"EZ LAN Manager Version {self.session[ConfigurationService].APP_VERSION} © EZ GG e.V.", align_x=0.5, align_y=0.5, style=TextStyle(fill=self.session.theme.primary_color, font_size=0.5)),
content=Text(f"EZGG LAN Manager Version {self.session[ConfigurationService].APP_VERSION} © EZ GG e.V.", align_x=0.5, align_y=0.5, fill=self.session.theme.primary_color, style=TextStyle(font_size=0.5)),
color=self.session.theme.neutral_color,
corner_radius=(0, 0, 0.5, 0.5),
grow_x=False,
grow_y=False,
min_height=1.2,
min_width=53.1,
min_width=self.footer_size,
margin_bottom=3
),
Spacer(grow_x=True, grow_y=False),
@ -60,10 +77,21 @@ class BasePage(Component):
grow_y=True
)
else:
return Text(
"Der EZ LAN Manager wird\nauf mobilen Endgeräten nur\nim Querformat unterstützt.\nBitte drehe dein Gerät.",
align_x=0.5,
align_y=0.5,
style=TextStyle(fill=Color.from_hex("FFFFFF"), font_size=0.8)
return Column(
Text(
"Wir empfehlen auf\nmobilen Endgeräten im\nQuerformat zu arbeiten.\n\nBitte drehe dein Gerät.",
fill=Color.from_hex("FFFFFF"),
align_x=0.5,
align_y=0.5,
style=TextStyle(font_size=0.8)
),
Button(
content=Text("Ohne drehen fortfahren", margin=0.2),
style="minor",
shape="rounded",
align_x=0.5,
align_y=0,
on_press=self.enforce_portrait_mode
)
)

View File

@ -2,14 +2,14 @@ from typing import Optional
from rio import Text, Column, TextStyle, Component, event, Button, Popup
from src.ez_lan_manager import ConfigurationService, UserService, TicketingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.TicketBuyCard import TicketBuyCard
from src.ez_lan_manager.services.AccountingService import InsufficientFundsError
from src.ez_lan_manager.services.TicketingService import TicketNotAvailableError, UserAlreadyHasTicketError
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.Ticket import Ticket
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager import ConfigurationService, UserService, TicketingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TicketBuyCard import TicketBuyCard
from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError
from src.ezgg_lan_manager.services.TicketingService import TicketNotAvailableError, UserAlreadyHasTicketError
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager.types.User import User
class BuyTicketPage(Component):

View File

@ -2,12 +2,12 @@ from typing import Optional, Callable
from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, SwitcherBar, SwitcherBarChangeEvent, ProgressCircle
from src.ez_lan_manager import ConfigurationService, CateringService
from src.ez_lan_manager.components.CateringSelectionItem import CateringSelectionItem
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItemCategory, CateringMenuItem
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager import ConfigurationService, CateringService
from src.ezgg_lan_manager.components.CateringSelectionItem import CateringSelectionItem
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItemCategory, CateringMenuItem
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class CateringPage(Component):

View File

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

@ -5,9 +5,9 @@ from typing import * # type: ignore
from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
class DbErrorPage(Component):
@ -62,7 +62,7 @@ class DbErrorPage(Component):
Row(
Spacer(grow_x=True, grow_y=False),
Card(
content=Text(f"EZ LAN Manager Version {self.session[ConfigurationService].APP_VERSION} © EZ GG e.V.", align_x=0.5, align_y=0.5, style=TextStyle(fill=self.session.theme.primary_color, font_size=0.5)),
content=Text(f"EZGG LAN Manager Version {self.session[ConfigurationService].APP_VERSION} © EZ GG e.V.", align_x=0.5, align_y=0.5, style=TextStyle(fill=self.session.theme.primary_color, font_size=0.5)),
color=self.session.theme.neutral_color,
corner_radius=(0, 0, 0.5, 0.5),
grow_x=False,
@ -82,7 +82,7 @@ class DbErrorPage(Component):
)
else:
return Text(
"Der EZ LAN Manager wird\nauf mobilen Endgeräten nur\nim Querformat unterstützt.\nBitte drehe dein Gerät.",
"Der EZGG LAN Manager wird\nauf mobilen Endgeräten nur\nim Querformat unterstützt.\nBitte drehe dein Gerät.",
align_x=0.5,
align_y=0.5,
style=TextStyle(fill=Color.from_hex("FFFFFF"), font_size=0.8)

View File

@ -2,11 +2,11 @@ from typing import Optional
from rio import Column, Component, event, Spacer
from src.ez_lan_manager import ConfigurationService, UserService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.UserEditForm import UserEditForm
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager import ConfigurationService, UserService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.UserEditForm import UserEditForm
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User
class EditProfilePage(Component):

View File

@ -1,7 +1,7 @@
from rio import Column, Component, event, TextStyle, Text, Revealer
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
FAQ: list[list[str]] = [
["Wie melde ich mich für die LAN an?",

View File

@ -4,8 +4,8 @@ from random import choices
from email_validator import validate_email, EmailNotValidError
from rio import Column, Component, event, Text, TextStyle, TextInput, TextInputChangeEvent, Button
from src.ez_lan_manager import ConfigurationService, UserService, MailingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager import ConfigurationService, UserService, MailingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
class ForgotPasswordPage(Component):
@ -31,7 +31,7 @@ class ForgotPasswordPage(Component):
await user_service.update_user(user)
await mailing_service.send_email(
subject=f"Dein neues Passwort für {lan_info.name}",
body=f"Du hast für den EZ-LAN Manager der {lan_info.name} ein neues Passwort angefragt. "
body=f"Du hast für den EZGG-LAN Manager der {lan_info.name} ein neues Passwort angefragt. "
f"Und hier ist es schon:\n\n{new_password}\n\nSolltest du kein neues Passwort angefordert haben, "
f"ignoriere diese E-Mail.\n\nLiebe Grüße\nDein {lan_info.name} - Team",
receiver=self.email_input.text.strip()

View File

@ -2,10 +2,10 @@ from typing import Optional
from rio import Column, Component, event, TextStyle, Text, Button, Row, TextInput, Spacer, TextInputChangeEvent
from src.ez_lan_manager import ConfigurationService, UserService, TicketingService, SeatingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.types.Seat import Seat
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager import ConfigurationService, UserService, TicketingService, SeatingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.User import User
class GuestsPage(Component):

View File

@ -1,7 +1,7 @@
from rio import Text, Column, TextStyle, Component, event, Link, Color
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
class ImprintPage(Component):

View File

@ -5,11 +5,11 @@ from typing import Optional, Callable
from rio import Column, Component, event, TextStyle, Text, Spacer, PointerEvent, Button, Popup, Card, Row
from src.ez_lan_manager import ConfigurationService, CateringService, SeatingService, AccountingService
from src.ez_lan_manager.components.CateringManagementOrderDisplay import CateringManagementOrderDisplay
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
from src.ez_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager import ConfigurationService, CateringService, SeatingService, AccountingService
from src.ezgg_lan_manager.components.CateringManagementOrderDisplay import CateringManagementOrderDisplay
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
from src.ezgg_lan_manager.types.Seat import Seat
logger = logging.getLogger(__name__.split(".")[-1])

View File

@ -5,11 +5,11 @@ from time import strptime
from rio import Column, Component, event, TextStyle, Text
from src.ez_lan_manager import ConfigurationService, UserService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.NewsPost import EditableNewsPost
from src.ez_lan_manager.services.NewsService import NewsService
from src.ez_lan_manager.types.News import News
from src.ezgg_lan_manager import ConfigurationService, UserService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.NewsPost import EditableNewsPost
from src.ezgg_lan_manager.services.NewsService import NewsService
from src.ezgg_lan_manager.types.News import News
logger = logging.getLogger(__name__.split(".")[-1])
@ -78,8 +78,8 @@ class ManageNewsPage(Component):
Column(
Text(
text="News Verwaltung",
fill=self.session.theme.background_color,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=2,
@ -88,8 +88,8 @@ class ManageNewsPage(Component):
),
Text(
text="Neuen News Post erstellen",
fill=self.session.theme.background_color,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.1
),
margin_top=2,
@ -106,8 +106,8 @@ class ManageNewsPage(Component):
),
Text(
text="Post erfolgreich erstellt",
fill=self.session.theme.success_color,
style=TextStyle(
fill=self.session.theme.success_color,
font_size=0.7 if self.show_success_message else 0
),
margin_top=0.1,
@ -116,8 +116,8 @@ class ManageNewsPage(Component):
),
Text(
text="Bisherige Posts",
fill=self.session.theme.background_color,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.1
),
margin_top=2,

View File

@ -2,8 +2,8 @@ import logging
from rio import Column, Component, event, TextStyle, Text, Spacer
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
logger = logging.getLogger(__name__.split(".")[-1])

View File

@ -6,14 +6,14 @@ from rio import Column, Component, event, TextStyle, Text, TextInput, ThemeConte
PointerEventListener, PointerEvent, Rectangle, CursorStyle, Color, TextInputChangeEvent, Spacer, Row, Switch, \
SwitchChangeEvent, EventHandler
from src.ez_lan_manager import ConfigurationService, UserService, AccountingService, SeatingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.NewTransactionForm import NewTransactionForm
from src.ez_lan_manager.components.UserEditForm import UserEditForm
from src.ez_lan_manager.services.AccountingService import InsufficientFundsError
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.Transaction import Transaction
from src.ez_lan_manager.types.User import User
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])
@ -104,11 +104,17 @@ class ManageUsersPage(Component):
self.accounting_section_result_success = False
return
else:
await self.session[AccountingService].add_balance(
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

View File

@ -1,8 +1,8 @@
from rio import Column, Component, event
from src.ez_lan_manager import ConfigurationService, NewsService
from src.ez_lan_manager.components.NewsPost import NewsPost
from src.ez_lan_manager.types.News import News
from src.ezgg_lan_manager import ConfigurationService, NewsService
from src.ezgg_lan_manager.components.NewsPost import NewsPost
from src.ezgg_lan_manager.types.News import News
class NewsPage(Component):

View File

@ -0,0 +1,141 @@
from rio import Column, Component, event, Text, Spacer, Row, Link
from src.ezgg_lan_manager import ConfigurationService, TicketingService
from src.ezgg_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

@ -1,7 +1,7 @@
from rio import Column, Component, event
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.NewsPost import NewsPost
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.NewsPost import NewsPost
class PlaceholderPage(Component):

View File

@ -3,9 +3,9 @@ import logging
from email_validator import validate_email, EmailNotValidError
from rio import Column, Component, event, Text, TextStyle, TextInput, TextInputChangeEvent, Button
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.ezgg_lan_manager import ConfigurationService, UserService, MailingService
from src.ezgg_lan_manager.components.AnimatedText import AnimatedText
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
MINIMUM_PASSWORD_LENGTH = 6
@ -13,7 +13,7 @@ logger = logging.getLogger(__name__.split(".")[-1])
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:
self.pw_1.is_valid = False
self.pw_2.is_valid = False
@ -21,14 +21,14 @@ class RegisterPage(Component):
self.pw_1.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:
validate_email(change_event.text, check_deliverability=False)
self.email_input.is_valid = True
except EmailNotValidError:
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
if len(current_text) > UserService.MAX_USERNAME_LENGTH:
self.user_name_input.text = current_text[:UserService.MAX_USERNAME_LENGTH]
@ -69,7 +69,7 @@ class RegisterPage(Component):
try:
new_user = await user_service.create_user(self.user_name_input.text, self.email_input.text, self.pw_1.text)
if not new_user:
raise RuntimeError("User could not be created")
logger.warning(f"UserService.create_user returned: {new_user}") # ToDo: Seems like the user is created fine, even if not returned #FixMe
except Exception as e:
logger.error(f"Unknown error during new user registration: {e}")
await self.animated_text.display_text(False, "Es ist ein unbekannter Fehler aufgetreten :(")
@ -79,7 +79,7 @@ class RegisterPage(Component):
await mailing_service.send_email(
subject="Erfolgreiche Registrierung",
body=f"Hallo {self.user_name_input.text},\n\n"
f"Du hast dich erfolgreich beim EZ-LAN Manager für {lan_info.name} {lan_info.iteration} registriert.\n\n"
f"Du hast dich erfolgreich beim EZGG-LAN Manager für {lan_info.name} {lan_info.iteration} registriert.\n\n"
f"Wenn du dich nicht registriert hast, kontaktiere bitte unser Team über unsere Homepage.\n\n"
f"Liebe Grüße\nDein {lan_info.name} - Team",
receiver=self.email_input.text
@ -100,7 +100,7 @@ class RegisterPage(Component):
margin_right=1,
margin_bottom=1,
grow_x=True,
on_change=self.on_user_name_input_change
on_lose_focus=self.on_user_name_focus_loss
)
self.email_input = TextInput(
label="E-Mail Adresse",
@ -109,7 +109,7 @@ class RegisterPage(Component):
margin_right=1,
margin_bottom=1,
grow_x=True,
on_change=self.on_email_changed
on_lose_focus=self.on_email_focus_loss
)
self.pw_1 = TextInput(
label="Passwort",
@ -119,7 +119,7 @@ class RegisterPage(Component):
margin_bottom=1,
grow_x=True,
is_secret=True,
on_change=self.on_pw_change
on_lose_focus=self.on_pw_focus_loss
)
self.pw_2 = TextInput(
label="Passwort wiederholen",
@ -129,7 +129,7 @@ class RegisterPage(Component):
margin_bottom=1,
grow_x=True,
is_secret=True,
on_change=self.on_pw_change
on_lose_focus=self.on_pw_focus_loss
)
self.submit_button = Button(
content=Text(

View File

@ -1,7 +1,7 @@
from rio import Column, Component, event, TextStyle, Text, Revealer
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
RULES: list[str] = [
"Respektvolles Verhalten: Sei höflich und respektvoll gegenüber anderen Gästen und dem Team.",

View File

@ -5,15 +5,15 @@ from typing import Optional
from rio import Text, Column, TextStyle, Component, event, PressEvent, ProgressCircle
from src.ez_lan_manager import ConfigurationService, SeatingService, TicketingService, UserService
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.SeatingPlanInfoBox import SeatingPlanInfoBox
from src.ez_lan_manager.components.SeatingPurchaseBox import SeatingPurchaseBox
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.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager import ConfigurationService, SeatingService, TicketingService, UserService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.SeatingPlan import SeatingPlan, SeatingPlanLegend
from src.ezgg_lan_manager.components.SeatingPlanInfoBox import SeatingPlanInfoBox
from src.ezgg_lan_manager.components.SeatingPurchaseBox import SeatingPurchaseBox
from src.ezgg_lan_manager.services.SeatingService import NoTicketError, SeatNotFoundError, WrongCategoryError, SeatAlreadyTakenError
from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User
logger = logging.getLogger(__name__.split(".")[-1])
@ -30,6 +30,7 @@ class SeatingPlanPage(Component):
purchase_box_loading: bool = False
purchase_box_success_msg: Optional[str] = None
purchase_box_error_msg: Optional[str] = None
seating_info_text = ""
is_booking_blocked: bool = False
@event.on_populate
@ -47,6 +48,7 @@ class SeatingPlanPage(Component):
self.is_booking_blocked = True
async def on_seat_clicked(self, seat_id: str, _: PressEvent) -> None:
self.seating_info_text = ""
self.show_info_box = True
self.show_purchase_box = False
seat = next(filter(lambda s: s.seat_id == seat_id, self.seating_info), None)
@ -62,6 +64,12 @@ class SeatingPlanPage(Component):
else:
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:
self.purchase_box_error_msg = msg
self.purchase_box_success_msg = None
@ -119,7 +127,7 @@ class SeatingPlanPage(Component):
Column(
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),
purchase_cb=self.on_purchase_clicked, override_text=self.seating_info_text),
SeatingPurchaseBox(
show=self.show_purchase_box,
seat_id=self.current_seat_id,
@ -132,7 +140,7 @@ class SeatingPlanPage(Component):
)
),
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),
Text("Sitzplan wird geladen", style=TextStyle(fill=self.session.theme.neutral_color), align_x=0.5, margin=1))
),

View File

@ -1,7 +1,7 @@
from rio import Column, Component, event, TextStyle, Text
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
class PAGENAME(Component):

View File

@ -1,7 +1,7 @@
from rio import Column, Component, event, TextStyle, Text
from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
class TournamentsPage(Component):

View File

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

View File

@ -2,9 +2,10 @@ import logging
from collections.abc import Callable
from datetime import datetime
from decimal import Decimal, ROUND_DOWN
from typing import Optional
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.types.Transaction import Transaction
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.types.Transaction import Transaction
logger = logging.getLogger(__name__.split(".")[-1])
@ -65,9 +66,11 @@ class AccountingService:
return await self._db_service.get_all_transactions_for_user(user_id)
@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.
"""
if euros is None:
return "0.00 €"
rounded_decimal = str(euros.quantize(Decimal(".01"), rounding=ROUND_DOWN))
return f"{rounded_decimal}"

View File

@ -3,11 +3,12 @@ from decimal import Decimal
from enum import Enum
from typing import Optional
from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus, CateringMenuItemsWithAmount
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
from src.ezgg_lan_manager.services.AccountingService import AccountingService
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.services.ReceiptPrintingService import ReceiptPrintingService
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus, CateringMenuItemsWithAmount
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
logger = logging.getLogger(__name__.split(".")[-1])
@ -25,10 +26,11 @@ class CateringError(Exception):
class CateringService:
def __init__(self, db_service: DatabaseService, accounting_service: AccountingService, user_service: UserService):
def __init__(self, db_service: DatabaseService, accounting_service: AccountingService, user_service: UserService, receipt_printing_service: ReceiptPrintingService) -> None:
self._db_service = db_service
self._accounting_service = accounting_service
self._user_service = user_service
self._receipt_printing_service = receipt_printing_service
self.cached_cart: dict[int, list[CateringMenuItem]] = {}
# ORDERS
@ -52,6 +54,7 @@ class CateringService:
await self._accounting_service.remove_balance(user_id, total_price, f"CATERING - {order.order_id}")
logger.info(
f"User '{order.customer.user_name}' (ID:{order.customer.user_id}) ordered from catering for {self._accounting_service.make_euro_string_from_decimal(total_price)}")
await self._receipt_printing_service.print_order(user, order)
# await self.cancel_order(order) # ToDo: Check if commented out before commit. Un-comment to auto-cancel every placed order
return order

View File

@ -7,8 +7,8 @@ import tomllib
from from_root import from_root
from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration, MailingServiceConfiguration, LanInfo, \
SeatingConfiguration, TicketInfo
from src.ezgg_lan_manager.types.ConfigurationTypes import DatabaseConfiguration, MailingServiceConfiguration, LanInfo, \
SeatingConfiguration, TicketInfo, ReceiptPrintingConfiguration
logger = logging.getLogger(__name__.split(".")[-1])
@ -71,15 +71,6 @@ class ConfigurationService:
logger.fatal("Error loading LAN Info, exiting...")
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, ...]:
try:
return tuple([TicketInfo(
@ -95,6 +86,19 @@ class ConfigurationService:
logger.fatal("Error loading seating configuration, exiting...")
sys.exit(1)
def get_receipt_printing_configuration(self) -> ReceiptPrintingConfiguration:
try:
receipt_printing_configuration = self._config["receipt_printing"]
return ReceiptPrintingConfiguration(
host=receipt_printing_configuration["host"],
port=receipt_printing_configuration["port"],
order_print_endpoint=receipt_printing_configuration["order_print_endpoint"],
password=receipt_printing_configuration["password"]
)
except KeyError:
logger.fatal("Error loading Receipt Printing Configuration, exiting...")
sys.exit(1)
@property
def APP_VERSION(self) -> str:
return self._version

View File

@ -6,15 +6,15 @@ from decimal import Decimal
import aiomysql
from src.ez_lan_manager.types.CateringOrder import CateringOrder
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
from src.ez_lan_manager.types.CateringOrder import CateringMenuItemsWithAmount, CateringOrderStatus
from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration
from src.ez_lan_manager.types.News import News
from src.ez_lan_manager.types.Seat import Seat
from src.ez_lan_manager.types.Ticket import Ticket
from src.ez_lan_manager.types.Transaction import Transaction
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
from src.ezgg_lan_manager.types.CateringOrder import CateringMenuItemsWithAmount, CateringOrderStatus
from src.ezgg_lan_manager.types.ConfigurationTypes import DatabaseConfiguration
from src.ezgg_lan_manager.types.News import News
from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager.types.Transaction import Transaction
from src.ezgg_lan_manager.types.User import User
logger = logging.getLogger(__name__.split(".")[-1])
@ -232,7 +232,6 @@ class DatabaseService:
except aiomysql.InterfaceError:
pool_init_result = await self.init_db_pool()
if not pool_init_result:
print(self._connection_pool)
raise NoDatabaseConnectionError
return await self.get_news(dt_start, dt_end)
except Exception as e:

View File

@ -3,7 +3,7 @@ from typing import Optional
from rio import UserSettings
from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class LocalData(UserSettings):

View File

@ -1,10 +1,12 @@
import logging
from decimal import Decimal
from email.message import EmailMessage
from asyncio import sleep
import aiosmtplib
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService
from src.ezgg_lan_manager.types.User import User
logger = logging.getLogger(__name__.split(".")[-1])
@ -16,6 +18,9 @@ class MailingService:
async def send_email(self, subject: str, body: str, receiver: str) -> None:
if self._configuration_service.DEV_MODE_ACTIVE:
logger.info(f"Skipped sending mail to {receiver} because demo mode is active.")
logger.info(f"Subject: {subject}")
logger.info(f"Receiver: {receiver}")
logger.info(f"Body: {body}")
await sleep(1)
return
@ -35,3 +40,15 @@ class MailingService:
)
except Exception as e:
logger.error(f"Failed to send email: {e}")
def generate_account_balance_added_mail_body(self, user: User, added_balance: Decimal, total_balance: Decimal) -> str:
return f"""
Hallo {user.user_name},
deinem Account wurden {added_balance} hinzugefügt. Dein neues Guthaben beträgt nun {total_balance} .
Wenn du zu dieser Aufladung Fragen hast, stehen wir dir in unserem Discord Server oder per Mail an {self._configuration_service.get_lan_info().organizer_mail} zur Verfügung.
Liebe Grüße
Dein {self._configuration_service.get_lan_info().name} Team
"""

View File

@ -2,8 +2,8 @@ import logging
from datetime import date
from typing import Optional
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.types.News import News
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.types.News import News
logger = logging.getLogger(__name__.split(".")[-1])

View File

@ -0,0 +1,48 @@
import logging
import requests
from src.ezgg_lan_manager.services.SeatingService import SeatingService
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder
from src.ezgg_lan_manager.types.ConfigurationTypes import ReceiptPrintingConfiguration
from src.ezgg_lan_manager.types.User import User
logger = logging.getLogger(__name__.split(".")[-1])
logging.getLogger("urllib3").setLevel(logging.FATAL) # Disable logging for urllib3
class ReceiptPrintingService:
def __init__(self, seating_service: SeatingService, config: ReceiptPrintingConfiguration, dev_mode_enabled: bool) -> None:
self._seating_service = seating_service
self._config = config
self._dev_mode_enabled = dev_mode_enabled
async def print_order(self, user: User, order: CateringOrder) -> None:
seat_id = await self._seating_service.get_user_seat(user.user_id)
if not seat_id:
seat_id = " - "
menu_items_payload = []
for item, amount in order.items.items():
menu_items_payload.append({
"menu_item_name": item.name,
"amount": amount
})
payload = {
"order_id": str(order.order_id),
"order_date": order.order_date.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
"customer_name": user.user_name,
"seat_id": seat_id,
"items": menu_items_payload
}
try:
requests.post(
f"http://{self._config.host}:{self._config.port}/{self._config.order_print_endpoint}",
json=payload,
headers={"x-password": self._config.password}
)
except Exception as e:
if self._dev_mode_enabled:
logger.info("An error occurred trying to print a receipt:", e)
return
logger.error("An error occurred trying to print a receipt:", e)

View File

@ -2,10 +2,10 @@ import logging
from typing import Optional
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.services.TicketingService import TicketingService
from src.ez_lan_manager.types.ConfigurationTypes import LanInfo, SeatingConfiguration
from src.ez_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.services.TicketingService import TicketingService
from src.ezgg_lan_manager.types.ConfigurationTypes import LanInfo, SeatingConfiguration
from src.ezgg_lan_manager.types.Seat import Seat
logger = logging.getLogger(__name__.split(".")[-1])
@ -22,8 +22,7 @@ class SeatAlreadyTakenError(Exception):
pass
class SeatingService:
def __init__(self, seating_configuration: SeatingConfiguration, lan_info: LanInfo, db_service: DatabaseService, ticketing_service: TicketingService) -> None:
self._seating_configuration = seating_configuration
def __init__(self, lan_info: LanInfo, db_service: DatabaseService, ticketing_service: TicketingService) -> None:
self._lan_info = lan_info
self._db_service = db_service
self._ticketing_service = ticketing_service

View File

@ -1,10 +1,10 @@
import logging
from typing import Optional
from src.ez_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.types.ConfigurationTypes import TicketInfo
from src.ez_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.types.ConfigurationTypes import TicketInfo
from src.ezgg_lan_manager.types.Ticket import Ticket
logger = logging.getLogger(__name__.split(".")[-1])

View File

@ -2,8 +2,8 @@ from hashlib import sha256
from typing import Union, Optional
from string import ascii_letters, digits
from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.types.User import User
class NameNotAllowedError(Exception):
@ -47,7 +47,8 @@ class UserService:
user_name = user_name.lower()
hashed_pw = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
return await self._db_service.create_user(user_name, user_mail, hashed_pw)
created_user = await self._db_service.create_user(user_name, user_mail, hashed_pw)
return created_user
async def update_user(self, user: User) -> User:
disallowed_char = self._check_for_disallowed_char(user.user_name)

View File

@ -4,8 +4,8 @@ from decimal import Decimal
from enum import StrEnum
from typing import Optional, Iterable, Self
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
from src.ezgg_lan_manager.types.User import User
CateringMenuItemsWithAmount = dict[CateringMenuItem, int]

View File

@ -48,3 +48,10 @@ class LanInfo:
@dataclass(frozen=True)
class SeatingConfiguration:
seats: dict[str, str]
@dataclass(frozen=True)
class ReceiptPrintingConfiguration:
host: str
port: int
order_print_endpoint: str
password: str

View File

@ -2,7 +2,7 @@ from dataclasses import dataclass
from datetime import date
from typing import Optional
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.types.User import User
@dataclass(frozen=True)

View File

@ -1,7 +1,7 @@
from dataclasses import dataclass
from typing import Optional
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.types.User import User
@dataclass(frozen=True)

View File

@ -2,7 +2,7 @@ from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from src.ez_lan_manager.types.User import User
from src.ezgg_lan_manager.types.User import User
@dataclass(frozen=True)

View File

@ -3,8 +3,8 @@ from datetime import datetime
from unittest.mock import MagicMock, AsyncMock
from decimal import Decimal
from src.ez_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError
from src.ez_lan_manager.types.Transaction import Transaction
from src.ezgg_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError
from src.ezgg_lan_manager.types.Transaction import Transaction
class AccountingServiceTests(unittest.IsolatedAsyncioTestCase):