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
diff --git a/erpnext/e_commerce/doctype/item_review/item_review.json b/erpnext/e_commerce/doctype/item_review/item_review.json
index 918f433..7b6071b 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 bbb85b3..84a1274 100644
--- a/erpnext/e_commerce/doctype/item_review/item_review.py
+++ b/erpnext/e_commerce/doctype/item_review/item_review.py
@@ -4,15 +4,49 @@
 
 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. """
 	if not frappe.db.exists("Item Review", {"user": frappe.session.user, "website_item": web_item}):
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py
index d8ebffc..a7c56cf 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.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 @@
 		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 @@
 
 		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 6ffab56..37e91a0 100644
--- a/erpnext/e_commerce/product_query.py
+++ b/erpnext/e_commerce/product_query.py
@@ -67,7 +67,7 @@
 			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 8d7f59d..905d7e6 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -705,7 +705,6 @@
 
 .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 a9e3679..cdff775 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_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 c271fdb..f6b1831 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 %}
 
-<div class="mt-8 ratings-reviews-section">
-	<!-- Ratings Summary -->
+<div class="mt-8 ratings-reviews-section" style="display: flex;">
 	<div class="col-md-4 order-md-1 mt-8" style="max-width: 300px;">
-		<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
-				</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);">
-							</div>
-						</div>
-					</div>
-					<div class="col-sm-1 small">
-						{{ percent }}%
-					</div>
-				</div>
-			{% endfor %}
-		</div>
+		{{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating) }}
 
 		<!-- 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">
+		<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>
 
 	<!-- Reviews and Comments -->
@@ -50,28 +19,14 @@
 			{{ _("Reviews") }}
 		</h2>
 		{% if reviews %}
-			{% for review in reviews %}
-			<!-- User review -->
-			<div class="mb-3 review">
-				{{ ratings_with_title(review.rating, _(review.review_title), "md", "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">
-					<p>
-						{{ _(review.comment) }}
-					</p>
-				</div>
-			</div>
-			{% endfor %}
+			{{ user_review(reviews) }}
 
 			{% if total_reviews > 4 %}
 				<div class="mt-6 mb-6"style="color: var(--primary);">
-					<a href="/reviews">{{ _("View all reviews") }}</a>
+					<a href="/customer_reviews?item_code={{ doc.item_code }}">{{ _("View all reviews") }}</a>
 				</div>
 			{% endif %}
+
 		{% else %}
 			<h6 class="text-muted mt-6">
 				{{ _("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 e05bc63..cd29494 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -236,3 +236,59 @@
 	</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
+		</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);">
+					</div>
+				</div>
+			</div>
+			<div class="col-sm-1 small">
+				{{ percent }}%
+			</div>
+		</div>
+	{% endfor %}
+</div>
+{%- endmacro -%}
+
+{%- macro user_review(reviews)-%}
+<!-- User Reviews -->
+<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") }}
+
+			<div class="review-signature">
+				<span class="reviewer">{{ _(review.customer) }}</span>
+				<span>{{ review.published_on }}</span>
+			</div>
+			<div class="product-description mb-4 mt-4">
+				<p>
+					{{ _(review.comment) }}
+				</p>
+			</div>
+		</div>
+	{% endfor %}
+</div>
+{%- endmacro -%}
diff --git a/erpnext/templates/pages/customer_reviews.html b/erpnext/templates/pages/customer_reviews.html
new file mode 100644
index 0000000..9d8ba9e
--- /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 %}
+<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) }}
+
+		<!-- 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>
+
+	<!-- 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>
+		{% if reviews %}
+			{{ user_review(reviews) }}
+
+			{% if not reviews | len >= total_reviews %}
+				<button class="btn btn-light btn-view-more mr-2 mt-4 mb-4 w-30"
+					data-web-item="{{ web_item }}">
+					{{ _("View More") }}
+				</button>
+			{% endif %}
+
+		{% else %}
+			<h6 class="text-muted mt-6">
+				{{ _("No Reviews") }}
+			</h6>
+		{% endif %}
+	</div>
+</div>
+</div>
+
+{% 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 0000000..453b96a
--- /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(`
+					<div class="mb-3 review">
+						<div style="display: flex;">
+							<div class="rating">
+								${me.get_review_stars(review.rating)}
+							</div>
+							<p class="ml-4 user-review-title">
+								<span>${__(review.review_title)}</span>
+							</p>
+						</div>
+						<div class="review-signature">
+							<span class="reviewer">${__(review.customer)}</span>
+							<span>${__(review.published_on)}</span>
+						</div>
+						<div class="product-description mb-4 mt-4">
+							<p>
+								${__(review.comment)}
+							</p>
+						</div>
+					</div>
+				`);
+			});
+		}
+
+		get_review_stars(rating) {
+			let stars = ``;
+			for(let i = 1; i < 6; i++) {
+				let fill_class = i <= rating ? 'star-click' : '';
+				stars += `<svg class="icon icon-md ${fill_class}">
+						<use href="#icon-star"></use>
+					</svg>`;
+			}
+			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 0000000..3bb0142
--- /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 e69de29..0000000
--- a/erpnext/templates/pages/reviews.html
+++ /dev/null