[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:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user