chore: UI refresh for grid/list view and search
- enhanced UI for grid/list view, actions visible on hover only
- enhanced search UI
- Added indicator to show if item is in cart
- Moved search with view togglers
diff --git a/erpnext/e_commerce/doctype/item_review/item_review.py b/erpnext/e_commerce/doctype/item_review/item_review.py
index 772da04..f7437f6 100644
--- a/erpnext/e_commerce/doctype/item_review/item_review.py
+++ b/erpnext/e_commerce/doctype/item_review/item_review.py
@@ -61,7 +61,7 @@
doc.published_on = datetime.today().strftime("%d %B %Y")
doc.insert()
-def get_customer():
+def get_customer(silent=False):
user = frappe.session.user
contact_name = get_contact_name(user)
customer = None
@@ -75,5 +75,7 @@
if customer:
return frappe.db.get_value("Customer", customer)
+ elif silent:
+ return None
else:
frappe.throw(_("You are not verified to write a review yet. Please contact us for verification."))
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json
index bf0f4f1..a321584 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.json
+++ b/erpnext/e_commerce/doctype/website_item/website_item.json
@@ -310,7 +310,7 @@
{
"description": "Short Description for List View",
"fieldname": "short_description",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Short Website Description"
}
],
@@ -318,7 +318,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-07-08 19:25:15.115746",
+ "modified": "2021-07-11 20:49:45.415421",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Website Item",
diff --git a/erpnext/e_commerce/product_grid.js b/erpnext/e_commerce/product_grid.js
index c0e5ffa..d29f62f 100644
--- a/erpnext/e_commerce/product_grid.js
+++ b/erpnext/e_commerce/product_grid.js
@@ -22,7 +22,7 @@
this.items.forEach(item => {
let title = item.web_item_name || item.item_name || item.item_code || "";
- title = title.length > 50 ? title.substr(0, 50) + "..." : title;
+ title = title.length > 90 ? title.substr(0, 90) + "..." : title;
html += `<div class="col-sm-4 item-card"><div class="card text-left">`;
html += me.get_image_html(item, title);
@@ -60,13 +60,20 @@
get_card_body_html(item, title, settings) {
let body_html = `
- <div class="card-body text-left" style="width:100%">
+ <div class="card-body text-left card-body-flex" style="width:100%">
<div style="margin-top: 16px; display: flex;">
`;
- body_html += this.get_title_with_indicator(item, title, settings);
+ body_html += this.get_title(item, title);
- if (!item.has_variants && settings.enable_wishlist) {
- body_html += this.get_wishlist_icon(item);
+ // get floating elements
+ if (!item.has_variants) {
+ if (settings.enable_wishlist) {
+ body_html += this.get_wishlist_icon(item);
+ }
+ if (settings.enabled) {
+ body_html += this.get_cart_indicator(item);
+ }
+
}
body_html += `</div>`; // close div on line 50
@@ -76,29 +83,28 @@
body_html += this.get_price_html(item);
}
+ body_html += this.get_stock_availability(item, settings);
body_html += this.get_primary_button(item, settings);
body_html += `</div>`; // close div on line 49
return body_html;
}
- get_title_with_indicator(item, title, settings) {
+ get_title(item, title) {
let title_html = `
<a href="/${ item.route || '#' }">
<div class="product-title">
${ title || '' }
+ </div>
+ </a>
`;
- if (item.in_stock && settings.show_stock_availability) {
- title_html += `<span class="indicator ${ item.in_stock } card-indicator"></span>`;
- }
- title_html += `</div></a>`;
return title_html;
}
get_wishlist_icon(item) {
let icon_class = item.wished ? "wished" : "not-wished";
return `
- <div class="like-action"
+ <div class="like-action ${ item.wished ? "like-action-wished" : ''}"
data-item-code="${ item.item_code }"
data-price="${ item.price || '' }"
data-formatted-price="${ item.formatted_price || '' }">
@@ -109,6 +115,14 @@
`;
}
+ get_cart_indicator(item) {
+ return `
+ <div class="cart-indicator ${item.in_cart ? '' : 'hidden'}" data-item-code="${ item.item_code }">
+ 1
+ </div>
+ `;
+ }
+
get_price_html(item) {
let price_html = `
<div class="product-price">
@@ -117,10 +131,10 @@
if (item.formatted_mrp) {
price_html += `
- <small class="ml-1 text-muted">
- <s>${ item.formatted_mrp }</s>
+ <small class="striked-price">
+ <s>${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" }</s>
</small>
- <small class="ml-1" style="color: #F47A7A; font-weight: 500;">
+ <small class="ml-1 product-info-green">
${ item.discount } OFF
</small>
`;
@@ -129,6 +143,13 @@
return price_html;
}
+ get_stock_availability(item, settings) {
+ if (!item.has_variants && !item.in_stock && settings.show_stock_availability) {
+ return `<span class="out-of-stock">Out of stock</span>`;
+ }
+ return ``;
+ }
+
get_primary_button(item, settings) {
if (item.has_variants) {
return `
@@ -138,13 +159,29 @@
</div>
</a>
`;
- } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock !== "red")) {
+ } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) {
return `
<div id="${ item.name }" class="btn
- btn-sm btn-add-to-cart-list not-added w-100 mt-4"
+ btn-sm btn-primary btn-add-to-cart-list
+ w-100 mt-4 ${ item.in_cart ? 'hidden' : '' }"
data-item-code="${ item.item_code }">
+ <span class="mr-2">
+ <svg class="icon icon-md">
+ <use href="#icon-assets"></use>
+ </svg>
+ </span>
${ __('Add to Cart') }
</div>
+
+ <a href="/cart">
+ <div id="${ item.name }" class="btn
+ btn-sm btn-primary btn-add-to-cart-list
+ w-100 mt-4 go-to-cart-grid
+ ${ item.in_cart ? '' : 'hidden' }"
+ data-item-code="${ item.item_code }">
+ ${ __('Go to Cart') }
+ </div>
+ </a>
`;
} else {
return ``;
diff --git a/erpnext/e_commerce/product_list.js b/erpnext/e_commerce/product_list.js
index 8fc8da8..03cef60 100644
--- a/erpnext/e_commerce/product_list.js
+++ b/erpnext/e_commerce/product_list.js
@@ -24,8 +24,8 @@
let title = item.web_item_name || item.item_name || item.item_code || "";
title = title.length > 200 ? title.substr(0, 200) + "..." : title;
- html += `<div class='row mt-6 w-100' style="border-bottom: 1px solid var(--table-border-color); padding-bottom: 1rem;">`;
- html += me.get_image_html(item, title);
+ html += `<div class='row list-row w-100'>`;
+ html += me.get_image_html(item, title, me.settings);
html += me.get_row_body_html(item, title, me.settings);
html += `</div>`;
});
@@ -34,20 +34,23 @@
$product_wrapper.append(html);
}
- get_image_html(item, title) {
+ get_image_html(item, title, settings) {
let image = item.website_image || item.image;
+ let wishlist_enabled = !item.has_variants && settings.enable_wishlist;
+ let image_html = ``;
if (image) {
- return `
+ image_html += `
<div class="col-2 border text-center rounded list-image">
<a class="product-link product-list-link" href="/${ item.route || '#' }">
<img itemprop="image" class="website-image h-100 w-100" alt="${ title }"
src="${ image }">
</a>
+ ${ wishlist_enabled ? this.get_wishlist_icon(item): '' }
</div>
`;
} else {
- return `
+ image_html += `
<div class="col-2 border text-center rounded list-image">
<a class="product-link product-list-link" href="/${ item.route || '#' }"
style="text-decoration: none">
@@ -55,13 +58,16 @@
${ frappe.get_abbr(title) }
</div>
</a>
+ ${ wishlist_enabled ? this.get_wishlist_icon(item): '' }
</div>
`;
}
+
+ return image_html;
}
get_row_body_html(item, title, settings) {
- let body_html = `<div class='col-9 text-left'>`;
+ let body_html = `<div class='col-10 text-left'>`;
body_html += this.get_title_html(item, title, settings);
body_html += this.get_item_details(item, settings);
body_html += `</div>`;
@@ -76,18 +82,11 @@
style="color: var(--gray-800); font-weight: 500;">
${ title }
</a>
+ </div>
`;
- if (item.in_stock && settings.show_stock_availability) {
- title_html += `<span class="indicator ${ item.in_stock } card-indicator"></span>`;
- }
- title_html += `</div>`;
-
- if (settings.enable_wishlist || settings.enabled) {
+ if (settings.enabled) {
title_html += `<div class="col-4" style="display:flex">`;
- if (!item.has_variants && settings.enable_wishlist) {
- title_html += this.get_wishlist_icon(item);
- }
title_html += this.get_primary_button(item, settings);
title_html += `</div>`;
}
@@ -96,12 +95,12 @@
return title_html;
}
- get_item_details(item) {
+ get_item_details(item, settings) {
let details = `
<p class="product-code">
- Item Code : ${ item.item_code }
+ ${ item.item_group } | Item Code : ${ item.item_code }
</p>
- <div class="text-muted mt-2">
+ <div class="mt-2" style="color: var(--gray-600) !important; font-size: 13px;">
${ item.short_description || '' }
</div>
<div class="product-price">
@@ -110,24 +109,33 @@
if (item.formatted_mrp) {
details += `
- <small class="ml-1 text-muted">
- <s>${ item.formatted_mrp }</s>
+ <small class="striked-price">
+ <s>${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" }</s>
</small>
- <small class="ml-1" style="color: #F47A7A; font-weight: 500;">
+ <small class="ml-1 product-info-green">
${ item.discount } OFF
</small>
`;
}
+
+ details += this.get_stock_availability(item, settings);
details += `</div>`;
return details;
}
+ get_stock_availability(item, settings) {
+ if (!item.has_variants && !item.in_stock && settings.show_stock_availability) {
+ return `<br><span class="out-of-stock mt-2">Out of stock</span>`;
+ }
+ return ``;
+ }
+
get_wishlist_icon(item) {
let icon_class = item.wished ? "wished" : "not-wished";
return `
- <div class="like-action mr-4"
+ <div class="like-action-list ${ item.wished ? "like-action-wished" : ''}"
data-item-code="${ item.item_code }"
data-price="${ item.price || '' }"
data-formatted-price="${ item.formatted_price || '' }">
@@ -142,19 +150,43 @@
if (item.has_variants) {
return `
<a href="/${ item.route || '#' }">
- <div class="btn btn-sm btn-explore-variants" style="margin-bottom: 0; margin-top: 4px; max-height: 30px;">
+ <div class="btn btn-sm btn-explore-variants btn"
+ style="margin-bottom: 0; max-height: 30px; float: right;
+ padding: 0.25rem 1rem; min-width: 135px;">
${ __('Explore') }
</div>
</a>
`;
- } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock !== "red")) {
+ } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) {
return `
<div id="${ item.name }" class="btn
- btn-sm btn-add-to-cart-list not-added"
+ btn-sm btn-primary btn-add-to-cart-list
+ ${ item.in_cart ? 'hidden' : '' }"
data-item-code="${ item.item_code }"
- style="margin-bottom: 0; margin-top: 0px; max-height: 30px;">
+ style="margin-bottom: 0; margin-top: 0px !important; max-height: 30px; float: right;
+ padding: 0.25rem 1rem; min-width: 135px;">
+ <span class="mr-2">
+ <svg class="icon icon-md">
+ <use href="#icon-assets"></use>
+ </svg>
+ </span>
${ __('Add to Cart') }
</div>
+
+ <div class="cart-indicator ${item.in_cart ? '' : 'hidden'}" style="position: unset;">
+ 1
+ </div>
+
+ <a href="/cart">
+ <div id="${ item.name }" class="btn
+ btn-sm btn-primary btn-add-to-cart-list
+ ml-4 go-to-cart
+ ${ item.in_cart ? '' : 'hidden' }"
+ data-item-code="${ item.item_code }"
+ style="padding: 0.25rem 1rem; min-width: 135px;">
+ ${ __('Go to Cart') }
+ </div>
+ </a>
`;
} else {
return ``;
diff --git a/erpnext/e_commerce/product_query.py b/erpnext/e_commerce/product_query.py
index 804a7de..3db87ab 100644
--- a/erpnext/e_commerce/product_query.py
+++ b/erpnext/e_commerce/product_query.py
@@ -2,7 +2,8 @@
# License: GNU General Public License v3. See license.txt
import frappe
-
+from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
+from erpnext.e_commerce.doctype.item_review.item_review import get_customer
from frappe.utils import flt
from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
@@ -41,7 +42,7 @@
"""
# track if discounts included in field filters
self.filter_with_discount = bool(fields.get("discount"))
- result, discount_list, website_item_groups, count = [], [], [], 0
+ result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0
website_item_groups = self.get_website_item_group_results(item_group, website_item_groups)
@@ -62,7 +63,11 @@
# sort combined results by ranking
result = sorted(result, key=lambda x: x.get("ranking"), reverse=True)
- result, discount_list = self.add_display_details(result, discount_list)
+
+ if self.settings.enabled:
+ cart_items = self.get_cart_items()
+
+ result, discount_list = self.add_display_details(result, discount_list, cart_items)
discounts = []
if discount_list:
@@ -193,7 +198,7 @@
)
return website_item_groups
- def add_display_details(self, result, discount_list):
+ def add_display_details(self, result, discount_list, cart_items):
"""Add price and availability details in result."""
for item in result:
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
@@ -205,6 +210,8 @@
if self.settings.show_stock_availability:
self.get_stock_availability(item)
+ item.in_cart = item.item_code in cart_items
+
item.wished = False
if frappe.db.exists("Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user}):
item.wished = True
@@ -227,13 +234,31 @@
def get_stock_availability(self, item):
"""Modify item object and add stock details."""
+ item.in_stock = False
+
if item.get("website_warehouse"):
stock_qty = frappe.utils.flt(
frappe.db.get_value("Bin", {"item_code": item.item_code, "warehouse": item.get("website_warehouse")},
"actual_qty"))
- item.in_stock = "green" if stock_qty else "red"
- elif not frappe.db.get_value("Item", item.item_code, "is_stock_item"):
- item.in_stock = "green" # non-stock item will always be available
+ item.in_stock = bool(stock_qty)
+
+ def get_cart_items(self):
+ customer = get_customer(silent=True)
+ if customer:
+ quotation = frappe.get_all("Quotation", fields=["name"], filters=
+ {"party_name": customer, "order_type": "Shopping Cart", "docstatus": 0},
+ order_by="modified desc", limit_page_length=1)
+ if quotation:
+ items = frappe.get_all(
+ "Quotation Item",
+ fields=["item_code"],
+ filters={
+ "parent": quotation[0].get("name")
+ })
+ items = [row.item_code for row in items]
+ return items
+
+ return []
def combine_web_item_group_results(self, item_group, result, website_item_groups):
"""Combine results with context of website item groups into item results."""
diff --git a/erpnext/e_commerce/product_search.js b/erpnext/e_commerce/product_search.js
index b5ee083..ade43bc 100644
--- a/erpnext/e_commerce/product_search.js
+++ b/erpnext/e_commerce/product_search.js
@@ -10,8 +10,6 @@
setupSearchDropDown() {
this.search_area = $("#dropdownMenuSearch");
this.setupSearchResultContainer();
- this.setupProductsContainer();
- this.setupCategoryRecentsContainer();
this.populateRecentSearches();
}
@@ -82,60 +80,46 @@
<div class="overflow-hidden shadow dropdown-menu w-100 hidden"
id="search-results-container"
aria-labelledby="dropdownMenuSearch"
- style="display: flex;">
+ style="display: flex; flex-direction: column;">
</div>
`).find("#search-results-container");
+
+ this.setupCategoryContainer()
+ this.setupProductsContainer();
+ this.setupRecentsContainer();
}
setupProductsContainer() {
- let $products_section = this.search_dropdown.append(`
- <div class="col-7 mr-2 mt-1"
- id="product-results"
- style="border-right: 1px solid var(--gray-200);">
- </div>
- `).find("#product-results");
-
- this.products_container = $products_section.append(`
- <div id="product-scroll" style="overflow: scroll; max-height: 300px">
- <div class="mt-6 w-100 text-muted" style="font-weight: 400; text-align: center;">
- ${ __("Type something ...") }
+ this.products_container = this.search_dropdown.append(`
+ <div id="product-results mt-2">
+ <div id="product-scroll" style="overflow: scroll; max-height: 300px">
</div>
</div>
`).find("#product-scroll");
}
- setupCategoryRecentsContainer() {
- let $category_recents_section = $("#search-results-container").append(`
- <div id="category-recents-container"
- class="col-5 mt-2 h-100"
- style="margin-left: -15px;">
- </div>
- `).find("#category-recents-container");
-
- this.category_container = $category_recents_section.append(`
- <div class="category-container">
- <div class="mb-2"
- style="border-bottom: 1px solid var(--gray-200);">
- ${ __("Categories") }
- </div>
- <div class="categories">
- <span class="text-muted" style="font-weight: 400;"> ${ __('No results') } <span>
+ setupCategoryContainer() {
+ this.category_container = this.search_dropdown.append(`
+ <div class="category-container mt-2 mb-1">
+ <div class="category-chips">
</div>
</div>
- `).find(".categories");
+ `).find(".category-chips");
+ }
- let $recents_section = $("#category-recents-container").append(`
- <div class="mb-2 mt-4 recent-searches">
- <div style="border-bottom: 1px solid var(--gray-200);">
- ${ __("Recent") }
+ setupRecentsContainer() {
+ let $recents_section = this.search_dropdown.append(`
+ <div class="mb-2 mt-2 recent-searches">
+ <div>
+ <b>${ __("Recent") }</b>
</div>
</div>
`).find(".recent-searches");
this.recents_container = $recents_section.append(`
- <div id="recent-chips" style="padding: 1rem 0;">
+ <div id="recents" style="padding: .25rem 0 1rem 0;">
</div>
- `).find("#recent-chips");
+ `).find("#recents");
}
getRecentSearches() {
@@ -144,12 +128,12 @@
attachEventListenersToChips() {
let me = this;
- const chips = $(".recent-chip");
+ const chips = $(".recent-search");
window.chips = chips;
for (let chip of chips) {
chip.addEventListener("click", () => {
- me.searchBox[0].value = chip.innerText;
+ me.searchBox[0].value = chip.innerText.trim();
// Start search with `recent query`
me.searchBox.trigger("input");
@@ -179,15 +163,22 @@
let recents = this.getRecentSearches();
if (!recents.length) {
+ this.recents_container.html(`<span class=""text-muted">No searches yet.</span>`);
return;
}
let html = "";
recents.forEach((key) => {
html += `
- <button class="btn btn-sm recent-chip mr-1 mb-2" style="font-size: 13px">
+ <div class="recent-search mr-1" style="font-size: 13px">
+ <span class="mr-2">
+ <svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="var(--gray-500)"" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M8.00027 5.20947V8.00017L10 10" stroke="var(--gray-500)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>
+ </span>
${ key }
- </button>
+ </div>
`;
});
@@ -197,11 +188,7 @@
populateResults(data) {
if (data.length === 0 || data.message.results.length === 0) {
- let empty_html = `
- <div class="mt-6 w-100 text-muted" style="font-weight: 400; text-align: center;">
- ${ __('No results') }
- </div>
- `;
+ let empty_html = ``;
this.products_container.html(empty_html);
return;
}
@@ -228,21 +215,27 @@
populateCategoriesList(data) {
if (data.length === 0 || data.message.results.length === 0) {
let empty_html = `
- <span class="text-muted" style="font-weight: 400;">
- ${__('No results')}
- </span>
+ <div class="category-container mt-2">
+ <div class="category-chips">
+ </div>
+ </div>
`;
this.category_container.html(empty_html);
return;
}
- let html = "";
+ let html = `
+ <div class="mb-2">
+ <b>${ __("Categories") }</b>
+ </div>
+ `;
let search_results = data.message.results;
search_results.forEach((category) => {
html += `
- <div class="mb-2" style="font-weight: 400;">
- <a href="/${category.route}">${category.name}</a>
- </div>
+ <a href="/${category.route}" class="btn btn-sm category-chip mr-2 mb-2"
+ style="font-size: 13px" role="button">
+ ${ category.name }
+ </button>
`;
});
diff --git a/erpnext/e_commerce/product_view.js b/erpnext/e_commerce/product_view.js
index a70be44..9f54000 100644
--- a/erpnext/e_commerce/product_view.js
+++ b/erpnext/e_commerce/product_view.js
@@ -12,11 +12,25 @@
make(from_filters=false) {
this.products_section.empty();
- this.prepare_view_toggler();
+ this.prepare_toolbar();
this.get_item_filter_data(from_filters);
}
+ prepare_toolbar() {
+ this.products_section.append(`
+ <div class="toolbar d-flex">
+ </div>
+ `);
+ this.prepare_search();
+ this.prepare_view_toggler();
+
+ frappe.require('/assets/js/e-commerce.min.js', function() {
+ new erpnext.ProductSearch();
+ });
+ }
+
prepare_view_toggler() {
+
if (!$("#list").length || !$("#image-view").length) {
this.render_view_toggler();
this.bind_view_toggler_actions();
@@ -173,19 +187,45 @@
}
}
+ prepare_search() {
+ $(".toolbar").append(`
+ <div class="input-group col-6">
+ <div class="dropdown w-100" id="dropdownMenuSearch">
+ <input type="search" name="query" id="search-box" class="form-control font-md"
+ placeholder="Search for Products"
+ aria-label="Product" aria-describedby="button-addon2">
+ <div class="search-icon">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor" stroke-width="2" stroke-linecap="round"
+ stroke-linejoin="round"
+ class="feather feather-search">
+ <circle cx="11" cy="11" r="8"></circle>
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+ </svg>
+ </div>
+ <!-- Results dropdown rendered in product_search.js -->
+ </div>
+ </div>
+ `);
+ }
+
render_view_toggler() {
+ $(".toolbar").append(`<div class="toggle-container col-6"></div>`);
+
["btn-list-view", "btn-grid-view"].forEach(view => {
let icon = view === "btn-list-view" ? "list" : "image-view";
- this.products_section.append(`
- <div class="form-group mb-0" id="toggle-view">
- <button id="${ icon }" class="btn ${ view } mr-2">
- <span>
- <svg class="icon icon-md">
- <use href="#icon-${ icon }"></use>
- </svg>
- </span>
- </button>
- </div>`);
+ $(".toggle-container").append(`
+ <div class="form-group mb-0" id="toggle-view">
+ <button id="${ icon }" class="btn ${ view } mr-2">
+ <span>
+ <svg class="icon icon-md">
+ <use href="#icon-${ icon }"></use>
+ </svg>
+ </span>
+ </button>
+ </div>
+ `);
});
}
@@ -448,9 +488,6 @@
render_item_sub_categories(categories) {
if (categories && categories.length) {
let sub_group_html = `
- <div class="sub-category-container">
- <div class="heading"> ${ __('Sub Categories') } </div>
- </div>
<div class="sub-category-container scroll-categories">
`;
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index 31d34bc..be0c21f 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -166,19 +166,6 @@
});
},
- animate_add_to_cart(button) {
- // Create 'added to cart' animation
- let btn_id = "#" + button[0].id;
- this.toggle_button_class(button, 'not-added', 'added-to-cart');
- $(btn_id).text('Added to Cart');
-
- // undo
- setTimeout(() => {
- this.toggle_button_class(button, 'added-to-cart', 'not-added');
- $(btn_id).text('Add to Cart');
- }, 2000);
- },
-
toggle_button_class(button, remove, add) {
button.removeClass(remove);
button.addClass(add);
@@ -189,7 +176,10 @@
const $btn = $(e.currentTarget);
$btn.prop('disabled', true);
- this.animate_add_to_cart($btn);
+ $btn.addClass('hidden');
+ $btn.parent().find('.go-to-cart').removeClass('hidden');
+ $btn.parent().find('.go-to-cart-grid').removeClass('hidden');
+ $btn.parent().find('.cart-indicator').removeClass('hidden');
const item_code = $btn.data('item-code');
e_commerce.shopping_cart.update_cart({
diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js
index 9845723..4333587 100644
--- a/erpnext/public/js/wishlist.js
+++ b/erpnext/public/js/wishlist.js
@@ -79,7 +79,7 @@
bind_wishlist_action() {
// 'wish'('like') or 'unwish' item in product listing
- $('.page_content').on('click', '.like-action', (e) => {
+ $('.page_content').on('click', '.like-action, .like-action-list', (e) => {
const $btn = $(e.currentTarget);
const $wish_icon = $btn.find('.wish-icon');
let me = this;
@@ -101,6 +101,7 @@
if ($wish_icon.hasClass('wished')) {
// un-wish item
$btn.removeClass("like-animate");
+ $btn.addClass("like-action-wished");
this.toggle_button_class($wish_icon, 'wished', 'not-wished');
let args = { item_code: $btn.data('item-code') };
@@ -111,6 +112,7 @@
} else {
// wish item
$btn.addClass("like-animate");
+ $btn.addClass("like-action-wished");
this.toggle_button_class($wish_icon, 'not-wished', 'wished');
let args = {
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 5f0f023..27270f3 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -1,5 +1,9 @@
@import "frappe/public/scss/common/mixins";
+:root {
+ --green-info: #38A160;
+}
+
body.product-page {
background: var(--gray-50);
}
@@ -80,6 +84,7 @@
.item-card-group-section {
.card {
+ height: 100%;
align-items: center;
justify-content: center;
@@ -89,6 +94,19 @@
}
}
+ .card:hover, .card:focus-within {
+ .btn-add-to-cart-list {
+ visibility: visible;
+ }
+ .like-action {
+ visibility: visible;
+ }
+ .btn-explore-variants {
+ visibility: visible;
+ }
+ }
+
+
.card-img-container {
height: 210px;
width: 100%;
@@ -102,12 +120,10 @@
.no-image {
@include flex(flex, center, center, null);
- height: 200px;
- margin: 0 auto;
- margin-top: var(--margin-xl);
+ height: 220px;
background: var(--gray-100);
- width: 80%;
- border-radius: var(--border-radius);
+ width: 100%;
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
font-size: 2rem;
color: var(--gray-500);
}
@@ -123,6 +139,11 @@
margin-bottom: 15px;
}
+ .card-body-flex {
+ display: flex;
+ flex-direction: column;
+ }
+
.product-title {
font-size: 14px;
color: var(--gray-800);
@@ -153,6 +174,24 @@
font-weight: 600;
color: var(--text-color);
margin: var(--margin-sm) 0;
+
+ .striked-price {
+ font-weight: 500;
+ font-size: 15px;
+ color: var(--gray-500);
+ }
+ }
+
+ .product-info-green {
+ color: var(--green-info);
+ font-weight: 600;
+ }
+
+ .out-of-stock {
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 20px;
+ color: #F47A7A;
}
.item-card {
@@ -166,6 +205,28 @@
}
}
+.list-row {
+ padding-bottom: 1rem;
+ padding-top: 1.5rem !important;
+ border-radius: 8px;
+ border-bottom: 1px solid var(--gray-50);
+
+ &:hover, &:focus-within {
+ box-shadow: 0px 16px 60px rgba(0, 0, 0, 0.08), 0px 8px 30px -20px rgba(0, 0, 0, 0.04);
+ transition: box-shadow 400ms;
+
+ .btn-add-to-cart-list {
+ visibility: visible;
+ }
+ .like-action-list {
+ visibility: visible;
+ }
+ .btn-explore-variants {
+ visibility: visible;
+ }
+ }
+}
+
[data-doctype="Item Group"],
#page-all-products {
.page-header {
@@ -220,6 +281,7 @@
}
.list-image {
+ border: none !important;
overflow: hidden;
max-height: 200px;
background-color: white;
@@ -341,7 +403,7 @@
.product-code {
color: var(--text-muted);
- font-size: 13px;
+ font-size: 14px;
}
.item-configurator-dialog {
@@ -391,7 +453,7 @@
}
.sub-category-container {
- padding-bottom: 1rem;
+ padding-bottom: .5rem;
margin-bottom: 1.25rem;
border-bottom: 1px solid var(--table-border-color);
@@ -668,14 +730,68 @@
}
}
-.card-indicator {
- margin-left: 6px;
+.cart-indicator {
+ position: absolute;
+ text-align: center;
+ width: 22px;
+ height: 22px;
+ left: calc(100% - 40px);
+ top: 22px;
+
+ border-radius: 66px;
+ box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1);
+ background: white;
+ color: var(--primary-color);
+ font-size: 14px;
}
+
.like-action {
+ visibility: hidden;
text-align: center;
- margin-top: -2px;
- margin-left: 12px;
+ position: absolute;
+ cursor: pointer;
+ width: 28px;
+ height: 28px;
+ left: 20px;
+ top: 20px;
+
+ /* White */
+ background: white;
+ box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1);
+ border-radius: 66px;
+
+ &.like-action-wished {
+ visibility: visible !important;
+ }
+
+ @media (max-width: 992px) {
+ visibility: visible !important;
+ }
+}
+
+.like-action-list {
+ visibility: hidden;
+ text-align: center;
+ position: absolute;
+ cursor: pointer;
+ width: 28px;
+ height: 28px;
+ left: 20px;
+ top: 0;
+
+ /* White */
+ background: white;
+ box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1);
+ border-radius: 66px;
+
+ &.like-action-wished {
+ visibility: visible !important;
+ }
+
+ @media (max-width: 992px) {
+ visibility: visible !important;
+ }
}
.like-animate {
@@ -684,21 +800,19 @@
@keyframes expand {
30% {
- transform: scale(1.6);
+ transform: scale(1.3);
}
50% {
transform: scale(0.8);
}
70% {
- transform: scale(1.3);
+ transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
-@keyframes heart { 0%, 17.5% { font-size: 0; } }
-
.not-wished {
cursor: pointer;
stroke: #F47A7A !important;
@@ -725,52 +839,52 @@
}
.btn-explore-variants {
+ visibility: hidden;
box-shadow: none;
margin: var(--margin-sm) 0;
width: 90px;
max-height: 50px; // to avoid resizing on window resize
flex: none;
transition: 0.3s ease;
- color: var(--orange-500);
- background-color: white;
+
+ color: white;
+ background-color: var(--orange-500);
border: 1px solid var(--orange-500);
font-size: 13px;
&:hover {
color: white;
- background-color: var(--orange-500);
}
}
.btn-add-to-cart-list{
+ visibility: hidden;
box-shadow: none;
margin: var(--margin-sm) 0;
+ margin-top: auto !important;
max-height: 50px; // to avoid resizing on window resize
flex: none;
transition: 0.3s ease;
-}
-.not-added {
- color: var(--primary-color);
- background-color: transparent;
- border: 1px solid var(--blue-500);
- font-size: 13px;
-
- &:hover {
- background-color: var(--primary-color);
- color: white;
- }
-}
-
-.added-to-cart {
- background-color: var(--dark-green-400);
- color: white;
- border: 2px solid var(--green-300);
font-size: 13px;
&:hover {
color: white;
}
+
+ @media (max-width: 992px) {
+ visibility: visible !important;
+ }
+}
+
+.go-to-cart-grid {
+ max-height: 30px;
+ margin-top: 1rem !important;
+}
+
+.go-to-cart {
+ max-height: 30px;
+ float: right;
}
.wishlist-cart-not-added {
@@ -882,6 +996,41 @@
font-size: 14px;
}
+#search-results-container {
+ padding: .25rem 1rem;
+
+ .category-chip {
+ background-color: var(--gray-100);
+ border: none !important;
+ box-shadow: none;
+ }
+
+ .recent-search {
+ padding: .5rem .5rem;
+ border-radius: var(--border-radius);
+
+ &:hover {
+ background-color: var(--gray-100);
+ }
+ }
+}
+
+#search-box {
+ padding-left: 2.5rem;
+}
+
+.search-icon {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 2.5rem;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding-bottom: 1px;
+}
+
#toggle-view {
float: right;
}
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index d8ae763..51bce74 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -101,6 +101,7 @@
context.no_breadcrumbs = False
context.title = self.website_title or self.name
context.name = self.name
+ context.item_group_name = self.item_group_name
return context
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index a2b0f59..e099cdd 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -2,18 +2,7 @@
{% extends "templates/web.html" %}
{% block header %}
-<div class="row mb-6" style="width: 65vw">
- <div class="mb-6 col-4 order-1">{{ title }}</div>
-
- <div class="input-group mb-6 col-8 order-2">
- <div class="dropdown w-100" id="dropdownMenuSearch">
- <input type="search" name="query" id="search-box" class="form-control font-md"
- placeholder="Search for products..."
- aria-label="Product" aria-describedby="button-addon2">
- <!-- Results dropdown rendered in product_search.js -->
- </div>
- </div>
-</div>
+<div class="mb-6">{{ _(item_group_name) }}</div>
{% endblock header %}
{% block script %}
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index cb5277d..3d5517c 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -1,20 +1,9 @@
{% from "erpnext/templates/includes/macros.html" import attribute_filter_section, field_filter_section, discount_range_filters %}
{% extends "templates/web.html" %}
-{% block title %}{{ _('Products') }}{% endblock %}
+{% block title %}{{ _('All Products') }}{% endblock %}
{% block header %}
-<div class="row mb-6" style="width: 65vw">
- <div class="mb-6 col-4 order-1">{{ _('Products') }}</div>
-
- <div class="input-group mb-6 col-8 order-2">
- <div class="dropdown w-100" id="dropdownMenuSearch">
- <input type="search" name="query" id="search-box" class="form-control font-md"
- placeholder="Search for products..."
- aria-label="Product" aria-describedby="button-addon2">
- <!-- Results dropdown rendered in product_search.js -->
- </div>
- </div>
-</div>
+<div class="mb-6">{{ _('All Products') }}</div>
{% endblock header %}
{% block page_content %}
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index db2dec0..68d60b2 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -9,8 +9,6 @@
// Render Product Views, Filters & Search
frappe.require('/assets/js/e-commerce.min.js', function() {
- new erpnext.ProductSearch();
-
new erpnext.ProductView({
view_type: view_type,
products_section: $('#product-listing'),