From f06329f7278ea342937b46f6a3b5a045ed571624 Mon Sep 17 00:00:00 2001 From: David Rodenkirchen Date: Sat, 2 May 2026 13:00:57 +0200 Subject: [PATCH] Improve Catering Management UI --- VERSION | 2 +- .../CateringManagementOrderDisplay.py | 122 ++++++++++-------- .../components/StatusChangePopup.py | 92 +++++++++++++ .../pages/ManageCateringPage.py | 1 + 4 files changed, 162 insertions(+), 55 deletions(-) create mode 100644 src/ezgg_lan_manager/components/StatusChangePopup.py diff --git a/VERSION b/VERSION index 79a2734..5d4294b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.0 \ No newline at end of file +0.5.1 \ No newline at end of file diff --git a/src/ezgg_lan_manager/components/CateringManagementOrderDisplay.py b/src/ezgg_lan_manager/components/CateringManagementOrderDisplay.py index b21c378..3f44dcb 100644 --- a/src/ezgg_lan_manager/components/CateringManagementOrderDisplay.py +++ b/src/ezgg_lan_manager/components/CateringManagementOrderDisplay.py @@ -1,29 +1,30 @@ +import logging +from asyncio import create_task, sleep from functools import partial from typing import Optional, Callable -from rio import Component, Row, Card, Column, Text, TextStyle, Spacer, PointerEventListener, Button +from rio import Component, Row, Card, Column, Text, TextStyle, Spacer, PointerEventListener, Button, Rectangle, Popup, Icon, Color +from src.ezgg_lan_manager import ReceiptPrintingService +from src.ezgg_lan_manager.components.StatusChangePopup import StatusChangePopup 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 - clicked_cb: Callable - def build(self) -> Component: - return Button( - content=Text( - CateringOrder.translate_order_status(self.status) - ), - shape="rectangle", - on_press=partial(self.clicked_cb, self.status) - ) +logger = logging.getLogger(__name__.split(".")[-1]) class CateringManagementOrderDisplay(Component): order: CateringOrder seat: Optional[Seat] clicked_cb: Callable + status_change_popup_open: bool = False + + def reprint_order(self) -> None: + create_task(self.session[ReceiptPrintingService].print_order(self.order.customer, self.order)) + + def open_status_change_popup(self) -> None: + self.status_change_popup_open = True def format_order_status(self, status: CateringOrderStatus) -> Text: status_text = CateringOrder.translate_order_status(status) @@ -36,14 +37,12 @@ class CateringManagementOrderDisplay(Component): return Text(text=status_text, style=TextStyle(fill=color)) - async def status_button_clicked(self, new_status: CateringOrderStatus) -> None: - if self.order.status == CateringOrderStatus.CANCELED: - return + async def change_status(self, new_status: CateringOrderStatus) -> Optional[str]: + await sleep(1) - if new_status == CateringOrderStatus.CANCELED: - # ToDo: Hier sollten wir nochmal nachfragen ob der Bediener sich wirklich sicher ist, - # und anwarnen das eine stornierte Bestellung nicht ent-storniert werden kann. - pass + logger.debug(f"Status of order with ID {self.order.order_id} changing from {self.order.status} to {new_status}") + if self.order.status == CateringOrderStatus.CANCELED: # Can not un-cancel + return "Stornierte Bestellungen können nicht angepasst werden" if self.order.status != new_status: if new_status == CateringOrderStatus.CANCELED: @@ -61,43 +60,58 @@ class CateringManagementOrderDisplay(Component): is_delivery=self.order.is_delivery ) + self.status_change_popup_open = False + + def build(self) -> Component: - return PointerEventListener( - content=Card( - content=Column( - Row( - Text(f"ID: {self.order.order_id}", margin_left=0.3, margin_top=0.2, justify="center", style=TextStyle(font_size=1.2)), - ), - 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, margin_bottom=0.5), - Spacer(), - Text("Geliefert" if self.order.is_delivery else "Abgeholt", margin_right=0.3, margin_bottom=0.5), - ), - Row( - CateringManagementOrderDisplayStatusButton(CateringOrderStatus.RECEIVED, self.status_button_clicked), - CateringManagementOrderDisplayStatusButton(CateringOrderStatus.CANCELED, self.status_button_clicked), - CateringManagementOrderDisplayStatusButton(CateringOrderStatus.EN_ROUTE, self.status_button_clicked) - ), - Row( - CateringManagementOrderDisplayStatusButton(CateringOrderStatus.READY_FOR_PICKUP, self.status_button_clicked), - CateringManagementOrderDisplayStatusButton(CateringOrderStatus.COMPLETED, self.status_button_clicked), - CateringManagementOrderDisplayStatusButton(CateringOrderStatus.DELAYED, self.status_button_clicked), - ) + card = Card( + content=Column( + Row( + Text(f"ID: {self.order.order_id}", margin_left=0.3, margin_top=0.2, justify="center", style=TextStyle(font_size=1.2)), ), - color=self.session.theme.hud_color, - colorize_on_hover=True, - margin=1 + 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, margin_bottom=0.5), + Spacer(), + Text("Geliefert" if self.order.is_delivery else "Abgeholt", margin_right=0.3, margin_bottom=0.5), + ), + Row( + Rectangle( + content=Button( + content=Text("Beleg drucken", justify="left"), + shape="rectangle", + on_press=self.reprint_order + ), + stroke_width=0.1 + ), + Rectangle( + content=Button( + content=Text("Status ändern", justify="right"), + shape="rectangle", + on_press=self.open_status_change_popup + ), + stroke_width=0.1 + ), + ) ), + color=self.session.theme.hud_color, + colorize_on_hover=True, + margin=1 + ) + + status_change_popup = StatusChangePopup(card, self.status_change_popup_open, self.change_status) + + return PointerEventListener( + content=status_change_popup, on_press=partial(self.clicked_cb, self.order) ) \ No newline at end of file diff --git a/src/ezgg_lan_manager/components/StatusChangePopup.py b/src/ezgg_lan_manager/components/StatusChangePopup.py new file mode 100644 index 0000000..c00adbe --- /dev/null +++ b/src/ezgg_lan_manager/components/StatusChangePopup.py @@ -0,0 +1,92 @@ +from functools import partial +from typing import Callable, Optional + +from rio import Column, Row, Text, Button, Component, Icon, Popup, Rectangle, Color, Tooltip, PointerEventListener, PointerEvent, ProgressCircle + +from src.ezgg_lan_manager.types.CateringOrder import CateringOrderStatus, CateringOrder + +ICONS_BY_STATUS = { + CateringOrderStatus.RECEIVED: "material/move_to_inbox", + CateringOrderStatus.DELAYED: "material/hourglass_top", + CateringOrderStatus.READY_FOR_PICKUP: "material/takeout_dining", + CateringOrderStatus.EN_ROUTE: "material/local_shipping", + CateringOrderStatus.COMPLETED: "material/check_circle", + CateringOrderStatus.CANCELED: "material/cancel", +} + + +class StatusChangeButton(Component): + status: CateringOrderStatus + clicked_cb: Callable + + def build(self) -> Component: + return Tooltip( + anchor=PointerEventListener( + content=Rectangle( + fill=Color.TRANSPARENT, + content=Column( + Icon( + icon=ICONS_BY_STATUS[self.status] + ) + ), + stroke_width=0.1, + stroke_color=Color.TRANSPARENT, + hover_stroke_width=0.1, + hover_stroke_color=Color.BLACK + ), + on_press=partial(self.clicked_cb, self.status) + ), + tip=Text(text=CateringOrder.translate_order_status(self.status)), + position="top" + ) + + +class StatusChangePopup(Component): + anchor: Component + popup_open: bool + status_should_change_cb: Callable + response: Optional[str] = None + is_loading: bool = False + + async def handle_button_clicked(self, status: CateringOrderStatus, _: PointerEvent) -> None: + self.is_loading = True + self.response = await self.status_should_change_cb(status) + self.is_loading = False + + def close(self) -> None: + self.popup_open = False + + def build(self) -> Component: + if self.is_loading: + content = Row( + ProgressCircle(margin=1) + ) + elif self.response: + content = Row( + Text(text=self.response, justify="center", overflow="wrap", margin=1) + ) + else: + content = Row( + StatusChangeButton(CateringOrderStatus.RECEIVED, self.handle_button_clicked), + StatusChangeButton(CateringOrderStatus.DELAYED, self.handle_button_clicked), + StatusChangeButton(CateringOrderStatus.READY_FOR_PICKUP, self.handle_button_clicked), + StatusChangeButton(CateringOrderStatus.EN_ROUTE, self.handle_button_clicked), + StatusChangeButton(CateringOrderStatus.COMPLETED, self.handle_button_clicked), + StatusChangeButton(CateringOrderStatus.CANCELED, self.handle_button_clicked), + spacing=0.5, + margin=0.5 + ) + return Popup( + anchor=self.anchor, + content=Rectangle( + content=Column( + content, + Button(content=Text(text="Abbrechen", justify="center", fill=self.session.theme.secondary_color), shape="rectangle", style="colored-text", on_press=self.close), + proportions=[2.5, 1] + ), + fill=self.session.theme.hud_color, + min_width=34, + min_height=8.3 + ), + is_open=self.popup_open + ) \ No newline at end of file diff --git a/src/ezgg_lan_manager/pages/ManageCateringPage.py b/src/ezgg_lan_manager/pages/ManageCateringPage.py index 0f078e7..0d83695 100644 --- a/src/ezgg_lan_manager/pages/ManageCateringPage.py +++ b/src/ezgg_lan_manager/pages/ManageCateringPage.py @@ -106,6 +106,7 @@ class ManageCateringPage(Component): return sorted_list async def order_clicked(self, order: CateringOrder, _: PointerEvent) -> None: + await self.update_orders() self.order_popup_order = order self.order_popup_open = True