feat: Wishlist Page
- Navbar icon with badge count for wishlist - Wishlist page with cards - Cards can be moved to cart or removed in a click - Separated all wishlist related methods into wishlist.js - Made a common js method(util) to add/remove wishlist items - Bug fix: Make sure items are removed from session user's wishlist
This commit is contained in:
parent
96cc5068b2
commit
59514408b9
@ -10,18 +10,22 @@ class Wishlist(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_to_wishlist(item_code, price):
|
||||
def add_to_wishlist(item_code, price, formatted_price=None):
|
||||
"""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)
|
||||
["image", "website_warehouse", "name", "item_name", "item_group", "route"]
|
||||
, as_dict=1)
|
||||
|
||||
wished_item_dict = {
|
||||
"item_code": item_code,
|
||||
"item_name": web_item_data.get("item_name"),
|
||||
"item_group": web_item_data.get("item_group"),
|
||||
"website_item": web_item_data.get("name"),
|
||||
"price": frappe.utils.flt(price),
|
||||
"formatted_price": formatted_price,
|
||||
"image": web_item_data.get("image"),
|
||||
"website_warehouse": web_item_data.get("website_warehouse")
|
||||
"warehouse": web_item_data.get("website_warehouse"),
|
||||
"route": web_item_data.get("route")
|
||||
}
|
||||
|
||||
if not frappe.db.exists("Wishlist", frappe.session.user):
|
||||
|
@ -9,13 +9,16 @@
|
||||
"website_item",
|
||||
"column_break_3",
|
||||
"item_name",
|
||||
"item_group",
|
||||
"item_details_section",
|
||||
"description",
|
||||
"column_break_7",
|
||||
"section_break_8",
|
||||
"price",
|
||||
"route",
|
||||
"image",
|
||||
"image_view",
|
||||
"section_break_8",
|
||||
"price",
|
||||
"formatted_price",
|
||||
"warehouse_section",
|
||||
"warehouse"
|
||||
],
|
||||
@ -101,12 +104,28 @@
|
||||
"fieldname": "price",
|
||||
"fieldtype": "Float",
|
||||
"label": "Price"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Group",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Route"
|
||||
},
|
||||
{
|
||||
"fieldname": "formatted_price",
|
||||
"fieldtype": "Data",
|
||||
"label": "Formatted Price"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-12 18:23:03.487891",
|
||||
"modified": "2021-03-15 16:37:40.405333",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "Wishlist Items",
|
||||
|
@ -81,7 +81,7 @@ class ProductQuery:
|
||||
item.in_stock = "green" if stock_qty else "red"
|
||||
|
||||
item.wished = False
|
||||
if frappe.db.exists("Wishlist Items", {"item_code": item.item_code}):
|
||||
if frappe.db.exists("Wishlist Items", {"item_code": item.item_code, "parent": frappe.session.user}):
|
||||
item.wished = True
|
||||
|
||||
return result
|
||||
|
@ -186,5 +186,40 @@ $.extend(shopping_cart, {
|
||||
$(".shopping-cart").toggleClass('hidden', r.message ? false : true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
animate_add_to_cart(button) {
|
||||
// Create 'added to cart' animation
|
||||
let btn_id = "#" + button[0].id;
|
||||
this.toggle_button_class(button, 'not-added', 'added-to-cart');
|
||||
$(btn_id).text('Added to Cart');
|
||||
|
||||
// undo
|
||||
setTimeout(() => {
|
||||
this.toggle_button_class(button, 'added-to-cart', 'not-added');
|
||||
$(btn_id).text('Add to Cart');
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
toggle_button_class(button, remove, add) {
|
||||
button.removeClass(remove);
|
||||
button.addClass(add);
|
||||
},
|
||||
|
||||
bind_add_to_cart_action() {
|
||||
$('.page_content').on('click', '.btn-add-to-cart-list', (e) => {
|
||||
const $btn = $(e.currentTarget);
|
||||
$btn.prop('disabled', true);
|
||||
|
||||
this.animate_add_to_cart($btn);
|
||||
|
||||
const item_code = $btn.data('item-code');
|
||||
erpnext.shopping_cart.update_cart({
|
||||
item_code,
|
||||
qty: 1
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -1,13 +1,12 @@
|
||||
frappe.provide("erpnext.e_commerce");
|
||||
var wishlist = erpnext.e_commerce;
|
||||
frappe.provide("erpnext.wishlist");
|
||||
var wishlist = erpnext.wishlist;
|
||||
|
||||
frappe.ready(function() {
|
||||
$(".wishlist").toggleClass('hidden', true);
|
||||
wishlist.set_wishlist_count();
|
||||
});
|
||||
frappe.provide("erpnext.shopping_cart");
|
||||
var shopping_cart = erpnext.shopping_cart;
|
||||
|
||||
$.extend(wishlist, {
|
||||
set_wishlist_count: function() {
|
||||
// set badge count for wishlist icon
|
||||
var wish_count = frappe.get_cookie("wish_count");
|
||||
if(frappe.session.user==="Guest") {
|
||||
wish_count = 0;
|
||||
@ -35,5 +34,127 @@ $.extend(wishlist, {
|
||||
} else {
|
||||
$badge.remove();
|
||||
}
|
||||
},
|
||||
|
||||
bind_move_to_cart_action: function() {
|
||||
// move item to cart from wishlist
|
||||
$('.page_content').on("click", ".btn-add-to-cart", (e) => {
|
||||
const $move_to_cart_btn = $(e.currentTarget);
|
||||
let item_code = $move_to_cart_btn.data("item-code");
|
||||
|
||||
shopping_cart.shopping_cart_update({
|
||||
item_code,
|
||||
qty: 1,
|
||||
cart_dropdown: true
|
||||
});
|
||||
|
||||
let success_action = function() {
|
||||
const $card_wrapper = $move_to_cart_btn.closest(".item-card");
|
||||
$card_wrapper.addClass("wish-removed");
|
||||
};
|
||||
let args = { item_code: item_code };
|
||||
this.add_remove_from_wishlist("remove", args, success_action, null, true);
|
||||
});
|
||||
},
|
||||
|
||||
bind_remove_action: function() {
|
||||
// remove item from wishlist
|
||||
$('.page_content').on("click", ".remove-wish", (e) => {
|
||||
const $remove_wish_btn = $(e.currentTarget);
|
||||
let item_code = $remove_wish_btn.data("item-code");
|
||||
|
||||
let success_action = function() {
|
||||
const $card_wrapper = $remove_wish_btn.closest(".item-card");
|
||||
$card_wrapper.addClass("wish-removed");
|
||||
};
|
||||
let args = { item_code: item_code };
|
||||
this.add_remove_from_wishlist("remove", args, success_action);
|
||||
});
|
||||
},
|
||||
|
||||
bind_wishlist_action() {
|
||||
// 'wish'('like') or 'unwish' item in product listing
|
||||
$('.page_content').on('click', '.like-action', (e) => {
|
||||
const $btn = $(e.currentTarget);
|
||||
const $wish_icon = $btn.find('.wish-icon');
|
||||
let me = this;
|
||||
|
||||
let success_action = function() {
|
||||
erpnext.wishlist.set_wishlist_count();
|
||||
};
|
||||
|
||||
if ($wish_icon.hasClass('wished')) {
|
||||
// un-wish item
|
||||
$btn.removeClass("like-animate");
|
||||
this.toggle_button_class($wish_icon, 'wished', 'not-wished');
|
||||
|
||||
let args = { item_code: $btn.data('item-code') };
|
||||
let failure_action = function() {
|
||||
me.toggle_button_class($wish_icon, 'not-wished', 'wished');
|
||||
};
|
||||
this.add_remove_from_wishlist("remove", args, success_action, failure_action);
|
||||
} else {
|
||||
// wish item
|
||||
$btn.addClass("like-animate");
|
||||
this.toggle_button_class($wish_icon, 'not-wished', 'wished');
|
||||
|
||||
let args = {
|
||||
item_code: $btn.data('item-code'),
|
||||
price: $btn.data('price'),
|
||||
formatted_price: $btn.data('formatted-price')
|
||||
};
|
||||
let failure_action = function() {
|
||||
me.toggle_button_class($wish_icon, 'wished', 'not-wished');
|
||||
};
|
||||
this.add_remove_from_wishlist("add", args, success_action, failure_action);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggle_button_class(button, remove, add) {
|
||||
button.removeClass(remove);
|
||||
button.addClass(add);
|
||||
},
|
||||
|
||||
add_remove_from_wishlist(action, args, success_action, failure_action, async=false) {
|
||||
/* AJAX call to add or remove Item from Wishlist
|
||||
action: "add" or "remove"
|
||||
args: args for method (item_code, price, formatted_price),
|
||||
success_action: method to execute on successs,
|
||||
failure_action: method to execute on failure,
|
||||
async: make call asynchronously (true/false). */
|
||||
let method = "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist";
|
||||
if (action === "remove") {
|
||||
method = "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist";
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
type: "POST",
|
||||
method: method,
|
||||
args: args,
|
||||
callback: function (r) {
|
||||
if (r.exc) {
|
||||
if (failure_action && (typeof failure_action === 'function')) {failure_action();}
|
||||
frappe.msgprint({
|
||||
message: __("Sorry, something went wrong. Please refresh."),
|
||||
indicator: "red", title: __("Note")
|
||||
});
|
||||
} else {
|
||||
if (success_action && (typeof success_action === 'function')) {success_action();}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
frappe.ready(function() {
|
||||
if (window.location.pathname !== "/wishlist") {
|
||||
$(".wishlist").toggleClass('hidden', true);
|
||||
wishlist.set_wishlist_count();
|
||||
} else {
|
||||
wishlist.bind_move_to_cart_action();
|
||||
wishlist.bind_remove_action();
|
||||
}
|
||||
|
||||
});
|
@ -648,3 +648,27 @@ body.product-page {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.wishlist-cart-not-added {
|
||||
color: var(--blue-500);
|
||||
background-color: white;
|
||||
border: 1px solid var(--blue-500);
|
||||
--icon-stroke: var(--blue-500);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--blue-500);
|
||||
color: white;
|
||||
--icon-stroke: white;
|
||||
}
|
||||
}
|
||||
|
||||
.remove-wish {
|
||||
&:hover {
|
||||
background-color: var(--gray-100);
|
||||
border: 1px solid var(--icon-stroke);
|
||||
}
|
||||
}
|
||||
|
||||
.wish-removed {
|
||||
display: none;
|
||||
}
|
||||
|
@ -118,7 +118,6 @@
|
||||
'text-left': align == 'Left' or is_featured,
|
||||
}) -%}
|
||||
<div class="card-body {{ align_class }}" style="width:100%">
|
||||
|
||||
<div style="margin-top: 16px; display: flex;">
|
||||
<a href="/{{ item.route or '#' }}">
|
||||
<div class="product-title">{{ title or '' }}</div>
|
||||
@ -128,7 +127,9 @@
|
||||
{% endif %}
|
||||
{% if not item.has_variants %}
|
||||
<div class="like-action"
|
||||
data-item-code="{{ item.item_code }}" data-price="{{ item.price }}">
|
||||
data-item-code="{{ item.item_code }}"
|
||||
data-price="{{ item.price }}"
|
||||
data-formatted-price="{{ item.get('formatted_price') }}">
|
||||
<svg class="icon sm">
|
||||
{%- set icon_class = "wished" if item.wished else "not-wished"-%}
|
||||
<use class="{{ icon_class }} wish-icon" href="#icon-heart"></use>
|
||||
@ -161,3 +162,63 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
|
||||
|
||||
{%- macro wishlist_card(item, settings) %}
|
||||
<div class="col-sm-3 item-card" style="min-width: 220px;">
|
||||
<div class="card text-center">
|
||||
{% if item.image %}
|
||||
<div class="card-img-container">
|
||||
<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
|
||||
<img class="card-img" src="{{ item.image }}" alt="{{ title }}">
|
||||
</a>
|
||||
<div class="remove-wish"
|
||||
style="position:absolute; top:10px; right: 20px; border-radius: 50%; border: 1px solid var(--gray-100); width: 25px; height: 25px;"
|
||||
data-item-code="{{ item.item_code }}">
|
||||
<span style="padding-bottom: 2px;">
|
||||
<svg class="icon sm remove-wish-icon" style="margin-bottom: 4px; margin-left: 0.5px;">
|
||||
<use class="close" href="#icon-close"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
|
||||
<div class="card-img-top no-image">
|
||||
{{ frappe.utils.get_abbr(title) }}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{{ wishlist_card_body(item, settings) }}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro wishlist_card_body(item, settings) %}
|
||||
<div class="card-body text-center" style="width:100%">
|
||||
<div style="margin-top: 16px;">
|
||||
<div class="product-title">{{ item.item_name or item.item_code or ''}}</div>
|
||||
</div>
|
||||
<div class="product-price">{{ item.formatted_price or '' }}</div>
|
||||
|
||||
{% if (item.available and settings.show_stock_availability) or (not settings.show_stock_availability) %}
|
||||
<!-- Show move to cart button if in stock or if showing stock availability is disabled -->
|
||||
<button data-item-code="{{ item.item_code}}" class="btn btn-add-to-cart w-100 wishlist-cart-not-added">
|
||||
<span class="mr-2">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-assets"></use>
|
||||
</svg>
|
||||
</span>
|
||||
{{ _("Move to Cart") }}
|
||||
</button>
|
||||
{% else %}
|
||||
<div style="color: #F47A7A; width: 100%;">
|
||||
{{ _("Not in Stock") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
|
@ -10,7 +10,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="wishlist wishlist-icon hidden">
|
||||
<a class="nav-link" href="/cart">
|
||||
<a class="nav-link" href="/wishlist">
|
||||
<svg class="icon icon-lg">
|
||||
<use href="#icon-heart"></use>
|
||||
</svg>
|
||||
|
24
erpnext/templates/pages/wishlist.html
Normal file
24
erpnext/templates/pages/wishlist.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %} {{ _("Wishlist") }} {% endblock %}
|
||||
|
||||
{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Wishlist") }}</h1>{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
{% if items %}
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-11 item-card-group-section">
|
||||
<div class="row products-list">
|
||||
{% from "erpnext/templates/includes/macros.html" import wishlist_card %}
|
||||
{% for item in items %}
|
||||
{{ wishlist_card(item, settings) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- TODO: Make empty state for wishlist -->
|
||||
{% include "erpnext/www/all-products/not_found.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
39
erpnext/templates/pages/wishlist.py
Normal file
39
erpnext/templates/pages/wishlist.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Copyright (c) 2021, 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.shopping_cart.cart import get_cart_quotation
|
||||
|
||||
def get_context(context):
|
||||
settings = frappe.get_doc("E Commerce Settings")
|
||||
items = get_wishlist_items()
|
||||
|
||||
if settings.show_stock_availability:
|
||||
for item in items:
|
||||
stock_qty = frappe.utils.flt(
|
||||
frappe.db.get_value("Bin",
|
||||
{
|
||||
"item_code": item.item_code,
|
||||
"warehouse": item.get("warehouse")
|
||||
},
|
||||
"actual_qty")
|
||||
)
|
||||
item.available = True if stock_qty else False
|
||||
|
||||
context.items = items
|
||||
context.settings = settings
|
||||
|
||||
def get_wishlist_items():
|
||||
if frappe.db.exists("Wishlist", frappe.session.user):
|
||||
return frappe.db.sql("""
|
||||
Select
|
||||
item_code, item_name, website_item, price,
|
||||
warehouse, image, item_group, route, formatted_price
|
||||
from
|
||||
`tabWishlist Items`
|
||||
where
|
||||
parent=%(user)s"""%{"user": frappe.db.escape(frappe.session.user)}, as_dict=1)
|
||||
return
|
@ -73,98 +73,8 @@ $(() => {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
this.animate_add_to_cart($btn);
|
||||
|
||||
const item_code = $btn.data('item-code');
|
||||
erpnext.shopping_cart.update_cart({
|
||||
item_code,
|
||||
qty: 1
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
animate_add_to_cart(button) {
|
||||
// Create 'added to cart' animation
|
||||
let btn_id = "#" + button[0].id;
|
||||
this.toggle_button_class(button, 'not-added', 'added-to-cart');
|
||||
$(btn_id).text('Added to Cart');
|
||||
|
||||
// undo
|
||||
setTimeout(() => {
|
||||
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);
|
||||
erpnext.shopping_cart.bind_add_to_cart_action();
|
||||
erpnext.wishlist.bind_wishlist_action();
|
||||
}
|
||||
|
||||
bind_search() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user