refactor catering, add orders view

This commit is contained in:
David Rodenkirchen 2024-08-28 14:20:41 +02:00
parent b00a819325
commit 90a344e460
10 changed files with 472 additions and 120 deletions

View File

@ -0,0 +1,40 @@
from datetime import datetime
from typing import Callable
import rio
from rio import Component, Row, Text, IconButton, TextStyle, Color
from src.ez_lan_manager import AccountingService
from src.ez_lan_manager.types.CateringOrder import CateringOrderStatus
MAX_LEN = 24
class CateringOrderItem(Component):
order_id: int
order_datetime: datetime
order_status: CateringOrderStatus
def get_display_text_and_color_for_order_status(self, order_status: CateringOrderStatus) -> tuple[str, Color]:
match order_status:
case CateringOrderStatus.RECEIVED:
return "In Bearbeitung", self.session.theme.success_color
case CateringOrderStatus.DELAYED:
return "Verspätet", Color.from_hex("eed202")
case CateringOrderStatus.READY_FOR_PICKUP:
return "Abholbereit", self.session.theme.success_color
case CateringOrderStatus.EN_ROUTE:
return "Unterwegs", self.session.theme.success_color
case CateringOrderStatus.COMPLETED:
return "Abgeschlossen", self.session.theme.success_color
case CateringOrderStatus.CANCELED:
return "Storniert", self.session.theme.danger_color
case _:
return "Unbekannt(wtf?)", self.session.theme.danger_color
def build(self) -> rio.Component:
order_status, color = self.get_display_text_and_color_for_order_status(self.order_status)
return Row(
Text(f"ID: {str(self.order_id):0>6}", align_x=0, wrap=True, min_width=10, style=TextStyle(fill=self.session.theme.background_color, font_size=0.9), margin_right=1),
Text(order_status, wrap=True, min_width=10, style=TextStyle(fill=color, font_size=0.9), margin_right=1),
Text(self.order_datetime.strftime("%d.%m. %H:%M"), style=TextStyle(fill=self.session.theme.background_color, font_size=0.9), align_x=1)
)

View File

@ -0,0 +1,70 @@
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
MAX_LEN = 24
class CateringSelectionItem(Component):
article_name: str
article_price: int
article_id: int
on_add_callback: Callable
is_sensitive: bool
additional_info: str
is_grey: bool
@staticmethod
def split_article_name(article_name: str) -> tuple[str, str]:
if len(article_name) <= MAX_LEN:
return article_name, ""
top, bottom = "", ""
words = article_name.split(" ")
last_word_added = ""
while len(top) <= MAX_LEN:
w = words.pop(0)
top += f" {w}"
last_word_added = w
top = top.replace(last_word_added, "")
bottom = f"{last_word_added} " + " ".join(words)
return top.strip(), bottom.strip()
def build(self) -> rio.Component:
article_name_top, article_name_bottom = self.split_article_name(self.article_name)
return Card(
content=Column(
Row(
Text(article_name_top, align_x=0, wrap=True, min_width=19, style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)),
Text(AccountingService.make_euro_string_from_int(self.article_price), style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)),
IconButton(
icon="material/add",
size=2,
color=self.session.theme.success_color,
style="plain",
on_press=lambda: self.on_add_callback(self.article_id),
is_sensitive=self.is_sensitive
),
proportions=(19, 5, 2),
margin_bottom=0
),
Spacer() if not article_name_bottom else Text(article_name_bottom, align_x=0, wrap=True, min_width=19, style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)),
Row(
Text(
self.additional_info,
align_x=0,
wrap=True,
min_width=19,
style=TextStyle(fill=self.session.theme.background_color, font_size=0.6)
),
margin_top=0
),
margin_bottom=0.5,
),
color=Color.from_hex("d3d3d3") if self.is_grey else self.session.theme.primary_color
)

View File

@ -16,7 +16,7 @@ class MainViewContentBox(Component):
fill=self.session.theme.primary_color, fill=self.session.theme.primary_color,
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_top=2, margin_top=1,
margin_bottom=1, margin_bottom=1,
shadow_radius=0.5, shadow_radius=0.5,
shadow_color=self.session.theme.hud_color, shadow_color=self.session.theme.hud_color,

View File

@ -0,0 +1,116 @@
import rio
from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer
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
from src.ez_lan_manager.types.CateringOrder import CateringOrder
from src.ez_lan_manager.types.SessionStorage import SessionStorage
class ShoppingCartAndOrders(Component):
show_cart: bool = True
async def switch(self) -> None:
self.show_cart = not self.show_cart
async def on_remove_item(self, list_id: int) -> None:
catering_service = self.session[CateringService]
user_id = self.session[SessionStorage].user_id
cart = catering_service.get_cart(user_id)
try:
cart.pop(list_id)
except IndexError:
return
catering_service.save_cart(user_id, cart)
await self.force_refresh()
async def on_empty_cart_pressed(self) -> None:
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
await self.force_refresh()
async def on_add_item(self, article_id: int) -> None:
catering_service = self.session[CateringService]
user_id = self.session[SessionStorage].user_id
if not user_id:
return
cart = catering_service.get_cart(user_id)
cart.append(catering_service.get_menu_item_by_id(article_id))
catering_service.save_cart(user_id, cart)
await self.force_refresh()
def build(self) -> rio.Component:
user_id = self.session[SessionStorage].user_id
catering_service = self.session[CateringService]
if self.show_cart:
cart = catering_service.get_cart(user_id)
cart_container = ScrollContainer(
content=Column(
*[CateringCartItem(
article_name=cart_item.name,
article_price=cart_item.price,
article_id=cart_item.item_id,
remove_item_cb=self.on_remove_item,
list_id=idx
) for idx, cart_item in enumerate(cart)],
Spacer(grow_y=True)
),
min_height=8,
min_width=33,
margin=1
)
return Column(
cart_container,
Row(
Text(
text=f"Preis: {AccountingService.make_euro_string_from_int(sum(cart_item.price for cart_item in cart))}",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.8
),
margin=1
),
Button(
content=Text(
"Warenkorb leeren",
style=TextStyle(fill=self.session.theme.danger_color, font_size=0.9),
align_x=0.2
),
margin=1,
margin_left=0,
shape="rectangle",
style="major",
color="primary",
on_press=self.on_empty_cart_pressed
),
Button(
content=Text(
"Bestellen",
style=TextStyle(fill=self.session.theme.success_color, font_size=0.9),
align_x=0.2
),
margin=1,
margin_left=0,
shape="rectangle",
style="major",
color="primary"
)
)
)
else:
orders = catering_service.get_orders_for_user(user_id)
orders_container = ScrollContainer(
content=Column(
*[CateringOrderItem(
order_id=order_item.order_id,
order_datetime=order_item.order_date,
order_status=order_item.status,
) for order_item in orders],
Spacer(grow_y=True)
),
min_height=8,
min_width=33,
margin=1
)
return Column(orders_container)

View File

@ -75,11 +75,11 @@ if __name__ == "__main__":
catering_service.add_menu_item("Nudelsalat mit Bockwurst", "", 600, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Nudelsalat mit Bockwurst", "", 600, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Kartoffelsalat", "", 450, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Kartoffelsalat", "", 450, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Kartoffelsalat mit Bockwurst", "", 600, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Kartoffelsalat mit Bockwurst", "", 600, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Schinken", "mit Margarine", 180, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Sandwichtoast - Schinken", "", 180, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Käse", "mit Margarine", 180, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Sandwichtoast - Käse", "", 180, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Schinken/Käse", "mit Margarine", 210, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Sandwichtoast - Schinken/Käse", "", 210, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Salami", "mit Margarine", 180, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Sandwichtoast - Salami", "", 180, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Salami/Käse", "mit Margarine", 210, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Sandwichtoast - Salami/Käse", "", 210, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Chips - Western Style", "", 130, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Chips - Western Style", "", 130, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Nachos - Salted", "", 130, CateringMenuItemCategory.SNACK) catering_service.add_menu_item("Nachos - Salted", "", 130, CateringMenuItemCategory.SNACK)

View File

@ -27,7 +27,6 @@ class BasePage(Component):
if self.session.window_width > 28: if self.session.window_width > 28:
return Container( return Container(
content=Column( content=Column(
Row(),
Column( Column(
Row( Row(
Spacer(grow_x=True, grow_y=True), Spacer(grow_x=True, grow_y=True),
@ -50,10 +49,9 @@ class BasePage(Component):
), ),
Spacer(grow_x=True, grow_y=False), Spacer(grow_x=True, grow_y=False),
grow_y=False grow_y=False
) ),
), margin_top=4
Row(), )
proportions=[4, 92, 4]
), ),
grow_x=True, grow_x=True,
grow_y=True grow_y=True

View File

@ -1,16 +1,17 @@
from typing import Optional from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, SwitcherBar, SwitcherBarChangeEvent
from rio import Column, Component, event, TextStyle, Text, ScrollContainer, Row, Button, Spacer, IconButton from src.ez_lan_manager import ConfigurationService, CateringService
from src.ez_lan_manager.components.CateringSelectionItem import CateringSelectionItem
from src.ez_lan_manager import ConfigurationService, CateringService, AccountingService
from src.ez_lan_manager.components.CateringCartItem import CateringCartItem
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders
from src.ez_lan_manager.pages import BasePage from src.ez_lan_manager.pages import BasePage
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItemCategory
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
class CateringPage(Component): class CateringPage(Component):
show_cart = True
def __post_init__(self) -> None: def __post_init__(self) -> None:
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(self.__class__.__name__, self.on_user_logged_in_status_changed) self.session[SessionStorage].subscribe_to_logged_in_or_out_event(self.__class__.__name__, self.on_user_logged_in_status_changed)
@ -21,110 +22,244 @@ class CateringPage(Component):
async def on_user_logged_in_status_changed(self) -> None: async def on_user_logged_in_status_changed(self) -> None:
await self.force_refresh() await self.force_refresh()
async def on_remove_item(self, list_id: int) -> None: async def on_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None:
catering_service = self.session[CateringService] await self.shopping_cart_and_orders.switch()
user_id = self.session[SessionStorage].user_id
cart = catering_service.get_cart(user_id)
try:
cart.pop(list_id)
except IndexError:
return
catering_service.save_cart(user_id, cart)
await self.force_refresh()
async def on_empty_cart_pressed(self) -> None:
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
await self.force_refresh()
def build(self) -> Component: def build(self) -> Component:
user_id = self.session[SessionStorage].user_id user_id = self.session[SessionStorage].user_id
catering_service = self.session[CateringService] catering_service = self.session[CateringService]
cart = catering_service.get_cart(user_id) self.shopping_cart_and_orders = ShoppingCartAndOrders()
cart_container = ScrollContainer( switcher_bar = SwitcherBar(
content=Column( values=["cart", "orders"],
*[CateringCartItem( names=["Warenkorb", "Bestellungen"],
article_name=cart_item.name, selected_value="cart",
article_price=cart_item.price, margin_left=5,
article_id=cart_item.item_id, margin_right=5,
remove_item_cb=self.on_remove_item, margin_top=1,
list_id=idx margin_bottom=1,
) for idx, cart_item in enumerate(cart)], color=self.session.theme.hud_color,
Spacer(grow_y=True) on_change=self.on_switcher_bar_changed
),
min_height=8,
min_width=33,
margin=1
) )
shopping_cart = MainViewContentBox(
Column(
Text(
text="Catering",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=2,
margin_bottom=0,
align_x=0.5
),
Text(
text="Warenkorb",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.8
),
margin_top=0.2,
margin_bottom=0,
align_x=0.5
),
cart_container,
Row(
Text(
text=f"Preis: {AccountingService.make_euro_string_from_int(sum(cart_item.price for cart_item in cart))}",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.8
),
margin=1
),
Button(
content=Text(
"Warenkorb leeren",
style=TextStyle(fill=self.session.theme.danger_color, font_size=0.9),
align_x=0.2
),
margin=1,
margin_left=0,
shape="rectangle",
style="major",
color="primary",
on_press=self.on_empty_cart_pressed
),
Button(
content=Text(
"Bestellen",
style=TextStyle(fill=self.session.theme.success_color, font_size=0.9),
align_x=0.2
),
margin=1,
margin_left=0,
shape="rectangle",
style="major",
color="primary"
),
)
)
) if user_id else Spacer()
shopping_cart_and_orders_container = MainViewContentBox(
Column(
Text(
text="Catering",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=2,
margin_bottom=0,
align_x=0.5
),
switcher_bar,
self.shopping_cart_and_orders
)
) if user_id else Spacer()
return BasePage( return BasePage(
content=Column( content=Column(
# SHOPPING CART # SHOPPING CART
shopping_cart, shopping_cart_and_orders_container,
# ITEM SELECTION # ITEM SELECTION
MainViewContentBox( MainViewContentBox(
Revealer(
header="Snacks",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.SNACK))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Frühstück",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BREAKFAST))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Hauptspeisen",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.MAIN_COURSE))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Desserts",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.DESSERT))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Wasser & Softdrinks",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_NON_ALCOHOLIC))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Alkoholische Getränke",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_ALCOHOLIC))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Cocktails & Longdrinks",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_COCKTAIL))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Shots",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_SHOT))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Sonstiges",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.NON_FOOD))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
), ),
align_y=0 align_y=0
) )

View File

@ -19,16 +19,7 @@ class CateringService:
self._db_service = db_service self._db_service = db_service
self._accounting_service = accounting_service self._accounting_service = accounting_service
self._user_service = user_service self._user_service = user_service
self.cached_cart: dict[int, list[CateringMenuItem]] = { # REMOVE self.cached_cart: dict[int, list[CateringMenuItem]] = {}
27: [
CateringMenuItem(1, "Bockwurst", 150, CateringMenuItemCategory.SNACK),
CateringMenuItem(2, "Pils", 120, CateringMenuItemCategory.SNACK),
CateringMenuItem(3, "Pfezzi", 200, CateringMenuItemCategory.SNACK),
CateringMenuItem(3, "Pfezzi", 200, CateringMenuItemCategory.SNACK),
CateringMenuItem(4, "Pizza", 1150, CateringMenuItemCategory.MAIN_COURSE),
CateringMenuItem(5, "Zigaretten", 800, CateringMenuItemCategory.NON_FOOD),
]
}
# ORDERS # ORDERS

View File

@ -1,5 +1,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from enum import StrEnum from enum import StrEnum
from typing import Self
class CateringMenuItemCategory(StrEnum): class CateringMenuItemCategory(StrEnum):
MAIN_COURSE = "MAIN_COURSE" MAIN_COURSE = "MAIN_COURSE"

View File

@ -7,7 +7,7 @@ from typing import Optional
# Note for ToDo: rio.UserSettings are saved LOCALLY, do not just read a user_id here! # Note for ToDo: rio.UserSettings are saved LOCALLY, do not just read a user_id here!
@dataclass(frozen=False) @dataclass(frozen=False)
class SessionStorage: class SessionStorage:
_user_id: Optional[int] = None # DEBUG: Put user ID here to skip login _user_id: Optional[int] = 30 # DEBUG: Put user ID here to skip login
_notification_callbacks: dict[str, Callable] = field(default_factory=dict) _notification_callbacks: dict[str, Callable] = field(default_factory=dict)
async def clear(self) -> None: async def clear(self) -> None: