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,
margin_left=1,
margin_right=1,
margin_top=2,
margin_top=1,
margin_bottom=1,
shadow_radius=0.5,
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("Kartoffelsalat", "", 450, 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 - Käse", "mit Margarine", 180, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Schinken/Käse", "mit Margarine", 210, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Salami", "mit Margarine", 180, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Salami/Käse", "mit Margarine", 210, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Schinken", "", 180, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Käse", "", 180, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Schinken/Käse", "", 210, CateringMenuItemCategory.SNACK)
catering_service.add_menu_item("Sandwichtoast - Salami", "", 180, 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("Nachos - Salted", "", 130, CateringMenuItemCategory.SNACK)

View File

@ -27,7 +27,6 @@ class BasePage(Component):
if self.session.window_width > 28:
return Container(
content=Column(
Row(),
Column(
Row(
Spacer(grow_x=True, grow_y=True),
@ -50,10 +49,9 @@ class BasePage(Component):
),
Spacer(grow_x=True, grow_y=False),
grow_y=False
)
),
Row(),
proportions=[4, 92, 4]
),
margin_top=4
)
),
grow_x=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, AccountingService
from src.ez_lan_manager.components.CateringCartItem import CateringCartItem
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.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
class CateringPage(Component):
show_cart = True
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)
@ -21,110 +22,244 @@ class CateringPage(Component):
async def on_user_logged_in_status_changed(self) -> None:
await self.force_refresh()
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_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None:
await self.shopping_cart_and_orders.switch()
def build(self) -> Component:
user_id = self.session[SessionStorage].user_id
catering_service = self.session[CateringService]
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
self.shopping_cart_and_orders = ShoppingCartAndOrders()
switcher_bar = SwitcherBar(
values=["cart", "orders"],
names=["Warenkorb", "Bestellungen"],
selected_value="cart",
margin_left=5,
margin_right=5,
margin_top=1,
margin_bottom=1,
color=self.session.theme.hud_color,
on_change=self.on_switcher_bar_changed
)
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(
content=Column(
# SHOPPING CART
shopping_cart,
shopping_cart_and_orders_container,
# ITEM SELECTION
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
)

View File

@ -19,16 +19,7 @@ class CateringService:
self._db_service = db_service
self._accounting_service = accounting_service
self._user_service = user_service
self.cached_cart: dict[int, list[CateringMenuItem]] = { # REMOVE
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),
]
}
self.cached_cart: dict[int, list[CateringMenuItem]] = {}
# ORDERS

View File

@ -1,5 +1,7 @@
from dataclasses import dataclass
from enum import StrEnum
from typing import Self
class CateringMenuItemCategory(StrEnum):
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!
@dataclass(frozen=False)
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)
async def clear(self) -> None: