From 25ffafae811af51b019261b6e59f32dc76a208bd Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 22 Mar 2021 16:17:59 +0530 Subject: [PATCH] feat: Product Details Tabbed Section and Add to Wishlist in Item Full Page - Add to Wishlist button next to add to cart - Beautified Product Specifications table section - Product Specifications can be optionally in a tabbed section with custom tabs added - Removed hard coded gray bg to allow custom theme overwrites - Fixed resizing issues in Item Full Page view - Cleaned up inline styles and ported to scss --- .../doctype/website_item/website_item.json | 25 ++++++- .../doctype/website_item/website_item.py | 37 ++++++++++- .../website_item_tabbed_section/__init__.py | 0 .../website_item_tabbed_section.json | 37 +++++++++++ .../website_item_tabbed_section.py | 10 +++ .../e_commerce/doctype/wishlist/wishlist.json | 13 ++-- .../e_commerce/doctype/wishlist/wishlist.py | 6 +- .../wishlist_items/wishlist_items.json | 8 ++- erpnext/public/scss/shopping_cart.scss | 57 ++++++++++++---- .../setup/doctype/item_group/item_group.py | 1 - erpnext/templates/generators/item/item.html | 20 +++++- .../generators/item/item_add_to_cart.html | 66 +++++++++++++++++-- .../generators/item/item_details.html | 5 +- .../templates/generators/item/item_image.html | 3 +- .../generators/item/item_specifications.html | 15 +++-- erpnext/templates/includes/macros.html | 6 +- erpnext/www/all-products/index.py | 1 - .../category_card_section.html | 6 +- 18 files changed, 268 insertions(+), 48 deletions(-) create mode 100644 erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py create mode 100644 erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json create mode 100644 erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index 3a2990639b..3b11c0f3ac 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -34,6 +34,9 @@ "copy_from_item_group", "column_break_27", "web_long_description", + "display_additional_information_section", + "show_tabbed_section", + "tabs", "section_break_6", "ranking", "set_meta_tags", @@ -234,7 +237,7 @@ "collapsible": 1, "fieldname": "advanced_display_section", "fieldtype": "Section Break", - "label": "Advanced Display" + "label": "Advanced Display Content" }, { "fieldname": "display_section", @@ -265,13 +268,31 @@ "no_copy": 1, "options": "WEB-ITM-.####", "print_hide": 1 + }, + { + "fieldname": "display_additional_information_section", + "fieldtype": "Section Break", + "label": "Display Additional Information" + }, + { + "depends_on": "show_tabbed_section", + "fieldname": "tabs", + "fieldtype": "Table", + "label": "Tabs", + "options": "Website Item Tabbed Section" + }, + { + "default": "0", + "fieldname": "show_tabbed_section", + "fieldtype": "Check", + "label": "Add Section with Tabs" } ], "has_web_view": 1, "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-02-18 13:23:18.286883", + "modified": "2021-03-18 20:37:54.955364", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index 856b9b7508..7424bc9fe3 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -11,6 +11,7 @@ from frappe import _ from frappe.website.website_generator import WebsiteGenerator 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) @@ -166,7 +167,6 @@ class WebsiteItem(WebsiteGenerator): context.search_link = '/search' context.parents = get_parent_item_groups(self.item_group, from_item=True) - context.body_class = "product-page" self.attributes = frappe.get_all("Item Variant Attribute", fields=["attribute", "attribute_value"], filters={"parent": self.item_code}) @@ -175,6 +175,11 @@ class WebsiteItem(WebsiteGenerator): self.set_disabled_attributes(context) self.set_metatags(context) self.set_shopping_cart_data(context) + self.get_product_details_section(context) + + context.wished = False + if frappe.db.exists("Wishlist Items", {"item_code": self.item_code, "parent": frappe.session.user}): + context.wished = True return context @@ -205,6 +210,12 @@ class WebsiteItem(WebsiteGenerator): context[fieldname] = value + if self.slideshow: + if context.variant and context.variant.slideshow: + context.update(get_slideshow(context.variant)) + else: + context.update(get_slideshow(self)) + def set_attribute_context(self, context): if self.has_variants: attribute_values_available = {} @@ -326,6 +337,30 @@ class WebsiteItem(WebsiteGenerator): row.label = label row.description = desc + def get_product_details_section(self, context): + """ Get section with tabs or website specifications. """ + context.show_tabs = self.show_tabbed_section + if self.show_tabbed_section and self.tabs: + context.tabs = self.get_tabs() + else: + context.website_specifications = self.website_specifications + + def get_tabs(self): + tab_values = {} + tab_values["tab_1_title"] = "Product Details" + tab_values["tab_1_content"] = frappe.render_template( + "templates/generators/item/item_specifications.html", + { + "website_specifications": self.website_specifications, + "show_tabs": self.show_tabbed_section + }) + + for row in self.tabs: + tab_values[f"tab_{row.idx + 1}_title"] = _(row.label) + tab_values[f"tab_{row.idx + 1}_content"] = row.content + + return tab_values + 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/doctype/website_item_tabbed_section/__init__.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json new file mode 100644 index 0000000000..6601dd81f2 --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json @@ -0,0 +1,37 @@ +{ + "actions": [], + "creation": "2021-03-18 20:32:15.321402", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "label", + "content" + ], + "fields": [ + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label" + }, + { + "fieldname": "content", + "fieldtype": "HTML Editor", + "in_list_view": 1, + "label": "Content" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-03-18 20:35:26.991192", + "modified_by": "Administrator", + "module": "E-commerce", + "name": "Website Item Tabbed Section", + "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/website_item_tabbed_section/website_item_tabbed_section.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py new file mode 100644 index 0000000000..8459e625cf --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class WebsiteItemTabbedSection(Document): + pass diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.json b/erpnext/e_commerce/doctype/wishlist/wishlist.json index 7e2c67478d..653c656fcc 100644 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.json +++ b/erpnext/e_commerce/doctype/wishlist/wishlist.json @@ -31,37 +31,32 @@ "options": "Wishlist Items" } ], + "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-10 19:05:52.373601", + "modified": "2021-03-18 16:10:29.534522", "modified_by": "Administrator", "module": "E-commerce", "name": "Wishlist", "owner": "Administrator", "permissions": [ { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "share": 1, - "write": 1 + "share": 1 }, { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "Stock Manager", - "share": 1, - "write": 1 + "share": 1 } ], "sort_field": "modified", diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py index 83bdff6115..eb86dc8f91 100644 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.py +++ b/erpnext/e_commerce/doctype/wishlist/wishlist.py @@ -12,6 +12,10 @@ class Wishlist(Document): @frappe.whitelist() def add_to_wishlist(item_code, price, formatted_price=None): """Insert Item into wishlist.""" + + if frappe.db.exists("Wishlist Items", {"item_code": item_code, "parent": frappe.session.user}): + return + web_item_data = frappe.db.get_value("Website Item", {"item_code": item_code}, ["image", "website_warehouse", "name", "item_name", "item_group", "route"] , as_dict=1) @@ -44,7 +48,7 @@ def add_to_wishlist(item_code, price, formatted_price=None): @frappe.whitelist() def remove_from_wishlist(item_code): - if frappe.db.exists("Wishlist Items", {"item_code": item_code}): + if frappe.db.exists("Wishlist Items", {"item_code": item_code, "parent": frappe.session.user}): frappe.db.sql(""" delete from `tabWishlist Items` diff --git a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json index 0b132737b3..6623921f18 100644 --- a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json +++ b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json @@ -24,6 +24,8 @@ ], "fields": [ { + "fetch_from": "website_item.item_code", + "fetch_if_empty": 1, "fieldname": "item_code", "fieldtype": "Link", "in_list_view": 1, @@ -106,12 +108,16 @@ "label": "Price" }, { + "fetch_from": "item_code.item_group", + "fetch_if_empty": 1, "fieldname": "item_group", "fieldtype": "Link", "label": "Item Group", "options": "Item Group" }, { + "fetch_from": "website_item.route", + "fetch_if_empty": 1, "fieldname": "route", "fieldtype": "Small Text", "label": "Route" @@ -125,7 +131,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-15 16:37:40.405333", + "modified": "2021-03-18 16:04:52.965613", "modified_by": "Administrator", "module": "E-commerce", "name": "Wishlist Items", diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 6a96e41256..126c6fd4a6 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -1,16 +1,13 @@ @import "frappe/public/scss/common/mixins"; +$wish-red: #F47A7A; + body.product-page { background: var(--gray-50); } - .item-breadcrumbs { .breadcrumb-container { - ol.breadcrumb { - background-color: var(--gray-50) !important; - } - a { color: var(--gray-900); } @@ -140,6 +137,7 @@ body.product-page { .item-card { padding: var(--padding-sm); + min-width: 250px; } } @@ -189,14 +187,40 @@ body.product-page { min-height: 70vh; .product-details { - max-width: 40%; - margin-left: -30px; + max-width: 50%; .btn-add-to-cart { font-size: var(--text-base); } } + .expand { + max-width: 100% !important; // expand in absence of slideshow + } + + @media (max-width: 789px) { + .product-details { + max-width: 90% !important; + + .btn-add-to-cart { + font-size: var(--text-base); + } + } + } + + .btn-add-to-wishlist { + svg use { + stroke: var(--wish-red); + } + } + + .btn-view-in-wishlist { + svg use { + fill: var(--wish-red); + stroke: none; + } + } + .product-title { font-size: 24px; font-weight: 600; @@ -578,16 +602,16 @@ body.product-page { .not-wished { cursor: pointer; - stroke: #F47A7A !important; + stroke: var(--wish-red) !important; &:hover { - fill: #F47A7A; + fill: var(--wish-red); } } .wished { stroke: none; - fill: #F47A7A !important; + fill: var(--wish-red) !important; } .list-row-checkbox { @@ -663,12 +687,23 @@ body.product-page { } .remove-wish { + position: absolute; + top:10px; + right: 20px; + border-radius: 50%; + border: 1px solid var(--gray-100); + width: 25px; + height: 25px; + &:hover { background-color: var(--gray-100); - border: 1px solid var(--icon-stroke); } } .wish-removed { display: none; } + +.item-website-specification { + font-size: .875rem; +} diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index b375711b52..c9924e19e2 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -126,7 +126,6 @@ class ItemGroup(NestedSet, WebsiteGenerator): context.no_breadcrumbs = False context.title = self.website_title or self.name - context.body_class = "product-page" return context diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html index 17f6880293..eebfb9247d 100644 --- a/erpnext/templates/generators/item/item.html +++ b/erpnext/templates/generators/item/item.html @@ -9,17 +9,33 @@ {% endblock %} {% block page_content %} -
+
{% from "erpnext/templates/includes/macros.html" import product_image %}
+
{% include "templates/generators/item/item_image.html" %} {% include "templates/generators/item/item_details.html" %}
- {% include "templates/generators/item/item_specifications.html" %} + + {% if show_tabs and tabs %} +
+ + {{ web_block( + "Section with Tabs", + values=tabs, + add_container=0, + add_top_padding=0, + add_bottom_padding=0 + ) }} +
+ {% elif website_specifications %} + {% include "templates/generators/item/item_specifications.html"%} + {% endif %} + {{ doc.website_content or '' }}
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index 167c848eff..c7713c1219 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -5,6 +5,7 @@
+ {% if cart_settings.show_price and product_info.price %}
{{ product_info.price.formatted_price_sales_uom }} @@ -30,17 +31,21 @@ {% endif %}
{% endif %} + +
- {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %} +
+ + {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %} {{ _("View in Cart") }} - {% endif %} + {% endif %} + + + + + {% set price = product_info.get("price") or {} %} + +
+ {% if cart_settings.show_contact_us_button %} {% include "templates/generators/item/item_inquiry.html" %} {% endif %} @@ -60,6 +95,7 @@ diff --git a/erpnext/templates/generators/item/item_details.html b/erpnext/templates/generators/item/item_details.html index 3b77585827..95591294a0 100644 --- a/erpnext/templates/generators/item/item_details.html +++ b/erpnext/templates/generators/item/item_details.html @@ -1,11 +1,12 @@ -
+{% set width_class = "expand" if not slides else "" %} +

{{ item_name }}

{{ _("Item Code") }}: - {{ doc.name }} + {{ doc.item_code }}

{% if has_variants %} diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html index e9b0916a70..4de3b62e74 100644 --- a/erpnext/templates/generators/item/item_image.html +++ b/erpnext/templates/generators/item/item_image.html @@ -1,4 +1,5 @@ -
+{% set column_size = 5 if slides else 4 %} +
{% if slides %}
{% for item in slides %} diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html index d4dfa8e591..1dccff9f92 100644 --- a/erpnext/templates/generators/item/item_specifications.html +++ b/erpnext/templates/generators/item/item_specifications.html @@ -1,10 +1,13 @@ -{% if doc.website_specifications -%} -
-
- - {% for d in doc.website_specifications -%} +{% if website_specifications -%} +
+
+ {% if not show_tabs %} +

Product Details

+ {% endif %} +
+ {% for d in website_specifications -%} - + {%- endfor %} diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index aec201e0d9..77c56495d3 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -172,9 +172,7 @@ {{ title }} -
+
@@ -199,7 +197,7 @@ {%- endmacro -%} {%- macro wishlist_card_body(item, settings) %} -
+
{{ item.item_name or item.item_code or ''}}
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py index d0871fc14e..618ec76cce 100644 --- a/erpnext/www/all-products/index.py +++ b/erpnext/www/all-products/index.py @@ -28,7 +28,6 @@ def get_context(context): context.attribute_filters = filter_engine.get_attribute_filters() context.e_commerce_settings = engine.settings - context.body_class = "product-page" context.page_length = engine.settings.products_per_page or 20 context.no_cache = 1 diff --git a/erpnext/www/shop-by-category/category_card_section.html b/erpnext/www/shop-by-category/category_card_section.html index 532a198661..56cb63a5b6 100644 --- a/erpnext/www/shop-by-category/category_card_section.html +++ b/erpnext/www/shop-by-category/category_card_section.html @@ -2,10 +2,12 @@
{% if image %} - {{ title }} + {{ title }} {% else %}
- AB + + {{ frappe.utils.get_abbr(title) }} +
{% endif %}
{{ d.label }}{{ d.label }} {{ d.description }}