[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:
@@ -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;
|
||||
|
||||
+6
-12
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+66
-57
@@ -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>
|
||||
|
||||
+48
-35
@@ -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) && 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 && !isComboCurrent
|
||||
}">
|
||||
<t t-esc="combo_id_index + 1"/>
|
||||
<i t-if="isComboSelected && !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>
|
||||
|
||||
+13
@@ -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%);
|
||||
}
|
||||
}
|
||||
+13
-19
@@ -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>
|
||||
|
||||
+68
-61
@@ -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>
|
||||
|
||||
+10
-9
@@ -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;
|
||||
|
||||
+8
-50
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
-31
@@ -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;
|
||||
}
|
||||
+24
-25
@@ -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">⌫</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>
|
||||
|
||||
Reference in New Issue
Block a user