feat: Recommended Items and Item full page refresh
- Added Optional Recommended Items
- Item Full Page minor UI Refresh
- Floating wishlist button in item full page
- Reviews section UI Refresh
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
index 232a061..8eeaf53 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
@@ -27,6 +27,8 @@
"enable_wishlist",
"column_break_22",
"enable_reviews",
+ "column_break_23",
+ "enable_recommendations",
"section_break_18",
"company",
"price_list",
@@ -367,12 +369,22 @@
{
"fieldname": "column_break_22",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_23",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_recommendations",
+ "fieldtype": "Check",
+ "label": "Enable Recommendations"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-07-07 21:32:17.363276",
+ "modified": "2021-07-13 16:30:14.715949",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "E Commerce Settings",
diff --git a/erpnext/e_commerce/doctype/recommended_items/__init__.py b/erpnext/e_commerce/doctype/recommended_items/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/e_commerce/doctype/recommended_items/__init__.py
diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.json b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json
new file mode 100644
index 0000000..06ac3dc
--- /dev/null
+++ b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json
@@ -0,0 +1,87 @@
+{
+ "actions": [],
+ "creation": "2021-07-12 20:52:12.503470",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "website_item",
+ "website_item_name",
+ "column_break_2",
+ "item_code",
+ "more_information_section",
+ "route",
+ "column_break_6",
+ "website_item_image",
+ "website_item_thumbnail"
+ ],
+ "fields": [
+ {
+ "fieldname": "website_item",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Website Item",
+ "options": "Website Item"
+ },
+ {
+ "fetch_from": "website_item.web_item_name",
+ "fieldname": "website_item_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Website Item Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "more_information_section",
+ "fieldtype": "Section Break",
+ "label": "More Information"
+ },
+ {
+ "fetch_from": "website_item.route",
+ "fieldname": "route",
+ "fieldtype": "Small Text",
+ "label": "Route",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "website_item.image",
+ "fieldname": "website_item_image",
+ "fieldtype": "Attach",
+ "label": "Website Item Image",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "website_item.thumbnail",
+ "fieldname": "website_item_thumbnail",
+ "fieldtype": "Data",
+ "label": "Website Item Thumbnail",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "website_item.item_code",
+ "fieldname": "item_code",
+ "fieldtype": "Data",
+ "label": "Item Code"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-13 21:02:19.031652",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Recommended Items",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.py b/erpnext/e_commerce/doctype/recommended_items/recommended_items.py
new file mode 100644
index 0000000..9782abd
--- /dev/null
+++ b/erpnext/e_commerce/doctype/recommended_items/recommended_items.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class RecommendedItems(Document):
+ pass
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json
index a321584..c33cb51 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.json
+++ b/erpnext/e_commerce/doctype/website_item/website_item.json
@@ -39,6 +39,8 @@
"display_additional_information_section",
"show_tabbed_section",
"tabs",
+ "recommended_items_section",
+ "recommended_items",
"offers_section",
"offers",
"section_break_6",
@@ -312,13 +314,25 @@
"fieldname": "short_description",
"fieldtype": "Small Text",
"label": "Short Website Description"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "recommended_items_section",
+ "fieldtype": "Section Break",
+ "label": "Recommended Items"
+ },
+ {
+ "fieldname": "recommended_items",
+ "fieldtype": "Table",
+ "label": "Recommended/Similar Items",
+ "options": "Recommended Items"
}
],
"has_web_view": 1,
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-07-11 20:49:45.415421",
+ "modified": "2021-07-12 21:00:04.065803",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Website Item",
@@ -373,10 +387,10 @@
"write": 1
}
],
- "search_fields": "item_code, item_name ,item_group",
+ "search_fields": "web_item_name, item_code, item_group",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "title_field": "item_name",
+ "title_field": "web_item_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py
index 3ff58fd..081c4bb 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/website_item.py
@@ -13,6 +13,8 @@
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews
+from erpnext.e_commerce.shopping_cart.cart import _set_price_list
+from erpnext.utilities.product import get_price
# SEARCH
from erpnext.e_commerce.website_item_indexing import (
@@ -199,6 +201,12 @@
context.wished = True
context.user_is_customer = check_if_user_is_customer()
+
+ context.recommended_items = None
+ settings = context.shopping_cart.cart_settings
+ if settings.enable_recommendations:
+ context.recommended_items = self.get_recommended_items(settings)
+
return context
def set_variant_context(self, context):
@@ -379,6 +387,38 @@
return tab_values
+ def get_recommended_items(self, settings):
+ items = frappe.db.sql(f"""
+ select
+ ri.website_item_thumbnail, ri.website_item_name,
+ ri.route, ri.item_code
+ from
+ `tabRecommended Items` ri, `tabWebsite Item` wi
+ where
+ ri.item_code = wi.item_code
+ and ri.parent = '{self.name}'
+ and wi.published = 1
+ order by ri.idx
+ """, as_dict=1)
+
+ if settings.show_price:
+ is_guest = frappe.session.user == "Guest"
+ # Show Price if logged in.
+ # If not logged in and price is hidden for guest, skip price fetch.
+ if is_guest and settings.hide_price_for_guest:
+ return items
+
+ selling_price_list = _set_price_list(settings, None)
+ for item in items:
+ item.price_info = get_price(
+ item.item_code,
+ selling_price_list,
+ settings.default_customer_group,
+ settings.company
+ )
+
+ return items
+
def invalidate_cache_for_web_item(doc):
"""Invalidate Website Item Group cache and rebuild ItemVariantsCacheManager."""
from erpnext.stock.doctype.item.item import invalidate_item_variants_cache_for_website
diff --git a/erpnext/e_commerce/website_item_indexing.py b/erpnext/e_commerce/website_item_indexing.py
index 18dac93..f66d2ef 100644
--- a/erpnext/e_commerce/website_item_indexing.py
+++ b/erpnext/e_commerce/website_item_indexing.py
@@ -11,6 +11,7 @@
WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict'
ALLOWED_INDEXABLE_FIELDS_SET = {
+ 'web_item_name',
'item_code',
'item_name',
'item_group',
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index be0c21f..4c134e2 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -176,6 +176,16 @@
const $btn = $(e.currentTarget);
$btn.prop('disabled', true);
+ if (frappe.session.user==="Guest") {
+ if (localStorage) {
+ localStorage.setItem("last_visited", window.location.pathname);
+ }
+ frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => {
+ window.location.href = res.message || "/login";
+ });
+ return;
+ }
+
$btn.addClass('hidden');
$btn.parent().find('.go-to-cart').removeClass('hidden');
$btn.parent().find('.go-to-cart-grid').removeClass('hidden');
diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js
index 4333587..3c8d842 100644
--- a/erpnext/public/js/wishlist.js
+++ b/erpnext/public/js/wishlist.js
@@ -81,53 +81,55 @@
// 'wish'('like') or 'unwish' item in product listing
$('.page_content').on('click', '.like-action, .like-action-list', (e) => {
const $btn = $(e.currentTarget);
- const $wish_icon = $btn.find('.wish-icon');
- let me = this;
-
- if (frappe.session.user==="Guest") {
- if (localStorage) {
- localStorage.setItem("last_visited", window.location.pathname);
- }
-
- this.redirect_guest();
-
- return;
- }
-
- let success_action = function() {
- e_commerce.wishlist.set_wishlist_count();
- };
-
- 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') };
- let failure_action = function() {
- me.toggle_button_class($wish_icon, 'not-wished', 'wished');
- };
- this.add_remove_from_wishlist("remove", args, success_action, failure_action);
- } else {
- // wish item
- $btn.addClass("like-animate");
- $btn.addClass("like-action-wished");
- this.toggle_button_class($wish_icon, 'not-wished', 'wished');
-
- let args = {
- item_code: $btn.data('item-code'),
- price: $btn.data('price'),
- formatted_price: $btn.data('formatted-price')
- };
- let failure_action = function() {
- me.toggle_button_class($wish_icon, 'wished', 'not-wished');
- };
- this.add_remove_from_wishlist("add", args, success_action, failure_action);
- }
+ this.wishlist_action($btn);
});
},
+ wishlist_action(btn) {
+ const $wish_icon = btn.find('.wish-icon');
+ let me = this;
+
+ if (frappe.session.user==="Guest") {
+ if (localStorage) {
+ localStorage.setItem("last_visited", window.location.pathname);
+ }
+ this.redirect_guest();
+ return;
+ }
+
+ let success_action = function() {
+ e_commerce.wishlist.set_wishlist_count();
+ };
+
+ 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') };
+ let failure_action = function() {
+ me.toggle_button_class($wish_icon, 'not-wished', 'wished');
+ };
+ this.add_remove_from_wishlist("remove", args, success_action, failure_action);
+ } else {
+ // wish item
+ btn.addClass("like-animate");
+ btn.addClass("like-action-wished");
+ this.toggle_button_class($wish_icon, 'not-wished', 'wished');
+
+ let args = {
+ item_code: btn.data('item-code'),
+ price: btn.data('price'),
+ formatted_price: btn.data('formatted-price')
+ };
+ let failure_action = function() {
+ me.toggle_button_class($wish_icon, 'wished', 'not-wished');
+ };
+ this.add_remove_from_wishlist("add", args, success_action, failure_action);
+ }
+ },
+
toggle_button_class(button, remove, add) {
button.removeClass(remove);
button.addClass(add);
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 27270f3..0130e70 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -2,6 +2,7 @@
:root {
--green-info: #38A160;
+ --product-bg-color: white;
}
body.product-page {
@@ -289,6 +290,7 @@
.product-container {
@include card($padding: var(--padding-md));
+ background-color: var(--product-bg-color) !important;
min-height: 70vh;
.product-details {
@@ -299,6 +301,12 @@
}
}
+ &.item-main {
+ .product-image {
+ width: 100%;
+ }
+ }
+
.expand {
max-width: 100% !important; // expand in absence of slideshow
}
@@ -327,9 +335,10 @@
}
.product-title {
- font-size: 24px;
+ font-size: 16px;
font-weight: 600;
color: var(--text-color);
+ padding: 0 !important;
}
.product-description {
@@ -385,7 +394,7 @@
.item-cart {
.product-price {
- font-size: 20px;
+ font-size: 22px;
color: var(--text-color);
font-weight: 600;
@@ -398,12 +407,94 @@
.no-stock {
font-size: var(--text-base);
}
+
+ .offers-heading {
+ font-size: 16px !important;
+ color: var(--text-color);
+ .tag-icon {
+ --icon-stroke: var(--gray-500);
+ }
+ }
+
+ .w-30-40 {
+ width: 30%;
+
+ @media (max-width: 992px) {
+ width: 40%;
+ }
+ }
+ }
+
+ .tab-content {
+ font-size: 14px;
+ }
+}
+
+// Item Recommendations
+.recommended-item-section {
+ padding-right: 0;
+
+ .recommendation-header {
+ font-size: 16px;
+ font-weight: 500
+ }
+
+ .recommendation-container {
+ padding: .5rem;
+ min-height: 0px;
+
+ .r-item-image {
+ width: 40%;
+
+ .no-image-r-item {
+ display: flex; justify-content: center;
+ background-color: var(--gray-200);
+ align-items: center;
+ color: var(--gray-400);
+ margin-top: .15rem;
+ border-radius: 6px;
+ height: 100%;
+ font-size: 24px;
+ }
+ }
+
+ .r-item-info {
+ font-size: 14px;
+ padding-left: 8px;
+ padding-right: 0;
+ width: 60%;
+
+ a {
+ color: var(--gray-800);
+ font-weight: 400;
+ }
+
+ .item-price {
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--text-color);
+ }
+
+ .striked-item-price {
+ font-weight: 500;
+ color: var(--gray-500);
+ }
+ }
}
}
.product-code {
+ padding: .5rem 0;
color: var(--text-muted);
font-size: 14px;
+ .product-item-group {
+ padding-right: .25rem;
+ border-right: solid 1px var(--text-muted);
+ }
+
+ .product-item-code {
+ padding-left: .5rem;
+ }
}
.item-configurator-dialog {
@@ -794,6 +885,12 @@
}
}
+.like-action-item-fp {
+ visibility: visible !important;
+ position: unset;
+ float: right;
+}
+
.like-animate {
animation: expand cubic-bezier(0.04, 0.4, 0.5, 0.95) 1.6s forwards 1;
}
@@ -919,16 +1016,63 @@
.item-website-specification {
font-size: .875rem;
+ .product-title {
+ font-size: 18px;
+ }
+
+ .table {
+ width: 70%;
+ }
+
+ td {
+ border: none !important;
+ }
+
+ .spec-label {
+ color: var(--gray-600);
+ }
+
+ .spec-content {
+ color: var(--gray-800);
+ }
+}
+
+.reviews-full-page {
+ padding: 1rem 2rem;
}
.ratings-reviews-section {
border-top: 1px solid #E2E6E9;
+ padding: .5rem 1rem;
}
.reviews-header {
font-size: 20px;
font-weight: 600;
color: var(--gray-800);
+ display: flex;
+ align-items: center;
+ padding: 0;
+}
+
+.btn-write-review {
+ float: right;
+ padding: .5rem 1rem;
+ font-size: 14px;
+ font-weight: 400;
+ border: none !important;
+ box-shadow: none;
+
+ color: var(--gray-900);
+ background-color: var(--gray-100);
+
+ &:hover {
+ box-shadow: var(--btn-shadow);
+ }
+}
+
+.rating-summary-section {
+ display: flex;
}
.rating-summary-title {
@@ -936,9 +1080,17 @@
font-size: 18px;
}
+.rating-summary-numbers {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ border-right: solid 1px var(--gray-100);
+}
+
.user-review-title {
margin-top: 0.15rem;
- font-size: 16px;
+ font-size: 15px;
font-weight: 600;
}
@@ -952,6 +1104,12 @@
}
}
+.ratings-pill {
+ background-color: var(--gray-100);
+ padding: .5rem 1rem;
+ border-radius: 66px;
+}
+
.review {
max-width: 80%;
line-height: 1.6;
@@ -961,21 +1119,18 @@
.review-signature {
display: flex;
- font-size: 14px;
+ font-size: 13px;
color: var(--gray-500);
font-weight: 400;
.reviewer {
padding-right: 8px;
- margin-right: 8px;
- border-right: 1px solid var(--gray-400);
+ color: var(--gray-600);
}
}
.rating-progress-bar-section {
padding-bottom: 2rem;
- border-bottom: 1px solid #E2E6E9;
- margin-right: -10px;
.rating-bar-title {
margin-left: -15px;
@@ -985,14 +1140,15 @@
margin-bottom: 4px;
height: 7px;
margin-top: 6px;
+
+ .progress-bar-cosmetic {
+ background-color: var(--gray-600);
+ border-radius: var(--border-radius);
+ }
}
}
.offer-container {
- border: 1px solid var(--gray-300);
- border-style: dashed;
- border-radius: 4px;
- padding: 6px;
font-size: 14px;
}
@@ -1075,3 +1231,12 @@
.font-md {
font-size: 14px !important;
}
+
+.in-green {
+ color: var(--green-info) !important;
+ font-weight: 500;
+}
+
+.mt-minus-2 {
+ margin-top: -2rem;
+}
diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html
index 427e568..e19cfb0 100644
--- a/erpnext/templates/generators/item/item.html
+++ b/erpnext/templates/generators/item/item.html
@@ -1,4 +1,5 @@
{% extends "templates/web.html" %}
+{% from "erpnext/templates/includes/macros.html" import recommended_item_row %}
{% block title %} {{ title }} {% endblock %}
@@ -9,7 +10,7 @@
{% endblock %}
{% block page_content %}
-<div class="product-container col-md-12">
+<div class="product-container item-main">
{% from "erpnext/templates/includes/macros.html" import product_image %}
<div class="item-content">
<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
@@ -18,33 +19,56 @@
{% include "templates/generators/item/item_image.html" %}
{% include "templates/generators/item/item_details.html" %}
</div>
-
- <!-- Product Specifications Table Section -->
- {% if show_tabs and tabs %}
- <div class="category-tabs">
- <!-- tabs -->
- {{ web_block(
- "Section with Tabs",
- values=tabs,
- add_container=0,
- add_top_padding=0,
- add_bottom_padding=0
- ) }}
- </div>
- {% elif website_specifications %}
- {% include "templates/generators/item/item_specifications.html"%}
- {% endif %}
-
- <!-- Advanced Custom Website Content -->
- {{ doc.website_content or '' }}
-
- <!-- Reviews and Comments -->
- {% if shopping_cart.cart_settings.enable_reviews and not doc.has_variants %}
- {% include "templates/generators/item/item_reviews.html"%}
- {% endif %}
</div>
</div>
</div>
+
+<!-- Additional Info/Reviews, Recommendations -->
+<div class="d-flex">
+ {% set show_recommended_items = recommended_items and shopping_cart.cart_settings.enable_recommendations %}
+ {% set info_col = 'col-9' if show_recommended_items else 'col-12' %}
+
+ {% set padding_top = 'pt-0' if (show_tabs and tabs) else '' %}
+
+ <div class="product-container mt-4 {{ padding_top }} {{ info_col }}">
+ <div class="item-content {{ 'mt-minus-2' if (show_tabs and tabs) else '' }}">
+ <div class="product-page-content" itemscope itemtype="http://schema.org/Product">
+ <!-- Product Specifications Table Section -->
+ {% if show_tabs and tabs %}
+ <div class="category-tabs">
+ <!-- tabs -->
+ {{ web_block("Section with Tabs", values=tabs, add_container=0,
+ add_top_padding=0, add_bottom_padding=0)
+ }}
+ </div>
+ {% elif website_specifications %}
+ {% include "templates/generators/item/item_specifications.html"%}
+ {% endif %}
+
+ <!-- Advanced Custom Website Content -->
+ {{ doc.website_content or '' }}
+
+ <!-- Reviews and Comments -->
+ {% if shopping_cart.cart_settings.enable_reviews and not doc.has_variants %}
+ {% include "templates/generators/item/item_reviews.html"%}
+ {% endif %}
+ </div>
+ </div>
+ </div>
+
+ <!-- Recommended Items -->
+ {% if show_recommended_items %}
+ <div class="mt-4 col-3 recommended-item-section">
+ <span class="recommendation-header">Recommended</span>
+ <div class="product-container mt-2 recommendation-container">
+ {% for item in recommended_items %}
+ {{ recommended_item_row(item) }}
+ {% endfor %}
+ </div>
+ </div>
+ {% endif %}
+
+</div>
{% endblock %}
{% block base_scripts %}
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html
index fd243f5..d52168e 100644
--- a/erpnext/templates/generators/item/item_add_to_cart.html
+++ b/erpnext/templates/generators/item/item_add_to_cart.html
@@ -7,34 +7,39 @@
<div class="col-md-12">
<!-- Price and Availability -->
{% if cart_settings.show_price and product_info.price %}
- {% set price_info = product_info.price %}
+ {% set price_info = product_info.price %}
- {% if price_info.formatted_mrp %}
- <small class="formatted-price">
- M.R.P.:
- <s>{{ price_info.formatted_mrp }}</s>
- </small>
- <small class="ml-2 formatted-price" style="color: #F47A7A; font-weight: 500;">
- {{ price_info.get("formatted_discount_percent") or price_info.get("formatted_discount_rate")}} OFF
- </small>
- {% endif %}
+ <div class="product-price">
+ <!-- Final Price -->
+ {{ price_info.formatted_price_sales_uom }}
- <div class="product-price">
- {{ price_info.formatted_price_sales_uom }}
- <small class="formatted-price">({{ price_info.formatted_price }} / {{ product_info.uom }})</small>
- </div>
+ <!-- Striked Price and Discount -->
+ {% if price_info.formatted_mrp %}
+ <small class="formatted-price">
+ <s>MRP {{ price_info.formatted_mrp }}</s>
+ </small>
+ <small class="ml-1 formatted-price in-green">
+ -{{ price_info.get("formatted_discount_percent") or price_info.get("formatted_discount_rate")}}
+ </small>
+ {% endif %}
+
+ <!-- Price per UOM -->
+ <small class="formatted-price ml-2">
+ ({{ price_info.formatted_price }} / {{ product_info.uom }})
+ </small>
+ </div>
{% else %}
{{ _("UOM") }} : {{ product_info.uom }}
{% endif %}
{% if cart_settings.show_stock_availability %}
- <div>
+ <div class="mt-2">
{% if product_info.in_stock == 0 %}
<span class="text-danger no-stock">
{{ _('Not in stock') }}
</span>
{% elif product_info.in_stock == 1 %}
- <span class="text-success has-stock">
+ <span class="in-green has-stock">
{{ _('In stock') }}
{% if product_info.show_stock_qty and product_info.stock_qty %}
({{ product_info.stock_qty[0][0] }})
@@ -47,10 +52,15 @@
<!-- Offers -->
{% if doc.offers %}
<br>
- <h3>Offers</h3>
+ <div class="offers-heading mb-4">
+ <span class="mr-1 tag-icon">
+ <svg class="icon icon-lg"><use href="#icon-tag"></use></svg>
+ </span>
+ <b>Available Offers</b>
+ </div>
<div class="offer-container">
{% for offer in doc.offers %}
- <div class="mt-2" style="display: flex;">
+ <div class="mt-2 d-flex">
<div class="mr-2" >
<svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--yellow-500)" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
@@ -59,8 +69,8 @@
<path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
- <p class="mr-1">
- <strong>{{ _(offer.offer_title) }}:</strong>
+ <p class="mr-1 mb-1">
+ {{ _(offer.offer_title) }}:
{{ _(offer.offer_subtitle) }}
<a class="offer-details" href="#"
data-offer-title="{{ offer.offer_title }}" data-offer-id="{{ offer.name }}"
@@ -74,20 +84,14 @@
{% endif %}
<!-- Add to Cart / View in Cart, Contact Us -->
- <div class="mt-5 mb-5">
- <div style="display: flex;" class="mb-4">
+ <div class="mt-6 mb-5">
+ <div class="mb-4 d-flex">
<!-- Add to Cart -->
{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
- <a href="/cart"
- class="btn btn-light btn-view-in-cart hidden mr-2 font-md"
- role="button"
- >
+ <a href="/cart" class="btn btn-light btn-view-in-cart hidden mr-2 font-md" role="button">
{{ _("View in Cart") }}
</a>
- <button
- data-item-code="{{item_code}}"
- class="btn btn-primary btn-add-to-cart w-50 mr-2"
- >
+ <button data-item-code="{{item_code}}" class="btn btn-primary btn-add-to-cart mr-2 w-30-40">
<span class="mr-2">
<svg class="icon icon-md">
<use href="#icon-assets"></use>
@@ -97,41 +101,11 @@
</button>
{% endif %}
- <!-- Add to Wishlist -->
- {% if cart_settings.enable_wishlist %}
- <a href="/wishlist"
- class="btn btn-view-in-wishlist font-md hidden"
- role="button"
- >
- <span class="mr-2">
- <svg class="icon icon-md">
- <use href="#icon-heart"></use>
- </svg>
- </span>
- {{ _("View in Wishlist") }}
- </a>
-
- {% set price = product_info.get("price") or {} %}
- <button
- data-item-code="{{item_code}}"
- data-price="{{ price.get('price_list_rate') or 0}}"
- data-formatted-price="{{ price.get('formatted_price') or 0 }}"
- class="btn btn-add-to-wishlist font-md"
- >
- <span class="mr-2">
- <svg class="icon icon-md">
- <use href="#icon-heart"></use>
- </svg>
- </span>
- {{ _("Add to Wishlist") }}
- </button>
+ <!-- Contact Us -->
+ {% if cart_settings.show_contact_us_button %}
+ {% include "templates/generators/item/item_inquiry.html" %}
{% endif %}
</div>
-
- <!-- Contact Us -->
- {% if cart_settings.show_contact_us_button %}
- {% include "templates/generators/item/item_inquiry.html" %}
- {% endif %}
</div>
</div>
</div>
@@ -155,28 +129,6 @@
});
});
- $('.page_content').on('click', '.btn-add-to-wishlist', (e) => {
- // Bind action on wishlist button
- const $btn = $(e.currentTarget);
- $btn.prop('disabled', true);
-
- let args = {
- item_code: $btn.data('item-code'),
- price: $btn.data('price'),
- formatted_price: $btn.data('formatted-price')
- };
- let failure_action = function() {
- $btn.prop('disabled', false);
- };
- let success_action = function() {
- $btn.prop('disabled', false);
- e_commerce.wishlist.set_wishlist_count();
- $('.btn-add-to-wishlist, .btn-view-in-wishlist').toggleClass('hidden');
-
- };
- e_commerce.wishlist.add_remove_from_wishlist("add", args, success_action, failure_action);
- });
-
$('.page_content').on('click', '.offer-details', (e) => {
// Bind action on More link in Offers
const $btn = $(e.currentTarget);
diff --git a/erpnext/templates/generators/item/item_details.html b/erpnext/templates/generators/item/item_details.html
index cf6e2b9..307e0fe 100644
--- a/erpnext/templates/generators/item/item_details.html
+++ b/erpnext/templates/generators/item/item_details.html
@@ -1,28 +1,65 @@
{% set width_class = "expand" if not slides else "" %}
+{% set cart_settings = shopping_cart.cart_settings %}
+{% set product_info = shopping_cart.product_info %}
+{% set price_info = product_info.get('price') or {} %}
+
<div class="col-md-7 product-details {{ width_class }}">
-<!-- title -->
-<h1 class="product-title" itemprop="name">
- {{ doc.web_item_name }}
-</h1>
-<p class="product-code">
- <span>{{ _("Item Code") }}:</span>
- <span itemprop="productID">{{ doc.item_code }}</span>
-</p>
-{% if has_variants %}
- <!-- configure template -->
- {% include "templates/generators/item/item_configure.html" %}
-{% else %}
- <!-- add variant to cart -->
- {% include "templates/generators/item/item_add_to_cart.html" %}
-{% endif %}
-<!-- description -->
-<div class="product-description" itemprop="description">
-{% if frappe.utils.strip_html(doc.web_long_description or '') %}
- {{ doc.web_long_description | safe }}
-{% elif frappe.utils.strip_html(doc.description or '') %}
- {{ doc.description | safe }}
-{% else %}
- {{ _("No description given") }}
-{% endif %}
+ <div class="d-flex">
+ <!-- title -->
+ <div class="product-title col-11" itemprop="name">
+ {{ doc.web_item_name }}
+ </div>
+
+ <!-- Wishlist -->
+ {% if cart_settings.enable_wishlist %}
+ <div class="like-action-item-fp like-action {{ 'like-action-wished' if wished else ''}} ml-2"
+ data-item-code="{{ doc.item_code }}"
+ data-price="{{ price_info.get('price_list_rate') if price_info else 0 }}"
+ data-formatted-price="{{ price_info.get('formatted_price') if price_info else 0 }}">
+ <svg class="icon sm">
+ <use class="{{ 'wished' if wished else 'not-wished' }} wish-icon" href="#icon-heart"></use>
+ </svg>
+ </div>
+ {% endif %}
+ </div>
+
+ <p class="product-code">
+ <span class="product-item-group">
+ {{ _(doc.item_group) }}
+ </span>
+ <span class="product-item-code">
+ {{ _("Item Code") }}:
+ </span>
+ <span itemprop="productID">{{ doc.item_code }}</span>
+ </p>
+ {% if has_variants %}
+ <!-- configure template -->
+ {% include "templates/generators/item/item_configure.html" %}
+ {% else %}
+ <!-- add variant to cart -->
+ {% include "templates/generators/item/item_add_to_cart.html" %}
+ {% endif %}
+ <!-- description -->
+ <div class="product-description" itemprop="description">
+ {% if frappe.utils.strip_html(doc.web_long_description or '') %}
+ {{ doc.web_long_description | safe }}
+ {% elif frappe.utils.strip_html(doc.description or '') %}
+ {{ doc.description | safe }}
+ {% else %}
+ {{ "" }}
+ {% endif %}
+ </div>
</div>
-</div>
+
+{% block base_scripts %}
+<!-- js should be loaded in body! -->
+<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
+{% endblock %}
+
+<script>
+ $('.page_content').on('click', '.like-action-item-fp', (e) => {
+ // Bind action on wishlist button
+ const $btn = $(e.currentTarget);
+ e_commerce.wishlist.wishlist_action($btn);
+ });
+</script>
\ No newline at end of file
diff --git a/erpnext/templates/generators/item/item_inquiry.html b/erpnext/templates/generators/item/item_inquiry.html
index 72b4167..af636f1 100644
--- a/erpnext/templates/generators/item/item_inquiry.html
+++ b/erpnext/templates/generators/item/item_inquiry.html
@@ -1,7 +1,7 @@
{% if shopping_cart and shopping_cart.cart_settings.enabled %}
{% set cart_settings = shopping_cart.cart_settings %}
{% if cart_settings.show_contact_us_button | int %}
- <button class="btn btn-inquiry btn-primary-light font-md" data-item-code="{{ doc.name }}">
+ <button class="btn btn-inquiry font-md w-30-40" data-item-code="{{ doc.name }}">
{{ _('Contact Us') }}
</button>
{% endif %}
diff --git a/erpnext/templates/generators/item/item_reviews.html b/erpnext/templates/generators/item/item_reviews.html
index cd38bf3..508dbce 100644
--- a/erpnext/templates/generators/item/item_reviews.html
+++ b/erpnext/templates/generators/item/item_reviews.html
@@ -1,23 +1,29 @@
{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %}
-<div class="mt-12 ratings-reviews-section" style="display: flex;">
- <div class="col-md-4 order-md-1 mt-8" style="max-width: 300px;">
- {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating) }}
+<div class="mt-4 ratings-reviews-section">
+ <!-- Title and Action -->
+ <div class="w-100 mt-4 mb-2 d-flex">
+ <div class="reviews-header col-9">
+ {{ _("Customer Reviews") }}
+ </div>
- <!-- Write a Review for legitimate users -->
- {% if frappe.session.user != "Guest" and user_is_customer %}
- <button class="btn btn-light btn-write-review mr-2 mt-4 mb-4 w-100"
- data-web-item="{{ doc.name }}">
- {{ _("Write a Review") }}
- </button>
- {% endif %}
- </div>
+ <div class="write-a-review-btn col-3">
+ <!-- Write a Review for legitimate users -->
+ {% if frappe.session.user != "Guest" and user_is_customer %}
+ <button class="btn btn-write-review"
+ data-web-item="{{ doc.name }}">
+ {{ _("Write a Review") }}
+ </button>
+ {% endif %}
+ </div>
+ </div>
+
+ <!-- Summary -->
+ {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }}
+
<!-- Reviews and Comments -->
- <div class="col-12 order-2 col-md-9 order-md-2 mt-8 ml-16">
- <h2 class="reviews-header">
- {{ _("Reviews") }}
- </h2>
+ <div class="mt-8">
{% if reviews %}
{{ user_review(reviews) }}
@@ -64,7 +70,7 @@
callback: function(r) {
if(!r.exc) {
frappe.msgprint({
- message: __("Thank you for submitting your review"),
+ message: __("Thank you for the review"),
title: __("Review Submitted"),
indicator: "green"
});
diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html
index f395761..4a59f83 100644
--- a/erpnext/templates/generators/item/item_specifications.html
+++ b/erpnext/templates/generators/item/item_specifications.html
@@ -3,13 +3,15 @@
<div class="mt-5 item-website-specification">
<div class="col-md-11">
{% if not show_tabs %}
- <h3 class="product-title mb-5 mt-8">Product Details</h3>
+ <div class="product-title mb-5 mt-8">
+ Product Details
+ </div>
{% endif %}
- <table class="table table-bordered table-hover">
+ <table class="table">
{% for d in website_specifications -%}
<tr>
- <td class="text-muted" style="width: 30%; font-weight: bold;">{{ d.label }}</td>
- <td>{{ d.description }}</td>
+ <td class="spec-label">{{ d.label }}</td>
+ <td class="spec-content">{{ d.description }}</td>
</tr>
{%- endfor %}
</table>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 8413bb0..2863e15 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -7,8 +7,8 @@
</div>
{% endmacro %}
-{% macro product_image(website_image, css_class="product-image", alt="") %}
- <div class="border text-center rounded {{ css_class }}" style="overflow: hidden;">
+{% macro product_image(website_image, css_class="product-image", alt="", no_border=False) %}
+ <div class="{{ 'border' if not no_border else ''}} text-center rounded {{ css_class }}" style="overflow: hidden;">
{% if website_image %}
<img itemprop="image" class="website-image h-100 w-100" alt="{{ alt }}" src="{{ frappe.utils.quoted(website_image) | abs_url }}">
{% else %}
@@ -208,9 +208,12 @@
</div>
{%- endmacro -%}
-{%- macro ratings_with_title(avg_rating, title, size, rating_header_class) -%}
-<div style="display: flex;">
- <div class="rating">
+{%- macro ratings_with_title(avg_rating, title, size, rating_header_class, for_summary=False) -%}
+<div class="{{ 'd-flex' if not for_summary else '' }}">
+ <p class="mr-4 {{ rating_header_class }}">
+ <span>{{ title }}</span>
+ </p>
+ <div class="rating {{ 'ratings-pill' if for_summary else ''}}">
{% for i in range(1,6) %}
{% set fill_class = 'star-click' if i <= avg_rating else '' %}
<svg class="icon icon-{{ size }} {{ fill_class }}">
@@ -218,44 +221,50 @@
</svg>
{% endfor %}
</div>
- <p class="ml-4 {{ rating_header_class }}">
- <span>{{ title }}</span>
- </p>
</div>
{%- endmacro -%}
-{%- macro ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating)-%}
-<!-- Ratings Summary -->
-<h2 class="reviews-header">
- {{ _("Customer Ratings") }}
-</h2>
-
-{% if reviews %}
- {% set rating_title = frappe.utils.cstr(average_rating) + " " + _("out of 5") %}
- {{ ratings_with_title(average_whole_rating, rating_title, "lg", "rating-summary-title") }}
-{% endif %}
-
-<!-- Rating Progress Bars -->
-<div class="rating-progress-bar-section">
- {% for percent in reviews_per_rating %}
- <div class="mt-4 col-sm-4 small rating-bar-title">
- {{ loop.index }} star
+{%- macro ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=False, total_reviews=None)-%}
+<div class="rating-summary-section mt-4">
+ <div class="rating-summary-numbers col-3">
+ <h2 style="font-size: 2rem;">
+ {{ average_rating or 0 }}
+ </h2>
+ <div class="mb-2" style="margin-top: -.5rem;">
+ {{ frappe.utils.cstr(total_reviews) + " " + _("ratings") }}
</div>
- <div class="row">
- <div class="col-md-7">
- <div class="progress rating-progress-bar" title="{{ percent }} % of reviews are {{ loop.index }} star">
- <div class="progress-bar" role="progressbar"
- aria-valuenow="{{ percent }}"
- aria-valuemin="0" aria-valuemax="100"
- style="width: {{ percent }}%; background-color: var(--text-on-green);">
+
+ <!-- Ratings Summary -->
+ {% if reviews %}
+ {% set rating_title = frappe.utils.cstr(average_rating) + " " + _("out of 5") if not for_summary else ''%}
+ {{ ratings_with_title(average_whole_rating, rating_title, "md", "rating-summary-title", for_summary) }}
+ {% endif %}
+
+ <div class="mt-2">{{ frappe.utils.cstr(average_rating or 0) + " " + _("out of 5") }}</div>
+ </div>
+
+ <!-- Rating Progress Bars -->
+ <div class="rating-progress-bar-section col-4 ml-4">
+ {% for percent in reviews_per_rating %}
+ <div class="col-sm-4 small rating-bar-title">
+ {{ loop.index }} star
+ </div>
+ <div class="row">
+ <div class="col-md-7">
+ <div class="progress rating-progress-bar" title="{{ percent }} % of reviews are {{ loop.index }} star">
+ <div class="progress-bar progress-bar-cosmetic" role="progressbar"
+ aria-valuenow="{{ percent }}"
+ aria-valuemin="0" aria-valuemax="100"
+ style="width: {{ percent }}%;">
+ </div>
</div>
</div>
+ <div class="col-sm-1 small">
+ {{ percent }}%
+ </div>
</div>
- <div class="col-sm-1 small">
- {{ percent }}%
- </div>
- </div>
- {% endfor %}
+ {% endfor %}
+ </div>
</div>
{%- endmacro -%}
@@ -264,17 +273,19 @@
<div class="user-reviews">
{% for review in reviews %}
<div class="mb-3 review">
- {{ ratings_with_title(review.rating, _(review.review_title), "md", "user-review-title") }}
+ {{ ratings_with_title(review.rating, _(review.review_title), "sm", "user-review-title") }}
- <div class="review-signature">
- <span class="reviewer">{{ _(review.customer) }}</span>
- <span>{{ review.published_on }}</span>
- </div>
- <div class="product-description mb-4 mt-4">
+ <div class="product-description mb-4">
<p>
{{ _(review.comment) }}
</p>
</div>
+
+ <div class="review-signature mb-2">
+ <span class="reviewer">{{ _(review.customer) }}</span>
+ <span class="indicator grey" style="--text-on-gray: var(--gray-300);"></span>
+ <span class="reviewer">{{ review.published_on }}</span>
+ </div>
</div>
{% endfor %}
</div>
@@ -347,3 +358,42 @@
</div>
{% endfor %}
{%- endmacro -%}
+
+{%- macro recommended_item_row(item)-%}
+<div class="recommended-item mb-6 d-flex">
+ <div class="r-item-image">
+ {% if item.website_item_thumbnail %}
+ {{ product_image(item.website_item_thumbnail, alt="item.website_item_name", no_border=True) }}
+ {% else %}
+ <div class = "no-image-r-item">
+ {{ frappe.utils.get_abbr(item.website_item_name) or "NA" }}
+ </div>
+ {% endif %}
+ </div>
+ <div class="r-item-info">
+ <a href="/{{ item.route or '#'}}" target="_blank">
+ {% set title = item.website_item_name %}
+ {{ title[:70] + "..." if title|len > 70 else title }}
+ </a>
+
+ {% if item.get('price_info') %}
+ {% set price = item.get('price_info') %}
+ <div class="mt-2">
+ <span class="item-price">
+ {{ price.get('formatted_price') or '' }}
+ </span>
+
+ {% if price.get('formatted_mrp') %}
+ <br>
+ <span class="striked-item-price">
+ <s>MRP {{ price.formatted_mrp }}</s>
+ </span>
+ <span class="in-green">
+ - {{ price.get('formatted_discount_percent') or price.get('formatted_discount_rate')}}
+ </span>
+ {% endif %}
+ </div>
+ {% endif %}
+ </div>
+</div>
+{%- endmacro -%}
diff --git a/erpnext/templates/pages/customer_reviews.html b/erpnext/templates/pages/customer_reviews.html
index e11da3d..3621fcf 100644
--- a/erpnext/templates/pages/customer_reviews.html
+++ b/erpnext/templates/pages/customer_reviews.html
@@ -4,25 +4,30 @@
{% block title %} {{ _("Customer Reviews") }} {% endblock %}
{% block page_content %}
-<div class="product-container col-md-12">
-<div style="display: flex;">
- <div class="col-md-4 order-md-1 mt-8" style="max-width: 300px;">
- {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating) }}
+<div class="product-container reviews-full-page col-md-12">
+ <!-- Title and Action -->
+ <div class="w-100 mb-6 d-flex">
+ <div class="reviews-header col-9">
+ {{ _("Customer Reviews") }}
+ </div>
- <!-- Write a Review for legitimate users -->
- {% if frappe.session.user != "Guest" %}
- <button class="btn btn-light btn-write-review mr-2 mt-4 mb-4 w-100"
- data-web-item="{{ web_item }}">
- {{ _("Write a Review") }}
- </button>
- {% endif %}
+ <div class="write-a-review-btn col-3">
+ <!-- Write a Review for legitimate users -->
+ {% if frappe.session.user != "Guest" and user_is_customer %}
+ <button class="btn btn-write-review"
+ data-web-item="{{ web_item }}">
+ {{ _("Write a Review") }}
+ </button>
+ {% endif %}
+ </div>
</div>
+ <!-- Summary -->
+ {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }}
+
+
<!-- Reviews and Comments -->
- <div class="col-12 order-2 col-md-9 order-md-2 mt-8 ml-16">
- <h2 class="reviews-header">
- {{ _("Reviews") }}
- </h2>
+ <div class="mt-8">
{% if reviews %}
{{ user_review(reviews) }}
@@ -40,7 +45,6 @@
{% endif %}
</div>
</div>
-</div>
{% endblock %}
diff --git a/erpnext/templates/pages/customer_reviews.py b/erpnext/templates/pages/customer_reviews.py
index b9c8a01..2b8ebff 100644
--- a/erpnext/templates/pages/customer_reviews.py
+++ b/erpnext/templates/pages/customer_reviews.py
@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import frappe
from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews
+from erpnext.e_commerce.doctype.website_item.website_item import check_if_user_is_customer
def get_context(context):
context.no_cache = 1
@@ -11,4 +12,5 @@
if frappe.form_dict and frappe.form_dict.get("item_code"):
context.item_code = frappe.form_dict.get("item_code")
context.web_item = frappe.db.get_value("Website Item", {"item_code": context.item_code}, "name")
+ context.user_is_customer = check_if_user_is_customer()
get_item_reviews(context.web_item, 0, 10, context)