From c842305be01cbe6105e8f9b78c4d837db675d8a2 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 7 Apr 2021 23:49:04 +0530 Subject: [PATCH] feat: Customer Ratings & Reviews Full Page - Created macros for repetitive snippets - Created Customer Reviews full page - View more button to reveal 10 more reviews at a time - Common function to get reviews with start and end --- .../doctype/item_review/item_review.json | 18 ++- .../doctype/item_review/item_review.py | 36 ++++- .../doctype/website_item/website_item.py | 25 +--- erpnext/e_commerce/product_query.py | 2 +- erpnext/public/scss/shopping_cart.scss | 1 - .../setup/doctype/item_group/item_group.py | 2 +- .../generators/item/item_reviews.html | 63 ++------ erpnext/templates/includes/macros.html | 56 ++++++++ erpnext/templates/pages/customer_reviews.html | 45 ++++++ erpnext/templates/pages/customer_reviews.js | 135 ++++++++++++++++++ erpnext/templates/pages/customer_reviews.py | 16 +++ erpnext/templates/pages/reviews.html | 0 12 files changed, 316 insertions(+), 83 deletions(-) create mode 100644 erpnext/templates/pages/customer_reviews.html create mode 100644 erpnext/templates/pages/customer_reviews.js create mode 100644 erpnext/templates/pages/customer_reviews.py delete mode 100644 erpnext/templates/pages/reviews.html diff --git a/erpnext/e_commerce/doctype/item_review/item_review.json b/erpnext/e_commerce/doctype/item_review/item_review.json index 918f433935..7b6071b41f 100644 --- a/erpnext/e_commerce/doctype/item_review/item_review.json +++ b/erpnext/e_commerce/doctype/item_review/item_review.json @@ -79,13 +79,14 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-24 22:27:28.094535", + "modified": "2021-04-02 15:56:00.447950", "modified_by": "Administrator", "module": "E-commerce", "name": "Item Review", "owner": "Administrator", "permissions": [ { + "create": 1, "delete": 1, "email": 1, "export": 1, @@ -93,9 +94,11 @@ "read": 1, "report": 1, "role": "System Manager", - "share": 1 + "share": 1, + "write": 1 }, { + "create": 1, "delete": 1, "email": 1, "export": 1, @@ -103,6 +106,17 @@ "read": 1, "report": 1, "role": "Website Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "report": 1, + "role": "Customer", "share": 1 } ], diff --git a/erpnext/e_commerce/doctype/item_review/item_review.py b/erpnext/e_commerce/doctype/item_review/item_review.py index bbb85b3d5d..84a1274d4c 100644 --- a/erpnext/e_commerce/doctype/item_review/item_review.py +++ b/erpnext/e_commerce/doctype/item_review/item_review.py @@ -4,14 +4,48 @@ from __future__ import unicode_literals from datetime import datetime +from six import string_types +import json + import frappe from frappe.model.document import Document - from frappe.contacts.doctype.contact.contact import get_contact_name +from frappe.utils import flt, cint +from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import get_shopping_cart_settings class ItemReview(Document): pass +@frappe.whitelist() +def get_item_reviews(web_item, start, end, data=None): + if not data: + data = frappe._dict() + + settings = get_shopping_cart_settings() + + if settings and settings.get("enable_reviews"): + data.reviews = frappe.db.get_all("Item Review", filters={"website_item": web_item}, + fields=["*"], limit_start=cint(start), limit_page_length=cint(end)) + + rating_data = frappe.db.get_all("Item Review", filters={"website_item": web_item}, + fields=["avg(rating) as average, count(*) as total"])[0] + data.average_rating = flt(rating_data.average, 1) + data.average_whole_rating = flt(data.average_rating, 0) + + # get % of reviews per rating + reviews_per_rating = [] + for i in range(1,6): + count = frappe.db.get_all("Item Review", filters={"website_item": web_item, "rating": i}, + fields=["count(*) as count"])[0].count + + percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0 + reviews_per_rating.append(percent) + + data.reviews_per_rating = reviews_per_rating + data.total_reviews = rating_data.total + + return data + @frappe.whitelist() def add_item_review(web_item, title, rating, comment=None): """ Add an Item Review by a user if non-existent. """ diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index d8ebffcca2..a7c56cf681 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -14,6 +14,7 @@ from frappe.utils import cstr, random_string, cint, flt from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow 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 class WebsiteItem(WebsiteGenerator): website = frappe._dict( @@ -176,7 +177,7 @@ class WebsiteItem(WebsiteGenerator): self.set_metatags(context) self.set_shopping_cart_data(context) self.get_product_details_section(context) - self.get_reviews(context) + get_item_reviews(self.name, 0, 4, context) context.wished = False if frappe.db.exists("Wishlist Items", {"item_code": self.item_code, "parent": frappe.session.user}): @@ -362,28 +363,6 @@ class WebsiteItem(WebsiteGenerator): return tab_values - def get_reviews(self, context): - if context.shopping_cart.cart_settings.enable_reviews: - context.reviews = frappe.db.get_all("Item Review", filters={"item": self.item_code}, - fields=["*"], limit=4) - - rating_data = frappe.db.get_all("Item Review", filters={"item": self.item_code}, - fields=["avg(rating) as average, count(*) as total"])[0] - context.average_rating = rating_data.average - context.average_whole_rating = flt(context.average_rating, 0) - - # get % of reviews per rating - reviews_per_rating = [] - for i in range(1,6): - count = frappe.db.get_all("Item Review", filters={"item": self.item_code, "rating": i}, - fields=["count(*) as count"])[0].count - - percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0 - reviews_per_rating.append(percent) - - context.reviews_per_rating = reviews_per_rating - context.total_reviews = rating_data.total - 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/product_query.py b/erpnext/e_commerce/product_query.py index 6ffab56229..37e91a09e9 100644 --- a/erpnext/e_commerce/product_query.py +++ b/erpnext/e_commerce/product_query.py @@ -67,7 +67,7 @@ class ProductQuery: product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info') if product_info: item.formatted_price = (product_info.get('price') or {}).get('formatted_price') - item.price = product_info['price'].get('price_list_rate') + item.price = (product_info.get('price') or {}).get('price_list_rate') if self.settings.show_stock_availability: if item.get("website_warehouse"): diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 8d7f59de27..905d7e6d49 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -705,7 +705,6 @@ body.product-page { .ratings-reviews-section { border-top: 1px solid #E2E6E9; - display: flex; } .reviews-header { diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index a9e36799ad..cdff775f74 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -170,7 +170,7 @@ def get_item_for_list_in_html(context): def get_parent_item_groups(item_group_name, from_item=False): base_nav_page = {"name": frappe._("Shop by Category"), "route":"/shop-by-category"} - if from_item: + if from_item and frappe.request.environ.get("HTTP_REFERER"): # base page after 'Home' will vary on Item page last_page = frappe.request.environ["HTTP_REFERER"].split('/')[-1] if last_page and last_page in ("shop-by-category", "all-products"): diff --git a/erpnext/templates/generators/item/item_reviews.html b/erpnext/templates/generators/item/item_reviews.html index c271fdb808..f6b1831cda 100644 --- a/erpnext/templates/generators/item/item_reviews.html +++ b/erpnext/templates/generators/item/item_reviews.html @@ -1,47 +1,16 @@ -{% from "erpnext/templates/includes/macros.html" import ratings_with_title %} +{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %} -
- +
-

- {{ _("Customer Ratings") }} -

- - {% 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 %} - - -
- {% for percent in reviews_per_rating %} -
- {{ loop.index }} star -
-
-
-
-
-
-
-
-
- {{ percent }}% -
-
- {% endfor %} -
+ {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating) }} {% if frappe.session.user != "Guest" %} - {% endif %} -
@@ -50,28 +19,14 @@ {{ _("Reviews") }} {% if reviews %} - {% for review in reviews %} - -
- {{ ratings_with_title(review.rating, _(review.review_title), "md", "user-review-title") }} - -
- {{ _(review.customer) }} - {{ review.published_on }} -
-
-

- {{ _(review.comment) }} -

-
-
- {% endfor %} + {{ user_review(reviews) }} {% if total_reviews > 4 %} {% endif %} + {% else %}
{{ _("No Reviews") }} @@ -96,7 +51,6 @@ ], primary_action: function() { var data = d.get_values(); - $btn.prop('hidden', true); frappe.call({ method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review", args: { @@ -115,6 +69,7 @@ indicator: "green" }); d.hide(); + location.reload(); } } }); diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index e05bc636fd..cd29494e03 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -236,3 +236,59 @@

{%- endmacro -%} + +{%- macro ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating)-%} + +

+ {{ _("Customer Ratings") }} +

+ +{% 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 %} + + +
+ {% for percent in reviews_per_rating %} +
+ {{ loop.index }} star +
+
+
+
+
+
+
+
+
+ {{ percent }}% +
+
+ {% endfor %} +
+{%- endmacro -%} + +{%- macro user_review(reviews)-%} + +
+ {% for review in reviews %} +
+ {{ ratings_with_title(review.rating, _(review.review_title), "md", "user-review-title") }} + +
+ {{ _(review.customer) }} + {{ review.published_on }} +
+
+

+ {{ _(review.comment) }} +

+
+
+ {% endfor %} +
+{%- endmacro -%} diff --git a/erpnext/templates/pages/customer_reviews.html b/erpnext/templates/pages/customer_reviews.html new file mode 100644 index 0000000000..9d8ba9e304 --- /dev/null +++ b/erpnext/templates/pages/customer_reviews.html @@ -0,0 +1,45 @@ +{% extends "templates/web.html" %} +{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %} + +{% block title %} {{ _("Customer Reviews") }} {% endblock %} + +{% block page_content %} +
+
+
+ {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating) }} + + + {% if frappe.session.user != "Guest" %} + + {% endif %} +
+ + +
+

+ {{ _("Reviews") }} +

+ {% if reviews %} + {{ user_review(reviews) }} + + {% if not reviews | len >= total_reviews %} + + {% endif %} + + {% else %} +
+ {{ _("No Reviews") }} +
+ {% endif %} +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/customer_reviews.js b/erpnext/templates/pages/customer_reviews.js new file mode 100644 index 0000000000..453b96a168 --- /dev/null +++ b/erpnext/templates/pages/customer_reviews.js @@ -0,0 +1,135 @@ +$(() => { + class CustomerReviews { + constructor() { + this.bind_button_actions(); + this.start = 0; + this.page_length = 10; + } + + bind_button_actions() { + this.write_review(); + this.view_more(); + } + + write_review() { + //TODO: make dialog popup on stray page + $('.page_content').on('click', '.btn-write-review', (e) => { + // Bind action on write a review button + const $btn = $(e.currentTarget); + + let d = new frappe.ui.Dialog({ + title: __("Write a Review"), + fields: [ + {fieldname: "title", fieldtype: "Data", label: "Headline", reqd: 1}, + {fieldname: "rating", fieldtype: "Rating", label: "Overall Rating", reqd: 1}, + {fieldtype: "Section Break"}, + {fieldname: "comment", fieldtype: "Small Text", label: "Your Review"} + ], + primary_action: function() { + let me = this; + let data = d.get_values(); + frappe.call({ + method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review", + args: { + web_item: $btn.attr('data-web-item'), + title: data.title, + rating: data.rating, + comment: data.comment + }, + freeze: true, + freeze_message: __("Submitting Review ..."), + callback: (r) => { + if(!r.exc) { + frappe.msgprint({ + message: __("Thank you for submitting your review"), + title: __("Review Submitted"), + indicator: "green" + }); + d.hide(); + location.reload(); + } + } + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }); + } + + view_more() { + $('.page_content').on('click', '.btn-view-more', (e) => { + // Bind action on view more button + const $btn = $(e.currentTarget); + $btn.prop('disabled', true); + + this.start += this.page_length; + let me = this; + + frappe.call({ + method: "erpnext.e_commerce.doctype.item_review.item_review.get_item_reviews", + args: { + web_item: $btn.attr('data-web-item'), + start: me.start, + end: me.page_length + }, + callback: (result) => { + if(result.message) { + let res = result.message; + me.get_user_review_html(res.reviews); + + $btn.prop('disabled', false); + if (res.total_reviews <= (me.start + me.page_length)) { + $btn.hide(); + } + + } + } + }) + }); + + } + + get_user_review_html(reviews) { + let me = this; + let $content = $('.user-reviews'); + + reviews.forEach((review) => { + $content.append(` +
+
+
+ ${me.get_review_stars(review.rating)} +
+

+ ${__(review.review_title)} +

+
+
+ ${__(review.customer)} + ${__(review.published_on)} +
+
+

+ ${__(review.comment)} +

+
+
+ `); + }); + } + + get_review_stars(rating) { + let stars = ``; + for(let i = 1; i < 6; i++) { + let fill_class = i <= rating ? 'star-click' : ''; + stars += ` + + `; + } + return stars; + } + } + + new CustomerReviews(); +}); \ No newline at end of file diff --git a/erpnext/templates/pages/customer_reviews.py b/erpnext/templates/pages/customer_reviews.py new file mode 100644 index 0000000000..3bb0142d0a --- /dev/null +++ b/erpnext/templates/pages/customer_reviews.py @@ -0,0 +1,16 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals + +no_cache = 1 + +import frappe +from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews + +def get_context(context): + context.full_page = True + context.reviews = None + 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") + get_item_reviews(context.web_item, 0, 10, context) diff --git a/erpnext/templates/pages/reviews.html b/erpnext/templates/pages/reviews.html deleted file mode 100644 index e69de29bb2..0000000000