From 96cc5068b29ae5a20d7f9dd3608a6ce05db04a09 Mon Sep 17 00:00:00 2001 From: marination Date: Sun, 14 Mar 2021 17:28:49 +0530 Subject: [PATCH] feat: Wishlist from card actions - Add remove items from wishlist - Wishlist icon at nav bar - Animate wishlist icon in card and navbar - Remember wished state after refresh as well --- .../e_commerce/doctype/wishlist/wishlist.py | 46 +++++++++++- .../wishlist_items/wishlist_items.json | 16 ++++- erpnext/e_commerce/product_query.py | 6 ++ erpnext/e_commerce/shopping_cart/cart.py | 2 +- erpnext/public/build.json | 3 +- erpnext/public/js/wishlist.js | 39 ++++++++++ erpnext/public/scss/shopping_cart.scss | 53 +++++++++----- erpnext/templates/includes/macros.html | 7 +- .../includes/navbar/navbar_items.html | 10 ++- erpnext/www/all-products/index.js | 72 +++++++++++++++++-- 10 files changed, 224 insertions(+), 30 deletions(-) create mode 100644 erpnext/public/js/wishlist.js diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py index 94e2754f88b..7527c6f4157 100644 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.py +++ b/erpnext/e_commerce/doctype/wishlist/wishlist.py @@ -3,8 +3,52 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document class Wishlist(Document): pass + +@frappe.whitelist() +def add_to_wishlist(item_code, price): + """Insert Item into wishlist.""" + web_item_data = frappe.db.get_value("Website Item", {"item_code": item_code}, + ["image", "website_warehouse", "name", "item_name"], as_dict=1) + + wished_item_dict = { + "item_code": item_code, + "item_name": web_item_data.get("item_name"), + "website_item": web_item_data.get("name"), + "price": frappe.utils.flt(price), + "image": web_item_data.get("image"), + "website_warehouse": web_item_data.get("website_warehouse") + } + + if not frappe.db.exists("Wishlist", frappe.session.user): + # initialise wishlist + wishlist = frappe.get_doc({"doctype": "Wishlist"}) + wishlist.user = frappe.session.user + wishlist.append("items", wished_item_dict) + wishlist.save(ignore_permissions=True) + else: + wishlist = frappe.get_doc("Wishlist", frappe.session.user) + item = wishlist.append('items', wished_item_dict) + item.db_insert() + + if hasattr(frappe.local, "cookie_manager"): + frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items))) + +@frappe.whitelist() +def remove_from_wishlist(item_code): + if frappe.db.exists("Wishlist Items", {"item_code": item_code}): + frappe.db.sql(""" + delete + from `tabWishlist Items` + where item_code=%(item_code)s + """%{"item_code": frappe.db.escape(item_code)}) + + frappe.db.commit() + + wishlist = frappe.get_doc("Wishlist", frappe.session.user) + if hasattr(frappe.local, "cookie_manager"): + frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items))) \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json index 29f40660a45..18065a8861c 100644 --- a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json +++ b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json @@ -12,6 +12,8 @@ "item_details_section", "description", "column_break_7", + "section_break_8", + "price", "image", "image_view", "warehouse_section", @@ -52,6 +54,7 @@ }, { "fetch_from": "item_code.description", + "fetch_if_empty": 1, "fieldname": "description", "fieldtype": "Text Editor", "label": "Description" @@ -62,6 +65,7 @@ }, { "fetch_from": "item_code.image", + "fetch_if_empty": 1, "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -69,6 +73,7 @@ }, { "fetch_from": "item_code.image", + "fetch_if_empty": 1, "fieldname": "image_view", "fieldtype": "Image", "hidden": 1, @@ -87,12 +92,21 @@ "in_list_view": 1, "label": "Warehouse", "options": "Warehouse" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, + { + "fieldname": "price", + "fieldtype": "Float", + "label": "Price" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-10 19:13:41.310816", + "modified": "2021-03-12 18:23:03.487891", "modified_by": "Administrator", "module": "E-commerce", "name": "Wishlist Items", diff --git a/erpnext/e_commerce/product_query.py b/erpnext/e_commerce/product_query.py index 9743b768a54..28d33e6e81d 100644 --- a/erpnext/e_commerce/product_query.py +++ b/erpnext/e_commerce/product_query.py @@ -67,6 +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') if self.settings.show_stock_availability and item.get("website_warehouse"): stock_qty = frappe.utils.flt( @@ -78,6 +79,11 @@ class ProductQuery: "actual_qty") ) item.in_stock = "green" if stock_qty else "red" + + item.wished = False + if frappe.db.exists("Wishlist Items", {"item_code": item.item_code}): + item.wished = True + return result def query_items(self, conditions, or_conditions, substitutions, start=0): diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index c2c94a36644..011f29cdc6f 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -138,7 +138,7 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False): "additional_notes": additional_notes }) else: - quotation_items[0].qty = qty + 1 + quotation_items[0].qty = qty quotation_items[0].additional_notes = additional_notes apply_cart_settings(quotation=quotation) diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 6b70dab8037..a8911216f72 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -11,7 +11,8 @@ ], "js/erpnext-web.min.js": [ "public/js/website_utils.js", - "public/js/shopping_cart.js" + "public/js/shopping_cart.js", + "public/js/wishlist.js" ], "css/erpnext-web.css": [ "public/scss/website.scss", diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js new file mode 100644 index 00000000000..328bdb996df --- /dev/null +++ b/erpnext/public/js/wishlist.js @@ -0,0 +1,39 @@ +frappe.provide("erpnext.e_commerce"); +var wishlist = erpnext.e_commerce; + +frappe.ready(function() { + $(".wishlist").toggleClass('hidden', true); + wishlist.set_wishlist_count(); +}); + +$.extend(wishlist, { + set_wishlist_count: function() { + var wish_count = frappe.get_cookie("wish_count"); + if(frappe.session.user==="Guest") { + wish_count = 0; + } + + if(wish_count) { + $(".wishlist").toggleClass('hidden', false); + } + + var $wishlist = $('.wishlist-icon'); + var $badge = $wishlist.find("#wish-count"); + + if(parseInt(wish_count) === 0 || wish_count === undefined) { + $wishlist.css("display", "none"); + } + else { + $wishlist.css("display", "inline"); + } + if(wish_count) { + $badge.html(wish_count); + $wishlist.addClass('cart-animate'); + setTimeout(() => { + $wishlist.removeClass('cart-animate'); + }, 500); + } else { + $badge.remove(); + } + } +}); \ No newline at end of file diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 8380f6cf338..3d66f146c0c 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -349,20 +349,20 @@ body.product-page { } } -.cart-icon { - .cart-badge { - position: relative; - top: -10px; - left: -12px; - background: var(--red-600); - width: 16px; - align-items: center; - height: 16px; - font-size: 10px; - border-radius: 50%; - } + +.shopping-badge { + position: relative; + top: -10px; + left: -12px; + background: var(--red-600); + width: 16px; + align-items: center; + height: 16px; + font-size: 10px; + border-radius: 50%; } + .cart-animate { animation: wiggle 0.5s linear; } @@ -555,7 +555,28 @@ body.product-page { margin-left: 12px; } -.wish-icon { +.like-animate { + animation: expand cubic-bezier(0.04, 0.4, 0.5, 0.95) 1.6s forwards 1; +} + +@keyframes expand { + 30% { + transform: scale(1.6); + } + 50% { + transform: scale(0.8); + } + 70% { + transform: scale(1.3); + } + 100% { + transform: scale(1); + } + } + +@keyframes heart { 0%, 17.5% { font-size: 0; } } + +.not-wished { cursor: pointer; stroke: #F47A7A !important; @@ -565,10 +586,8 @@ body.product-page { } .wished { - .wish-icon { - stroke: none; - fill: #F47A7A !important; - } + stroke: none; + fill: #F47A7A !important; } .list-row-checkbox { diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index 818316c0cf9..743daaf7e19 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -127,12 +127,11 @@ {% endif %} {% if not item.has_variants %} -
+ data-item-code="{{ item.item_code }}" data-price="{{ item.price }}"> - + {%- set icon_class = "wished" if item.wished else "not-wished"-%} +
{% endif %} diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html index 291220629c9..54ed98af882 100644 --- a/erpnext/templates/includes/navbar/navbar_items.html +++ b/erpnext/templates/includes/navbar/navbar_items.html @@ -6,7 +6,15 @@ - + + + + {% endblock %} diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js index 4572ee73704..fc1a3f4dc36 100644 --- a/erpnext/www/all-products/index.js +++ b/erpnext/www/all-products/index.js @@ -73,6 +73,11 @@ $(() => { } bind_card_actions() { + this.bind_add_to_cart_action(); + this.bind_wishlist_action(); + } + + bind_add_to_cart_action() { $('.page_content').on('click', '.btn-add-to-cart-list', (e) => { const $btn = $(e.currentTarget); $btn.prop('disabled', true); @@ -91,18 +96,77 @@ $(() => { animate_add_to_cart(button) { // Create 'added to cart' animation let btn_id = "#" + button[0].id; - button.removeClass('not-added'); - button.addClass('added-to-cart'); + this.toggle_button_class(button, 'not-added', 'added-to-cart'); $(btn_id).text('Added to Cart'); // undo setTimeout(() => { - button.removeClass('added-to-cart'); - button.addClass('not-added'); + this.toggle_button_class(button, 'added-to-cart', 'not-added'); $(btn_id).text('Add to Cart'); }, 2000); } + bind_wishlist_action() { + $('.page_content').on('click', '.like-action', (e) => { + const $btn = $(e.currentTarget); + const $wish_icon = $btn.find('.wish-icon'); + let me = this; + + if ($wish_icon.hasClass('wished')) { + // un-wish item + $btn.removeClass("like-animate"); + this.toggle_button_class($wish_icon, 'wished', 'not-wished'); + frappe.call({ + type: "POST", + method: "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist", + args: { + item_code: $btn.data('item-code') + }, + callback: function (r) { + if (r.exc) { + me.toggle_button_class($wish_icon, 'wished', 'not-wished'); + frappe.msgprint({ + message: __("Sorry, something went wrong. Please refresh."), + indicator: "red", + title: __("Note")} + ); + } else { + erpnext.e_commerce.set_wishlist_count(); + } + } + }); + } else { + $btn.addClass("like-animate"); + this.toggle_button_class($wish_icon, 'not-wished', 'wished'); + frappe.call({ + type: "POST", + method: "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist", + args: { + item_code: $btn.data('item-code'), + price: $btn.data('price') + }, + callback: function (r) { + if (r.exc) { + me.toggle_button_class($wish_icon, 'wished', 'not-wished'); + frappe.msgprint({ + message: __("Sorry, something went wrong. Please refresh."), + indicator: "red", + title: __("Note")} + ); + } else { + erpnext.e_commerce.set_wishlist_count(); + } + } + }); + } + }); + } + + toggle_button_class(button, remove, add) { + button.removeClass(remove); + button.addClass(add); + } + bind_search() { $('input[type=search]').on('keydown', (e) => { if (e.keyCode === 13) {