Browse Source

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
develop
Prateeksha Singh 7 years ago
committed by Rushabh Mehta
parent
commit
b60a52b194
  1. 2
      erpnext/config/desktop.py
  2. 98
      erpnext/hub_node/__init__.py
  3. 4
      erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
  4. 139
      erpnext/hub_node/doctype/hub_settings/hub_settings.js
  5. 335
      erpnext/hub_node/doctype/hub_settings/hub_settings.json
  6. 46
      erpnext/hub_node/doctype/hub_settings/hub_settings.py
  7. 0
      erpnext/hub_node/doctype/hub_tracked_item/__init__.py
  8. 8
      erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js
  9. 93
      erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
  10. 10
      erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py
  11. 23
      erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js
  12. 10
      erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py
  13. 0
      erpnext/hub_node/doctype/hub_users/__init__.py
  14. 72
      erpnext/hub_node/doctype/hub_users/hub_users.json
  15. 10
      erpnext/hub_node/doctype/hub_users/hub_users.py
  16. 5
      erpnext/public/build.json
  17. 130
      erpnext/public/css/hub.css
  18. 154
      erpnext/public/images/hub_logo.svg
  19. 44
      erpnext/public/js/hub/hub_factory.js
  20. 386
      erpnext/public/js/hub/hub_form.js
  21. 665
      erpnext/public/js/hub/hub_listing.js
  22. 244
      erpnext/public/js/hub/hub_page.js
  23. 211
      erpnext/public/less/hub.less
  24. 2
      erpnext/utilities/user_progress.py

2
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")
},
{

98
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():

4
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"

139
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");
});
}
}

335
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": "",

46
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 = {

0
erpnext/hub_node/doctype/hub_tracked_item/__init__.py

8
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) {
}
});

93
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
}

10
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

23
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()
]);
});

10
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

0
erpnext/hub_node/doctype/hub_users/__init__.py

72
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
}

10
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

5
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",

130
erpnext/public/css/hub.css

@ -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;
}

154
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"><metadata
id="metadata3724"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs3722"><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3734"><path
d="M 0,600 H 1000 V 0 H 0 Z"
id="path3732"
inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview
id="svg4635"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="hub-logo.svg"
inkscape:export-filename="/home/raghu/Desktop/hub-logo.png"
inkscape:export-xdpi="95.878258"
inkscape:export-ydpi="95.878258">
<defs
id="defs4629" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-width="740"
inkscape:window-height="449"
id="namedview3720"
inkscape:zoom="0.7"
inkscape:cx="234.27717"
inkscape:cy="167.57445"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:zoom="1.1278353"
inkscape:cx="108.86803"
inkscape:cy="50.640564"
inkscape:window-x="605"
inkscape:window-y="98"
inkscape:window-maximized="0"
inkscape:current-layer="g3726"
fit-margin-top="10"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="10" /><g
id="g3726"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1149"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1" />
<metadata
id="metadata4632">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
inkscape:label="hub logo"
transform="matrix(1.3333333,0,0,-1.3333333,-579.98759,522.65266)"><g
id="g3728"><g
id="g3730"
clip-path="url(#clipPath3734)"><g
id="g3740"
transform="translate(469.3467,296.2959)"><path
d="m 0,0 c -16.597,0 -31.587,12.581 -32.298,13.186 l -2.058,1.751 1.131,2.452 c 0.44,0.956 11.012,23.476 30.001,27.662 l 1.527,0.336 C 10.532,48.083 22.675,40.327 25.37,28.098 28.065,15.871 20.307,3.728 8.081,1.033 L 6.551,0.698 C 4.36,0.214 2.167,0 0,0"
style="fill:#6be273;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path3742"
inkscape:connector-curvature="0" /></g><g
id="g3744"
transform="translate(519.9316,324.9663)"><path
d="m 0,0 c -8.052,0 -16.033,3.513 -21.457,10.266 -4.599,5.724 -6.689,12.896 -5.892,20.196 0.797,7.297 4.39,13.85 10.111,18.446 l 1.524,1.225 c 7.78,6.245 18.871,9.487 32.081,9.388 9.773,-0.076 17.263,-1.928 17.578,-2.006 l 2.621,-0.657 0.074,-2.699 C 36.675,52.862 37.313,22.21 18.708,7.269 L 17.184,6.044 C 12.118,1.977 6.044,0 0,0"
style="fill:#49c44c;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path3746"
inkscape:connector-curvature="0" /></g><g
id="g3748"
transform="translate(533.21,367.6562)"><path
d="m 0,0 c -5.312,-1.335 -13.328,-3.353 -20.459,-7.646 -8.907,-5.364 -13.925,-12.827 -14.923,-22.183 l -0.02,-0.188 v -14.901 c -3.873,3.824 -8.159,6.202 -12.809,7.101 -12.143,2.341 -23.14,-5.967 -29.049,-10.433 l 4.296,-5.686 c 5.024,3.797 14.373,10.864 23.402,9.122 5.207,-1.005 9.965,-4.887 14.16,-11.54 v -33.538 h 7.128 v 59.49 c 1.878,16.413 20.17,21.016 30.01,23.492 z"
style="fill:#cef6d1;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path3750"
inkscape:connector-curvature="0" /></g></g></g></g></svg>
id="layer1"
transform="translate(121.51931,-138.66452)">
<rect
rx="13.229166"
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
inkscape:export-filename="/home/raghu/Desktop/send/hub-02.png"
style="opacity:1;vector-effect:none;fill:#89da29;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect828"
width="87.3125"
height="87.3125"
x="-121.51931"
y="142.74918"
ry="13.229166" />
<path
style="opacity:1;vector-effect:none;fill:#63c923;fill-opacity:1;stroke:none;stroke-width:3.96875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
clip-path="none"
d="m -121.51931,202.96343 v 13.86892 c 0,7.32897 5.90017,13.22917 13.22916,13.22917 h 60.854162 c 6.610072,0 12.056133,-4.80013 13.061216,-11.1187 -43.339761,0.1608 -54.359752,-16.03276 -87.144538,-15.97939 z"
id="path830"
inkscape:connector-curvature="0" />
<path
style="opacity:1;vector-effect:none;fill:#59b81c;fill-opacity:1;stroke:none;stroke-width:3.96875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
clip-path="none"
d="m -34.20681,202.96343 c -32.784694,-0.0533 -43.804846,16.14019 -87.14455,15.97939 1.00509,6.31857 6.45115,11.1187 13.06122,11.1187 h 60.854164 c 7.328992,0 13.229166,-5.9002 13.229166,-13.22917 z"
id="path832"
inkscape:connector-curvature="0" />
<path
id="path834"
d="m -84.351263,175.75725 c -1.30945,0 -2.376091,1.06665 -2.376091,2.37608 v 10.02885 0.001 c 0.06583,4.83083 4.01156,8.73477 8.857351,8.73486 4.8718,5e-5 8.846821,-3.94421 8.871295,-8.81134 v -0.001 -9.95288 c 0,-1.30943 -1.066113,-2.37557 -2.375589,-2.37557 -1.309396,0 -2.376064,1.06614 -2.376064,2.37557 v 9.8888 c 0,2.26045 -1.858169,4.10983 -4.119642,4.10983 -2.263616,0 -4.105699,-1.82766 -4.105699,-4.08968 v -9.90844 c 0,-1.30943 -1.066138,-2.37608 -2.375561,-2.37608 z m -20.887107,0.0925 c -1.30943,0 -2.37609,1.06717 -2.37609,2.3766 v 16.45119 c 0,1.30944 1.06666,2.37609 2.37609,2.37609 1.30945,0 2.37556,-1.06665 2.37556,-2.37609 v -5.97327 h 8.22534 v 5.97327 c 0,1.30944 1.066641,2.37609 2.376091,2.37609 1.309423,0 2.375561,-1.06665 2.375561,-2.37609 v -16.45119 c 0,-1.30943 -1.066138,-2.3766 -2.375561,-2.3766 -1.30945,0 -2.376091,1.06717 -2.376091,2.3766 v 5.72627 h -8.22534 v -5.72627 c 0,-1.30943 -1.06611,-2.3766 -2.37556,-2.3766 z m 41.77419,0 c -0.654712,0 -1.248675,0.26711 -1.678967,0.69764 -0.05368,0.0537 -0.105119,0.10983 -0.153458,0.16846 v 5.3e-4 c -0.04839,0.0586 -0.09427,0.11929 -0.136949,0.18242 v 5.3e-4 c -0.256381,0.37936 -0.406691,0.83617 -0.406691,1.32705 v 16.45119 c 0,0.1635 0.01693,0.3242 0.04858,0.47852 0.09512,0.46331 0.32594,0.87828 0.64852,1.20096 0.161369,0.16136 0.345308,0.29938 0.547264,0.40928 v 0 c 0.134567,0.0732 0.276781,0.13403 0.425318,0.18035 v 0 c 0.148537,0.0463 0.303186,0.0783 0.462518,0.0946 v 0 c 0.07959,0.008 0.160708,0.0124 0.242358,0.0124 h 8.33181 c 0.08747,0 0.167931,-0.0145 0.251142,-0.0238 l 0.09509,0.005 c 0.06019,0.003 0.119407,0.005 0.178779,0.006 h 0.0037 0.0048 c 3.578305,-2e-5 6.487954,-2.90916 6.487981,-6.48747 v -0.001 c -0.0026,-1.51334 -0.578009,-2.9475 -1.540484,-4.10673 0.962448,-1.15892 1.537785,-2.59314 1.540484,-4.10621 v -0.001 c -2.7e-5,-3.57831 -2.909676,-6.48744 -6.487981,-6.48746 h -0.533294 z m 8.865103,4.75062 c 0.96393,0 1.736831,0.77394 1.736831,1.73788 0,0.96394 -0.772901,1.73684 -1.736831,1.73684 v 0 h -0.532792 -5.955718 v -3.47317 h 5.956248 z m 0,8.21552 v 0 c 0.963507,5.3e-4 1.735799,0.77373 1.735799,1.73736 0,0.96394 -0.772901,1.73684 -1.736831,1.73684 h -0.0048 l -0.533294,0.0119 h -5.951591 v -3.4742 h 5.959846 z"
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79375005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#63c923;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.93750048;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -77.859375,138.66406 c -9.653316,0 -18.439915,3.93483 -24.767575,10.28125 a 3.9691471,3.9691471 0 1 0 5.621091,5.60352 c 4.899576,-4.9141 11.6422,-7.94727 19.146484,-7.94727 7.501101,0 14.241542,3.03098 19.140625,7.94141 a 3.9691471,3.9691471 0 1 0 5.619141,-5.60547 c -6.327038,-6.34169 -15.110547,-10.27344 -24.759766,-10.27344 z"
id="path838"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

44
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 = `<div class='page-card' style='margin: 140px auto;'>
<div class='page-card-head'>
<span class='indicator red'>${'Failed to connect'}</span>
</div>
<p>${ __("Please check your network connection.") }</p>
<div><a href='#Hub/Item' class='btn btn-primary btn-sm'>
${ __("Reload") }</a></div>
</div>`;
let page = $('#body_div');
page.append(html);
return;
}
});

386
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 `<div class="timeline">
<div class="timeline-head">
</div>
<div class="timeline-new-email">
<button class="btn btn-default btn-reply-email btn-xs">
${__("Reply")}
</button>
</div>
<div class="timeline-items"></div>
</div>`;
}
get_footer() {
return `<div class="form-footer">
<div class="after-save">
<div class="form-comments"></div>
</div>
<div class="pull-right scroll-to-top">
<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
</div>
</div>`;
}
get_args() {
return {
hub_sync_id: this.unique_id,
@ -48,108 +110,296 @@ erpnext.hub.HubForm = class HubForm extends frappe.views.BaseList {
<span class="helper"></span>` :
`<div class="standard-image">${frappe.get_abbr(this.page_title)}</div>`;
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 = `<div class="form-footer">
<div class="form-comments"></div>
<div class="pull-right scroll-to-top">
<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
</div>
</div>`;
let parent = $('<div>').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 = `<div class="timeline">
<div class="timeline-head">
</div>
<div class="timeline-new-email">
<button class="btn btn-default btn-reply-email btn-xs">
${ __("Reply") }
</button>
</div>
<div class="timeline-items"></div>
</div>`;
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
? `<div class="avatar-frame" style="background-image: url(${data.user_image})"></div>`
: `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
let editHtml = data.own
? `<div class="pull-right hidden-xs close-btn-container">
<span class="small text-muted">
${'data.delete'}
</span>
</div>
<div class="pull-right edit-btn-container">
<span class="small text-muted">
${'data.edit'}
</span>
</div>`
: '';
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 += `<i class="fa fa-fw ${starIcon} star-icon" data-idx='${i}'></i>`;
}
fields.unshift(category_field);
$(this.getTimelineItem(data, imageHtml, editHtml, ratingHtml))
.appendTo(this.$timelineList);
}
getTimelineItem(data, imageHtml, editHtml, ratingHtml) {
return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
${imageHtml}
</span>
<div class="pull-left media-body">
<div class="media-content-wrapper">
<div class="action-btns">${editHtml}</div>
<div class="comment-header clearfix small ${'linksActive'}">
<span class="pull-left avatar avatar-small visible-xs">
${imageHtml}
</span>
<div class="asset-details">
<span class="author-wrap">
<i class="octicon octicon-quote hidden-xs fa-fw"></i>
<span>${data.username}</span>
</span>
<a href="#Form/${''}" class="text-muted">
<span class="text-muted hidden-xs">&ndash;</span>
<span class="indicator-right ${'green'}
delivery-status-indicator">
<span class="hidden-xs">${data.pretty_date}</span>
</span>
</a>
return fields;
<a class="text-muted reply-link pull-right timeline-content-show"
title="${__('Reply')}"> ${''} </a>
<span class="comment-likes hidden-xs">
<i class="octicon octicon-heart like-action text-extra-muted not-liked fa-fw">
</i>
<span class="likes-count text-muted">10</span>
</span>
</div>
</div>
<div class="reply timeline-content-show">
<div class="timeline-item-content">
<p class="text-muted small">
<b>${data.subject}</b>
</p>
<hr>
<p class="text-muted small">
${ratingHtml}
</p>
<hr>
<p>
${data.content}
</p>
</div>
</div>
</div>
</div>
</div>`;
}
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);
}
}

665
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 = `<img class="hub-icon" src="assets/erpnext/images/hub_logo.svg">`;
let titleHtml = `<span class="hub-page-title">${title}</span>`;
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(`<div class="image-view-container border-top">${html}</div>`);
}
render() {
this.data_dict = {};
this.render_image_view();
this.setup_quick_view();
this.setup_like();
}
render_offline_card() {
let html = `<div class='page-card'>
<div class='page-card-head'>
<span class='indicator red'>
{{ _("Payment Cancelled") }}</span>
</div>
<p>${ __("Your payment is cancelled.") }</p>
<div><a href='' class='btn btn-primary btn-sm'>
${ __("Continue") }</a></div>
</div>`;
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()}
<div class="image-view-container small">
${html}
</div>
`);
}
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 `
<header class="list-row-head text-muted small">
<div style="display: flex;">
<div class="list-header-icon">
<img title="Riadco%20Group" alt="Riadco Group" src="https://cdn.pbrd.co/images/HdaPxcg.png">
</div>
<div class="list-header-info">
<h5>Riadco Group</h5>
<span class="margin-vertical-10 level-item">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</span>
</div>
</div>
</header>
`;
}
renderHeader() {
return `<header class="level list-row-head text-muted small">
<div class="level-left list-header-subject">
<div class="list-row-col list-subject level ">
<img title="Riadco%20Group" alt="Riadco Group" src="https://cdn.pbrd.co/images/HdaPxcg.png">
<span class="level-item">Products by Blah blah</span>
</div>
</div>
<div class="level-left checkbox-actions">
<div class="level list-subject">
<input class="level-item list-check-all hidden-xs" type="checkbox" title="${__("Select All")}">
<span class="level-item list-header-meta"></span>
</div>
</div>
<div class="level-right">
${''}
</div>
</header>`;
}
get_image_html(encoded_name, src, alt_text) {
return `<img data-name="${encoded_name}" src="${ src }" alt="${ alt_text }">`;
}
get_image_placeholder(title) {
return `<span class="placeholder-text">${ frappe.get_abbr(title) }</span>`;
}
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 += `<i class='fa fa-fw ${starClass} star-icon' data-index=${i}></i>`;
}
let item_html = `
<div class="image-view-item">
<div class="image-view-header">
<div class="list-row-col list-subject ellipsis level">
<span class="level-item bold ellipsis" title="McGuffin">
<a href="${route}">${title}</a>
</span>
</div>
<div class="text-muted small" style="margin: 5px 0px;">
${ratingHtml}
(${reviewLength})
</div>
<div class="list-row-col">
<a href="${'#Hub/Company/'+company_name}"><p>${ company_name }</p></a>
</div>
</div>
<div class="image-view-body">
<a data-name="${encoded_name}"
title="${encoded_name}"
href="${route}"
>
<div class="image-field ${_class}"
data-name="${encoded_name}"
>
<button class="btn btn-default zoom-view" data-name="${encoded_name}">
<i class="octicon octicon-eye" data-name="${encoded_name}"></i>
</button>
<button class="btn btn-default like-button" data-name="${encoded_name}">
<i class="octicon octicon-heart" data-name="${encoded_name}"></i>
</button>
</div>
</a>
</div>
</div>
`;
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 = $(`<div class="border" style="
margin-top: 10px;
padding: 0px 10px;
border-radius: 3px;
">
<h5>Sell on HubMarket</h5>
<p>Over 2000 products listed. Register your company to start selling.</p>
</div>`);
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 ?
`<img src="${company.company_logo}"><span class="helper"></span>` :
`<div class="standard-image">${frappe.get_abbr(company.company_name)}</div>`;
return `
<div class="hub-item-wrapper margin-bottom" style="width: 200px;">
<a href="${route}">
<div class="hub-item-image">
<div class="img-wrapper" style="height: 200px; width: 200px">
${ image_html }
</div>
</div>
<div class="hub-item-title">
<h5 class="bold">
${ company.company_name }
</h5>
</div>
</a>
</div>
`;
}
};

244
erpnext/public/js/hub/hub_page.js

@ -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(`<div class="image-view-container border-top">${html}</div>`);
}
render() {
this.render_image_view();
}
render_image_view() {
let data = this.data;
if (this.start === 0) {
this.$result.html('<div class="image-view-container small padding-top">');
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 ?
`<img src="${item.image}">
<span class="helper"></span>` :
`<div class="standard-image">${frappe.get_abbr(title)}</div>`;
return `
<div class="hub-item-wrapper margin-bottom" style="width: 200px;">
<a href="${route}">
<div class="hub-item-image">
<div class="img-wrapper" style="height: 200px; width: 200px">
${ image_html }
</div>
</div>
<div class="hub-item-title">
<h5 class="bold">
${ title }
</h5>
</div>
</a>
<a href="${'#Hub/Company/'+company_name}"><p>${ company_name }</p></a>
</div>
`;
}
};
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 ?
`<img src="${company.company_logo}"><span class="helper"></span>` :
`<div class="standard-image">${frappe.get_abbr(company.company_name)}</div>`;
return `
<div class="hub-item-wrapper margin-bottom" style="width: 200px;">
<a href="${route}">
<div class="hub-item-image">
<div class="img-wrapper" style="height: 200px; width: 200px">
${ image_html }
</div>
</div>
<div class="hub-item-title">
<h5 class="bold">
${ company.company_name }
</h5>
</div>
</a>
</div>
`;
}
};

211
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;
}
}
}
.rating-area .star-icon {
cursor: pointer;
font-size: 15px;
}

2
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:

Loading…
Cancel
Save