From 0a2647bcf86f4ff22bda96fbf52e1fdf82ce6b33 Mon Sep 17 00:00:00 2001 From: David Rodenkirchen Date: Mon, 27 Jan 2025 09:34:23 +0100 Subject: [PATCH] add basic overview page for catering management --- .../components/CateringCartItem.py | 2 +- .../CateringManagementOrderDisplay.py | 47 ++++++++++++++ .../components/ShoppingCartAndOrders.py | 12 ++-- src/ez_lan_manager/components/UserInfoBox.py | 2 + .../pages/ManageCateringPage.py | 63 ++++++++++++++++++- src/ez_lan_manager/types/CateringOrder.py | 17 +++++ 6 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 src/ez_lan_manager/components/CateringManagementOrderDisplay.py diff --git a/src/ez_lan_manager/components/CateringCartItem.py b/src/ez_lan_manager/components/CateringCartItem.py index f14b37c..e34263f 100644 --- a/src/ez_lan_manager/components/CateringCartItem.py +++ b/src/ez_lan_manager/components/CateringCartItem.py @@ -25,6 +25,6 @@ class CateringCartItem(Component): return Row( Text(self.ellipsize_string(self.article_name), align_x=0, overflow="wrap", 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/close", size=2, color=self.session.theme.danger_color, style="plain-text", on_press=lambda: self.remove_item_cb(self.list_id)), + IconButton(icon="material/close", min_size=2, color=self.session.theme.danger_color, style="plain-text", on_press=lambda: self.remove_item_cb(self.list_id)), proportions=(19, 5, 2) ) diff --git a/src/ez_lan_manager/components/CateringManagementOrderDisplay.py b/src/ez_lan_manager/components/CateringManagementOrderDisplay.py new file mode 100644 index 0000000..162d128 --- /dev/null +++ b/src/ez_lan_manager/components/CateringManagementOrderDisplay.py @@ -0,0 +1,47 @@ +from typing import Optional + +from rio import Component, Row, Card, Column, Text, TextStyle, Spacer + +from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus +from src.ez_lan_manager.types.Seat import Seat + + +class CateringManagementOrderDisplay(Component): + order: CateringOrder + seat: Optional[Seat] + + def format_order_status(self, status: CateringOrderStatus) -> Text: + status_text = CateringOrder.translate_order_status(status) + + color = self.session.theme.warning_color + if status == CateringOrderStatus.DELAYED or status == CateringOrderStatus.CANCELED: + color = self.session.theme.danger_color + elif status == CateringOrderStatus.COMPLETED: + color = self.session.theme.success_color + + return Text(text=status_text, style=TextStyle(fill=color)) + + def build(self) -> Component: + return Card( + content=Column( + Row( + Text(f"Status: ", margin_left=0.3, margin_top=0.2), + self.format_order_status(self.order.status), + Spacer(), + Text(self.order.order_date.strftime("%d.%m. - %H:%M Uhr"), margin_right=0.3), + ), + Row( + Text(f"Gast: {self.order.customer.user_name}", margin_left=0.3), + Spacer(), + Text(f"Sitzplatz: {'-' if not self.seat else self.seat.seat_id}", margin_right=0.3), + ), + Row( + Text("Diese Bestellung wird:", margin_left=0.3), + Spacer(), + Text("Geliefert" if self.order.is_delivery else "Abgeholt", margin_right=0.3, margin_bottom=0.2), + ) + ), + color=self.session.theme.hud_color, + colorize_on_hover=True, + margin=1 + ) \ No newline at end of file diff --git a/src/ez_lan_manager/components/ShoppingCartAndOrders.py b/src/ez_lan_manager/components/ShoppingCartAndOrders.py index d4d6b73..58a860c 100644 --- a/src/ez_lan_manager/components/ShoppingCartAndOrders.py +++ b/src/ez_lan_manager/components/ShoppingCartAndOrders.py @@ -65,8 +65,9 @@ class ShoppingCartAndOrders(Component): user_id = self.session[SessionStorage].user_id cart = self.session[CateringService].get_cart(user_id) + show_popup_task = None if len(cart) < 1: - _ = create_task(self.show_popup("Warenkorb leer", True)) + show_popup_task = create_task(self.show_popup("Warenkorb leer", True)) else: items_with_amounts: CateringMenuItemsWithAmount = {} for item in cart: @@ -78,14 +79,15 @@ class ShoppingCartAndOrders(Component): await self.session[CateringService].place_order(items_with_amounts, user_id) except CateringError as catering_error: if catering_error.error_type == CateringErrorType.INCLUDES_DISABLED_ITEM: - _ = create_task(self.show_popup("Warenkorb enthält gesperrte Artikel", True)) + show_popup_task = create_task(self.show_popup("Warenkorb enthält gesperrte Artikel", True)) elif catering_error.error_type == CateringErrorType.INSUFFICIENT_FUNDS: - _ = create_task(self.show_popup("Guthaben nicht ausreichend", True)) + show_popup_task = create_task(self.show_popup("Guthaben nicht ausreichend", True)) else: - _ = create_task(self.show_popup("Unbekannter Fehler", True)) + show_popup_task = create_task(self.show_popup("Unbekannter Fehler", True)) self.session[CateringService].save_cart(self.session[SessionStorage].user_id, []) self.order_button_loading = False - _ = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False)) + if not show_popup_task: + show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False)) async def _create_order_info_modal(self, order: CateringOrder) -> None: def build_dialog_content() -> rio.Component: diff --git a/src/ez_lan_manager/components/UserInfoBox.py b/src/ez_lan_manager/components/UserInfoBox.py index a303140..0d635c8 100644 --- a/src/ez_lan_manager/components/UserInfoBox.py +++ b/src/ez_lan_manager/components/UserInfoBox.py @@ -65,6 +65,8 @@ class UserInfoBox(Component): self.session[AccountingService].add_update_hook(self.update) async def update(self) -> None: + if not self.user: + self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id) self.user_balance = await self.session[AccountingService].get_balance(self.user.user_id) self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id) self.user_seat = await self.session[SeatingService].get_user_seat(self.user.user_id) diff --git a/src/ez_lan_manager/pages/ManageCateringPage.py b/src/ez_lan_manager/pages/ManageCateringPage.py index d1e90bc..bef7c07 100644 --- a/src/ez_lan_manager/pages/ManageCateringPage.py +++ b/src/ez_lan_manager/pages/ManageCateringPage.py @@ -1,16 +1,52 @@ import logging +from dataclasses import field, dataclass +from datetime import datetime +from typing import Optional from rio import Column, Component, event, TextStyle, Text, Spacer -from src.ez_lan_manager import ConfigurationService +from src.ez_lan_manager import ConfigurationService, CateringService, SeatingService +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 logger = logging.getLogger(__name__.split(".")[-1]) +@dataclass +class CateringOrderWithSeat: + catering_order: CateringOrder + seat: Optional[Seat] + class ManageCateringPage(Component): + all_orders: list[CateringOrderWithSeat] = field(default_factory=list) + last_updated: Optional[datetime] = None + @event.on_populate async def on_populate(self) -> None: await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Catering Verwaltung") + self.all_orders = await self.populate_seating(await self.session[CateringService].get_orders()) + self.last_updated = datetime.now() + + + @event.periodic(30) + async def update_orders(self) -> None: + polled_orders = await self.session[CateringService].get_orders() + self.all_orders = await self.populate_seating(polled_orders) + self.last_updated = datetime.now() + + async def populate_seating(self, orders: list[CateringOrder]) -> list[CateringOrderWithSeat]: + result = [] + for order in orders: + seat = await self.session[SeatingService].get_user_seat(order.customer.user_id) + result.append(CateringOrderWithSeat(catering_order=order, seat=seat)) + return result + + def get_all_pending_orders(self) -> list[CateringOrderWithSeat]: + filtered_list = list(filter(lambda o: o.catering_order.status != CateringOrderStatus.COMPLETED and o.catering_order.status != CateringOrderStatus.CANCELED, self.all_orders)) + sorted_list = sorted(filtered_list, key=lambda o: o.catering_order.order_date) + return sorted_list + def build(self) -> Component: return Column( @@ -28,5 +64,30 @@ class ManageCateringPage(Component): ) ) ), + MainViewContentBox( + Column( + Text( + text="Offene Bestellungen", + style=TextStyle( + fill=self.session.theme.background_color, + font_size=1.2 + ), + margin_top=2, + margin_bottom=0.2, + align_x=0.5 + ), + Text( + text=f"Letzte Aktualisierung: {'-' if not self.last_updated else self.last_updated.strftime('%H:%M:%S')}", + style=TextStyle( + fill=self.session.theme.background_color, + font_size=0.7 + ), + margin_top=0.2, + margin_bottom=2, + align_x=0.5 + ), + *[CateringManagementOrderDisplay(v.catering_order, v.seat) for v in self.get_all_pending_orders()], + ) + ), Spacer() ) diff --git a/src/ez_lan_manager/types/CateringOrder.py b/src/ez_lan_manager/types/CateringOrder.py index 1901e21..5385c90 100644 --- a/src/ez_lan_manager/types/CateringOrder.py +++ b/src/ez_lan_manager/types/CateringOrder.py @@ -31,3 +31,20 @@ class CateringOrder: for item, amount in self.items.items(): total += (item.price * amount) return total + + @staticmethod + def translate_order_status(status: CateringOrderStatus) -> str: + if status == CateringOrderStatus.RECEIVED: + return "Eingegangen" + elif status == CateringOrderStatus.DELAYED: + return "Verzögert" + elif status == CateringOrderStatus.READY_FOR_PICKUP: + return "Abholbereit" + elif status == CateringOrderStatus.EN_ROUTE: + return "In Zustellung" + elif status == CateringOrderStatus.COMPLETED: + return "Abgeschlossen" + elif status == CateringOrderStatus.CANCELED: + return "Storniert" + else: + raise RuntimeError("Unknown CateringOrderStatus:", status)