[REF] pos_self_order, pos_self_order_sale: design

Following the merge of mobile and kiosk functionalities [1], this commit
adapt and improve the UI in order to fit both mobile devices and TV
screens.

[1] https://github.com/odoo/odoo/pull/136051

Part-of: odoo/odoo#137704
This commit is contained in:
stefanorigano (SRI)
2023-10-10 15:42:52 +00:00
parent 2d7af57bd5
commit 46655303d0
30 changed files with 791 additions and 643 deletions
+1
View File
@@ -34,6 +34,7 @@
"pos_self_order/static/src/upgrade_selection_field.js",
],
"pos_self_order.assets": [
"pos_self_order/static/src/app/primary_variables.scss",
"pos_self_order/static/src/app/bootstrap_overridden.scss",
("include", "web._assets_helpers"),
("include", "web._assets_backend_helpers"),
@@ -1,2 +1,25 @@
$border-radius: .65rem !default;
$border-radius-lg: .85rem !default;
$font-size-base: 1rem !default;
$font-size-root: $o-so-font-size-base !default; // ~16px
$font-size-sm: $font-size-base * .875 !default;
$font-size-lg: $font-size-base * 1.25 !default;
$secondary: $o-gray-200 !default;
$body-bg: $o-gray-200 !default;
$spacer: 1rem !default;
$spacers: (
0: 0,
1: $spacer * .25,
2: $spacer * .5,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 3,
) !default;
$input-btn-padding-y: 0.4125rem !default;
$input-btn-padding-x: 0.825rem !default;
$input-btn-padding-y-lg: 1rem !default;
$input-btn-padding-x-lg: 2rem !default;
$input-btn-font-size-lg: $font-size-lg !default;
@@ -1,17 +1,11 @@
.self_order_attribute_selection {
.self_order_attribute_selection_option {
@include media-breakpoint-down(sm) {
--aspect-ratio: #{map-get($aspect-ratios, "21x9")};
}
.attribute-option {
height: 250px;
aspect-ratio: 1/1;
&.active {
border: 1px solid #714B67;
> .active {
transform: scale(.95);
}
}
}
.attribute-selection-content {
.o_kiosk-card {
aspect-ratio: 1/1;
}
}
@@ -1,68 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.AttributeSelection">
<div class="self_order_attribute_selection">
<div class="attribute_name py-1 mt-3 mb-4 fw-bold">
<t t-if="state.showResume">
<div class="self_order_attribute_selection d-flex flex-column flex-grow-1">
<t t-if="state.showResume">
<div class="attribute_name py-1 mt-4 mb-2 px-3">
<h1>Your resume</h1>
</t>
</div>
<div t-if="!state.showResume" class="attribute-selection-content align-items-center justify-content-start">
<div class="d-flex flex-column">
<div t-foreach="this.props.product.attributes" t-as="attribute" t-key="attribute.id" class="row row-cols-3 g-3 mb-5">
<h2 t-esc="attribute.name" class="w-100" />
<t t-foreach="availableAttributeValue(attribute)" t-as="value" t-key="value.id">
<div class="col">
<label t-attf-for="{{ attribute.id }}_{{ value.id }}" t-attf-class="{{ this.isChecked(attribute, value) ? 'border-2 border-primary' : '' }} d-flex btn btn-light flex-column align-items-center justify-content-center p-0 rounded border overflow-hidden">
<div class="name position-relative d-flex flex-column justify-content-center align-items-center flex-grow-1 w-100 py-2 text-center">
<h2 class="m-0" t-esc="value.name" />
<span t-if="value.price_extra.list_price">
+ <t t-esc="selfOrder.formatMonetary(value.price_extra.list_price)" />
</span>
<span t-if="this.isChecked(attribute, value)" class="selected-badge position-absolute top-0 start-50 translate-middle-x px-3 rounded-bottom text-bg-primary">
Selected
</span>
</div>
</label>
</div>
<input
type="radio"
class="d-none"
t-if="attribute.display_type !== 'multi'"
t-att-value="value.id"
t-attf-id="{{ attribute.id }}_{{ value.id }}"
t-model="this.selectedValues[attribute.id]" />
<input
type="checkbox"
class="d-none"
t-else=""
t-att-checked="this.isChecked(attribute, value)"
t-att-value="value.id"
t-model="this.selectedValues[attribute.id][value.id]"
t-attf-id="{{ attribute.id }}_{{ value.id }}" />
<input t-if="this.isChecked(attribute, value) and selfOrder.attributeValueById[value.id].is_custom" type="text" t-model="this.env.customValues[value.id].custom_value" class="form-control" placeholder="Enter your custom value" />
</t>
</div>
<div t-if="this.showNextBtn" class="page-buttons position-absolute bottom-0 end-0 w-100 py-3 px-2 d-flex flex-row w-100 gap-3 shadow-sm z-index-1">
<button class="btn btn-secondary w-100 h-100 p-0 m-0 fs-2 flex-grow-1 text-uppercase" t-on-click="this.toggleResume">Next</button>
</div>
</div>
</div>
<div t-else="" class="attribute-selection-content d-flex flex-column gap-3">
<t t-foreach="attributeSelected" t-as="value" t-key="value.name">
<div class="d-flex flex-row overflow-hidden rounded shadow-sm">
<div class="name position-relative d-flex flex-row justify-content-center align-items-center w-100 flex-grow-1 p-3 bg-view text-center">
<div class="d-flex flex-column flex-grow-1 align-items-start">
<h2 class="m-0" t-esc="value.name" />
<span class="px-1" t-esc="value.value" />
<ul class="attribute-selection-content list-group mx-3 mb-5">
<li class="list-group-item d-flex align-items-center flex-wrap gap-2 py-3" t-foreach="attributeSelected" t-as="value" t-key="value.name">
<div class="flex-grow-1">
<span class="me-2 text-muted"><t t-out="value.name"/>:</span><strong t-out="value.value"/>
</div>
<button t-attf-class="{{ !this.env.editable ? 'disabled' : '' }} btn btn-secondary ms-auto" t-on-click="this.toggleResume">
<i class="fa fa-pencil me-1"/> Edit
</button>
</li>
</ul>
</t>
<t t-else="">
<div class="attribute-selection-content align-items-center justify-content-start px-3 flex-grow-1">
<div class="d-flex flex-column">
<div t-foreach="this.props.product.attributes" t-as="attribute" t-key="attribute.id">
<h2 t-out="attribute.name"/>
<div class="row g-2 g-md-3 g-xl-4 justify-content-between justify-content-md-start row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-xl-5 row-cols-xxl-6 mb-5">
<t t-foreach="availableAttributeValue(attribute)" t-as="value" t-key="value.id">
<div class="col">
<div class="self_order_attribute_selection_option ratio ratio-16x9 w-100">
<label t-attf-for="{{ attribute.id }}_{{ value.id }}"
t-attf-class="{{ this.isChecked(attribute, value) ? 'text-bg-primary border-primary active' : '' }}
d-flex flex-column align-items-center justify-content-center rounded border">
<div class="name position-relative d-flex flex-column justify-content-center align-items-center flex-grow-1 w-100 py-2 text-center">
<span t-out="value.name"/>
<span t-if="value.price_extra.list_price">
+ <t t-out="selfOrder.formatMonetary(value.price_extra.list_price)" />
</span>
</div>
</label>
<input
type="radio"
class="d-none"
t-if="attribute.display_type !== 'multi'"
t-att-value="value.id"
t-attf-id="{{ attribute.id }}_{{ value.id }}"
t-model="this.selectedValues[attribute.id]" />
<input
type="checkbox"
class="d-none"
t-else=""
t-att-checked="this.isChecked(attribute, value)"
t-att-value="value.id"
t-model="this.selectedValues[attribute.id][value.id]"
t-attf-id="{{ attribute.id }}_{{ value.id }}" />
</div>
</div>
<div t-if="this.isChecked(attribute, value) and selfOrder.attributeValueById[value.id].is_custom" class="col w-100 order-2">
<input type="text" t-model="this.env.customValues[value.id].custom_value" class="form-control form-control-lg" placeholder="Enter your custom value" />
</div>
</t>
</div>
<button t-attf-class="{{ !this.env.editable ? 'disabled' : '' }} btn btn-secondary btn-lg px-5 py-2" t-on-click="this.toggleResume">
EDIT
</button>
</div>
</div>
</t>
</div>
</div>
</t>
</div>
<t t-if="!state.showResume">
<div class="page-buttons position-fixed bottom-0 w-100 d-flex justify-content-end p-3 gap-3 bg-view border-top z-index-1">
<button class="btn btn-primary btn-lg" t-att-class="this.showNextBtn ? '' : 'disabled'" t-on-click="this.toggleResume">Next</button>
</div>
<!-- This element is just a placeholder used to generate enough verstical room for the 'page-buttons' one. -->
<section class="p-3 border-top">
<button class="btn btn-lg">Placeholder</button>
</section>
</t>
</t>
</templates>
@@ -1,44 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.ComboSelection">
<div class="self_order_attribute_selection h-100 p-4">
<t t-if="!props.comboState.selectedProduct">
<div class="attribute_name py-1 mt-3 mb-4 fw-bold">
<h1>Choose your <t t-esc="props.combo.name" /></h1>
</div>
<div class="combo-list align-items-center justify-content-start">
<div class="row row-cols-3 g-3">
<t t-foreach="props.combo.combo_line_ids" t-as="line" t-key="line.id">
<div class="col">
<div
t-attf-for="{{ props.combo.id }}_{{ line.id }}"
class="position-relative btn btn-light d-flex flex-column align-items-center justify-content-center p-3 rounded border overflow-hidden"
t-on-click="() => this.productClicked(line.id)">
<t t-set="product" t-value="this.selfOrder.productByIds[line.product_id[0]]"/>
<img
class="h-100 w-100 rounded bg-view"
t-attf-src="/menu/get-image/{{ product.id }}/512?unique={{product.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()" />
<div class="name d-flex flex-column justify-content-center align-items-center flex-grow-1 w-100 pt-3 text-center">
<h2 class="m-0" t-esc="line.product_id[1]" />
<span t-if="line.combo_price" class="position-absolute top-0 end-0 m-1 px-2 rounded border border-white text-bg-primary bg-opacity-75 fs-4">
+ <t t-esc="selfOrder.formatMonetary(line.combo_price)" />
</span>
<div class="text-center ms-2 fs-lighter" t-if="!this.selfOrder.productByIds[line.product_id[0]].self_order_available">
<div class="fs-lighter bg-secondary rounded">Out of stock</div>
</div>
</div>
<div t-if="!props.comboState.selectedProduct" class="self_order_attribute_selection p-3 mt-3">
<h2 class="attribute_name mb-5 mb-md-3"><small class="text-muted">Choose your</small> <strong t-esc="props.combo.name" /></h2>
<div class="combo-list align-items-center justify-content-start">
<div class="combo-list-products o-so-products-row">
<t t-foreach="props.combo.combo_line_ids" t-as="line" t-key="line.id">
<t t-set="isOutOfStock" t-value="!this.selfOrder.productByIds[line.product_id[0]].self_order_available"/>
<article
t-attf-for="{{ props.combo.id }}_{{ line.id }}"
t-on-click="() => this.productClicked(line.id)"
class="self_order_product_card d-flex flex-row-reverse flex-md-column align-items-start gap-2 user-select-none"
role="button"
>
<t t-set="product" t-value="this.selfOrder.productByIds[line.product_id[0]]"/>
<div
class="ratio ratio-1x1 w-25 w-sm-50 w-md-100"
t-att-class="{
'd-none d-md-block': !product.has_image
}">
<div class="placeholder-glow">
<div class="placeholder w-100 h-100 bg-300 rounded"/>
</div>
<img
class="o_self_order_item_card_image w-100 rounded"
t-attf-src="/menu/get-image/{{ product.id }}/512?unique={{product.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()"/>
</div>
<div class="product-infos d-flex flex-column justify-content-between text-start flex-grow-1 w-100 lh-1">
<strong t-esc="product.name" class="fs-4 fw-bold mb-1 mb-sm-2"/>
<div class="d-flex justify-content-between gap-3">
<span
t-if="line.combo_price"
class="badge rounded-pill fs-4"
t-att-class="isOutOfStock ? 'text-bg-secondary' : 'text-bg-primary'">
+ <t t-out="selfOrder.formatMonetary(line.combo_price)"/>
</span>
<span t-if="isOutOfStock" class="badge text-bg-danger rounded-pill fs-4">
Out of stock
</span>
</div>
</div>
</t>
</div>
</article>
</t>
</div>
</t>
<t t-else="">
<AttributeSelection toggleQtyBtn.bind="toggleQtyBtn" product="props.comboState.selectedProduct" />
</t>
</div>
</div>
<t t-else="">
<AttributeSelection toggleQtyBtn.bind="toggleQtyBtn" product="props.comboState.selectedProduct" />
</t>
</t>
</templates>
@@ -8,7 +8,7 @@ import { CancelPopup } from "@pos_self_order/app/components/cancel_popup/cancel_
export class OrderWidget extends Component {
static template = "pos_self_order.OrderWidget";
static props = ["action"];
static props = ["action", "removeTopClasses"];
setup() {
this.selfOrder = useSelfOrder();
@@ -1,21 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.OrderWidget">
<div class="page-buttons fixed-bottom w-100 shadow-sm bg-white">
<div class="row m-2 h-100 ">
<button t-attf-class="btn btn-secondary col-3 p-0 m-0 text-uppercase {{ cancelAvailable ? '' : 'disabled' }}" t-on-click="cancel">
Cancel Order
</button>
<div class="col-6 p-2 m-0">
<div
class="page-buttons d-flex flex-nowrap justify-content-between py-2 px-3 gap-2 gap-md-3 bg-view z-index-1"
t-att-class="{
'shadow-lg border-top' : !props.removeTopClasses
}">
<button t-attf-class="btn btn-secondary btn-lg btn-close h-auto w-auto opacity-75 px-4 d-sm-none {{ cancelAvailable ? '' : 'd-none' }}" t-on-click="cancel">
<span class="px-1"/><!-- Spacer -->
</button>
<button t-attf-class="btn btn-secondary btn-lg d-none d-sm-inline text-nowrap {{ cancelAvailable ? '' : 'd-none' }}" t-on-click="cancel">
Cancel Order
</button>
<div class="d-flex align-items-center flex-grow-1 w-100 w-md-auto">
<div class="to-order">
<span t-if="!this.selfOrder.currentOrder.isSavedOnServer">To Order</span>
<span t-else="">To Pay</span>
<div class="m-2">
<span class="p-2 bg-300 rounded" t-esc="lineNotSend.count" />
<span class="mx-2" t-if="selfOrder.currentOrder.lines.length" t-esc="selfOrder.formatMonetary(lineNotSend.price)" />
<div class="d-flex align-items-center">
<span class="o-so-tabular-nums badge text-bg-secondary rounded" t-esc="lineNotSend.count"/>
<span class="o-so-tabular-nums mx-2" t-if="selfOrder.currentOrder.lines.length" t-esc="selfOrder.formatMonetary(lineNotSend.price)" />
</div>
</div>
<button t-attf-class="col-3 cart btn btn-primary col-3 p-0 m-0 text-uppercase {{ buttonToShow.disabled ? 'disabled' : '' }}" t-on-click="props.action" t-esc="buttonToShow.label" />
</div>
<button t-attf-class="cart btn btn-primary btn-lg flex-grow-1 flex-md-grow-0 {{ buttonToShow.disabled ? 'disabled' : '' }}" t-on-click="props.action" t-esc="buttonToShow.label"/>
</div>
</t>
</templates>
@@ -8,9 +8,7 @@
<h3>Confirm your table</h3>
<span>to place the order</span>
</div>
<button class="btn rounded" t-on-click="close">
<i class="fa fa-times"/>
</button>
<button class="btn btn-close" t-on-click="close"/>
</div>
<select class="form-select form-select-lg mb-5" t-model="state.selectedTable">
<t t-foreach="availableFloor" t-as="floor" t-key="floor.id">
@@ -25,36 +25,39 @@ export class ProductCard extends Component {
return;
}
const cartButton = document.querySelector(".cart");
if (!cartButton || window.getComputedStyle(cartButton).display === "none") {
const toOrder = document.querySelector(".to-order");
if (!toOrder || window.getComputedStyle(toOrder).display === "none") {
return;
}
const clonedCard = productCardEl.cloneNode(true);
const cardRect = productCardEl.getBoundingClientRect();
const cartRect = cartButton.getBoundingClientRect();
const pic = this.selfRef.el.querySelector(".o_self_order_item_card_image");
if (!pic) {
return;
}
clonedCard.style.position = "absolute";
clonedCard.style.top = `${cardRect.top}px`;
clonedCard.style.left = `${cardRect.left}px`;
clonedCard.style.width = `${cardRect.width}px`;
clonedCard.style.height = `${cardRect.height}px`;
clonedCard.style.opacity = ".85";
clonedCard.style.transition =
"top 1s cubic-bezier(0.95, 0.1, 0.25, 0.95), left 1s cubic-bezier(0.95, 0.1, 0.25, 0.95), width 1s cubic-bezier(0.95, 0.1, 0.25, 0.95), height 1s cubic-bezier(0.95, 0.1, 0.25, 0.95), opacity 0.6s 0.2s ease-in-out";
const picRect = pic.getBoundingClientRect();
const clonedPic = pic.cloneNode(true);
const toOrderRect = toOrder.getBoundingClientRect();
document.body.appendChild(clonedCard);
clonedPic.classList.remove('w-100', 'h-100');
clonedPic.classList.add('position-fixed', 'border', 'border-white', 'border-4', 'z-index-1');
clonedPic.style.top = `${picRect.top}px`;
clonedPic.style.left = `${picRect.left}px`;
clonedPic.style.width = `${picRect.width}px`;
clonedPic.style.height = `${picRect.height}px`;
clonedPic.style.transition = "all 400ms cubic-bezier(0.6, 0, 0.9, 1.000)";
document.body.appendChild(clonedPic);
requestAnimationFrame(() => {
clonedCard.style.top = `${cartRect.top}px`;
clonedCard.style.left = `${cartRect.left * 1.25}px`;
clonedCard.style.width = `${cardRect.width * 0.8}px`;
clonedCard.style.height = `${cardRect.height * 0.8}px`;
clonedCard.style.opacity = "0"; // Fading out the card
let offsetTop = (toOrderRect.top - picRect.top) - picRect.height * 0.5;
let offsetLeft = (toOrderRect.left - picRect.left) - picRect.width * 0.25;
clonedPic.style.transform = 'translateY(' + offsetTop +'px) translateX(' + offsetLeft +'px) scale(0.5)';
clonedPic.style.opacity = "0"; // Fading out the card
});
clonedCard.addEventListener("transitionend", () => {
clonedCard.remove();
clonedPic.addEventListener("transitionend", () => {
clonedPic.remove();
});
}
@@ -1,30 +1,4 @@
.self_order_product_card {
width: 100%;
display:flex;
flex-direction: column;
.o_self_order_item_card_image {
width: 100%;
object-fit: contain;
aspect-ratio: 1/1;
}
}
.scale-up {
transform: scale(1.2);
transition: transform .75s ease-in-out;
}
@media (max-width: 576px) {
.self_order_product_card {
display: flex;
width: 100%;
flex-direction: row-reverse;
.o_self_order_item_card_image {
width: 25%;
object-fit: contain;
aspect-ratio: 1/1;
}
}
}
@@ -1,26 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.ProductCard">
<div class="col-sm-6 col-md-4 col-lg-3 col-xxl-2 p-2">
<button class="self_order_product_card btn btn-light m-0 overflow-hidden border fw-bolder"
t-on-click="selectProduct"
t-ref="selfProductCard">
<article class="self_order_product_card d-flex flex-row-reverse flex-md-column align-items-start gap-2 user-select-none"
role="button"
t-att-title="props.product.name"
t-on-click="selectProduct"
t-ref="selfProductCard">
<div
class="ratio ratio-1x1 w-25 w-sm-50 w-md-100"
t-att-class="{
'd-none d-md-block': !props.product.has_image
}">
<div class="placeholder-glow">
<div class="placeholder w-100 h-100 bg-300 rounded"/>
</div>
<img
class="o_self_order_item_card_image rounded bg-view"
class="o_self_order_item_card_image w-100 rounded"
t-attf-src="/menu/get-image/{{ props.product.id }}/512?unique={{props.product.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()" />
<div class="product-infos d-flex flex-column justify-content-between text-start flex-grow-1 px-2 mt-3 mb-1">
<span t-esc="props.product.name" class="fs-3 fw-bolder flex-grow-1 lh-sm" />
<div class="d-flex justify-content-between align-items-end gap-3">
<span t-esc="selfOrder.formatMonetary(selfOrder.getProductDisplayPrice(props.product))" class="fs-4 text-muted flex-grow-1" />
<div class="text-center ms-2 fs-lighter">
<div t-if="!props.product.self_order_available" class="fs-lighter bg-secondary rounded">Out of stock</div>
</div>
onerror="this.remove()"/>
</div>
<div class="product-infos d-flex flex-column justify-content-between text-start flex-grow-1 w-100 lh-1">
<span t-esc="props.product.name" class="fs-4 fw-bold mb-1 mb-sm-2"/>
<div class="d-flex justify-content-between align-items-end gap-3">
<span t-esc="selfOrder.formatMonetary(selfOrder.getProductDisplayPrice(props.product))" class="o-so-tabular-nums fs-4 text-muted flex-grow-1" />
<div class="text-center ms-2 fs-lighter">
<div t-if="!props.product.self_order_available" class="fs-lighter bg-secondary rounded">Out of stock</div>
</div>
</div>
</button>
</div>
</div>
</article>
</t>
</templates>
@@ -1,31 +1,5 @@
.order-cart-content {
height: calc(100% - 160px);
.o_kiosk_product_card {
height: 100%
}
.order-lines {
height: 150px;
}
}
.upsale-product .o_kiosk_product_card {
height: auto;
}
.upsale-product .o_self_order_item_card_image {
height: 320px;
}
@media (max-height: 1280px) {
.upsale-product .o_self_order_item_card_image {
height: 120px;
}
}
@media (max-height: 1024px) {
.upsale-product .o_self_order_item_card_image {
height: 60px;
.o_self_order_item_card_image_wrapper {
width: 3rem;
}
}
@@ -1,74 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.CartPage">
<div class="order-cart-content d-flex flex-column justify-content-between bg-100">
<div class="p-3 position-absolute top-0 m-3">
<button class="btn btn-secondary" t-on-click="() => this.router.back()">
<i class="fa fa-arrow-left" aria-hidden="true"></i>
Back
<div class="order-cart-content d-flex flex-column flex-grow-1 justify-content-between overflow-y-auto">
<div class="d-flex align-items-center flex-shrink-0 justify-content-between gap-3 px-3 py-2 w-100 bg-view border-bottom overflow-x-auto z-index-1">
<button class="btn btn-secondary btn-lg px-3 text-nowrap" t-on-click="() => this.router.back()">
<i class="oi oi-chevron-left" aria-hidden="true"/><span class="ms-2 d-none d-md-inline">Back</span>
</button>
<h1 class="mb-0 fw-bolder text-nowrap">Your Order</h1>
<span class="d-none d-md-inline px-5"/> <!-- Spacer -->
</div>
<div class="d-flex align-items-end justify-content-center p-4 w-100 bg-100">
<h1 class="mb-0 fw-bolder">Your Order</h1>
</div>
<div class="order-content bg-100 px-3 py-4 flex-grow-1 overflow-auto">
<div class="order-content flex-grow-1 overflow-auto pb-4">
<t t-foreach="linesToDisplay" t-as="line" t-key="line.uuid">
<div t-if="!line.combo_parent_uuid" class="gap-3 py-3 p-2 pe-4 mb-3 rounded shadow-sm bg-view d-flex align-items-start gap-2 justify-content-between">
<div t-if="!line.combo_parent_uuid" class="gap-3 py-3 py-lg-4 px-3 bg-view border-bottom">
<t t-set="childLines" t-value="getChildLines(line)"/>
<t t-set="product" t-value="this.selfOrder.productByIds[line.product_id]"/>
<div t-if="!selfOrder.currentOrder.isSavedOnServer" class="w-25 d-flex flex-column align-items-center gap-2 justify-content-between mt-1">
<button t-if="canDeleteLine(line)" type="button" class="btn btn-danger rounded" t-on-click="() => this.removeLine(line)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
<div class="d-flex gap-2 justify-content-center align-items-center w-100">
<button type="button"
t-on-click = "() => this.changeQuantity(line, false)"
t-attf-class="btn btn-secondary rounded"> - </button>
<div class="d-flex align-items-center fw-bold" t-esc="getLineChangeQty(line)"/>
<button type="button"
t-on-click = "() => this.changeQuantity(line, true)"
class="btn btn-secondary rounded" > + </button>
</div>
</div>
<div class="product-wrapper d-flex flex-grow-1">
<div class="product-info d-flex flex-row align-items-center justify-content-center flex-grow-1 h-auto">
<div class="d-flex flex-column flex-grow-1 justify-content-center">
<div class="fw-bolder">
<span t-if="selfOrder.currentOrder.isSavedOnServer"><t t-esc="line.qty" />x </span>
<span t-esc="line.full_product_name"/>
</div>
<div t-foreach="childLines" t-as="cline" t-key="cline.uuid" class="text-muted">
- <span t-esc="cline.full_product_name" /> <span t-if="cline.price_subtotal_incl">(<t t-esc="selfOrder.formatMonetary(cline.price_subtotal_incl)"/>)</span>
<div class="product-info d-flex flex-column flex-grow-1 ms-3">
<div t-foreach="getSelectedAttributes(cline)" t-as="attribute" t-key="attribute.name">
- <span t-esc="attribute.name" /> : <span t-esc="attribute.value" />
<div class="product-wrapper d-flex align-items-start gap-2 gap-lg-3">
<div class="product-info d-flex flex-column gap-2 flex-grow-1">
<div>
<span t-if="selfOrder.currentOrder.isSavedOnServer"><t t-esc="line.qty" />x </span>
<strong t-esc="line.full_product_name"/>
</div>
<div t-if="!selfOrder.currentOrder.isSavedOnServer and this.selfOrder.productByIds[line.product_id]?.attributes.length > 0 || line.child_lines.length > 0" class="d-flex align-items-start gap-2 gap-md-3 my-2">
<button
type="button"
t-on-click="() => this.clickOnLine(line)"
class="btn btn-secondary"> <i class="fa fa-pencil"></i></button>
<div class="vr"/>
<div class="small">
<div t-foreach="childLines" t-as="cline" t-key="cline.uuid" class="text-muted">
- <span t-esc="cline.full_product_name" /> <span t-if="cline.price_subtotal_incl">(<t t-esc="selfOrder.formatMonetary(cline.price_subtotal_incl)"/>)</span>
<div class="product-info d-flex flex-column flex-grow-1 ms-3">
<div t-foreach="getSelectedAttributes(cline)" t-as="attribute" t-key="attribute.name">
- <span t-esc="attribute.name" /> : <span t-esc="attribute.value" />
<div t-foreach="getSelectedAttributes(line)" t-as="attribute" t-key="attribute.name" class="text-muted">
- <span t-esc="attribute.name" /> : <span t-esc="attribute.value" />
</div>
</div>
</div>
</div>
</div>
<div t-foreach="getSelectedAttributes(line)" t-as="attribute" t-key="attribute.name" class="text-muted">
- <span t-esc="attribute.name" /> : <span t-esc="attribute.value" />
</div>
</div>
</div>
<div class="line-price d-flex flex-column align-items-end justify-content-between h-auto w-25">
<span class="fw-bolder" t-esc="selfOrder.formatMonetary(getPrice(line))"/>
<button t-if="!selfOrder.currentOrder.isSavedOnServer and this.selfOrder.productByIds[line.product_id]?.attributes.length > 0 || line.child_lines.length > 0"
type="button"
t-on-click="() => this.clickOnLine(line)"
class="btn btn-secondary rounded" >Modify</button>
<div class="o_self_order_item_card_image_wrapper flex-shrink-0 ratio ratio-1x1">
<img
class="o_self_order_item_card_image w-100 w-100 rounded bg-view"
t-attf-src="/menu/get-image/{{ line.product_id}}/512"
alt="Product image"
loading="lazy"
onerror="this.remove()"/>
</div>
</div>
<div class="product-controllers d-flex flex-wrap flex-grow-1 align-items-center justify-content-between mt-3">
<div>
<div t-if="!selfOrder.currentOrder.isSavedOnServer" class="btn-group">
<button
t-if="canDeleteLine(line) &amp;&amp; getLineChangeQty(line) > 1"
type="button"
t-on-click= "() => this.changeQuantity(line, false)"
t-attf-class="btn btn-secondary px-3"><span class="fs-2 fa-fw d-inline-block">-</span></button>
<button
t-else=""
type="button"
t-on-click= "() => this.changeQuantity(line, false)"
t-attf-class="btn btn-secondary px-3"><i class="fa fa-fw fa-trash-o"/></button>
<div class="o-so-tabular-nums d-flex align-items-center fw-bold px-2 px-md-3 text-bg-200" t-esc="getLineChangeQty(line)"/>
<button type="button"
t-on-click = "() => this.changeQuantity(line, true)"
class="btn btn-secondary px-3"><span class="fs-2 fa-fw d-inline-block">+</span></button>
</div>
</div>
<div class="line-price o-so-tabular-nums" t-esc="selfOrder.formatMonetary(getPrice(line))"/>
</div>
</div>
</t>
</div>
</div>
<div class="order-price w-100">
<div class="absolute-content-price d-flex flex-column justify-content-center py-3 px-5 border-top bg-view text-end lh-sm">
<p class="mb-0 fw-bolder">Total: <t t-esc="selfOrder.formatMonetary(selfOrder.currentOrder.amount_total)" /></p>
<p class="mb-0 text-muted">Taxes: <t t-esc="selfOrder.formatMonetary(selfOrder.currentOrder.amount_tax)" /></p>
<div class="order-price d-flex-column flex-grow-0">
<div class="absolute-content-price d-flex flex-column flex-grow-0 justify-content-center p-3 border-top bg-view text-end lh-sm">
<p class="o-so-tabular-nums mb-0 fw-bolder">Total: <t t-esc="selfOrder.formatMonetary(selfOrder.currentOrder.amount_total)" /></p>
<p class="o-so-tabular-nums mb-0 text-muted">Taxes: <t t-esc="selfOrder.formatMonetary(selfOrder.currentOrder.amount_tax)" /></p>
</div>
<OrderWidget t-if="this.selfOrder.ordering" action.bind="pay" />
<OrderWidget t-if="this.selfOrder.ordering" action.bind="pay" removeTopClasses="true"/>
</div>
<PopupTable t-if="this.state.selectTable" selectTable.bind="selectTable" />
</t>
@@ -1,56 +1,9 @@
.breadcrumb {
max-height: 99px;
div:first-child {
margin-left: -15px;
}
div:last-child {
padding-right: 15px;
> span:after, > span:before {
border: none;
}
}
div > span {
padding: 1% 0 1% 60px;
padding-right: 20px;
&:after,
&:before {
content: " ";
border-top: 50px solid transparent;
border-bottom: 50px solid transparent;
position: absolute;
top: 50%;
margin-top: -50px;
margin-left: -1px;
}
&:after {
border-left: 30px solid $o-gray-200;
left: 100%;
z-index: 2;
}
&:before {
border-left: 30px solid $o-gray-300;
margin-left: 1px;
left: 100%;
z-index: 1;
}
}
div.active-step span {
&:after {
border-left: 30px solid $o-gray-100;
}
.pos_self_order_breadcrumb {
.pos_self_order_breadcrumb_pill {
width: 3rem;
}
}
.o_kiosk-combo {
.o_self_order_item_card_image {
aspect-ratio: 1/1;
object-fit: contain;
}
.pos_self_order_combo_image {
width: 3rem;
}
@@ -1,87 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.ComboPage">
<div class="d-flex flex-column w-100 fs-2 text-center border-bottom">
<div class="w-100 h-100 d-flex align-items-center justify-content-center bg-primary text-white"><t t-esc="props.product.name" /></div>
<div t-if="!state.showResume" class="breadcrumb h-100 w-100 d-flex flex-nowrap p-0 bg-200 overflow-x-auto overflow-y-hidden">
<t t-foreach="props.product.pos_combo_ids" t-as="combo_id" t-key="combo_id">
<div t-attf-class="d-flex align-items-center justify-content-center {{ currentComboId === combo_id ? 'active-step bg-100' : 'bg-200' }}">
<span class="position-relative d-block pe-3" t-esc="selfOrder.comboByIds[combo_id].name" />
</div>
</t>
</div>
<div class="d-flex align-items-center flex-shrink-0 justify-content-between gap-3 px-3 py-2 w-100 bg-view border-bottom overflow-x-auto z-index-1">
<button class="btn btn-secondary btn-lg px-3 text-nowrap" t-on-click="() => this.router.back()">
<i class="oi oi-chevron-left" aria-hidden="true"/><span class="ms-2 d-none d-md-inline">Discard</span>
</button>
<h1 class="mb-0 fw-bolder text-nowrap" t-esc="props.product.name"/>
<span class="d-none d-md-inline px-5"/> <!-- Spacer -->
</div>
<div class="h-100 overflow-y-auto">
<t t-if="state.selectedProduct">
<div class="d-flex flex-column h-25">
<div t-attf-class="h-100 m-4 mb-0">
<div class="d-flex flex-row h-100 pb-5 bg-view rounded shadow-sm">
<img
class="o_self_order_item_card_image w-25 p-3 rounded bg-view"
style="object-fit: contain;"
t-attf-src="/menu/get-image/{{ state.selectedProduct.id }}/512?unique={{state.selectedProduct.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()" />
<div class="d-flex flex-column justify-content-center w-75 p-3 ps-0">
<span class="fs-1 fw-bolder" t-esc="state.selectedProduct.name"/>
<div t-if="state.selectedProduct.description_sale"
class="o_self_order_main_desc text-muted overflow-y-auto"
t-esc="state.selectedProduct.description_sale"
/>
</div>
<div class="d-flex flex-column overflow-y-auto bg-view flex-grow-1">
<section t-if="!state.showResume" class="pos_self_order_breadcrumb position-relative px-3 px-md-0 pb-md-4 bg-view">
<div class="d-flex justify-content-around my-5">
<div t-foreach="props.product.pos_combo_ids" t-as="combo_id" t-key="combo_id" class="position-relative">
<t t-set="isComboCurrent" t-value="currentComboId === combo_id"/>
<t t-set="isComboSelected" t-value="false"/>
<t t-set="isNextSelected" t-value="false"/>
<t t-foreach="state.selectedCombos" t-as="selected" t-key="selected">
<t t-if="selected.id === combo_id" t-set="isComboSelected" t-value="true"/>
</t>
<span
t-if="!combo_id_first"
class="position-absolute end-0 top-50 border-top border-2"
t-attf-style="width: calc(100vw / {{
props.product.pos_combo_ids.length}})"
/>
<div class="pos_self_order_breadcrumb_pill d-flex align-items-center justify-content-center ratio ratio-1x1 mx-md-auto">
<span
class="rounded-pill d-flex justify-content-center align-items-center border border-2 z-index-1"
t-att-class="{
'text-bg-primary border-primary' : isComboCurrent,
'bg-view' : !isComboCurrent,
'border-success' : isComboSelected &amp;&amp; !isComboCurrent
}">
<t t-esc="combo_id_index + 1"/>
<i t-if="isComboSelected &amp;&amp; !isComboCurrent" class="position-absolute top-0 start-100 translate-middle-x fa fa-check fs-4 bg-view text-success"/>
</span>
</div>
<div class="position-absolute start-50 d-none d-md-block mt-3 translate-middle text-nowrap" t-esc="selfOrder.comboByIds[combo_id].name" />
</div>
</div>
</section>
<t t-if="state.selectedProduct">
<div class="o-so-product-details d-flex flex-row align-items-start p-3 gap-3">
<div class="o-so-product-details-image ratio ratio-1x1 w-25 flex-shrink-0">
<div class="placeholder-glow">
<div class="placeholder w-100 h-100 bg-300 rounded"/>
</div>
<img
class="o_self_order_item_card_image w-100 rounded"
t-attf-src="/menu/get-image/{{ state.selectedProduct.id }}/512?unique={{state.selectedProduct.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()"/>
</div>
<div class="o-so-product-details-description">
<h2 t-esc="state.selectedProduct.name"/>
<small t-if="state.selectedProduct.description_sale"
class="o_self_order_main_desc d-block mb-3 text-muted"
t-out="state.selectedProduct.description_sale"
/>
<span class="fs-3" t-esc="selfOrder.formatMonetary(state.selectedProduct.prices)"/>
</div>
</div>
</t>
<div class="d-flex flex-column h-75" t-if="!state.showResume">
<div t-attf-class="h-100 {{ state.selectedProduct ? 'm-4 mt-0 rounded-bottom-3' : '' }}">
<ComboSelection combo="currentCombo" comboState="state" next.bind="next"/>
</div>
<div t-if="!state.showResume" class="d-flex flex-column flex-grow-1" t-attf-class="{{ state.selectedProduct ? '' : '' }}">
<ComboSelection combo="currentCombo" comboState="state" next.bind="next"/>
</div>
<div t-else="" t-attf-class="o_kiosk-combo d-flex flex-column {{ state.showResume ? 'h-75' : 'h-50' }} p-4 overflow-auto">
<div class="attribute_name py-1 mt-3 mb-4 fw-bold">
<h1>Your Selection</h1>
</div>
<div class="h-100 align-items-center justify-content-start">
<div class="d-flex flex-column">
<div t-foreach="state.selectedCombos" t-as="combo" t-key="combo.id" class="d-flex align-items-center justify-content-between w-100 gap-3 py-3 p-2 pe-4 mb-3 rounded shadow-sm bg-view">
<t t-set="product" t-value="this.selfOrder.productByIds[combo.product.id]"/>
<img
class="o_self_order_item_card_image w-auto h-100 rounded bg-view"
t-attf-src="/menu/get-image/{{ combo.product.id }}/512?unique={{product.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()" />
<div class="name d-flex flex-column justify-content-center align-items-start flex-grow-1 h-auto w-100 text-center">
<h2 class="m-0"><t t-esc="combo.product.name"/></h2>
<div t-foreach="getAttributeSelected(combo.product)" t-as="attribute" t-key="attribute.id">
- <span t-esc="attribute.name" /> : <span t-esc="attribute.value" />
<div t-else="" t-attf-class="o_kiosk-combo d-flex flex-column flex-grow-1 px-3">
<h2 class="attribute_name mt-5 mb-3 fw-bold">Your Selection</h2>
<ul class="list-group">
<li class="list-group-item d-flex flex-wrap align-items-start gap-3" t-foreach="state.selectedCombos" t-as="combo" t-key="combo.id">
<t t-set="product" t-value="this.selfOrder.productByIds[combo.product.id]"/>
<div class="d-flex align-items-start gap-3">
<div class="pos_self_order_combo_image ratio ratio-1x1">
<div class="placeholder-glow">
<div class="placeholder w-100 h-100 bg-300 rounded"/>
</div>
<img
class="o_self_order_item_card_image w-100 rounded"
t-attf-src="/menu/get-image/{{ combo.product.id }}/512?unique={{product.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()"/>
</div>
<div>
<strong class="fs-4" t-esc="combo.product.name"/>
<ul t-if="getAttributeSelected(combo.product)">
<li t-foreach="getAttributeSelected(combo.product)" t-as="attribute" t-key="attribute.id">
<span t-esc="attribute.name" /> : <span t-esc="attribute.value" />
</li>
</ul>
</div>
<button class="btn btn-secondary btn-lg px-5 py-2" t-on-click="() => this.editCombo(combo.id)">EDIT</button>
</div>
</div>
</div>
<button class="btn btn-secondary ms-auto" t-on-click="() => this.editCombo(combo.id)">Edit</button>
</li>
</ul>
</div>
<t t-if="state.showResume">
<div class="d-flex flex-column justify-content-center align-items-center w-100 h-25 p-5 bg-view border-top" style="z-index:1">
<div class="o_self_order_incr_button d-flex flex-row text-center border-light mb-1" role="group" aria-label="Quantity select">
<div class="bg-view p-3 text-end">
<div class="o_self_order_incr_button btn-group" role="group" aria-label="Quantity select">
<button type="button"
t-on-click = "() => this.changeQuantity(false)"
class="btn btn-secondary fs-1 px-5 py-3"> - </button>
<div class="d-flex align-items-center px-5 fs-1 fw-bold" t-esc="state.qty"/>
t-attf-class="btn btn-secondary btn-lg"><span class="fs-2 lh-1 fa-fw d-inline-block">-</span></button>
<div class="o-so-tabular-nums d-flex align-items-center px-3 text-bg-200" t-esc="state.qty"/>
<button type="button"
t-on-click = "() => this.changeQuantity(true)"
class="btn btn-secondary fs-1 px-5 py-3"> + </button>
class="btn btn-secondary btn-lg"><span class="fs-2 lh-1 fa-fw d-inline-block">+</span></button>
</div>
</div>
</t>
</div>
<div class="page-buttons position-absolute bottom-0 end-0 w-100 py-3 px-2 d-flex flex-row w-100 gap-3 shadow-sm">
<button class="btn btn-secondary h-100 w-100 p-0 m-0 fs-2 flex-grow-1 text-uppercase" t-on-click="back">Discard</button>
<button t-if="!state.showResume and showQtyButtons" class="btn btn-primary h-100 w-100 p-0 m-0 fs-2 text-uppercase" t-on-click="next">Next</button>
<button t-if="state.showResume" class="btn btn-primary h-100 w-100 p-0 m-0 fs-2 text-uppercase" t-on-click="addToCart">Add to cart</button>
<div
t-if="state.showResume || (!state.showResume and showQtyButtons)"
class="page-buttons d-flex justify-content-end gap-3 p-3 border-top bg-view">
<button t-if="!state.showResume and showQtyButtons" class="btn btn-primary btn-lg" t-on-click="next">Next</button>
<button t-if="state.showResume" class="btn btn-primary btn-lg" t-on-click="addToCart">Add to cart</button>
</div>
</t>
</templates>
@@ -0,0 +1,13 @@
.o_kiosk_eating_location_box {
@include o-so-landscape() {
width: calc(49% - #{map-get($spacers, 3)});
}
@include o-so-portrait() {
width: 80%;
}
&.o_kiosk_eating_location_away img {
transform: translateX(-5%);
}
}
@@ -1,27 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.EatingLocationPage">
<div class="o_kiosk_eating_location d-flex flex-column align-items-center h-100 bg-100 overflow-hidden">
<div class="d-flex align-items-center justify-content-center flex-column w-100 h-100 my-auto px-5 text-center">
<h1>Choose your eating location</h1>
<div class="d-flex gap-3 h-50 h-lg-25 w-100 mt-5">
<button t-on-click="() => this.selectLocation('in')" class="btn btn-light d-flex flex-column align-items-center h-100 w-50 p-0 border">
<div class="d-flex flex-column flex-grow-1 align-items-center justify-content-center w-100">
<img src="/pos_self_order/static/img/eatin.svg" />
</div>
<span class="w-100 rounded-bottom border-top fs-1">Eat In</span>
</button>
<button t-on-click="() => this.selectLocation('out')" class="btn btn-light d-flex flex-column align-items-center h-100 w-50 p-0 border">
<div class="d-flex flex-column flex-grow-1 align-items-center justify-content-center w-100">
<img src="/pos_self_order/static/img/takeAway.svg" />
</div>
<span class="w-100 rounded-bottom border-top fs-1">Take Out</span>
</button>
</div>
<div class="o_kiosk_eating_location d-flex flex-column align-items-center flex-grow-1 bg-300 bg-gradient overflow-y-auto text-center">
<h1 class="pt-3 m-0">Choose your eating location</h1>
<div class="container d-flex flex-wrap align-items-center justify-content-center gap-3 my-auto">
<button t-on-click="() => this.selectLocation('in')" role="button" class="o_kiosk_eating_location_box btn btn-light">
<img src="/pos_self_order/static/img/eatin.svg" />
<h3>Eat In</h3>
</button>
<button t-on-click="() => this.selectLocation('out')" role="button" class="o_kiosk_eating_location_box o_kiosk_eating_location_away btn btn-light">
<img src="/pos_self_order/static/img/takeAway.svg" />
<h3>Take Out</h3>
</button>
</div>
</div>
<div class="page-buttons position-absolute bottom-0 end-0 w-100 py-3 px-2 d-flex flex-row w-100 gap-3 shadow-sm">
<button class="h-100 btn btn-secondary w-100 p-0 m-0 fs-2 text-uppercase" t-on-click="back">
<div class="page-buttons shadow-sm p-3 bg-view border-top text-center">
<button class="btn btn-secondary btn-lg" t-on-click="back">
<i class="oi oi-chevron-left"/>
Back
</button>
@@ -6,3 +6,13 @@
aspect-ratio: 3/2;
}
}
.o_pos_landing_footer {
@include o-so-portrait() {
flex-direction: column;
}
.gap-3 {
flex-direction: inherit;
}
}
@@ -17,23 +17,26 @@
</div>
</div>
</div>
<div class="position-absolute bottom-0 end-0 w-100 py-3 px-3">
<t t-if="showMyOrderBtn()">
<hr class="bg-500"/>
<a
type="button"
t-on-click="() => this.router.navigate('orderHistory')"
class="btn btn-primary h-100 w-100 p-0 m-0 fs-1 text-uppercase mb-2">
My Orders
</a>
</t>
<t t-foreach="selfOrder.custom_links" t-as="link" t-key="link.id">
<a type="button"
t-on-click="() => this.clickCustomLink(link)"
t-attf-class="btn btn-{{link.style}} h-100 w-100 p-0 m-0 fs-1 text-uppercase mb-2">
<t t-esc="link.name"/>
</a>
</t>
<div class="o_pos_landing_footer position-absolute bottom-0 end-0 d-flex w-100 gap-3 p-3">
<div class="d-flex gap-3" t-att-class="{'flex-grow-1 justify-content-around': !selfOrder.custom_links.length}">
<t t-if="showMyOrderBtn()">
<a
type="button"
t-on-click="() => this.router.navigate('orderHistory')"
class="btn btn-lg btn-primary">
My Orders
</a>
</t>
</div>
<div t-if="selfOrder.custom_links.length" class="d-flex gap-3 flex-grow-1">
<t t-foreach="selfOrder.custom_links" t-as="link" t-key="link.id">
<a type="button"
t-on-click="(event) => this.clickCustomLink(link)"
t-attf-class="btn btn-lg btn-{{link.style}}">
<t t-esc="link.name"/>
</a>
</t>
</div>
</div>
</t>
</templates>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.OrdersHistoryPage">
<div class="overflow-auto dvh-100 pb-5">
<div class="overflow-auto h-100">
<t t-if="state.loadingProgress">
<div class="d-flex align-items-center h-100 justify-content-center">
<div class="spinner-border" role="status">
@@ -9,78 +9,85 @@
</div>
</div>
</t>
<div t-else="">
<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 text-capitalize"
t-esc="order.state"/>
<div t-else="" class="d-flex flex-column h-100">
<div class="overflow-y-auto flex-grow-1 flex-shrink-1">
<t t-foreach="orders" t-as="order" t-key="order.access_token">
<div class="o_so_order d-flex flex-column flex-grow-1">
<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 py-2 rounded-pill text-capitalize"
t-att-class="{
'text-bg-success': order.state == 'paid',
'text-bg-primary': order.state != 'paid'
}"
t-esc="order.state"/>
</div>
<p class="small m-0 fst-italic text-muted"
t-esc="order.date"/>
</div>
<p class="small m-0 fst-italic text-muted"
t-esc="order.date"/>
</div>
<div class="o_so_order_body p-3 bg-300">
<div
t-foreach="order.lines"
t-as="line"
t-key="line.uuid"
t-attf-class="o_self_order_item_card position-relative d-flex align-items-start w-100 bg-white px-3 py-2 overflow-hidden"
t-on-click="() => this.clickOnLine(order, line)"
>
<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 o_self_product_name" t-esc="lineName.productName" />
<div t-if="line.qty">
<span class="text-primary fw-bolder small" t-esc="`${line.qty}x `" />
<div class="o_so_order_body p-3 bg-300">
<div
t-foreach="order.lines"
t-as="line"
t-key="line.uuid"
t-attf-class="o_self_order_item_card position-relative d-flex align-items-start w-100 bg-white px-3 py-2 overflow-hidden"
t-on-click="() => this.clickOnLine(order, line)"
>
<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 o_self_product_name" t-esc="lineName.productName" />
<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(getPrice(line) / line.qty)"
/>
</div>
<span
class="flex-grow-1 me-3 small text-muted"
t-esc="selfOrder.formatMonetary(getPrice(line) / line.qty)"
t-if="lineName.attributes"
class="m-0 text-muted small break-line"
t-esc="lineName.attributes"
/>
<t t-set="comboParent" t-value="line.combo_parent_uuid and order.lines.find(l => l.uuid === line.combo_parent_uuid)" />
<div t-if="comboParent" class="info ms-2 combo-parent-name">
<i class="fa fa-th-large me-2" role="img" aria-label="Combo" title="Combo"/>
<t t-esc="comboParent.full_product_name" />
</div>
<div t-if="line.customer_note" class="d-inline-block m-0 text-muted small break-line">
<i class="fa fa-pencil-square-o" aria-hidden="true" />
<span t-esc="line.customer_note" class="customer_note ms-1" />
</div>
</div>
<span
t-if="lineName.attributes"
class="m-0 text-muted small break-line"
t-esc="lineName.attributes"
/>
<t t-set="comboParent" t-value="line.combo_parent_uuid and order.lines.find(l => l.uuid === line.combo_parent_uuid)" />
<div t-if="comboParent" class="info ms-2 combo-parent-name">
<i class="fa fa-th-large me-2" role="img" aria-label="Combo" title="Combo"/>
<t t-esc="comboParent.full_product_name" />
</div>
<div t-if="line.customer_note" class="d-inline-block m-0 text-muted small break-line">
<i class="fa fa-pencil-square-o" aria-hidden="true" />
<span t-esc="line.customer_note" class="customer_note ms-1" />
</div>
<span t-attf-class="card-text line_price small"
t-esc="selfOrder.formatMonetary(getPrice(line))"/>
</div>
<span t-attf-class="card-text line_price small"
t-esc="selfOrder.formatMonetary(getPrice(line))"/>
</div>
</div>
<div class="d-flex flex-column align-items-center px-3 pt-3 bg-white">
<div class="d-grid gap-2 w-100 w-100 pb-3">
<div class="d-flex justify-content-between text-muted">
Tax:
<span t-if="!selfOrder.priceLoading" t-esc="selfOrder.formatMonetary(order.amount_tax)"/>
<span t-else="" class="spinner-border o-self-order-spinner-custom-height"></span>
</div>
<div class="d-flex justify-content-between fw-bold">
Total:
<span t-if="!selfOrder.priceLoading" t-esc="selfOrder.formatMonetary(order.amount_total)"/>
<div class="d-flex flex-column align-items-center px-3 pt-3 bg-white">
<div class="d-grid gap-2 w-100 w-100 pb-3">
<div class="d-flex justify-content-between text-muted">
Tax:
<span t-if="!selfOrder.priceLoading" t-esc="selfOrder.formatMonetary(order.amount_tax)"/>
<span t-else="" class="spinner-border o-self-order-spinner-custom-height"></span>
</div>
<div class="d-flex justify-content-between fw-bold">
Total:
<span t-if="!selfOrder.priceLoading" t-esc="selfOrder.formatMonetary(order.amount_total)"/>
</div>
</div>
</div>
</div>
</div>
</t>
<div t-if="orders.length === 0" class="d-flex justify-content-center mt-3">
<div>No order found</div>
</div>
</t>
<div t-if="orders.length === 0" class="d-flex justify-content-center mt-3">
<div>No order found</div>
</div>
<div class="d-flex w-100">
<button class="h-100 btn btn-secondary w-100 p-0 m-0 fs-2 text-uppercase" t-on-click="back">
<div class="bg-view p-3 border-top">
<button class="btn btn-secondary btn-lg" t-on-click="back">
<i class="oi oi-chevron-left"/>
Back
</button>
@@ -8,23 +8,24 @@
</div>
</div>
</div>
<div class="d-flex justify-content-center align-items-center flex-column text-center">
<h1 class="m-0">We're preparing<br/>your order!</h1>
<div class="d-flex flex-column justify-content-center align-items-center w-50 h-25 my-5 border rounded bg-view">
<div class="d-flex justify-content-center align-items-center flex-column h-100 px-3 text-center">
<h1 class="mb-4">We're preparing your order!</h1>
<div class="d-inline-flex flex-column border rounded py-4 px-5 bg-view mb-3">
<t t-if="selfOrder.currentOrder.take_away || selfOrder.config.self_ordering_service_mode !== 'table'">
<span class="fs-2 text-muted">Your order number</span>
<span class="number m-0" t-esc="selfOrder.currentOrder.tracking_number" />
<span class="number lh-1" t-esc="selfOrder.currentOrder.tracking_number" />
</t>
<t t-else="">
<span class="fs-2 text-muted">Your table number</span>
<span class="number m-0" t-esc="selfOrder.tablePadNumber" />
<h2 class="m-0 text-muted">Order number: <t t-esc="selfOrder.currentOrder.tracking_number" /></h2>
<span class="fs-2 text-muted">Your table number:</span>
<span class="number lh-1 mb-4" t-esc="selfOrder.tablePadNumber" />
<span class="fs-2 text-muted">Order number:</span>
<span class="number lh-1" t-esc="selfOrder.currentOrder.tracking_number" />
</t>
</div>
<h2 class="m-0 text-muted">Get your ticket below</h2>
</div>
<div class="d-flex flex-row w-100 gap-3">
<button class="btn btn-primary w-100 h-100 p-0 m-0 fs-2 text-uppercase" t-on-click="backToHome">Close</button>
<div class="px-3 py-4 text-center">
<button class="btn btn-primary btn-lg" t-on-click="backToHome">Close</button>
</div>
</t>
</templates>
@@ -77,10 +77,6 @@ export class ProductListPage extends Component {
);
}
get width() {
return window.innerWidth;
}
get productCategory() {
const productByCat = this.selfOrder.productsGroupedByCategory;
const currentCategory = this.selfOrder.currentCategory;
@@ -1,60 +1,18 @@
.navbar-container {
height: 225px;
overflow-y: hidden;
.category-list {
width: calc(100% - 50px);
overflow-y: hidden;
.category-item {
width: 150px;
height: 150px;
padding: 0px !important;
}
.category-item, .search-button {
min-width: calc(3.5rem + 2vw);
}
.category-item {
img {
object-fit: cover;
background-color: #f7f7f7;
}
}
.search-button {
width: 50px;
}
}
.product-list {
overflow-y: scroll;
margin-top: 225px;
padding-bottom: 90px;
height: calc(100% - 225px);
.product-list-category {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
h2:first-of-type {
padding-top: 2rem !important;
}
}
@media (max-width: 576px) {
.product-list {
margin-top: 50px;
height: calc(100% - 60px);
}
.navbar-container {
height: 60px;
overflow-y: hidden;
.category-list {
.category-item {
height: 40px;
}
&.active {
color: $headings-color;
img {
display: none;
outline: 1px solid $o-action;
}
}
}
@@ -1,46 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.ProductListPage">
<div class="vh-100 overflow-hidden">
<!-- Categories selector -->
<div class="navbar-container w-100 fixed-top bg-view shadow-sm">
<div t-attf-class="{{ this.state.search and width > 576 ? 'h-75' : 'h-100' }} {{ !this.state.search || width > 576 ? '' : 'd-none' }} d-flex w-100">
<nav id="listgroup-categories" class="navbar category-list d-flex flex-column flex-wrap gap-3 align-content-start py-2 px-3 h-100" t-ref="categoryList">
<a
t-foreach="Array.from(selfOrder.categoryList)"
t-as="category"
t-key="category.id"
t-ref="category_{{category.name}}"
t-attf-class="nav-link btn btn-light d-flex flex-column align-items-center justify-content-center category-item h-100 border px-1 overflow-hidden"
t-attf-href="#scrollspy_{{category.id}}">
<img t-if="category.has_image"
class="rounded w-100 h-75 ratio ratio-1x1"
<div class="d-flex flex-column vh-100 overflow-hidden">
<!-- Categories selector + Search -->
<div class="navbar-container position-relative d-flex flex-nowrap w-100 bg-view border-bottom z-index-1">
<nav id="listgroup-categories" class="category-list d-flex flex-grow-1 py-2 px-3 gap-2 gap-md-3 overflow-x-auto" t-ref="categoryList">
<a
t-foreach="Array.from(selfOrder.categoryList)"
t-as="category"
t-key="category.id"
t-ref="category_{{category.name}}"
t-attf-class="nav-link category-item flex-shrink-0 p-0"
t-attf-href="#scrollspy_{{category.id}}">
<div t-if="category.has_image" class="ratio ratio-1x1 mb-1">
<div class="placeholder-glow">
<div class="placeholder w-100 h-100 bg-300 rounded"/>
</div>
<img class="rounded w-100 h-100"
t-attf-src="/pos-self/get-category-image/{{ category.id }}"
alt="Product image"
loading="lazy"
onerror="this.remove()" />
<span class="w-100 fs-5 text-truncate" t-esc="category.name" />
</a>
</nav>
<button t-if="!this.state.search" class="btn btn-link text-dark px-3 search-button" t-on-click="focusSearch">
<i class="oi oi-search px-1"/>
</button>
</div>
<div t-if="this.state.search" t-attf-class="{{ this.state.search and width > 576 ? 'h-25' : 'h-100' }} d-flex align-items-center w-100">
</div>
<small
class="d-block fw-bold text-center"
t-att-class="{
'py-2' : !category.has_image,
'text-truncate' : category.has_image,
}"
t-esc="category.name"/>
</a>
</nav>
<button class="search-button btn btn-light border-0 border-start rounded-0" t-on-click="focusSearch">
<i class="oi oi-search"/>
</button>
<div t-if="this.state.search" t-attf-class="position-absolute top-0 left-0 w-100 h-100 px-3 pe-4 d-flex align-items-center bg-view">
<input type="text"
class="form-control p-3 border-0 shadow-none"
class="form-control py-3 ps-0 border-0 shadow-none"
placeholder="Search product"
t-model="state.searchInput"/>
<button class="btn bg-200 rounded" t-on-click="focusSearch">
<i class="fa fa-times"/>
</button>
<button class="btn-close" t-on-click="focusSearch"/>
</div>
</div>
<!-- Products list -->
<div
id="scrollspy-products"
class="product-list position-relative overflow-scroll"
class="product-list position-relative flex-grow-1 overflow-y-auto"
t-ref="productsList"
data-bs-spy="scroll"
data-bs-target="#listgroup-categories"
@@ -53,12 +61,16 @@
t-attf-id="scrollspy_{{category.id}}"
t-attf-categId="{{category.id}}"
t-ref="productsWithCategory_{{category.name}}"
class="product-list-category row p-3">
class="product-list-category d-empty-none bg-view py-4 px-3 mb-3">
<t t-set="products" t-value="selfOrder.productsGroupedByCategory[category.name]" />
<t t-set="availableProducts" t-value="!state.searchInput ? products : getFilteredProducts(products)" />
<h2 t-if="availableProducts.length > 0" class="pt-3 pb-1 px-4 m-0 fs-3" t-esc="category.name" />
<t t-foreach="availableProducts" t-as="product" t-key="product.id">
<ProductCard product="product" currentProductCard="product.id === selfOrder.lastEditedProductId and currentProductCard" />
<t t-if="availableProducts.length > 0">
<h2 t-esc="category.name" class="mb-4 display-6 fw-bold"/>
<div class="o-so-products-row">
<t t-foreach="availableProducts" t-as="product" t-key="product.id">
<ProductCard product="product" currentProductCard="product.id === selfOrder.lastEditedProductId and currentProductCard"/>
</t>
</div>
</t>
</section>
<p t-if="getFilteredProducts(selfOrder.products).length === 0" class="mx-auto mt-3 text-center">No products found</p>
@@ -1,48 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.ProductPage">
<div class="h-100">
<div class="d-flex flex-row align-content-flex-start p-3 gap-3 h-25">
<img
class="o_self_order_item_card_image w-25 py-2 rounded shadow-sm bg-view"
style="object-fit: contain;"
t-attf-src="/menu/get-image/{{ product.id }}/512?unique={{product.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()" />
<div class="d-flex flex-column flex-grow-1 w-75 justify-content-start p-4 rounded shadow-sm bg-view overflow-auto">
<h1 class="fw-bolder" t-esc="product.name"/>
<span t-if="product.description_sale"
class="o_self_order_main_desc flex-grow-1 pb-3 mb-3 bg-view text-muted "
t-esc="product.description_sale"
/>
<span class="fs-3 fw-bolder" t-esc="selfOrder.formatMonetary(selfOrder.getProductDisplayPrice(product))"/>
<div class="d-flex flex-column bg-view flex-grow-1 vh-100">
<div class="d-flex align-items-center flex-shrink-0 justify-content-between gap-3 px-3 py-2 w-100 bg-view border-bottom overflow-x-auto z-index-1">
<button class="btn btn-secondary btn-lg px-3 text-nowrap" t-on-click="back">
<i class="oi oi-chevron-left" aria-hidden="true"/><span class="ms-2 d-none d-md-inline">Discard</span>
</button>
<h1 class="mb-0 text-nowrap"><strong t-esc="product.name"/> options</h1>
<span class="d-none d-md-inline px-5"/> <!-- Spacer -->
</div>
<div class="pos_self_order_product_page_content d-flex flex-column flex-grow-1 overflow-y-auto">
<div class="o-so-product-details d-flex flex-row align-items-start p-3 gap-3">
<div class="o-so-product-details-image ratio ratio-1x1 w-25 flex-shrink-0">
<div class="placeholder-glow">
<div class="placeholder w-100 h-100 bg-300 rounded"/>
</div>
<img
class="o_self_order_item_card_image w-100 rounded"
t-attf-src="/menu/get-image/{{ props.product.id }}/512?unique={{props.product.write_date}}"
alt="Product image"
loading="lazy"
onerror="this.remove()"/>
</div>
<div class="o-so-product-details-description">
<h2 t-esc="product.name"/>
<small t-if="product.description_sale"
class="o_self_order_main_desc d-block mb-3 text-muted"
t-out="product.description_sale"
/>
<span class="fs-3" t-esc="selfOrder.formatMonetary(selfOrder.getProductDisplayPrice(product))"/>
</div>
</div>
<AttributeSelection
t-if="this.product.attributes.length"
toggleQtyBtn.bind="toggleQtyBtn"
product="product"/>
</div>
<div t-if="showQtyButtons" class="p-3 text-end">
<div class="o_self_order_incr_button btn-group " role="group" aria-label="Quantity select">
<button type="button"
t-on-click = "() => this.changeQuantity(false)"
t-attf-class="{{ !this.env.editable ? 'disabled' : '' }} btn btn-secondary btn-lg"><span class="fs-2 lh-1 fa-fw d-inline-block">-</span></button>
<div class="o-so-tabular-nums d-flex align-items-center px-3 text-bg-200" t-esc="state.qty"/>
<button type="button"
t-on-click = "() => this.changeQuantity(true)"
class="btn btn-secondary btn-lg"><span class="fs-2 lh-1 fa-fw d-inline-block">+</span></button>
</div>
</div>
<div t-attf-class="{{ showQtyButtons ? 'h-50' : 'h-75' }}">
<div class="h-100 px-4 overflow-y-auto">
<AttributeSelection
t-if="this.product.attributes.length"
toggleQtyBtn.bind="toggleQtyBtn"
product="product"/>
</div>
<div t-if="showQtyButtons and !props.onValidate" class="page-buttons d-flex justify-content-end p-3 gap-3 bg-view border-top">
<button t-if="showQtyButtons and !props.onValidate" class="btn btn-primary btn-lg" t-on-click="addToCart">Add to cart</button>
</div>
<div t-if="showQtyButtons" class="d-flex flex-column justify-content-center align-items-center w-100 h-25 p-5 bg-view border-top" style="z-index:1">
<div class="o_self_order_incr_button d-flex flex-row text-center border-light mb-1" role="group" aria-label="Quantity select">
<button type="button"
t-on-click = "() => this.changeQuantity(false)"
t-attf-class="{{ !this.env.editable ? 'disabled' : '' }} btn btn-secondary px-5 py-3 fs-1 "> - </button>
<div class="d-flex align-items-center px-5 fs-1 fw-bold" t-esc="state.qty"/>
<button type="button"
t-on-click = "() => this.changeQuantity(true)"
class="btn btn-secondary px-5 py-3 fs-1 "> + </button>
</div>
</div>
</div>
<div class="page-buttons position-absolute bottom-0 end-0 w-100 py-3 px-2 d-flex flex-row w-100 gap-3 shadow-sm">
<button class="btn btn-secondary w-100 h-100 p-0 m-0 fs-2 flex-grow-1 text-uppercase" t-on-click="back">Discard</button>
<button t-if="showQtyButtons and !props.onValidate" class="btn btn-primary w-100 h-100 p-0 m-0 fs-2 text-uppercase" t-on-click="addToCart">Add to cart</button>
</div>
</t>
</templates>
@@ -0,0 +1,3 @@
.touch-key {
aspect-ratio: 1.5;
}
@@ -1,37 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order.StandNumberPage">
<div class="self_order_stand_number">
<div class="d-flex flex-column justify-content-between h-100">
<div class="h-100 d-flex flex-column justify-content-center align-items-center">
<h1>Get a tracker and enter it's number here</h1>
<div class="input-number w-50 py-3 px-5 mt-5 text-center rounded border border-dark">
<span t-esc="tableInput" class="fs-1" />
</div>
<div class="self_order_stand_number d-flex flex-column flex-grow-1 justify-content-between px-3 overflow-y-auto">
<div class="text-center pt-3">
<h1>Get a tracker and enter it's number here</h1>
<div class="input-number form-contol form-control-lg text-center">
<span t-esc="tableInput" class="display-1"/>
</div>
<div class="numpad row row-cols-3">
<div t-on-click="numberClick" data="1" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">1</div>
<div t-on-click="numberClick" data="2" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">2</div>
<div t-on-click="numberClick" data="3" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">3</div>
<div t-on-click="numberClick" data="4" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">4</div>
<div t-on-click="numberClick" data="5" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">5</div>
<div t-on-click="numberClick" data="6" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">6</div>
<div t-on-click="numberClick" data="7" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">7</div>
<div t-on-click="numberClick" data="8" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">8</div>
<div t-on-click="numberClick" data="9" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">9</div>
<div t-on-click="numberClick" data="clear" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">
</div>
<div class="d-flex justify-content-around align-items-center py-4 py-md-5 my-auto">
<div class="numpad row g-0 row-cols-3 border">
<div t-on-click="numberClick" data="1" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">1</div>
<div t-on-click="numberClick" data="2" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">2</div>
<div t-on-click="numberClick" data="3" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">3</div>
<div t-on-click="numberClick" data="4" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">4</div>
<div t-on-click="numberClick" data="5" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">5</div>
<div t-on-click="numberClick" data="6" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">6</div>
<div t-on-click="numberClick" data="7" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">7</div>
<div t-on-click="numberClick" data="8" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">8</div>
<div t-on-click="numberClick" data="9" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">9</div>
<div t-on-click="numberClick" data="clear" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">
<i class="fa oi-close" aria-hidden="true"></i>
</div>
<div t-on-click="numberClick" data="0" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">0</div>
<div t-on-click="numberClick" data="reset" class="touch-key col btn btn-light d-flex align-items-center justify-content-center py-5 border rounded-0 fs-1">
<i class="fa fa-arrow-left" aria-hidden="true"></i>
</div>
<div t-on-click="numberClick" data="0" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">0</div>
<div t-on-click="numberClick" data="reset" class="touch-key col btn btn-light d-flex align-items-center justify-content-center border rounded-0 fs-1">&#9003;</div>
</div>
</div>
</div>
<div class="d-flex flex-row w-100 gap-3">
<button class="btn btn-secondary h-100 w-100 p-0 m-0 fs-2 text-uppercase" t-on-click="() => this.router.back()">Back</button>
<button class="btn btn-primary h-100 w-100 p-0 m-0 fs-2 text-uppercase" t-on-click="confirm">Pay</button>
<div class="d-flex justify-content-between p-3 bg-view border-top">
<button class="btn btn-secondary btn-lg" t-on-click="() => this.router.back()"><i class="oi oi-chevron-left me-2" aria-hidden="true"/>Back</button>
<button class="btn btn-primary btn-lg" t-att-class="{'disabled': !state.standNumber}" t-on-click="confirm">Pay</button>
</div>
</t>
</templates>
@@ -0,0 +1,68 @@
$o-so-font-size-base: 1rem !default;
$o-so-4k-multiplier: 2.3 !default;
$o-so-font-size-4k: $o-so-font-size-base * $o-so-4k-multiplier !default;
// Mirror enterprise grayscale
$o-white: #FFF !default;
$o-black: #000 !default;
$o-gray-100: #F9FAFB !default;
$o-gray-200: #eeeeee !default;
$o-gray-300: #d8dadd !default;
$o-gray-400: #9a9ca5 !default;
$o-gray-500: #7c7f89 !default;
$o-gray-600: #5f636f !default;
$o-gray-700: #374151 !default;
$o-gray-800: #1F2937 !default;
$o-gray-900: #111827 !default;
// Buttons
// Map of customized values for each button. If a button's design is defined
// here, the relative values will take priority over default BS ones.
// Notice: each map's entry is passed directly to the Bootstrap mixin, meaning
// that all states must be defined, there can't be omissions.
$o-btns-bs-override: () !default;
$o-btns-bs-override: map-merge((
"light": (
background: $o-white,
border: $o-gray-300,
color: $o-gray-700,
hover-background: $o-white,
hover-border: var(--companyColor),
hover-color: $o-gray-900,
active-background: $o-white,
active-border: var(--companyColor),
active-color: $o-gray-900,
),
"secondary": (
background: $o-gray-200,
border: $o-gray-200,
color: $o-gray-700,
hover-background: $o-gray-300,
hover-border: $o-gray-300,
hover-color: $o-gray-800,
active-background: var(--secondary-active-background),
active-border: var(--secondary-active-border),
active-color: $o-gray-900,
),
), $o-btns-bs-override);
// Mixins
@mixin o-so-landscape() {
@media (orientation: landscape) {
@content;
}
}
@mixin o-so-portrait() {
@media (orientation: portrait) {
@content;
}
}
@@ -1,7 +1,70 @@
body {
height: 100vh;
:root {
--secondary-active-background: #{mix($o-action, $o-white, 10%)};
--secondary-active-border: #{$o-action};
--btn-group-gap: 0;
// Target 4K devices
@media #{screen and (min-width: 3839px), (min-height: 3839px)} {
--root-font-size: #{$o-so-font-size-4k};
}
}
.page-buttons {
height: 80px;
html, body {
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
small {
font-size: .875em;
}
.o-so-tabular-nums {
font-variant: tabular-nums;
}
.o-so-product-details {
.o-so-product-details-image {
max-width: clamp(3rem, 25%, o-to-rem(170));
}
.o-so-product-details-description {
max-width: o-to-rem(750);
}
}
.o-so-products-row {
@include make-row();
--gutter-y: 8vw;
> * {
@include make-col-ready();
}
@include row-cols(1);
@include media-breakpoint-up(sm) {
@include row-cols(2);
}
@include media-breakpoint-up(md) {
@include row-cols(4);
--gutter-y: 2vw;
}
@include media-breakpoint-up(xl) {
@include o-so-landscape() {
@include row-cols(6);
}
@include o-so-portrait() {
@include row-cols(4);
}
}
}
@@ -1,24 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_self_order_sale.CartPage" t-inherit="pos_self_order.CartPage" t-inherit-mode="extension">
<xpath expr="//div[hasclass('absolute-content-price')]" position="before">
<div t-if="optionalProducts.length" class="upsale-content d-flex flex-column justify-content-center gap-3 h-75 border-top bg-100 pt-5 p-4">
<h2 class="m-0 fw-bolder">Want to add something ?</h2>
<div class="upsale-product d-flex flex-wrap flex-column overflow-auto gap-3">
<xpath expr="//div[hasclass('order-content')]" position="inside">
<div t-if="optionalProducts.length" class="upsale-content border-top bg-view mt-2 px-3 py-4 py-md-5">
<h2 class="mb-5">Want to add something ?</h2>
<div class="upsale-product row justify-content-between justify-content-md-start row-cols-sm-2 row-cols-md-4 row-cols-lg-4 row-cols-xl-5 row-cols-xxl-6">
<t t-foreach="optionalProducts" t-as="product" t-key="product.id">
<ProductCard product="product" />
</t>
</div>
</div>
</xpath>
<xpath expr="//div[hasclass('absolute-content-price')]" position="attributes">
<attribute name="t-attf-class">{{ optionalProducts.length === 0 ? 'h-100' : 'h-25' }}</attribute>
</xpath>
<xpath expr="//div[hasclass('order-price')]" position="attributes">
<attribute name="t-attf-class">{{ optionalProducts.length === 0 ? 'h-10' : 'h-50' }}</attribute>
</xpath>
<xpath expr="//div[hasclass('order-content')]" position="attributes">
<attribute name="t-attf-class">{{ optionalProducts.length === 0 ? 'h-90' : 'h-50' }}</attribute>
<attribute name="class" remove="pb-4" separator=" "/>
</xpath>
</t>
</templates>