[IMP] pos_self_order: ux improvements

- Remove the notification and add a persistent banner at the top of the
screen when the restaurant is closed.
- Change the size of the product card to make it more compact and
smaller.
- Added a little shadow to the element fixed at the top and bottom.
- Corrected the alignment of price and quantity in the product list.
- In the card, the calculated price is now below the product list.
- The "add to cart" button is now dynamic, and will be
"remove from cart" if if the requested quantity is 0.
- Make the "my orders" button visible in meal mode and in each mode.

Part-of: odoo/odoo#125941
This commit is contained in:
David Monnom (moda)
2023-07-13 19:28:01 +02:00
parent 6bc5cef302
commit 0a6cac7002
18 changed files with 70 additions and 45 deletions
+6 -5
View File
@@ -31,15 +31,16 @@ class PosQRMenuController(http.Controller):
auth="public", website=True, sitemap=True,
)
def pos_self_order_start(self, config_id=None, access_token=None, table_identifier=None):
if config_id.isnumeric():
self_order_mode = 'qr_code'
table_infos = False
pos_config_sudo = False
pos_config_access_token = False
if config_id and config_id.isnumeric():
pos_config_sudo = request.env["pos.config"].sudo().search([
("id", "=", config_id),
('access_token', '=', access_token)], limit=1)
self_order_mode = 'qr_code'
table_infos = False
pos_config_access_token = False
if pos_config_sudo and pos_config_sudo.has_active_session and pos_config_sudo.self_order_table_mode:
self_order_mode = pos_config_sudo.self_order_pay_after
pos_config_access_token = pos_config_sudo.access_token
@@ -10,11 +10,13 @@
<div t-attf-class="d-flex {{ line.qty ? 'flex-column align-items-start' : 'flex-row align-items-center' }} text-900 fw-bold fs-6">
<t t-set="lineName" t-value="getNameAndDescription(line)" />
<h3 class="mb-0 o_self_product_name" t-esc="lineName.productName" />
<span
t-if="line.qty"
class="flex-grow-1 me-3 small text-muted"
t-esc="`${line.qty} x ${selfOrder.formatMonetary(selfOrder.productByIds[line.product_id].prices)}`"
/>
<div t-if="line.qty">
<span class="text-primary fw-bolder small" t-esc="`${line.qty}x `" />
<span
class="flex-grow-1 me-3 small text-muted"
t-esc="selfOrder.formatMonetary(selfOrder.productByIds[line.product_id].prices)"
/>
</div>
<span
t-if="lineName.attributes"
class="m-0 text-muted small break-line"
@@ -8,6 +8,7 @@ export class NavBar extends Component {
static props = {
customText: { type: String, optional: true },
class: { type: String, optional: true },
shadow: { type: Boolean, optional: true },
};
static defaultProps = { class: "" };
@@ -15,4 +16,9 @@ export class NavBar extends Component {
this.router = useService("router");
this.selfOrder = useSelfOrder();
}
get navbarClasses() {
const shadowClass = this.props.shadow ? " shadow-sm " : "";
return this.props.class + shadowClass;
}
}
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.NavBar" owl="1">
<nav t-attf-class="{{props.class}} o_self_order_navbar d-flex align-items-center bg-white border-bottom">
<nav t-attf-class="{{navbarClasses}} o_self_order_navbar d-flex align-items-center bg-white border-bottom" style="z-index:100">
<button class="btn btn-link text-dark"
t-on-click="() => this.router.back()">
<i class="fa fa-fw fa-chevron-left"/>
@@ -8,9 +8,9 @@
>
<div class="d-flex flex-column w-100 py-1">
<h5 class="mb-0 text-900 fw-bold o_self_product_name" t-esc="props.product.name" />
<div class="d-flex flex-row">
<div class="d-flex flex-row align-items-center">
<span
class="me-1 text-primary fw-bolder"
class="me-1 text-primary fw-bolder small"
t-if="quantityInCart and selfOrder.ordering"
t-esc="`${quantityInCart}x`"
/>
@@ -13,4 +13,9 @@ export class LandingPage extends Component {
this.formatMonetary = formatMonetary;
this.selfOrder.currentProduct = 0;
}
showMyOrderBtn() {
const ordersNotDraft = this.selfOrder.orders.find((o) => o.access_token);
return this.selfOrder.ordering && ordersNotDraft;
}
}
@@ -21,7 +21,7 @@
class="btn btn-primary py-3 my-2">
View Menu
</a>
<t t-if="selfOrder.ordering and selfOrder.self_order_mode === 'each'">
<t t-if="showMyOrderBtn()">
<hr class="bg-500"/>
<a
type="button"
@@ -3,7 +3,7 @@
<t t-name="pos_self_order.OrderCart" owl="1">
<div class="d-flex flex-column dvh-100">
<t t-set="order" t-value="selfOrder.currentOrder"/>
<NavBar customText="'Cart'" />
<NavBar customText="'Cart'" shadow="true" />
<main class="h-100 bg-100 overflow-auto">
<Lines order="order" />
<div class="px-3 pt-4 pb-3 bg-white">
@@ -11,11 +11,13 @@
</div>
</main>
<div class="d-flex flex-column align-items-center w-100 p-3 bg-white shadow-lg">
<button t-on-click="processOrder" t-attf-class="{{ !order.isChange() ? 'disabled' : '' }} o_self_order_main_button btn btn-primary w-100 px-2 py-3 mt-2 rounded">
<t t-esc="buttonToShow" />
<t t-if="!order.isChange()">
already sent
<button t-on-click="processOrder" t-attf-class="o_self_order_main_button btn btn-primary w-100 px-2 py-3 mt-2 rounded">
<span t-if="orderToPay">Pay</span>
<span t-else="">Order</span>
<t t-if="!selfOrder.priceLoading">
<t t-esc="` | ${selfOrder.formatMonetary(order.amount_total)}`"/>
</t>
</button>
</div>
<PopupTable t-if="this.state.selectTable" selectTable.bind="selectTable" />
</div>
@@ -6,7 +6,7 @@ import { ProductCard } from "@pos_self_order/components/product_card/product_car
import { Lines } from "@pos_self_order/components/lines/lines";
import { PriceDetails } from "@pos_self_order/components/price_details/price_details";
import { NavBar } from "@pos_self_order/components/navbar/navbar";
import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks";
export class OrdersHistory extends Component {
static template = "pos_self_order.OrdersHistory";
@@ -15,6 +15,7 @@ export class OrdersHistory extends Component {
setup() {
this.selfOrder = useSelfOrder();
this.router = useService("router");
this.state = useState({
loadingProgress: true,
});
@@ -35,10 +36,6 @@ export class OrdersHistory extends Component {
if (order.state === "draft") {
this.selfOrder.editedOrder = order;
this.router.navigate("productList");
} else {
this.selfOrder.notification.add(_t("You cannot edit an posted order!"), {
type: "danger",
});
}
}
}
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.OrdersHistory" owl="1">
<NavBar customText="'Orders'" />
<NavBar customText="'Orders'" shadow="true" />
<div class="overflow-auto dvh-100 pb-5">
<t t-if="state.loadingProgress">
<div class="d-flex align-items-center h-100 justify-content-center">
@@ -11,13 +11,13 @@
</div>
</t>
<div t-else="">
<t t-foreach="orders" t-as="order" t-key="order.pos_reference">
<t t-foreach="orders" t-as="order" t-key="order.access_token">
<div class="o_so_order d-flex flex-column">
<div class="o_so_order_header p-3 bg-white" t-on-click="() => this.editOrder(order)">
<div class="d-flex align-items-center justify-content-between">
<h6 class="m-0"
t-esc="order.pos_reference"/>
<span class="badge p-2 text-bg-primary"
<span class="badge p-2 text-bg-primary text-capitalize"
t-esc="order.state"/>
</div>
<p class="small m-0 fst-italic text-muted"
@@ -5,7 +5,7 @@
<div class="d-flex flex-column dvh-100">
<t t-set="categories" t-value="Array.from(selfOrder.categoryList)" />
<header class="bg-white shadow-sm">
<NavBar class="shouldNavbarBeShown() ? '' : 'o_self_order_navbar_hide' " />
<NavBar class="shouldNavbarBeShown() ? '' : 'o_self_order_navbar_hide' " shadow="false" />
<div class="o_self_order_searchbar_filter d-flex justify-content-start align-items-center p-0 m-0 border-bottom">
<div t-if="search.isFocused" class="input-group d-flex align-items-center" >
<input type="text"
@@ -138,6 +138,11 @@ export class ProductMainView extends Component {
// If a command line does not have a quantity greater than 0, we consider it deleted
await this.selfOrder.getPricesFromServer();
this.selfOrder.currentOrder.lines = lines.filter((o) => o.qty > 0);
this.router.back();
if (this.selfOrder.currentOrder.lines.length === 0) {
this.router.navigate("productList");
} else {
this.router.back();
}
}
}
@@ -2,7 +2,7 @@
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.ProductMainView" owl="1">
<div class="d-flex flex-column dvh-100">
<NavBar />
<NavBar shadow="true" />
<div class="bg-white d-flex flex-column pt-3 overflow-auto h-100">
<div class="px-3">
<img t-if="props.product.has_image"
@@ -22,7 +22,7 @@
class="o_self_order_main_desc px-3 pb-3 text-muted"
t-esc="props.product.description_sale"
/>
<div class="o_self_order_main_options px-3 py-4 border-bottom bg-100">
<div class="o_self_order_main_options px-3 py-4 border-bottom bg-100" t-if="selfOrder.ordering" >
<t t-if="props.product.attributes"
t-foreach="props.product.attributes"
t-as="attribute"
@@ -8,10 +8,12 @@ body {
position: fixed;
box-sizing: border-box;
}
header {
z-index: 1;
}
.dvh-100{
.dvh-100 {
// we specify the 100vh as a fallback in case the browser does not
// know dvh
height: 100vh !important;
@@ -22,3 +24,9 @@ header {
height: 1rem;
width: 1rem;
}
.o_self_closed_notification {
background-color: var(--gray-dark);
color: white;
padding: 5px;
}
@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.SelfOrderRoot" owl="1">
<div t-if="!selfOrder.ordering" class="o_self_closed_notification w-100 text-center" style="z-index:999">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
The restaurant is currently closed
</div>
<Router pos_config_id="selfOrder.pos_config_id">
<t t-set-slot="default" route="`/menu/${selfOrder.pos_config_id}`">
<LandingPage />
@@ -46,14 +46,7 @@ export class SelfOrder {
effect((state) => this.saveOrderToLocalStorage(state.orders), [this]);
}
if (!this.has_active_session || this.self_order_mode === "qr_code") {
this.closeNotification = this.notification.add(
_t(
"The restaurant is closed. You can browse the menu, but ordering is not available."
),
{ type: "warning" }
);
} else {
if (this.has_active_session && this.self_order_mode !== "qr_code") {
this.ordering = true;
}
@@ -137,7 +130,7 @@ export class SelfOrder {
});
this.editedOrder.access_token = order.access_token;
this.updateOrdersFromServer([order], [this.access_token]);
this.updateOrdersFromServer([order], [order.access_token]);
this.editedOrder.computelastChangesSent();
if (this.self_order_mode === "each") {
@@ -8,10 +8,7 @@ registry.category("web_tour.tours").add("self_order_after_each_cart_tour", {
steps: [
// Verify if the self is open and My Orders is not displayed because we are in "meal" mode
PosSelf.check.isNotNotification(),
PosSelf.check.isPrimaryBtn("My Orders"),
PosSelf.action.clickPrimaryBtn("My Orders"),
PosSelf.check.isNoOrderInHistory(),
PosSelf.action.clickBack(),
PosSelf.check.isNotPrimaryBtn("My Orders"),
PosSelf.action.clickPrimaryBtn("View Menu"),
// Add some products
@@ -90,5 +90,10 @@ registry.category("web_tour.tours").add("self_order_after_meal_cart_tour", {
PosSelf.check.isOrderline("Desk Organizer", "5.87", "dav", "S, Custom"),
PosSelf.check.isOrderline("Desk Organizer", "11.74", "okkk", "L, Custom"),
PosSelf.action.clickPrimaryBtn("Order"),
PosSelf.action.clickBack(),
PosSelf.check.isPrimaryBtn("My Orders"),
PosSelf.action.clickPrimaryBtn("My Orders"),
PosSelf.action.clickBack(),
],
});