feat: (wip) Ratings and Reviews
- Added Ratings and Reviews section in Item full page view
- Added provision to write a review with popup
- Created Item Review Doctype to store User-Item unique reviews
- Added privision to enable/disable wishlist and reviews in e commerce settings
- Hide cart and wishlist actions everywhere (even navbar) depending on settings
- Moved some more inline css to scss
- Small logic fixes
TODO: Reviews full page view with paging
diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html
index eebfb92..5f027f7 100644
--- a/erpnext/templates/generators/item/item.html
+++ b/erpnext/templates/generators/item/item.html
@@ -37,6 +37,11 @@
<!-- Advanced Custom Website Content -->
{{ doc.website_content or '' }}
+
+ <!-- Reviews and Comments -->
+ {% if shopping_cart.cart_settings.enable_reviews %}
+ {% include "templates/generators/item/item_reviews.html"%}
+ {% endif %}
</div>
</div>
</div>
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html
index c7713c1..3af360f 100644
--- a/erpnext/templates/generators/item/item_add_to_cart.html
+++ b/erpnext/templates/generators/item/item_add_to_cart.html
@@ -57,34 +57,37 @@
{% endif %}
<!-- Add to Wishlist -->
- <a href="/wishlist"
- class="btn btn-view-in-wishlist hidden"
- role="button"
- >
- <span class="mr-2">
- <svg class="icon icon-md">
- <use href="#icon-heart"></use>
- </svg>
- </span>
- {{ _("View in Wishlist") }}
- </a>
+ {% if cart_settings.enable_wishlist %}
+ <a href="/wishlist"
+ class="btn btn-view-in-wishlist 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
+ {% 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"
>
- <span class="mr-2">
- <svg class="icon icon-md">
- <use href="#icon-heart"></use>
- </svg>
- </span>
- {{ _("Add to Wishlist") }}
- </button>
+ <span class="mr-2">
+ <svg class="icon icon-md">
+ <use href="#icon-heart"></use>
+ </svg>
+ </span>
+ {{ _("Add to Wishlist") }}
+ </button>
+ {% endif %}
</div>
+ <!-- Contact Us -->
{% if cart_settings.show_contact_us_button %}
{% include "templates/generators/item/item_inquiry.html" %}
{% endif %}
diff --git a/erpnext/templates/generators/item/item_reviews.html b/erpnext/templates/generators/item/item_reviews.html
new file mode 100644
index 0000000..c271fdb
--- /dev/null
+++ b/erpnext/templates/generators/item/item_reviews.html
@@ -0,0 +1,127 @@
+{% from "erpnext/templates/includes/macros.html" import ratings_with_title %}
+
+<div class="mt-8 ratings-reviews-section">
+ <!-- Ratings Summary -->
+ <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>
+
+ <!-- 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">
+ {{ _("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 %}
+ {% 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 %}
+
+ {% if total_reviews > 4 %}
+ <div class="mt-6 mb-6"style="color: var(--primary);">
+ <a href="/reviews">{{ _("View all reviews") }}</a>
+ </div>
+ {% endif %}
+ {% else %}
+ <h6 class="text-muted mt-6">
+ {{ _("No Reviews") }}
+ </h6>
+ {% endif %}
+ </div>
+</div>
+
+<script>
+ frappe.ready(() => {
+ $('.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() {
+ var data = d.get_values();
+ $btn.prop('hidden', true);
+ frappe.call({
+ method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review",
+ args: {
+ web_item: "{{ doc.name }}",
+ title: data.title,
+ rating: data.rating,
+ comment: data.comment
+ },
+ freeze: true,
+ freeze_message: __("Submitting Review ..."),
+ callback: function(r) {
+ if(!r.exc) {
+ frappe.msgprint({
+ message: __("Thank you for submitting your review"),
+ title: __("Review Submitted"),
+ indicator: "green"
+ });
+ d.hide();
+ }
+ }
+ });
+ },
+ primary_action_label: __('Submit')
+ });
+ d.show();
+ });
+ });
+</script>
diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html
index 1dccff9..f395761 100644
--- a/erpnext/templates/generators/item/item_specifications.html
+++ b/erpnext/templates/generators/item/item_specifications.html
@@ -1,8 +1,9 @@
-{% if website_specifications -%}
-<div class="row mt-5 item-website-specification">
+<!-- Is reused to render within tabs as well as independently -->
+{% if website_specifications %}
+<div class="mt-5 item-website-specification">
<div class="col-md-11">
{% if not show_tabs %}
- <h2 class="product-title mb-5">Product Details</h2>
+ <h3 class="product-title mb-5 mt-8">Product Details</h3>
{% endif %}
<table class="table table-bordered table-hover">
{% for d in website_specifications -%}
@@ -14,4 +15,4 @@
</table>
</div>
</div>
-{%- endif %}
+{% endif %}
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 2d771b4..e05bc63 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -59,7 +59,7 @@
{% endmacro %}
-{%- macro item_card(item, is_featured=False, is_full_width=False, align="Left") -%}
+{%- macro item_card(item, settings=None, is_featured=False, is_full_width=False, align="Left") -%}
{%- set align_items_class = resolve_class({
'align-items-end': align == 'Right',
'align-items-center': align == 'Center',
@@ -79,12 +79,12 @@
<img class="card-img" src="{{ image }}" alt="{{ title }}">
</div>
<div class="col-md-6">
- {{ item_card_body(title, description, item, is_featured, align) }}
+ {{ item_card_body(title, settings, description, item, is_featured, align) }}
</div>
</div>
{% else %}
<div class="col-md-12">
- {{ item_card_body(title, description, item, is_featured, align) }}
+ {{ item_card_body(title, settings, description, item, is_featured, align) }}
</div>
{% endif %}
</div>
@@ -105,13 +105,13 @@
</div>
</a>
{% endif %}
- {{ item_card_body(title, description, item, is_featured, align) }}
+ {{ item_card_body(title, settings, description, item, is_featured, align) }}
</div>
</div>
{% endif %}
{%- endmacro -%}
-{%- macro item_card_body(title, description, item, is_featured, align) -%}
+{%- macro item_card_body(title, settings, description, item, is_featured, align) -%}
{%- set align_class = resolve_class({
'text-right': align == 'Right',
'text-center': align == 'Center' and not is_featured,
@@ -125,7 +125,7 @@
{% if item.in_stock %}
<span class="indicator {{ item.in_stock }} card-indicator"></span>
{% endif %}
- {% if not item.has_variants %}
+ {% if not item.has_variants and settings.enable_wishlist %}
<div class="like-action"
data-item-code="{{ item.item_code }}"
data-price="{{ item.price }}"
@@ -152,7 +152,7 @@
{{ _('Explore') }}
</div>
</a>
- {% else %}
+ {% elif settings.enabled %}
<div id="{{ item.name }}" class="btn btn-sm btn-add-to-cart-list not-added"
data-item-code="{{ item.item_code }}">
{{ _('Add to Cart') }}
@@ -220,3 +220,19 @@
{% endif %}
</div>
{%- endmacro -%}
+
+{%- macro ratings_with_title(avg_rating, title, size, rating_header_class) -%}
+<div style="display: flex;">
+ <div class="rating">
+ {% for i in range(1,6) %}
+ {% set fill_class = 'star-click' if i <= avg_rating else '' %}
+ <svg class="icon icon-{{ size }} {{ fill_class }}">
+ <use href="#icon-star"></use>
+ </svg>
+ {% endfor %}
+ </div>
+ <p class="ml-4 {{ rating_header_class }}">
+ <span>{{ title }}</span>
+ </p>
+</div>
+{%- endmacro -%}
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index 793bacb..3275521 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -9,12 +9,14 @@
<span class="badge badge-primary shopping-badge" id="cart-count"></span>
</a>
</li>
- <li class="wishlist wishlist-icon hidden">
- <a class="nav-link" href="/wishlist">
- <svg class="icon icon-lg">
- <use href="#icon-heart"></use>
- </svg>
- <span class="badge badge-primary shopping-badge" id="wish-count"></span>
- </a>
- </li>
+ {% if frappe.db.get_single_value("E Commerce Settings", "enable_wishlist") %}
+ <li class="wishlist wishlist-icon hidden">
+ <a class="nav-link" href="/wishlist">
+ <svg class="icon icon-lg">
+ <use href="#icon-heart-active"></use>
+ </svg>
+ <span class="badge badge-primary shopping-badge" id="wish-count"></span>
+ </a>
+ </li>
+ {% endif %}
{% endblock %}
diff --git a/erpnext/templates/pages/reviews.html b/erpnext/templates/pages/reviews.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/templates/pages/reviews.html