[IMP] point_of_sale: improve product product_list

In mobile, the product list can now be displayed as a grid or a list.

Items in the list and grid views will now always have the same author,
to maintain consistency.

closes odoo/odoo#126861

Signed-off-by: Robin Heinz (rhe) <rhe@odoo.com>
This commit is contained in:
David Monnom (moda)
2023-07-17 10:40:38 +02:00
parent 8a20ae9b4a
commit a7fc2a0606
7 changed files with 58 additions and 19 deletions
@@ -14,6 +14,7 @@ import { BackButton } from "@point_of_sale/app/navbar/back_button/back_button";
import { Component, useState, useExternalListener } from "@odoo/owl";
import { ClosePosPopup } from "@point_of_sale/app/navbar/closing_popup/closing_popup";
import { _t } from "@web/core/l10n/translation";
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
export class Navbar extends Component {
static template = "point_of_sale.Navbar";
@@ -107,4 +108,14 @@ export class Navbar extends Component {
showBackButton() {
return this.pos.showBackButton() && this.ui.isSmall;
}
toggleProductView() {
const newView = this.pos.productListView === "grid" ? "list" : "grid";
window.localStorage.setItem("productListView", newView);
this.pos.productListView = this.pos.productListView === "grid" ? "list" : "grid";
}
get showToggleProductView() {
return this.pos.mainScreen.component === ProductScreen && this.ui.isSmall;
}
}
@@ -43,6 +43,11 @@
Close Session
</span>
</li>
<li t-if="showToggleProductView" class="menu-item navbar-button close-button" t-on-click="toggleProductView">
<span>
Switch Product View
</span>
</li>
<li t-if="this.env.debug" t-on-click="() => debug.toggleWidget()" class="menu-item navbar-button">
<span>Debug Window</span>
</li>
@@ -2,15 +2,13 @@
<templates id="template" xml:space="preserve">
<t t-name="point_of_sale.ProductItem" owl="1">
<article class="product" tabindex="0" t-on-keypress="spaceClickProduct"
<article t-attf-class="product {{this.pos.productListViewMode}}" tabindex="0" t-on-keypress="spaceClickProduct"
t-on-click="() => this.pos.addProductToCurrentOrder(props.product)"
t-att-data-product-id="props.product.id"
t-attf-aria-labelledby="article_product_{{props.product.id}}">
<t t-if="props.product.image_128">
<div class="product-img">
<img t-att-src="imageUrl" t-att-alt="props.product.display_name" />
</div>
</t>
<div class="product-img">
<img t-if="props.product.image_128" t-att-src="imageUrl" t-att-alt="props.product.display_name" />
</div>
<div class="product-content">
<div t-attf-class="#{props.product.image_128 ? 'product-name' : 'product-name no-image' }" t-attf-id="article_product_{{props.product.id}}">
<t t-esc="props.product.display_name" />
@@ -20,7 +20,6 @@ export class ProductsWidgetControlPanel extends Component {
this.updateSearch = debounce(this.updateSearch, 100);
this.state = useState({ mobileSearchBarIsShown: false, isMobile: false });
this.productsWidgetControl = useRef("products-widget-control");
this.ui = useService("ui");
const toggleIsMobile = () => this.toggleIsMobile();
onMounted(() => {
@@ -13,7 +13,7 @@
hasNoCategories="hasNoCategories"
loadProductFromServer.bind="onPressEnterKey" />
<div class="product-list-container">
<div t-if="productsToDisplay.length != 0 and posHasValidProduct" class="product-list">
<div t-if="productsToDisplay.length != 0 and posHasValidProduct" t-attf-class="{{this.pos.productListViewMode}} product-list">
<t t-foreach="productsToDisplay" t-as="product" t-key="product.id">
<ProductItem
product="product" />
@@ -80,18 +80,20 @@ export class PosStore extends Reactive {
"number_buffer",
"barcode_reader",
"hardware_proxy",
"ui",
];
constructor() {
super();
this.ready = this.setup(...arguments).then(() => this);
}
// use setup instead of constructor because setup can be patched.
async setup(env, { popup, orm, number_buffer, hardware_proxy, barcode_reader }) {
async setup(env, { popup, orm, number_buffer, hardware_proxy, barcode_reader, ui }) {
this.env = env;
this.orm = orm;
this.popup = popup;
this.numberBuffer = number_buffer;
this.barcodeReader = barcode_reader;
this.ui = ui;
this.db = new PosDB(); // a local database used to search trough products and categories & store pending orders
this.unwatched = markRaw({});
@@ -120,6 +122,7 @@ export class PosStore extends Reactive {
this.numpadMode = "quantity";
this.mobile_pane = "right";
this.productListView = window.localStorage.getItem("productListView") || "grid";
// Record<orderlineId, { 'qty': number, 'orderline': { qty: number, refundedQty: number, orderUid: string }, 'destinationOrderUid': string }>
this.toRefundLines = {};
@@ -174,7 +177,9 @@ export class PosStore extends Reactive {
this.preloadImages();
this.showScreen("ProductScreen");
}
get productListViewMode() {
return this.productListView && this.ui.isSmall ? this.productListView : "grid";
}
getDefaultSearchDetails() {
return {
fieldName: "RECEIPT_NUMBER",
+30 -9
View File
@@ -852,7 +852,7 @@ td {
/* ********* The product list ********* */
.pos .product-list {
.pos .product-list.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(122px, 1fr));
padding: 3px;
@@ -1112,28 +1112,49 @@ td {
/* e) the product */
.pos .product {
position:relative;
background:#fff;
overflow: hidden;
cursor: pointer;
display: flex;
flex-direction: column;
position:relative;
cursor: pointer;
overflow: hidden;
background:#fff;
align-items: stretch;
}
.pos .product.grid {
height: 170px;
flex-direction: column;
}
.pos .product.list {
height: 90px;
flex-direction: row-reverse;
margin: 3px;
justify-content: space-between;
}
.pos .product-content {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.pos .product.list .product-content {
height: 100%;
}
.pos .product.grid .product-img {
height: 100px;
width: 100%;
overflow: hidden;
margin: 3px;
}
.pos .product.list .product-img {
height: 84px;
width: 84px;
margin: 3px;
overflow: hidden;
}
.pos .product .product-img img {
width: 100%;
height: 100px;
object-fit: contain;
object-fit: cover;
}
@media screen and (max-width: 768px) {
.pos .product:active {
box-shadow: 0 0 0 3px $primary;