[IMP] pos_self_order: ux improvements

Improvement of several aspects of the self-order UX:
- Changed the sentence indicating that the self-order is menu only.
- Added a shadow to the header.
- Reduced the size of the product image in the product list.
- Reduced the size of the product title in the product list.
- Reduced the size of the product image when you are on the page to add
it to the shopping basket.
- Added a shadow above the sticky area on a product's main page.
- The quantity of a product already added to the basket has been moved
from next to the title to next to the price.
- The review button now floats above the product list and no longer has
a background colour.
- In the basket, taxes and the total are now displayed after
orderlines and no longer sticky at the bottom.
- When the quantity of a product in the cart is changed to 0, the button
is now called "remove from cart".

Part-of: odoo/odoo#125743
This commit is contained in:
David Monnom (moda)
2023-06-22 18:29:43 +02:00
parent 99ad5a9410
commit 004affffda
11 changed files with 52 additions and 45 deletions
@@ -9,7 +9,7 @@
<div class="d-flex w-100 py-1 justify-content-between">
<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" t-esc="lineName.productName" />
<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"
@@ -7,10 +7,10 @@
}
.o_self_order_item_card_image {
width: 110px !important;
min-width: 110px;
min-height: 110px;
height: 110px !important;
width: 90px !important;
min-width: 90px;
min-height: 90px;
height: 90px !important;
& img {
object-fit: cover;
@@ -7,18 +7,18 @@
t-ref="currentProductCard"
>
<div class="d-flex flex-column w-100 py-1">
<div class="d-flex flex-row align-items-center text-900 fw-bold fs-6">
<h5 class="mb-0 text-900 fw-bold o_self_product_name" t-esc="props.product.name" />
<div class="d-flex flex-row">
<span
class="d-flex align-items-center me-1 text-primary fw-bolder"
class="me-1 text-primary fw-bolder"
t-if="quantityInCart and selfOrder.table"
t-esc="`${quantityInCart}x`"
/>
<h3 class="mb-0" t-esc="props.product.name" />
<span class="card-text small" t-esc="getTotalPriceString()" />
</div>
<span class="card-text small" t-esc="getTotalPriceString()" />
<p
t-if="props.product.description_sale"
class="o_self_order_item_card_description m-0 text-muted small break-line"
class="m-0 text-muted small break-line"
t-esc="props.product.description_sale"
/>
</div>
@@ -2,22 +2,21 @@
<templates id="template" xml:space="preserve">
<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'" />
<main class="h-100 bg-100 overflow-auto">
<Lines order="this.selfOrder.currentOrder" />
<Lines order="order" />
<div class="px-3 pt-4 pb-3 bg-white">
<PriceDetails tax="order.amount_tax" total="order.amount_total"/>
</div>
</main>
<t t-call="pos_self_order.OrderButton"/>
</div>
</t>
<t t-name="pos_self_order.OrderButton" owl="1">
<div class="d-flex flex-column align-items-center w-100 p-3 bg-white shadow-lg">
<t t-set="order" t-value="selfOrder.currentOrder"/>
<PriceDetails tax="order.amount_tax" total="order.amount_total"/>
<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
</t>
<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
</t>
</div>
</div>
</t>
</templates>
@@ -38,7 +38,7 @@
</header>
<!-- the body is set to overflow: hidden and this div is set to overflow-auto
this way we only scroll on the product list, and not on the whole page, which includes the navbar -->
<main t-ref="productsList" class="bg-white overflow-auto d-flex flex-row flex-wrap">
<main t-ref="productsList" class="bg-white overflow-auto d-flex flex-row flex-wrap" style="padding-bottom: 120px">
<t t-if="search.isFocused">
<t t-set="filteredProducts" t-value="filteredProducts()"/>
<p t-if="!filteredProducts?.length" class="mx-auto mt-3 text-center">No products found</p>
@@ -52,7 +52,7 @@
<p t-if="!categories?.length" class="mx-auto mt-3 text-center">No products found</p>
<t t-foreach="categories" t-as="category" t-key="category">
<section t-if="selfOrder.productsGroupedByCategory[category].length" t-ref="productsWithCategory_{{category}}" class="w-100 d-flex flex-wrap">
<h3 class="w-100 m-0 mt-n1 pb-3 pt-4 px-3 bg-light" t-esc="category" />
<h4 class="w-100 m-0 mt-n1 pb-2 pt-3 px-3 bg-secondary" t-esc="category" />
<t t-foreach="selfOrder.productsGroupedByCategory[category]" t-as="product" t-key="product.id">
<!-- If this is the product that we last visited, we use currentProductCard to
get a reference to it -->
@@ -70,8 +70,7 @@
but if there are no products (for example if you search for something that does not exist), the button
would be at the top of the page )
-->
<div class="flex-grow-1"></div>
<div t-if="selfOrder.table and selfOrder.currentOrder.totalQuantity" class="d-flex flex-column align-items-center w-100 p-3 bg-white shadow-lg">
<div t-if="selfOrder.table and selfOrder.currentOrder.totalQuantity" class="d-flex flex-column align-items-center w-100 p-3 fixed-bottom">
<t t-set="order" t-value="selfOrder.currentOrder"/>
<button t-on-click="() => this.router.navigate('cart')" class="o_self_order_main_button btn btn-primary w-100 px-2 py-3 mt-2 rounded">
<div class="d-flex align-items-center justify-content-between mx-2">
@@ -7,7 +7,7 @@
<div class="px-3">
<img t-if="props.product.has_image"
class="w-100 rounded-3"
style="max-height: 400px; object-fit: cover;"
style="max-height: 30vh; object-fit: cover;"
t-attf-src="/menu/get-image/{{ props.product.id }}/1920"
alt="Product image"/>
<div class="pt-3">
@@ -90,7 +90,7 @@
</div>
</div>
</div>
<div t-if="selfOrder.table" class="d-flex flex-column align-items-center w-100 p-3 bg-white shadow-lg">
<div t-if="selfOrder.table" class="d-flex flex-column align-items-center w-100 p-3 bg-white shadow-lg" style="z-index:1">
<div class="o_self_order_incr_button btn-group text-center border-light mb-1" role="group" aria-label="Quantity select">
<button type="button"
t-on-click = "() => this.changeQuantity(false)"
@@ -100,10 +100,16 @@
t-on-click = "() => this.changeQuantity(true)"
class="btn btn-light rounded-circle"> + </button>
</div>
<button t-on-click="addToCart" class="o_self_order_main_button btn btn-primary w-100 px-2 py-3 mt-2 rounded">
<t t-if="this.selfOrder.editedLine">
<t t-set="button_disabled" t-value="!this.selfOrder.editedLine and state.qty + state.cartQty === 0" />
<button
t-on-click="addToCart"
t-attf-class="{{ button_disabled ? 'disabled' : '' }} o_self_order_main_button btn btn-primary w-100 px-2 py-3 mt-2 rounded">
<t t-if="this.selfOrder.editedLine and state.qty + state.cartQty > 0">
Update Cart
</t>
<t t-elif="this.selfOrder.editedLine">
Remove from Cart
</t>
<t t-else="">
Add to Cart
</t>
@@ -8,7 +8,9 @@ body {
position: fixed;
box-sizing: border-box;
}
header {
z-index: 1;
}
.dvh-100{
// we specify the 100vh as a fallback in case the browser does not
// know dvh
@@ -19,4 +21,4 @@ body {
.o-self-order-spinner-custom-height {
height: 1rem;
width: 1rem;
}
}
@@ -37,9 +37,11 @@ export class SelfOrder {
effect((state) => this.saveOrderToLocalStorage(state.orders), [this]);
}
if (!this.has_active_session && this.self_order_mode !== "qr_code") {
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 disabled."),
_t(
"The restaurant is closed. You can browse the menu, but ordering is not available."
),
{ type: "warning" }
);
}
@@ -23,7 +23,7 @@ registry.category("web_tour.tours").add("self_order_after_meal_product_tour", {
PosSelf.check.isProductQuantity("Conference Chair (Aluminium)", 7),
// Check if product price are shown.
PosSelf.check.isProductPrice("Large Cabinet", "368.00"),
PosSelf.check.isProductPrice("Cabinet with Doors", "161.00"),
PosSelf.check.isProductPrice("Desk Pad", "2.28"),
PosSelf.check.isProductPrice("Letter Tray", "5.52"),
],
@@ -33,28 +33,28 @@ export const PosSelf = {
isOrderline: (name, price, description = "", attributes = "") => {
return {
content: `Verify is there an orderline with ${name} and ${price} and ${description}`,
trigger: `.o_self_order_item_card:has(h3:contains("${name}")):has(span:contains("${description}")):has(span:contains("${attributes}")):has(span.card-text:contains("${price}"))`,
trigger: `.o_self_order_item_card:has(.o_self_product_name:contains("${name}")):has(span:contains("${description}")):has(span:contains("${attributes}")):has(span.card-text:contains("${price}"))`,
run: () => {},
};
},
isNotOrderline: (name, price, description = "", attributes = "") => {
return {
content: `Verify is there an orderline with ${name} and ${price} and ${description}`,
trigger: `.o_self_order_item_card:not(:has(h3:contains("${name}")):has(span:contains("${description}")):has(span:contains("${attributes}")):has(span.card-text:contains("${price}")))`,
trigger: `.o_self_order_item_card:not(:has(.o_self_product_name:contains("${name}")):has(span:contains("${description}")):has(span:contains("${attributes}")):has(span.card-text:contains("${price}")))`,
run: () => {},
};
},
isProductQuantity: (name, quantity) => {
return {
content: `Verify is there a product with ${name} and ${quantity} selected quantity`,
trigger: `.o_self_order_item_card span.text-primary:contains('${quantity}x') ~ h3:contains('${name}')`,
trigger: `.o_self_order_item_card .o_self_product_name:contains('${name}') ~ div span.text-primary:contains('${quantity}x')`,
run: () => {},
};
},
isProductPrice: (name, price) => {
return {
content: `Verify is there a product with ${name} and ${price} price`,
trigger: `.o_self_order_item_card div:contains('${name}') ~ span.card-text:contains('${price}')`,
trigger: `.o_self_order_item_card .o_self_product_name:contains('${name}') ~ div span.card-text:contains('${price}')`,
run: () => {},
};
},
@@ -62,7 +62,7 @@ export const PosSelf = {
return [
{
content: `Click on product '${name}'`,
trigger: `.o_self_order_item_card h3:contains('${name}')`,
trigger: `.o_self_order_item_card .o_self_product_name:contains('${name}')`,
},
{
content: `Click on 'Add' button`,
@@ -89,7 +89,7 @@ export const PosSelf = {
return [
{
content: `Click on product '${name}'`,
trigger: `.o_self_order_item_card h3:contains('${name}')`,
trigger: `.o_self_order_item_card .o_self_product_name:contains('${name}')`,
},
...quantityHelper(quantity),
descriptionHelper(description),
@@ -104,14 +104,14 @@ export const PosSelf = {
return [
{
content: `Click on orderline ${name}, price ${price} and description ${description}`,
trigger: `.o_self_order_item_card:has(h3:contains("${name}")):has(span:contains("${description}")):has(span.card-text:contains("${price}"))`,
trigger: `.o_self_order_item_card:has(.o_self_product_name:contains("${name}")):has(span:contains("${description}")):has(span.card-text:contains("${price}"))`,
},
...quantityHelper(addQuantity),
descriptionHelper(newDescription),
...attributeHelper(newAtr),
{
content: `Click on 'Add' button`,
trigger: `.o_self_order_main_button:contains('Update Cart')`,
trigger: `.o_self_order_main_button`,
},
];
},
@@ -34,7 +34,6 @@ class TestFrontendMobile(odoo.tests.HttpCase):
self.pos_config._get_self_order_route(),
"self_order_menu_only_tour",
login=None,
watch=True,
)
def test_self_order_pay_after_meal_tour(self):