From b60a52b19422ab03390f273d7e442f0c7e20b589 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 3 Apr 2018 10:44:13 +0530 Subject: [PATCH] Hub Market (#13325) * [hub] enable hub with OAuth token from user's client ID * [hub] settings schema for adding users * [hub] image view style listing * [hub] broken image link fallback * [wip] * [hub] bootstrap timeline * [hub] setup quick view * [hub] reformat ratings, add companies autocomplete filter * [hub] common listing frames * [hub] setup sort selector * [hub] timeline * [hub] customize list header * [hub] genuine form fieldtypes * [hub] review products * [hub] suggest missing categories remotely * [hub] Wishlist; header and title templates * [hub] link wishlist, remove company migration * [hub] calculate ratings --- erpnext/config/desktop.py | 2 +- erpnext/hub_node/__init__.py | 98 ++- .../hub_sync/hub_sync.json | 4 - .../doctype/hub_settings/hub_settings.js | 139 +++- .../doctype/hub_settings/hub_settings.json | 335 ++++++++- .../doctype/hub_settings/hub_settings.py | 46 +- .../doctype/hub_tracked_item/__init__.py | 0 .../hub_tracked_item/hub_tracked_item.js | 8 + .../hub_tracked_item/hub_tracked_item.json | 93 +++ .../hub_tracked_item/hub_tracked_item.py | 10 + .../hub_tracked_item/test_hub_tracked_item.js | 23 + .../hub_tracked_item/test_hub_tracked_item.py | 10 + .../hub_node/doctype/hub_users/__init__.py | 0 .../hub_node/doctype/hub_users/hub_users.json | 72 ++ .../hub_node/doctype/hub_users/hub_users.py | 10 + erpnext/public/build.json | 5 +- erpnext/public/css/hub.css | 130 ---- erpnext/public/images/hub_logo.svg | 154 ++-- erpnext/public/js/hub/hub_factory.js | 44 +- erpnext/public/js/hub/hub_form.js | 386 ++++++++-- erpnext/public/js/hub/hub_listing.js | 665 ++++++++++++++++++ erpnext/public/js/hub/hub_page.js | 244 ------- erpnext/public/less/hub.less | 211 ++---- erpnext/utilities/user_progress.py | 2 +- 24 files changed, 2000 insertions(+), 691 deletions(-) create mode 100644 erpnext/hub_node/doctype/hub_tracked_item/__init__.py create mode 100644 erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js create mode 100644 erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json create mode 100644 erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py create mode 100644 erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js create mode 100644 erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py create mode 100644 erpnext/hub_node/doctype/hub_users/__init__.py create mode 100644 erpnext/hub_node/doctype/hub_users/hub_users.json create mode 100644 erpnext/hub_node/doctype/hub_users/hub_users.py delete mode 100644 erpnext/public/css/hub.css create mode 100644 erpnext/public/js/hub/hub_listing.js delete mode 100644 erpnext/public/js/hub/hub_page.js diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py index b9161953ec..b0af397e89 100644 --- a/erpnext/config/desktop.py +++ b/erpnext/config/desktop.py @@ -336,7 +336,7 @@ def get_data(): "color": "#009248", "icon": "/assets/erpnext/images/hub_logo.svg", "type": "page", - "link": "Hub/Home", + "link": "Hub/Item", "label": _("Hub") }, { diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index b2a98b4968..371a23f3b4 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -22,13 +22,64 @@ def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=No response = connection.get_list(doctype, limit_start=start, limit_page_length=limit, filters=filters, fields=fields) - return response + + # Bad, need child tables in response + listing = [] + for obj in response: + doc = connection.get_doc(doctype, obj['name']) + listing.append(doc) + + return listing + +@frappe.whitelist() +def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): + doctype = 'Hub Item' + hub_settings = frappe.get_doc('Hub Settings') + item_names_str = hub_settings.get('custom_data') or '[]' + item_names = json.loads(item_names_str) + filters = json.dumps({ + 'hub_item_code': ['in', item_names] + }) + return get_list(doctype, start, limit, fields, filters, order_by) + +@frappe.whitelist() +def update_wishlist_item(item_name, remove=0): + remove = int(remove) + hub_settings = frappe.get_doc('Hub Settings') + data = hub_settings.get('custom_data') + if not data or not json.loads(data): + data = '[]' + hub_settings.custom_data = data + hub_settings.save() + + item_names_str = data + item_names = json.loads(item_names_str) + if not remove and item_name not in item_names: + item_names.append(item_name) + if remove and item_name in item_names: + item_names.remove(item_name) + + item_names_str = json.dumps(item_names) + + hub_settings.custom_data = item_names_str + hub_settings.save() @frappe.whitelist() def get_meta(doctype): connection = get_client_connection() meta = connection.get_doc('DocType', doctype) - return meta + categories = connection.get_list('Hub Category', + limit_start=0, limit_page_length=300, + filters={}, fields=['name']) + + categories = [d.get('name') for d in categories] + return { + 'meta': meta, + 'companies': connection.get_list('Hub Company', + limit_start=0, limit_page_length=300, + filters={}, fields=['name']), + 'categories': categories + } @frappe.whitelist() def get_categories(parent='All Categories'): @@ -40,12 +91,42 @@ def get_categories(parent='All Categories'): return response @frappe.whitelist() -def update_category(item_name, category): +def update_category(hub_item_code, category): connection = get_hub_connection() - response = connection.update('Hub Item', dict( + + # args = frappe._dict(dict( + # doctype='Hub Category', + # hub_category_name=category + # )) + # response = connection.insert('Hub Category', args) + + response = connection.update('Hub Item', frappe._dict(dict( + doctype='Hub Item', hub_category = category - ), item_name) - return response.ok + )), hub_item_code) + + return response + +@frappe.whitelist() +def send_review(hub_item_code, review): + review = json.loads(review) + hub_connection = get_hub_connection() + + item_doc = hub_connection.connection.get_doc('Hub Item', hub_item_code) + existing_reviews = item_doc.get('reviews') + + reviews = [review] + review.setdefault('idx', 0) + for r in existing_reviews: + if r.get('user') != review.get('user'): + reviews.append(r) + + response = hub_connection.update('Hub Item', dict( + doctype='Hub Item', + reviews = reviews + ), hub_item_code) + + return response @frappe.whitelist() def get_details(hub_sync_id=None, doctype='Hub Item'): @@ -53,6 +134,11 @@ def get_details(hub_sync_id=None, doctype='Hub Item'): return connection = get_client_connection() details = connection.get_doc(doctype, hub_sync_id) + reviews = details.get('reviews') + if len(reviews): + for r in reviews: + r.setdefault('pretty_date', frappe.utils.pretty_date(r.get('modified'))) + details.setdefault('reviews', reviews) return details def get_client_connection(): diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json index f6d96da0a7..d66ac24b1c 100644 --- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json +++ b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json @@ -4,10 +4,6 @@ "doctype": "Data Migration Plan", "idx": 1, "mappings": [ - { - "enabled": 1, - "mapping": "Company to Hub Company" - }, { "enabled": 1, "mapping": "Item to Hub Item" diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.js b/erpnext/hub_node/doctype/hub_settings/hub_settings.js index bc78927476..f13ad44b44 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.js +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.js @@ -8,18 +8,31 @@ frappe.ui.form.on("Hub Settings", { frm.trigger("enabled"); if (frm.doc.enabled) { frm.add_custom_button(__('View Hub'), - () => frappe.set_route('hub')); + () => frappe.set_route('Hub', 'Item')); frm.add_custom_button(__('Sync'), () => frm.call('sync')); } }, onload: function(frm) { + let token = frappe.urllib.get_arg("access_token"); + if(token) { + let email = frm.get_field("user"); + console.log('token', frappe.urllib.get_arg("access_token")); + + get_user_details(frm, token, email); + let row = frappe.model.add_child(frm.doc, "Hub Users", "users"); + row.user = frappe.session.user; + } + if(!frm.doc.country) { frm.set_value("country", frappe.defaults.get_default("Country")); } if(!frm.doc.company) { frm.set_value("company", frappe.defaults.get_default("Company")); } + if(!frm.doc.user) { + frm.set_value("user", frappe.session.user); + } }, onload_post_render: function(frm) { if(frm.get_field("unregister_from_hub").$input) @@ -48,33 +61,47 @@ frappe.ui.form.on("Hub Settings", { if(frappe.session.user === "Administrator") { frappe.msgprint(__("Please login as another user.")) } else { - frappe.verify_password(() => { - this.frm.call({ - doc: this.frm.doc, - method: "register", - args: {}, - freeze: true, - callback: function(r) {}, - onerror: function() { - frappe.msgprint(__("Wrong Password")); - frm.set_value("enabled", 0); - } - }); - } ); + // frappe.verify_password(() => { + + // } ); + + frm.trigger("call_pre_reg"); + // frm.trigger("call_register"); + + } + }); + }, + + call_pre_reg: (frm) => { + this.frm.call({ + doc: this.frm.doc, + method: "pre_reg", + args: {}, + freeze: true, + callback: function(r) { + console.log(r.message); + authorize(frm, r.message.client_id, r.message.redirect_uri); + }, + onerror: function() { + frappe.msgprint(__("Wrong Password")); + frm.set_value("enabled", 0); } }); }, - // update_hub: (frm) => { - // this.frm.call({ - // doc: this.frm.doc, - // method: "update_hub", - // args: {}, - // freeze: true, - // callback: function(r) { }, - // onerror: function() { } - // }); - // }, + call_register: (frm) => { + this.frm.call({ + doc: this.frm.doc, + method: "register", + args: {}, + freeze: true, + callback: function(r) {}, + onerror: function() { + frappe.msgprint(__("Wrong Password")); + frm.set_value("enabled", 0); + } + }); + }, unregister_from_hub: (frm) => { frappe.verify_password(() => { @@ -85,3 +112,67 @@ frappe.ui.form.on("Hub Settings", { }); }, }); + +// let hub_url = 'https://hubmarket.org' +let hub_url = 'http://159.89.175.122' +// let hub_url = 'http://erpnext.hub:8000' + +function authorize(frm, client_id, redirect_uri) { + + // queryStringData is details of OAuth Client (Implicit Grant) on Custom App + var queryStringData = { + response_type : "token", + client_id : client_id, + redirect_uri : redirect_uri + } + + // Get current raw route and build url + const route = "/desk#" + frappe.get_raw_route_str(); + localStorage.removeItem("route"); // Clear previously set route if any + localStorage.setItem("route", route); + + // Go authorize! + let api_route = "/api/method/frappe.integrations.oauth2.authorize?"; + let url = hub_url + api_route + $.param(queryStringData); + window.location.replace(url, 'test'); +} + +function get_user_details(frm, token, email) { + console.log('user_details'); + var route = localStorage.getItem("route"); + if (token && route) { + // Clean up access token from route + frappe.set_route(frappe.get_route().join("/")) + + // query protected resource e.g. Hub Items with token + var call = { + "async": true, + "crossDomain": true, + "url": hub_url + "/api/resource/User", + "method": "GET", + "data": { + // "email": email, + "fields": '["name", "first_name", "language"]', + "limit_page_length": 1 + }, + "headers": { + "authorization": "Bearer " + token, + "content-type": "application/x-www-form-urlencoded" + } + } + $.ajax(call).done(function (response) { + // display openid profile + console.log('response', response); + + let data = response.data[0]; + frm.set_value("enabled", 1); + frm.set_value("hub_username", data.first_name); + frm.set_value("hub_user_status", "Starter"); + frm.set_value("language", data.language); + frm.save(); + + // clear route from localStorage + localStorage.removeItem("route"); + }); + } +} diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json index b96d6b3643..7c7109c64c 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json @@ -40,6 +40,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -70,6 +71,39 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enabled", + "fieldname": "hub_username", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Hub Username", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -101,6 +135,102 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_0", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enabled", + "fieldname": "hub_user_status", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enabled", + "fieldname": "language", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Language", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -133,6 +263,38 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company_registered", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company Registered", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -164,6 +326,38 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company_email", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company Email", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -195,6 +389,38 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company_logo", + "fieldtype": "Attach Image", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company Logo", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -225,6 +451,70 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "users_sb", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Enabled Users", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "users", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Users", + "length": 0, + "no_copy": 0, + "options": "Hub Users", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -256,6 +546,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -286,6 +577,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -317,6 +609,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -349,6 +642,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -380,6 +674,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -411,6 +706,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -418,6 +714,39 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:1", + "fieldname": "custom_data", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Custom Data", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, "collapsible_depends_on": "", "columns": 0, "depends_on": "enabled", @@ -443,6 +772,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -473,6 +803,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -486,8 +817,8 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-09-21 12:13:50.841646", - "modified_by": "manas@erpnext.com", + "modified": "2018-03-26 00:55:17.929140", + "modified_by": "test1@example.com", "module": "Hub Node", "name": "Hub Settings", "name_case": "", diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 59807532e0..dc69056d93 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -10,7 +10,24 @@ from frappe import _ from erpnext.utilities.product import get_price, get_qty_in_stock from six import string_types -hub_url = "https://hubmarket.org" +# hub_url = "https://hubmarket.org" +hub_url = "http://159.89.175.122" +# hub_url = "http://erpnext.hub:8000" + +# test_hub_url = "https://hubmarket.org" + +class OAuth2Session(): + def __init__(self, headers): + self.headers = headers + def get(self, url, params, headers, verify): + res = requests.get(url, params=params, headers=self.headers, verify=verify) + return res + def post(self, url, data, verify): + res = requests.post(url, data=data, headers=self.headers, verify=verify) + return res + def put(self, url, data, verify): + res = requests.put(url, data=data, headers=self.headers, verify=verify) + return res class HubSetupError(frappe.ValidationError): pass @@ -35,6 +52,33 @@ class HubSettings(Document): doc.run() + def pre_reg(self): + site_name = frappe.local.site + ':' + str(frappe.conf.webserver_port) + protocol = 'http://' + route = '/token' + data = { + 'site_name': site_name, + 'protocol': protocol, + 'route': route + } + + redirect_url = protocol + site_name + route + post_url = hub_url + '/api/method/hub.hub.api.pre_reg' + + response = requests.post(post_url, data=data) + response.raise_for_status() + message = response.json().get('message') + + if message and message.get('client_id'): + print("======CLIENT_ID======") + print(message.get('client_id')) + + return { + 'client_id': message.get('client_id'), + 'redirect_uri': redirect_url + } + + def register(self): """ Create a User on hub.erpnext.org and return username/password """ data = { diff --git a/erpnext/hub_node/doctype/hub_tracked_item/__init__.py b/erpnext/hub_node/doctype/hub_tracked_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js new file mode 100644 index 0000000000..660532d13d --- /dev/null +++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Hub Tracked Item', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json new file mode 100644 index 0000000000..063c87817c --- /dev/null +++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json @@ -0,0 +1,93 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-03-18 09:33:50.267762", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_code", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Item Code", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-03-18 09:34:01.757713", + "modified_by": "Administrator", + "module": "Hub Node", + "name": "Hub Tracked Item", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 1, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py new file mode 100644 index 0000000000..be2cd6b3ee --- /dev/null +++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, 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 HubTrackedItem(Document): + pass diff --git a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js new file mode 100644 index 0000000000..9f7314d399 --- /dev/null +++ b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Hub Tracked Item", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Hub Tracked Item + () => frappe.tests.make('Hub Tracked Item', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py new file mode 100644 index 0000000000..92b294064f --- /dev/null +++ b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestHubTrackedItem(unittest.TestCase): + pass diff --git a/erpnext/hub_node/doctype/hub_users/__init__.py b/erpnext/hub_node/doctype/hub_users/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.json b/erpnext/hub_node/doctype/hub_users/hub_users.json new file mode 100644 index 0000000000..2027e72fa0 --- /dev/null +++ b/erpnext/hub_node/doctype/hub_users/hub_users.json @@ -0,0 +1,72 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-03-06 04:38:49.891787", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "user", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "User", + "length": 0, + "no_copy": 0, + "options": "User", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-03-06 04:41:17.916243", + "modified_by": "Administrator", + "module": "Hub Node", + "name": "Hub Users", + "name_case": "", + "owner": "test1@example.com", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.py b/erpnext/hub_node/doctype/hub_users/hub_users.py new file mode 100644 index 0000000000..440be14c0b --- /dev/null +++ b/erpnext/hub_node/doctype/hub_users/hub_users.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, 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 HubUsers(Document): + pass diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 0ce5c75aee..24ccfbf6f0 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -1,6 +1,7 @@ { "css/erpnext.css": [ - "public/less/erpnext.less" + "public/less/erpnext.less", + "public/less/hub.less" ], "js/erpnext-web.min.js": [ "public/js/website_utils.js", @@ -21,7 +22,7 @@ "public/js/pos/pos.html", "public/js/pos/pos_bill_item.html", "public/js/pos/pos_bill_item_new.html", - "public/js/pos/pos_selected_item.html", + "public/js/pos/pos_selected_item.html", "public/js/pos/pos_item.html", "public/js/pos/pos_tax_row.html", "public/js/pos/customer_toolbar.html", diff --git a/erpnext/public/css/hub.css b/erpnext/public/css/hub.css deleted file mode 100644 index 5f86cf4869..0000000000 --- a/erpnext/public/css/hub.css +++ /dev/null @@ -1,130 +0,0 @@ -body[data-route^="Hub/"] .freeze .image-view-container .list-row-col { - background-color: #fafbfc; - color: #fafbfc; -} -body[data-route^="Hub/"] .freeze .image-view-container .placeholder-text { - color: #fafbfc; -} -body[data-route^="Hub/"] .freeze { - display: none; -} -body[data-route^="Hub/"] .image-view-container { - justify-content: space-around; -} -.img-wrapper { - border: 1px solid #d1d8dd; - border-radius: 3px; - padding: 12px; - overflow: hidden; - text-align: center; - white-space: nowrap; -} -.img-wrapper .helper { - height: 100%; - display: inline-block; - vertical-align: middle; -} -/* hub */ -div[data-page-route="hub"] .page-head { - height: 80px; -} -div[data-page-route="hub"] .page-head .title-text { - cursor: pointer; -} -div[data-page-route="hub"] .page-content { - margin-top: 80px; -} -div[data-page-route="hub"] .page-title h1 { - margin-bottom: 0px; -} -div[data-page-route="hub"] .account-details { - margin-top: 20px; -} -div[data-page-route="hub"] [data-original-title="Search"] { - float: right; - width: 220px; -} -div[data-page-route="hub"] .hub-main-section { - padding: 30px; -} -div[data-page-route="hub"] .listing-body { - margin: 0; -} -div[data-page-route="hub"] .main-list-section { - padding: 0; -} -div[data-page-route="hub"] .side-list-section { - padding: 0; -} -div[data-page-route="hub"] .item-list-header h3 { - font-weight: normal; -} -div[data-page-route="hub"] .hub-item-page h2 { - margin-top: 10px; -} -div[data-page-route="hub"] .hub-item-page .item-header { - display: flex; -} -div[data-page-route="hub"] .hub-item-page .item-page-image { - flex: 1; -} -div[data-page-route="hub"] .hub-item-page .title-content { - flex: 3; -} -div[data-page-route="hub"] .hub-item-page .title-content .description { - margin: 30px 0px; -} -div[data-page-route="hub"] .hub-item-page .title-content .actions { - margin-top: 30px; -} -div[data-page-route="hub"] .hub-item-page .title-content .actions .rfq-btn.disabled { - background-color: #b1bdca; - color: #fff; - border-color: #b1bdca; -} -div[data-page-route="hub"] .hub-item-page .company-items { - margin-top: 40px; -} -div[data-page-route="hub"] .company-header { - display: flex; -} -div[data-page-route="hub"] .item-list { - display: flex; - flex-wrap: wrap; - justify-content: space-between; -} -div[data-page-route="hub"] .hub-item-wrapper { - margin-bottom: 20px; -} -div[data-page-route="hub"] .img-wrapper { - border: 1px solid #d1d8dd; - border-radius: 3px; - padding: 12px; - overflow: hidden; - text-align: center; - white-space: nowrap; -} -div[data-page-route="hub"] .img-wrapper img { - max-width: 100%; - max-height: 100%; - display: inline-block; - vertical-align: middle; -} -div[data-page-route="hub"] .img-wrapper .helper { - height: 100%; - display: inline-block; - vertical-align: middle; -} -div[data-page-route="hub"] .img-wrapper .standard-image { - font-size: 72px; - border: none; - background-color: #fafbfc; -} -div[data-page-route="hub"] .hub-item-title { - width: 100%; -} -div[data-page-route="hub"] .breadcrumb { - padding-left: 0; - padding-top: 0; - margin-bottom: 10px; -} diff --git a/erpnext/public/images/hub_logo.svg b/erpnext/public/images/hub_logo.svg index 2363090600..4af482176e 100644 --- a/erpnext/public/images/hub_logo.svg +++ b/erpnext/public/images/hub_logo.svg @@ -9,68 +9,104 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="330" + height="345.43808" + viewBox="0 0 87.312496 91.397155" version="1.1" - id="svg3718" - xml:space="preserve" - width="162.12097" - height="162.3004" - viewBox="0 0 162.12098 162.3004" - sodipodi:docname="hub_logo.svg" - inkscape:version="0.92.2 5c3e80d, 2017-08-06">image/svg+xml + + + + + + image/svg+xml + + + + + + + + + + + + + + + \ No newline at end of file + id="layer1" + transform="translate(121.51931,-138.66452)"> + + + + + + + diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js index 1578e4dc7f..01f55a3c6c 100644 --- a/erpnext/public/js/hub/hub_factory.js +++ b/erpnext/public/js/hub/hub_factory.js @@ -7,25 +7,33 @@ frappe.views.HubFactory = frappe.views.Factory.extend({ const assets = { 'List': [ - '/assets/erpnext/js/hub/hub_page.js', - '/assets/erpnext/css/hub.css', + '/assets/erpnext/js/hub/hub_listing.js', ], 'Form': [ - '/assets/erpnext/js/hub/hub_form.js', - '/assets/erpnext/css/hub.css', + '/assets/erpnext/js/hub/hub_form.js' ] }; frappe.model.with_doc('Hub Settings', 'Hub Settings', () => { this.hub_settings = frappe.get_doc('Hub Settings'); if (!erpnext.hub.pages[page_name]) { + if(!frappe.is_online()) { + this.render_offline_card(); + return; + } if (!route[2]) { frappe.require(assets['List'], () => { - erpnext.hub.pages[page_name] = new erpnext.hub[page+'Listing']({ - parent: this.make_page(true, page_name), - hub_settings: this.hub_settings - }); - window.hub_page = erpnext.hub.pages[page_name]; + if(page === 'Favourites') { + erpnext.hub.pages[page_name] = new erpnext.hub['Favourites']({ + parent: this.make_page(true, page_name), + hub_settings: this.hub_settings + }); + } else { + erpnext.hub.pages[page_name] = new erpnext.hub[page+'Listing']({ + parent: this.make_page(true, page_name), + hub_settings: this.hub_settings + }); + } }); } else { frappe.require(assets['Form'], () => { @@ -35,13 +43,29 @@ frappe.views.HubFactory = frappe.views.Factory.extend({ parent: this.make_page(true, page_name), hub_settings: this.hub_settings }); - window.hub_page = erpnext.hub.pages[page_name]; }); } + window.hub_page = erpnext.hub.pages[page_name]; } else { frappe.container.change_to(page_name); window.hub_page = erpnext.hub.pages[page_name]; } }); + }, + + render_offline_card() { + let html = `
+
+ ${'Failed to connect'} +
+

${ __("Please check your network connection.") }

+ +
`; + + let page = $('#body_div'); + page.append(html); + + return; } }); diff --git a/erpnext/public/js/hub/hub_form.js b/erpnext/public/js/hub/hub_form.js index 130a0db45c..5fdcadbbae 100644 --- a/erpnext/public/js/hub/hub_form.js +++ b/erpnext/public/js/hub/hub_form.js @@ -1,6 +1,6 @@ frappe.provide('erpnext.hub'); -erpnext.hub.HubForm = class HubForm extends frappe.views.BaseList { +erpnext.hub.HubDetailsPage = class HubDetailsPage extends frappe.views.BaseList { setup_defaults() { super.setup_defaults(); this.method = 'erpnext.hub_node.get_details'; @@ -8,6 +8,25 @@ erpnext.hub.HubForm = class HubForm extends frappe.views.BaseList { this.page_name = route[2]; } + setup_fields() { + return this.get_meta() + .then(r => { + this.meta = r.message.meta || this.meta; + this.categories = r.message.categories || []; + this.bootstrap_data(r.message); + + this.getFormFields(); + }); + } + + bootstrap_data() { } + + get_meta() { + return new Promise(resolve => + frappe.call('erpnext.hub_node.get_meta', {doctype: 'Hub ' + this.doctype}, resolve)); + } + + set_breadcrumbs() { frappe.breadcrumbs.add({ label: __('Hub'), @@ -21,12 +40,55 @@ erpnext.hub.HubForm = class HubForm extends frappe.views.BaseList { wrapper: this.$page.find('.layout-side-section'), css_class: 'hub-form-sidebar' }); + + this.attachFooter(); + this.attachTimeline(); + this.attachReviewArea(); } setup_filter_area() { } setup_sort_selector() { } + // let category = this.quick_view.get_values().hub_category; + // return new Promise((resolve, reject) => { + // frappe.call({ + // method: 'erpnext.hub_node.update_category', + // args: { + // hub_item_code: values.hub_item_code, + // category: category, + // }, + // callback: (r) => { + // resolve(); + // }, + // freeze: true + // }).fail(reject); + // }); + + get_timeline() { + return `
+
+
+
+ +
+
+
`; + } + + get_footer() { + return ``; + } + get_args() { return { hub_sync_id: this.unique_id, @@ -48,108 +110,296 @@ erpnext.hub.HubForm = class HubForm extends frappe.views.BaseList { ` : `
${frappe.get_abbr(this.page_title)}
`; + this.sidebar.remove_item('image'); this.sidebar.add_item({ + name: 'image', label: image_html }); - let fields = this.get_field_configs(); + if(!this.form) { + let fields = this.formFields; + this.form = new frappe.ui.FieldGroup({ + parent: this.$result, + fields + }); + this.form.make(); + } - this.form = new frappe.ui.FieldGroup({ - parent: this.$result, - fields - }); + if(this.data.hub_category) { + this.form.fields_dict.set_category.hide(); + } - this.form.make(); this.form.set_values(this.data); + this.$result.show(); + + this.$timelineList.empty(); + if(this.data.reviews.length) { + this.data.reviews.map(review => { + this.addReviewToTimeline(review); + }) + } + + this.postRender() } - toggle_result_area() { - this.$result.toggle(this.unique_id); - this.$paging_area.toggle(this.data.length > 0); - this.$no_result.toggle(this.data.length == 0); + postRender() {} - const show_more = (this.start + this.page_length) <= this.data.length; - this.$paging_area.find('.btn-more') - .toggle(show_more); + attachFooter() { + let footerHtml = ``; + + let parent = $('
').appendTo(this.page.main.parent()); + this.$footer = $(footerHtml).appendTo(parent); } -}; -erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubForm{ - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Item'; - this.image_field_name = 'image'; + attachTimeline() { + let timelineHtml = `
+
+
+
+ +
+
+
`; + + let parent = this.$footer.find(".form-comments"); + this.$timeline = $(timelineHtml).appendTo(parent); + + this.$timelineList = this.$timeline.find(".timeline-items"); } - get_field_configs() { - let fields = []; - this.fields.map(fieldname => { - fields.push({ - label: toTitle(frappe.model.unscrub(fieldname)), - fieldname, - fieldtype: 'Data', - read_only: 1 - }); + attachReviewArea() { + this.comment_area = new frappe.ui.ReviewArea({ + parent: this.$footer.find('.timeline-head'), + mentions: [], + on_submit: (val) => { + val.user = frappe.session.user; + val.username = frappe.session.user_fullname; + frappe.call({ + method: 'erpnext.hub_node.send_review', + args: { + hub_item_code: this.data.hub_item_code, + review: val + }, + callback: (r) => { + this.refresh(); + this.comment_area.reset(); + }, + freeze: true + }); + } }); + } - let category_field = { - label: 'Hub Category', - fieldname: 'hub_category', - fieldtype: 'Data' - } + addReviewToTimeline(data) { + let username = data.username || data.user || __("Anonymous") + let imageHtml = data.user_image + ? `
` + : `
${frappe.get_abbr(username)}
` + + let editHtml = data.own + ? ` +
+ + ${'data.edit'} + +
` + : ''; + + let ratingHtml = ''; - if(this.data.company_name === this.hub_settings.company) { - this.page.set_primary_action(__('Update'), () => { - this.update_on_hub(); - }, 'octicon octicon-plus'); - } else { - category_field.read_only = 1; + for(var i = 0; i < 5; i++) { + let starIcon = 'fa-star-o' + if(i < data.rating) { + starIcon = 'fa-star'; + } + ratingHtml += ``; } - fields.unshift(category_field); + $(this.getTimelineItem(data, imageHtml, editHtml, ratingHtml)) + .appendTo(this.$timelineList); + } + + getTimelineItem(data, imageHtml, editHtml, ratingHtml) { + return `
+ + +
+
+
${editHtml}
+ +
+ + ${imageHtml} + + +
+ + + ${data.username} + + + + + + + - return fields; + ${''} + +
+
+
+
+

+ ${data.subject} +

+ +
+ +

+ ${ratingHtml} +

+ +
+

+ ${data.content} +

+
+
+
+
+
`; } - update_on_hub() { - return new Promise((resolve, reject) => { - frappe.call({ - method: 'erpnext.hub_node.update_category', - args: { item: this.unique_id, category: this.form.get_value('hub_category') }, - callback: resolve, - freeze: true - }).fail(reject); + prepareFormFields(fields, fieldnames) { + return fields + .filter(field => fieldnames.includes(field.fieldname)) + .map(field => { + let { + label, + fieldname, + fieldtype, + } = field; + let read_only = 1; + return { + label, + fieldname, + fieldtype, + read_only, + }; }); } +}; - setup_fields() { - this.fields = ['hub_item_code', 'item_name', 'item_code', 'description', - 'seller', 'company_name', 'country']; +erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubDetailsPage { + constructor(opts) { + super(opts); + + this.show(); } -} -erpnext.hub.CompanyPage = class CompanyPage extends erpnext.hub.HubForm{ setup_defaults() { super.setup_defaults(); - this.doctype = 'Company'; - this.image_field_name = 'company_logo'; + this.doctype = 'Item'; + this.image_field_name = 'image'; } - get_field_configs() { - let fields = []; - this.fields.map(fieldname => { - fields.push({ - label: toTitle(frappe.model.unscrub(fieldname)), - fieldname, + postRender() { + this.categoryDialog = new frappe.ui.Dialog({ + title: __('Suggest Category'), + fields: [ + { + label: __('Category'), + fieldname: 'category', + fieldtype: 'Autocomplete', + options: this.categories, + reqd: 1 + } + ], + primary_action_label: __("Send"), + primary_action: () => { + let values = this.categoryDialog.get_values(); + frappe.call({ + method: 'erpnext.hub_node.update_category', + args: { + hub_item_code: this.data.hub_item_code, + category: values.category + }, + callback: () => { + this.refresh(); + }, + freeze: true + }).fail(() => {}); + } + }); + } + + getFormFields() { + let colOneFieldnames = ['item_name', 'item_code', 'description']; + let colTwoFieldnames = ['seller', 'company_name', 'country']; + let colOneFields = this.prepareFormFields(this.meta.fields, colOneFieldnames); + let colTwoFields = this.prepareFormFields(this.meta.fields, colTwoFieldnames); + + let miscFields = [ + { + label: __('Category'), + fieldname: 'hub_category', fieldtype: 'Data', read_only: 1 - }); - }); + }, + + { + label: __('Suggest Category?'), + fieldname: 'set_category', + fieldtype: 'Button', + click: () => { + this.categoryDialog.show(); + } + }, - return fields; + { + fieldname: 'cb1', + fieldtype: 'Column Break' + } + ]; + this.formFields = colOneFields.concat(miscFields, colTwoFields); } +} - setup_fields() { - this.fields = ['company_name', 'description', 'route', 'country', 'seller', 'site_name']; +erpnext.hub.CompanyPage = class CompanyPage extends erpnext.hub.HubDetailsPage { + constructor(opts) { + super(opts); + + this.show(); + } + + setup_defaults() { + super.setup_defaults(); + this.doctype = 'Company'; + this.image_field_name = 'company_logo'; + } + + getFormFields() { + let fieldnames = ['company_name', 'description', 'route', 'country', 'seller', 'site_name'];; + this.formFields = this.prepareFormFields(this.meta.fields, fieldnames); } } diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js new file mode 100644 index 0000000000..9e19f73835 --- /dev/null +++ b/erpnext/public/js/hub/hub_listing.js @@ -0,0 +1,665 @@ +frappe.provide('erpnext.hub'); + +erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { + setup_defaults() { + super.setup_defaults(); + this.page_title = __(''); + this.method = 'erpnext.hub_node.get_list'; + + this.cache = {}; + + const route = frappe.get_route(); + this.page_name = route[1]; + + this.menu_items = this.menu_items.concat(this.get_menu_items()); + + this.imageFieldName = 'image'; + + this.show_filters = 0; + } + + set_title() { + const title = this.page_title; + let iconHtml = ``; + let titleHtml = `${title}`; + this.page.set_title(iconHtml + titleHtml, '', false, title); + } + + setup_fields() { + return this.get_meta() + .then(r => { + this.meta = r.message.meta || this.meta; + frappe.model.sync(this.meta); + this.bootstrap_data(r.message); + + this.prepareFormFields(); + }); + } + + get_meta() { + return new Promise(resolve => + frappe.call('erpnext.hub_node.get_meta', {doctype: this.doctype}, resolve)); + } + + set_breadcrumbs() { } + + prepareFormFields() { } + + bootstrap_data() { } + + get_menu_items() { + const items = [ + { + label: __('Hub Settings'), + action: () => frappe.set_route('Form', 'Hub Settings'), + standard: true + }, + { + label: __('Favourites'), + action: () => frappe.set_route('Hub', 'Favourites'), + standard: true + }, + // { + // label: __('Toggle Sidebar'), + // action: () => this.toggle_side_bar(), + // standard: true + // } + ]; + + return items; + } + + setup_side_bar() { + this.sidebar = new frappe.ui.Sidebar({ + wrapper: this.page.wrapper.find('.layout-side-section'), + css_class: 'hub-sidebar' + }); + } + + setup_sort_selector() { + this.sort_selector = new frappe.ui.SortSelector({ + parent: this.filter_area.$filter_list_wrapper, + doctype: this.doctype, + args: this.order_by, + onchange: () => this.refresh(true) + }); + } + + setup_view() { } + + get_args() { + return { + doctype: this.doctype, + start: this.start, + limit: this.page_length, + order_by: this.order_by, + // fields: this.fields, + filters: this.get_filters_for_args() + }; + } + + update_data(r) { + const data = r.message; + + if (this.start === 0) { + this.data = data; + } else { + this.data = this.data.concat(data); + } + + this.data_dict = {}; + } + + freeze(toggle) { + // if(!this.$freeze) return; + // this.$freeze.toggle(toggle); + // if (this.$freeze.find('.image-view-container').length) return; + + // const html = Array.from(new Array(4)).map(d => this.card_html({ + // name: 'Loading...', + // item_name: 'Loading...' + // })).join(''); + + // this.$freeze.html(`
${html}
`); + } + + render() { + this.data_dict = {}; + this.render_image_view(); + + this.setup_quick_view(); + this.setup_like(); + } + + render_offline_card() { + let html = `
+
+ + {{ _("Payment Cancelled") }} +
+

${ __("Your payment is cancelled.") }

+ +
`; + + let page = this.page.wrapper.find('.layout-side-section') + page.append(html); + + return; + } + + render_image_view() { + var html = this.data.map(this.item_html.bind(this)).join(""); + + if (this.start === 0) { + // this.renderHeader(); + this.$result.html(` + ${this.getHeaderHtml()} +
+ ${html} +
+ `); + } + + if(this.data.length) { + this.doc = this.data[0]; + } + + this.data.map(this.loadImage.bind(this)); + + this.data_dict = {}; + this.data.map(d => { + this.data_dict[d.hub_item_code] = d; + }); + } + + getHeaderHtml() { + // let company_html = + return ` +
+
+
+ Riadco Group +
+
+
Riadco Group
+ Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam +
+
+
+ `; + } + + renderHeader() { + return `
+
+
+ Riadco Group + Products by Blah blah +
+
+
+
+ + +
+
+
+ ${''} +
+
`; + } + + get_image_html(encoded_name, src, alt_text) { + return `${ alt_text }`; + } + + get_image_placeholder(title) { + return `${ frappe.get_abbr(title) }`; + } + + loadImage(item) { + item._name = encodeURI(item.name); + const encoded_name = item._name; + const title = strip_html(item[this.meta.title_field || 'name']); + + let placeholder = this.get_image_placeholder(title); + let $container = this.$result.find(`.image-field[data-name="${encoded_name}"]`); + + if(!item[this.imageFieldName]) { + $container.prepend(placeholder); + $container.addClass('no-image'); + } + + frappe.load_image(item[this.imageFieldName], + (imageObj) => { + $container.prepend(imageObj) + }, + () => { + $container.prepend(placeholder); + $container.addClass('no-image'); + }, + (imageObj) => { + imageObj.title = encoded_name; + imageObj.alt = title; + } + ) + } + + item_html(item) { + item._name = encodeURI(item.name); + const encoded_name = item._name; + const title = strip_html(item[this.meta.title_field || 'name']); + const _class = !item[this.imageFieldName] ? 'no-image' : ''; + const route = `#Hub/Item/${item.hub_item_code}`; + const company_name = item['company_name']; + + const reviewLength = (item.reviews || []).length; + const ratingAverage = reviewLength + ? item.reviews + .map(r => r.rating) + .reduce((a, b) => (a + b, 0))/reviewLength + : -1; + + let ratingHtml = ``; + + for(var i = 0; i < 5; i++) { + let starClass = 'fa-star'; + if(i >= ratingAverage) starClass = 'fa-star-o'; + ratingHtml += ``; + } + + let item_html = ` +
+
+
+ + ${title} + +
+
+ ${ratingHtml} + (${reviewLength}) +
+ +
+ + +
+ `; + + return item_html; + } + + setup_quick_view() { + if(this.quick_view) return; + + this.quick_view = new frappe.ui.Dialog({ + title: 'Quick View', + fields: this.formFields + }); + this.$result.on('click', '.btn.zoom-view', (e) => { + e.preventDefault(); + e.stopPropagation(); + var name = $(e.target).attr('data-name'); + name = decodeURIComponent(name); + + this.quick_view.set_title(name); + let values = this.data_dict[name]; + this.quick_view.set_values(values); + + let fields = []; + + this.quick_view.show(); + + return false; + }); + } + + setup_like() { + if(this.setup_like_done) return; + this.setup_like_done = 1; + this.$result.on('click', '.btn.like-button', (e) => { + if($(e.target).hasClass('changing')) return; + $(e.target).addClass('changing'); + + e.preventDefault(); + e.stopPropagation(); + + var name = $(e.target).attr('data-name'); + name = decodeURIComponent(name); + let values = this.data_dict[name]; + + let heart = $(e.target); + if(heart.hasClass('like-button')) { + heart = $(e.target).find('.octicon'); + } + + let remove = 1; + + if(heart.hasClass('liked')) { + // unlike + heart.removeClass('liked'); + } else { + // like + remove = 0; + heart.addClass('liked'); + } + + frappe.call({ + method: 'erpnext.hub_node.update_wishlist_item', + args: { + item_name: values.hub_item_code, + remove: remove + }, + callback: (r) => { + let message = __("Added to Favourites"); + if(remove) { + message = __("Removed from Favourites"); + } + frappe.show_alert(message); + }, + freeze: true + }); + + $(e.target).removeClass('changing'); + return false; + }); + } +} + +erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { + constructor(opts) { + super(opts); + this.show(); + } + + setup_defaults() { + super.setup_defaults(); + this.doctype = 'Hub Item'; + this.page_title = __('Products'); + this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country']; + this.filters = []; + } + + bootstrap_data(response) { + let companies = response.companies.map(d => d.name); + this.custom_filter_configs = [ + { + fieldtype: 'Autocomplete', + label: __('Select Company'), + condition: 'like', + fieldname: 'company_name', + options: companies + }, + { + fieldtype: 'Link', + label: __('Select Country'), + options: 'Country', + condition: 'like', + fieldname: 'country' + } + ]; + } + + prepareFormFields() { + let fieldnames = ['item_name', 'description', 'company_name', 'country']; + this.formFields = this.meta.fields + .filter(field => fieldnames.includes(field.fieldname)) + .map(field => { + let { + label, + fieldname, + fieldtype, + } = field; + let read_only = 1; + return { + label, + fieldname, + fieldtype, + read_only, + }; + }); + + this.formFields.unshift({ + label: 'image', + fieldname: 'image', + fieldtype: 'Attach Image' + }); + } + + setup_side_bar() { + super.setup_side_bar(); + + let $pitch = $(`
+
Sell on HubMarket
+

Over 2000 products listed. Register your company to start selling.

+
`); + + this.sidebar.$sidebar.append($pitch); + + this.category_tree = new frappe.ui.Tree({ + parent: this.sidebar.$sidebar, + label: 'All Categories', + expandable: true, + + args: {parent: this.current_category}, + method: 'erpnext.hub_node.get_categories', + on_click: (node) => { + this.update_category(node.label); + } + }); + + this.sidebar.add_item({ + label: __('Companies'), + on_click: () => frappe.set_route('Hub', 'Company') + }, undefined, true); + + this.sidebar.add_item({ + label: this.hub_settings.company, + on_click: () => frappe.set_route('Form', 'Company', this.hub_settings.company) + }, __("Account")); + + this.sidebar.add_item({ + label: __("Favourites"), + on_click: () => frappe.set_route('Hub', 'Favourites') + }, __("Account")); + } + + update_category(label) { + this.current_category = (label=='All Categories') ? undefined : label; + this.refresh(); + } + + get_filters_for_args() { + if(!this.filter_area) return; + let filters = {}; + this.filter_area.get().forEach(f => { + let field = f[1] !== 'name' ? f[1] : 'item_name'; + filters[field] = [f[2], f[3]]; + }); + if(this.current_category) { + filters['hub_category'] = this.current_category; + } + return filters; + } + + update_data(r) { + super.update_data(r); + + this.data_dict = {}; + this.data.map(d => { + this.data_dict[d.hub_item_code] = d; + }); + } +}; + +erpnext.hub.Favourites = class Favourites extends erpnext.hub.ItemListing { + constructor(opts) { + super(opts); + this.show(); + } + + setup_defaults() { + super.setup_defaults(); + this.doctype = 'Hub Item'; + this.page_title = __('Favourites'); + this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country']; + this.filters = []; + this.method = 'erpnext.hub_node.get_item_favourites'; + } + + setup_filter_area() { } + + setup_sort_selector() { } + + // setupHe + + getHeaderHtml() { + return ''; + } + + get_args() { + return { + start: this.start, + limit: this.page_length, + order_by: this.order_by, + fields: this.fields + }; + } + + bootstrap_data(response) { } + + prepareFormFields() { } + + setup_side_bar() { + this.sidebar = new frappe.ui.Sidebar({ + wrapper: this.page.wrapper.find('.layout-side-section'), + css_class: 'hub-sidebar' + }); + + this.sidebar.add_item({ + label: __('Back to Products'), + on_click: () => frappe.set_route('Hub', 'Item') + }); + + // this.sidebar.add_item({ + // label: this.hub_settings.company, + // on_click: () => frappe.set_route('Form', 'Company', this.hub_settings.company) + // }, __("Account")); + + // this.sidebar.add_item({ + // label: __("My Orders"), + // on_click: () => frappe.set_route('List', 'Request for Quotation') + // }, __("Account")); + } + + update_category(label) { + this.current_category = (label=='All Categories') ? undefined : label; + this.refresh(); + } + + get_filters_for_args() { + if(!this.filter_area) return; + let filters = {}; + this.filter_area.get().forEach(f => { + let field = f[1] !== 'name' ? f[1] : 'item_name'; + filters[field] = [f[2], f[3]]; + }); + if(this.current_category) { + filters['hub_category'] = this.current_category; + } + return filters; + } + + update_data(r) { + super.update_data(r); + + this.data_dict = {}; + this.data.map(d => { + this.data_dict[d.hub_item_code] = d; + }); + } +}; + +erpnext.hub.CompanyListing = class CompanyListing extends erpnext.hub.HubListing { + constructor(opts) { + super(opts); + this.show(); + } + + setup_defaults() { + super.setup_defaults(); + this.doctype = 'Hub Company'; + this.page_title = __('Companies'); + this.fields = ['company_logo', 'name', 'site_name', 'seller_city', 'seller_description', 'seller', 'country', 'company_name']; + this.filters = []; + this.custom_filter_configs = [ + { + fieldtype: 'Link', + label: 'Country', + options: 'Country', + condition: 'like', + fieldname: 'country' + } + ]; + this.imageFieldName = 'company_logo'; + } + + get_filters_for_args() { + let filters = {}; + this.filter_area.get().forEach(f => { + let field = f[1] !== 'name' ? f[1] : 'company_name'; + filters[field] = [f[2], f[3]]; + }); + return filters; + } + + card_html(company) { + company._name = encodeURI(company.name); + const route = `#Hub/Company/${company.company_name}`; + + let image_html = company.company_logo ? + `` : + `
${frappe.get_abbr(company.company_name)}
`; + + return ` + + `; + } +}; \ No newline at end of file diff --git a/erpnext/public/js/hub/hub_page.js b/erpnext/public/js/hub/hub_page.js deleted file mode 100644 index 27986de563..0000000000 --- a/erpnext/public/js/hub/hub_page.js +++ /dev/null @@ -1,244 +0,0 @@ -frappe.provide('erpnext.hub'); - -erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { - setup_defaults() { - super.setup_defaults(); - this.page_title = __('Hub'); - this.method = 'erpnext.hub_node.get_list'; - - const route = frappe.get_route(); - this.page_name = route[1]; - } - - setup_fields() { - return this.get_meta() - .then(r => { - this.meta = r.message || this.meta; - frappe.model.sync(this.meta); - }); - } - - get_meta() { - return new Promise(resolve => - frappe.call('erpnext.hub_node.get_meta', {doctype: this.doctype}, resolve)); - } - - set_breadcrumbs() { } - - setup_side_bar() { - this.sidebar = new frappe.ui.Sidebar({ - wrapper: this.page.wrapper.find('.layout-side-section'), - css_class: 'hub-sidebar' - }); - } - - setup_sort_selector() { } - - setup_view() { } - - get_args() { - return { - doctype: this.doctype, - start: this.start, - limit: this.page_length, - order_by: this.order_by, - fields: this.fields, - filters: this.get_filters_for_args() - }; - } - - update_data(r) { - const data = r.message; - - if (this.start === 0) { - this.data = data; - } else { - this.data = this.data.concat(data); - } - - } - - freeze(toggle) { - this.$freeze.toggle(toggle); - if (this.$freeze.find('.image-view-container').length) return; - - const html = Array.from(new Array(4)).map(d => this.card_html({ - name: 'Loading...', - item_name: 'Loading...' - })).join(''); - - this.$freeze.html(`
${html}
`); - } - - render() { - this.render_image_view(); - } - - render_image_view() { - let data = this.data; - if (this.start === 0) { - this.$result.html('
'); - data = this.data.slice(this.start); - } - - var html = data.map(this.card_html.bind(this)).join(""); - this.$result.find('.image-view-container').append(html); - } -} - -erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Hub Item'; - this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name']; - this.filters = []; - this.custom_filter_configs = [ - { - fieldtype: 'Data', - label: 'Company', - condition: 'like', - fieldname: 'company_name', - }, - { - fieldtype: 'Link', - label: 'Country', - options: 'Country', - condition: 'like', - fieldname: 'country' - } - ]; - } - - setup_side_bar() { - super.setup_side_bar(); - this.category_tree = new frappe.ui.Tree({ - parent: this.sidebar.$sidebar, - label: 'All Categories', - expandable: true, - - args: {parent: this.current_category}, - method: 'erpnext.hub_node.get_categories', - on_click: (node) => { - this.update_category(node.label); - } - }); - - this.sidebar.add_item({ - label: __('Companies'), - on_click: () => frappe.set_route('Hub', 'Company') - }); - - this.sidebar.add_item({ - label: this.hub_settings.company, - on_click: () => frappe.set_route('Form', 'Company', this.hub_settings.company) - }, __("Account")); - - this.sidebar.add_item({ - label: __("My Orders"), - on_click: () => frappe.set_route('List', 'Request for Quotation') - }, __("Account")); - } - - update_category(label) { - this.current_category = (label=='All Categories') ? undefined : label; - this.refresh(); - } - - get_filters_for_args() { - let filters = {}; - this.filter_area.get().forEach(f => { - let field = f[1] !== 'name' ? f[1] : 'item_name'; - filters[field] = [f[2], f[3]]; - }); - if(this.current_category) { - filters['hub_category'] = this.current_category; - } - return filters; - } - - card_html(item) { - item._name = encodeURI(item.name); - const encoded_name = item._name; - const title = strip_html(item['item_name' || 'item_code']); - const company_name = item['company_name']; - - const route = `#Hub/Item/${item.hub_item_code}`; - - const image_html = item.image ? - ` - ` : - `
${frappe.get_abbr(title)}
`; - - return ` - - `; - } -}; - -erpnext.hub.CompanyListing = class CompanyListing extends erpnext.hub.HubListing { - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Hub Company'; - this.fields = ['company_logo', 'name', 'site_name', 'seller_city', 'seller_description', 'seller', 'country', 'company_name']; - this.filters = []; - this.custom_filter_configs = [ - { - fieldtype: 'Link', - label: 'Country', - options: 'Country', - condition: 'like', - fieldname: 'country' - } - ]; - } - - get_filters_for_args() { - let filters = {}; - this.filter_area.get().forEach(f => { - let field = f[1] !== 'name' ? f[1] : 'company_name'; - filters[field] = [f[2], f[3]]; - }); - return filters; - } - - card_html(company) { - company._name = encodeURI(company.name); - const route = `#Hub/Company/${company.company_name}`; - - let image_html = company.company_logo ? - `` : - `
${frappe.get_abbr(company.company_name)}
`; - - return ` - - `; - } -}; \ No newline at end of file diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 66199a47bc..f202fa4345 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -1,176 +1,109 @@ @import "../../../../frappe/frappe/public/less/variables.less"; body[data-route^="Hub/"] { - .freeze .image-view-container { - .list-row-col { - background-color: @light-bg; - color: @light-bg; - } - - .placeholder-text { - color: @light-bg; - } - } - - .freeze { - display: none; + .hub-icon { + width: 40px; } - .image-view-container { - justify-content: space-around; - } -} - -.img-wrapper { - border: 1px solid #d1d8dd; - border-radius: 3px; - padding: 12px; - overflow: hidden; - text-align: center; - white-space: nowrap; - - .helper { - height: 100%; - display: inline-block; - vertical-align: middle; + .hub-page-title { + margin-left: 10px; } -} -/* hub */ -div[data-page-route="hub"] { - .page-head { - height: 80px; + .img-wrapper { + border: 1px solid #d1d8dd; + border-radius: 3px; + padding: 12px; + overflow: hidden; + text-align: center; + white-space: nowrap; - .title-text { - cursor: pointer; + .helper { + height: 100%; + display: inline-block; + vertical-align: middle; } } - .page-content { - margin-top: 80px; + .tree { + margin: 10px 0px; + padding: 0px; + height: 100%; + position: relative; } - .page-title h1 { - margin-bottom: 0px; + .tree.with-skeleton.opened::before { + left: 9px; + top: 14px; + height: calc(~"100% - 32px"); } - .account-details { - margin-top: 20px; - } + .list-header-icon { + width: 72px; + border-radius: 4px; + flex-shrink: 0; + margin: 10px; + padding: 1px; + border: 1px solid @border-color; + height: 72px; + display: flex; + align-items: center; + justify-content: center; - [data-original-title="Search"] { - float: right; - width: 220px; + img { + border-radius: 4px; + } } - .hub-main-section { - padding: 30px; + .star-icon.fa-star { + color: @indicator-orange; } - .listing-body { - margin: 0; + .octicon-heart.liked { + color: @indicator-red; } - .main-list-section { - padding: 0; - // border-right: 1px solid #d1d8dd; + .margin-vertical-10 { + margin: 10px 0px; } - .side-list-section { - padding: 0; + .margin-vertical-15 { + margin: 15px 0px; } - .item-list-header h3 { - font-weight: normal; + .frappe-list .result { + min-height: 100px; } +} - .hub-item-page { - - h2 { - margin-top: 10px; - } - - .item-header { - display: flex; - } - - .item-page-image { - flex: 1; - } - - .title-content { - flex: 3; - - .description { - margin: 30px 0px; - } - - .actions { - margin-top: 30px; - - .rfq-btn.disabled { - background-color: #b1bdca; - color: #fff; - border-color: #b1bdca; - } - } - } - - .company-items { - margin-top: 40px; +.image-view-container { + .image-view-body { + &:hover .like-button { + opacity: 0.7; } } - .company-header { - display: flex; - } - - .item-list { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - } - - .hub-item-wrapper { - margin-bottom: 20px; - } + .like-button { + bottom: 10px !important; + left: 10px !important; + width: 36px; + height: 36px; + opacity: 0; + font-size: 16px; + color: @text-color; + position: absolute; - .img-wrapper { - border: 1px solid @border-color; - border-radius: 3px; - padding: 12px; - overflow: hidden; - text-align: center; - white-space: nowrap; - - img { - max-width: 100%; - max-height: 100%; - display: inline-block; - vertical-align: middle; - } - - .helper { - height: 100%; - display: inline-block; - vertical-align: middle; - } - - .standard-image { - font-size: 72px; - border: none; - background-color: @light-bg; + // show zoom button on mobile devices + @media (max-width: @screen-xs) { + opacity: 0.5 } } - .hub-item-title { - width: 100%; - } - - .breadcrumb { - padding-left: 0; - padding-top: 0; - margin-bottom: 10px; + .image-view-body:hover .like-button { + opacity: 0.7; } +} -} \ No newline at end of file +.rating-area .star-icon { + cursor: pointer; + font-size: 15px; +} diff --git a/erpnext/utilities/user_progress.py b/erpnext/utilities/user_progress.py index 9f5935e3cb..ae156b83d3 100644 --- a/erpnext/utilities/user_progress.py +++ b/erpnext/utilities/user_progress.py @@ -13,7 +13,7 @@ def get_slide_settings(): doc = frappe.get_doc("Setup Progress") item = [d for d in doc.get("actions") if d.action_name == "Set Sales Target"] - + if len(item): item = item[0] if not item.action_document: