diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index d86d2038adf..5ef6e8fc5db 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -117,6 +117,42 @@ overflow-y: scroll; overflow-x: hidden; + &.item-loading { + position: relative; + pointer-events: none; + } + + &.item-loading::after { + content: ""; + position: absolute; + inset: 0; + background: repeating-linear-gradient( + 90deg, + #f3f3f3 0px, + #f3f3f3 160px, + #e9ecef 160px, + #e9ecef 320px + ); + animation: skeletonMove 1.1s linear infinite; + z-index: 1; + } + + @keyframes skeletonMove { + from { + background-position: 0 0; + } + to { + background-position: 320px 0; + } + } + + &.items-not-found { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + } + &.show-item-image { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--margin-lg); diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 50ab2404078..35d22e40fb7 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -122,11 +122,13 @@ def filter_result_items(result, pos_profile): @frappe.whitelist() -def get_parent_item_group(): - # Using get_all to ignore user permission - item_group = frappe.get_all("Item Group", {"lft": 1, "is_group": 1}, pluck="name") - if item_group: - return item_group[0] +def get_parent_item_group(pos_profile): + item_groups = get_item_groups(pos_profile) + + if not item_groups: + item_groups = frappe.get_all("Item Group", {"lft": 1, "is_group": 1}, pluck="name") + + return item_groups[0] if item_groups else None @frappe.whitelist() diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 835fd65f846..69ec1e56934 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -7,8 +7,10 @@ erpnext.PointOfSale.ItemSelector = class { this.events = events; this.pos_profile = pos_profile; this.hide_images = settings.hide_images; + this.item_display_class = this.hide_images ? "hide-item-image" : "show-item-image"; this.auto_add_item = settings.auto_add_item_to_cart; + this.item_ready_group = this.get_parent_item_group(); this.inti_component(); } @@ -35,28 +37,36 @@ erpnext.PointOfSale.ItemSelector = class { this.$component = this.wrapper.find(".items-selector"); this.$items_container = this.$component.find(".items-container"); - const show_hide_images = this.hide_images ? "hide-item-image" : "show-item-image"; - this.$items_container.addClass(show_hide_images); + this.$items_container.addClass(this.item_display_class); + } + + async get_parent_item_group() { + const r = await frappe.call({ + method: "erpnext.selling.page.point_of_sale.point_of_sale.get_parent_item_group", + args: { + pos_profile: this.pos_profile, + }, + }); + if (r.message) this.item_group = this.parent_item_group = r.message; } async load_items_data() { - if (!this.item_group) { - frappe.call({ - method: "erpnext.selling.page.point_of_sale.point_of_sale.get_parent_item_group", - async: false, - callback: (r) => { - if (r.message) this.parent_item_group = r.message; - }, - }); - } + await this.item_ready_group; + + this.start_item_loading_animation(); + if (!this.price_list) { const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list"); this.price_list = res.message.selling_price_list; } - this.get_items({}).then(({ message }) => { - this.render_item_list(message.items); - }); + this.get_items({}) + .then(({ message }) => { + this.render_item_list(message.items); + }) + .always(() => { + this.stop_item_loading_animation(); + }); } get_items({ start = 0, page_length = 40, search_term = "" }) { @@ -64,8 +74,6 @@ erpnext.PointOfSale.ItemSelector = class { const price_list = (doc && doc.selling_price_list) || this.price_list; let { item_group, pos_profile } = this; - !item_group && (item_group = this.parent_item_group); - return frappe.call({ method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items", freeze: true, @@ -76,16 +84,32 @@ erpnext.PointOfSale.ItemSelector = class { render_item_list(items) { this.$items_container.html(""); + if (!items?.length) { + this.set_items_not_found_banner(); + return; + } + + if (this.$items_container.hasClass("items-not-found")) { + this.$items_container.removeClass("items-not-found"); + this.$items_container.addClass(this.item_display_class); + } + if (this.hide_images) { this.$items_container.append(this.render_item_list_column_header()); } - items.forEach((item) => { + items?.forEach((item) => { const item_html = this.get_item_html(item); this.$items_container.append(item_html); }); } + set_items_not_found_banner() { + this.$items_container.removeClass(this.item_display_class); + this.$items_container.addClass("items-not-found"); + this.$items_container.html(__("Items not found.")); + } + render_item_list_column_header() { return `