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
This commit is contained in:
Prateeksha Singh 2018-04-03 10:44:13 +05:30 committed by Rushabh Mehta
parent c5effbf009
commit b60a52b194
24 changed files with 2014 additions and 705 deletions

View File

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

View File

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

View File

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

View File

@ -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");
}
});
},
// update_hub: (frm) => {
// this.frm.call({
// doc: this.frm.doc,
// method: "update_hub",
// args: {},
// freeze: true,
// callback: function(r) { },
// onerror: function() { }
// });
// },
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);
}
});
},
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");
});
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
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");
}
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
});
}
});
}
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 = '';
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>`;
}
$(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>
<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>`;
}
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,
};
});
}
};
erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubForm{
erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubDetailsPage {
constructor(opts) {
super(opts);
this.show();
}
setup_defaults() {
super.setup_defaults();
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
});
});
},
let category_field = {
label: 'Hub Category',
fieldname: 'hub_category',
fieldtype: 'Data'
}
{
label: __('Suggest Category?'),
fieldname: 'set_category',
fieldtype: 'Button',
click: () => {
this.categoryDialog.show();
}
},
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;
}
fields.unshift(category_field);
return fields;
}
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);
});
}
setup_fields() {
this.fields = ['hub_item_code', 'item_name', 'item_code', 'description',
'seller', 'company_name', 'country'];
{
fieldname: 'cb1',
fieldtype: 'Column Break'
}
];
this.formFields = colOneFields.concat(miscFields, colTwoFields);
}
}
erpnext.hub.CompanyPage = class CompanyPage extends erpnext.hub.HubForm{
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';
}
get_field_configs() {
let fields = [];
this.fields.map(fieldname => {
fields.push({
label: toTitle(frappe.model.unscrub(fieldname)),
fieldname,
fieldtype: 'Data',
read_only: 1
});
});
return fields;
}
setup_fields() {
this.fields = ['company_name', 'description', 'route', 'country', 'seller', 'site_name'];
getFormFields() {
let fieldnames = ['company_name', 'description', 'route', 'country', 'seller', 'site_name'];;
this.formFields = this.prepareFormFields(this.meta.fields, fieldnames);
}
}

View File

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

View File

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

View File

@ -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;
}
.hub-icon {
width: 40px;
}
.freeze {
display: none;
}
.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 */
div[data-page-route="hub"] {
.page-head {
height: 80px;
.title-text {
cursor: pointer;
}
}
.page-content {
margin-top: 80px;
}
.page-title h1 {
margin-bottom: 0px;
}
.account-details {
margin-top: 20px;
}
[data-original-title="Search"] {
float: right;
width: 220px;
}
.hub-main-section {
padding: 30px;
}
.listing-body {
margin: 0;
}
.main-list-section {
padding: 0;
// border-right: 1px solid #d1d8dd;
}
.side-list-section {
padding: 0;
}
.item-list-header h3 {
font-weight: normal;
}
.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;
}
}
.company-header {
display: flex;
}
.item-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.hub-item-wrapper {
margin-bottom: 20px;
.hub-page-title {
margin-left: 10px;
}
.img-wrapper {
border: 1px solid @border-color;
border: 1px solid #d1d8dd;
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;
.tree {
margin: 10px 0px;
padding: 0px;
height: 100%;
position: relative;
}
.tree.with-skeleton.opened::before {
left: 9px;
top: 14px;
height: calc(~"100% - 32px");
}
.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;
img {
border-radius: 4px;
}
}
.hub-item-title {
width: 100%;
.star-icon.fa-star {
color: @indicator-orange;
}
.breadcrumb {
padding-left: 0;
padding-top: 0;
margin-bottom: 10px;
.octicon-heart.liked {
color: @indicator-red;
}
}
.margin-vertical-10 {
margin: 10px 0px;
}
.margin-vertical-15 {
margin: 15px 0px;
}
.frappe-list .result {
min-height: 100px;
}
}
.image-view-container {
.image-view-body {
&:hover .like-button {
opacity: 0.7;
}
}
.like-button {
bottom: 10px !important;
left: 10px !important;
width: 36px;
height: 36px;
opacity: 0;
font-size: 16px;
color: @text-color;
position: absolute;
// show zoom button on mobile devices
@media (max-width: @screen-xs) {
opacity: 0.5
}
}
.image-view-body:hover .like-button {
opacity: 0.7;
}
}
.rating-area .star-icon {
cursor: pointer;
font-size: 15px;
}

View File

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