[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:
@@ -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(),
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user