diff --git a/config.json b/config.json new file mode 100644 index 0000000000..cee89611ae --- /dev/null +++ b/config.json @@ -0,0 +1,49 @@ +{ + "modules": { + "To Do": { + "type": "page", + "link": "todo", + "color": "#febf04", + "label": "To Do", + "icon": "icon-check" + }, + "Calendar": { + "type": "view", + "link": "Calendar/Event", + "color": "#026584", + "label": "Calendar", + "icon": "icon-calendar" + }, + "Messages": { + "type": "page", + "link": "messages", + "color": "#8d016e", + "label": "Messages", + "icon": "icon-comments" + } + }, + "web": { + "pages": { + "404": { + "template": "lib/templates/pages/404" + }, + "error": { + "no_cache": true, + "template": "lib/templates/pages/error" + }, + "login": { + "template": "lib/templates/pages/login" + }, + "message": { + "no_cache": true, + "template": "lib/templates/pages/message" + }, + "print": { + "no_cache": true, + "template": "lib/templates/pages/print", + "args_method": "core.doctype.print_format.print_format.get_args" + } + }, + "generators": {} + } +} \ No newline at end of file diff --git a/core/page/data_import_tool/data_import_tool.py b/core/page/data_import_tool/data_import_tool.py index 84beef6690..1d47962932 100644 --- a/core/page/data_import_tool/data_import_tool.py +++ b/core/page/data_import_tool/data_import_tool.py @@ -8,7 +8,7 @@ from webnotes.utils import cstr, cint, flt from webnotes.utils.datautils import UnicodeWriter data_keys = webnotes._dict({ - "data_separator": '::: Start entering data below this line :::', + "data_separator": 'Start entering data below this line', "main_table": "Table:", "parent_table": "Parent Table:", "columns": "Column Name:" @@ -68,7 +68,7 @@ def get_template(): else: w.writerow(['']) - w.writerow([':::']) + w.writerow(['']) w.writerow(['Notes:']) w.writerow(['Please do not change the template headings.']) w.writerow(['First data column must be blank.']) @@ -80,7 +80,7 @@ def get_template(): if key == "parent": w.writerow(['"Parent" signifies the parent table in which this row must be added']) w.writerow(['If you are updating, please select "Overwrite" else existing rows will not be deleted.']) - w.writerow([':::']) + w.writerow(['']) labelrow = ["Column Labels", "ID"] fieldrow = [data_keys.columns, key] mandatoryrow = ['Mandatory:', 'Yes'] diff --git a/core/page/desktop/__init__.py b/core/page/desktop/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/page/desktop/desktop.css b/core/page/desktop/desktop.css new file mode 100644 index 0000000000..2f5be53d11 --- /dev/null +++ b/core/page/desktop/desktop.css @@ -0,0 +1,83 @@ +.case-border { + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + width: 32px; + height: 32px; + padding: 12px; + border: 2px solid white; + box-shadow: 0 0 4px 1px black; + -moz-box-shadow: 0 0 4px 1px black; + -webkit-box-shadow: 0 0 4px 1px black; + -o-box-shadow: 0 0 4px 1px black; + margin: auto; +} + +.case-wrapper { + margin: 24px; + float: left; + width: 100px; + height: 100px; +} + +.case-wrapper i { + font-size: 32px; + color: #f8f8f8; +} + +.case-label { + color: white; + padding-top: 10px; + text-align: center; + text-shadow: 1px 1px 2px #000, 1px 1px 2px #000, 1px 1px 2px #000, 0px 0px 2px #000; +} + +/* Hover and click effects */ +.case-border:hover, .circle:hover, .hover-effect { + box-shadow: 0 0 2px 0px black, 0 0 4px 1px white !important; + -moz-box-shadow: 0 0 2px 0px black, 0 0 4px 1px white !important; + -webkit-box-shadow: 0 0 2px 0px black, 0 0 4px 1px white !important; + -o-box-shadow: 0 0 2px 0px black, 0 0 10px 1px white !important; +} + +.case-border:active, .case-border:focus, .case-border-click { + transform: scale(0.98, 0.98); + -ms-transform: scale(0.98, 0.98); /* IE 9 */ + -webkit-transform: scale(0.98, 0.98); /* Safari and Chrome */ + -o-transform: scale(0.98, 0.98); /* Opera */ + -moz-transform: scale(0.98, 0.98); /* Firefox */ +} + +.circle:active, .circle:focus, .circle-click { + transform: scale(1, 1); + -ms-transform: scale(1, 1); /* IE 9 */ + -webkit-transform: scale(1, 1); /* Safari and Chrome */ + -o-transform: scale(1, 1); /* Opera */ + -moz-transform: scale(1, 1); /* Firefox */ +} + +.circle { + border-radius: 30px; + -moz-border-radius: 30px; + -webkit-border-radius: 30px; + height: 15px; + line-height: 12px; + min-width: 15px; + background: #B00D07; + padding: 3px; + float: right; + margin-top: -12px; + margin-right: 10px; + border: 2px solid white; + box-shadow: 0 0 4px 1px black; + -moz-box-shadow: 0 0 4px 1px black; + -webkit-box-shadow: 0 0 4px 1px black; + -o-box-shadow: 0 0 4px 1px black; +} + +.circle-text { + color: white; + text-align: center; + display: inline-block; + margin-top: 1px; +} \ No newline at end of file diff --git a/core/page/desktop/desktop.html b/core/page/desktop/desktop.html new file mode 100644 index 0000000000..43ee4236b7 --- /dev/null +++ b/core/page/desktop/desktop.html @@ -0,0 +1,6 @@ +
+
+
+
+
diff --git a/core/page/desktop/desktop.js b/core/page/desktop/desktop.js new file mode 100644 index 0000000000..9b1e9e1610 --- /dev/null +++ b/core/page/desktop/desktop.js @@ -0,0 +1,101 @@ +wn.provide('erpnext.desktop'); + +erpnext.desktop.refresh = function() { + erpnext.desktop.render(); + + $("#icon-grid").sortable({ + update: function() { + new_order = []; + $("#icon-grid .case-wrapper").each(function(i, e) { + new_order.push($(this).attr("data-name")); + }); + wn.defaults.set_default("_desktop_items", new_order); + } + }); +} + +erpnext.desktop.render = function() { + document.title = "Desktop"; + var add_icon = function(m) { + var module = wn.modules[m]; + if(!module.label) + module.label = m; + module.name = m; + module.label = wn._(module.label); + module.gradient_css = wn.get_gradient_css(module.color, 45); + module._link = module.link.toLowerCase().replace("/", "-"); + + $module_icon = $(repl('\ +
\ +
\ + \ +
\ +
%(label)s
\ +
', module)).click(function() { + wn.set_route($(this).attr("data-link")); + }).css({ + cursor:"pointer" + }).appendTo("#icon-grid"); + } + + // modules + var modules_list = wn.user.get_desktop_items(); + $.each(modules_list, function(i, m) { + if(m!="Setup") + add_icon(m); + }) + + // setup + if(user_roles.indexOf('System Manager')!=-1) + add_icon('Setup') + + // notifications + erpnext.desktop.show_pending_notifications(); + +} + +erpnext.desktop.show_pending_notifications = function() { + var add_circle = function(str_module, id, title) { + var module = $('#'+str_module); + module.prepend( + repl('', {id: id, title: wn._(title)})); + + var case_border = module.find('.case-border'); + var circle = module.find('.circle'); + + var add_hover_and_click = function(primary, secondary, hover_class, click_class) { + primary + .hover( + function() { secondary.addClass(hover_class); }, + function() { secondary.removeClass(hover_class); }) + .mousedown(function() { secondary.addClass(click_class); }) + .mouseup(function() { secondary.removeClass(click_class); }) + .focusin(function() { $(this).mousedown(); }) + .focusout(function() { $(this).mouseup(); }) + } + + add_hover_and_click(case_border, circle, 'hover-effect', 'circle-click'); + add_hover_and_click(circle, case_border, 'hover-effect', 'case-border-click'); + + } + + add_circle('module-icon-messages', 'unread_messages', 'Unread Messages'); + add_circle('module-icon-support-home', 'open_support_tickets', 'Open Support Tickets'); + add_circle('module-icon-todo', 'things_todo', 'Things To Do'); + add_circle('module-icon-calendar-event', 'todays_events', 'Todays Events'); + add_circle('module-icon-projects-home', 'open_tasks', 'Open Tasks'); + add_circle('module-icon-questions', 'unanswered_questions', 'Unanswered Questions'); + add_circle('module-icon-selling-home', 'open_leads', 'Open Leads'); + + erpnext.update_messages(); + +} + +pscript.onload_desktop = function() { + // load desktop + erpnext.desktop.refresh(); +} + diff --git a/core/page/desktop/desktop.txt b/core/page/desktop/desktop.txt new file mode 100644 index 0000000000..3e9219f7a6 --- /dev/null +++ b/core/page/desktop/desktop.txt @@ -0,0 +1,32 @@ +[ + { + "creation": "2013-02-14 17:37:37", + "docstatus": 0, + "modified": "2013-04-03 11:38:05", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Page", + "module": "Core", + "name": "__common__", + "page_name": "desktop", + "standard": "Yes", + "title": "Desktop" + }, + { + "doctype": "Page Role", + "name": "__common__", + "parent": "desktop", + "parentfield": "roles", + "parenttype": "Page", + "role": "All" + }, + { + "doctype": "Page", + "name": "desktop" + }, + { + "doctype": "Page Role" + } +] \ No newline at end of file diff --git a/core/page/messages/__init__.py b/core/page/messages/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/page/messages/messages.css b/core/page/messages/messages.css new file mode 100644 index 0000000000..88bbde7e86 --- /dev/null +++ b/core/page/messages/messages.css @@ -0,0 +1,32 @@ +#message-post-text { +} + +#message-list { +} + +.message { + padding: 7px; + padding-left: 17px; + border-bottom: 1px solid #ccc; +} + +.message-mark { + margin-left: -17px; + width: 9px; + position: absolute; + height: 30px; +} + +.message .help { + margin-bottom: 0px; + padding-bottom: 0px; + color: #888; + font-size: 11px; +} + +.message-other { +} + +.message-self { + background-color: #eee; +} \ No newline at end of file diff --git a/core/page/messages/messages.html b/core/page/messages/messages.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/page/messages/messages.js b/core/page/messages/messages.js new file mode 100644 index 0000000000..cf5d790e53 --- /dev/null +++ b/core/page/messages/messages.js @@ -0,0 +1,214 @@ +// ERPNext - web based ERP (http://erpnext.com) +// Copyright (C) 2012 Web Notes Technologies Pvt Ltd +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +wn.provide('erpnext.messages'); + +wn.pages.messages.onload = function(wrapper) { + wn.ui.make_app_page({ + parent: wrapper, + title: "Messages" + }); + + $('
\ +
\ +

Everyone

\ +

\ +
\ + \ +

\ +
\ +
').appendTo($(wrapper).find('.layout-main-section')); + + wrapper.appframe.add_home_breadcrumb(); + wrapper.appframe.add_breadcrumb(wn.modules["Messages"].icon); + + erpnext.messages = new erpnext.Messages(wrapper); + erpnext.toolbar.set_new_comments(0); +} + +$(wn.pages.messages).bind('show', function() { + // remove alerts + $('#alert-container .alert').remove(); + + erpnext.toolbar.set_new_comments(0); + erpnext.messages.show(); + setTimeout("erpnext.messages.refresh()", 17000); +}) + +erpnext.Messages = Class.extend({ + init: function(wrapper) { + this.wrapper = wrapper; + this.show_active_users(); + this.make_post_message(); + this.make_list(); + //this.update_messages('reset'); //Resets notification icons + }, + make_post_message: function() { + var me = this; + $('#post-message textarea').keydown(function(e) { + if(e.which==13) { + $('#post-message .btn').click(); + return false; + } + }); + + $('#post-message .btn').click(function() { + var txt = $('#post-message textarea').val(); + if(txt) { + wn.call({ + module:'core', + page:'messages', + method:'post', + args: { + txt: txt, + contact: me.contact + }, + callback:function(r,rt) { + $('#post-message textarea').val('') + me.list.run(); + }, + btn: this + }); + } + }); + }, + show: function() { + var contact = this.get_contact() || this.contact || user; + + $('#message-title').html(contact==user ? "Everyone" : + wn.user_info(contact).fullname) + + $('#avatar-image').attr("src", wn.utils.get_file_link(wn.user_info(contact).image)); + + $("#show-everyone").toggle(contact!=user); + + $("#post-message button").text(contact==user ? "Post Publicly" : "Post to user") + + this.contact = contact; + this.list.opts.args.contact = contact; + this.list.run(); + + }, + // check for updates every 5 seconds if page is active + refresh: function() { + setTimeout("erpnext.messages.refresh()", 17000); + if(wn.container.page.label != 'Messages') return; + this.show(); + }, + get_contact: function() { + var route = location.hash; + if(route.indexOf('/')!=-1) { + var name = decodeURIComponent(route.split('/')[1]); + if(name.indexOf('__at__')!=-1) { + name = name.replace('__at__', '@'); + } + return name; + } + }, + make_list: function() { + this.list = new wn.ui.Listing({ + parent: $(this.wrapper).find('.all-messages'), + method: 'core.page.messages.messages.get_list', + args: { + contact: null + }, + hide_refresh: true, + no_loading: true, + render_row: function(wrapper, data) { + $(wrapper).removeClass('list-row'); + + data.creation = dateutil.comment_when(data.creation); + data.comment_by_fullname = wn.user_info(data.owner).fullname; + data.image = wn.utils.get_file_link(wn.user_info(data.owner).image); + data.mark_html = ""; + + data.reply_html = ''; + if(data.owner==user) { + data.cls = 'message-self'; + data.comment_by_fullname = 'You'; + } else { + data.cls = 'message-other'; + } + + // delete + data.delete_html = ""; + if(data.owner==user || data.comment.indexOf("assigned to")!=-1) { + data.delete_html = repl('×', data); + } + + if(data.owner==data.comment_docname && data.parenttype!="Assignment") { + data.mark_html = "
" + } + + wrapper.innerHTML = repl('
%(mark_html)s\ + %(comment)s\ + %(delete_html)s\ +
by %(comment_by_fullname)s, %(creation)s
\ +
\ +
', data); + } + }); + }, + delete: function(ele) { + $(ele).parent().css('opacity', 0.6); + wn.call({ + method:'core.page.messages.messages.delete', + args: {name : $(ele).attr('data-name')}, + callback: function() { + $(ele).parent().toggle(false); + } + }); + }, + show_active_users: function() { + var me = this; + wn.call({ + module:'core', + page:'messages', + method:'get_active_users', + callback: function(r,rt) { + var $body = $(me.wrapper).find('.layout-side-section'); + $('

Users


\ +
\ + \ + Show messages from everyone
\ + ').appendTo($body); + r.message.sort(function(a, b) { return b.has_session - a.has_session; }); + for(var i in r.message) { + var p = r.message[i]; + if(p.name != user) { + p.fullname = wn.user_info(p.name).fullname; + p.image = wn.utils.get_file_link(wn.user_info(p.name).image); + p.name = p.name.replace('@', '__at__'); + p.status_color = p.has_session ? "green" : "#ddd"; + p.status = p.has_session ? "Online" : "Offline"; + $(repl('

\ + \ + %(fullname)s\ +

', p)) + .appendTo($body); + } + } + } + }); + } +}); + + diff --git a/core/page/messages/messages.py b/core/page/messages/messages.py new file mode 100644 index 0000000000..90f5e9152f --- /dev/null +++ b/core/page/messages/messages.py @@ -0,0 +1,117 @@ +# ERPNext - web based ERP (http://erpnext.com) +# Copyright (C) 2012 Web Notes Technologies Pvt Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import unicode_literals +import webnotes + +@webnotes.whitelist() +def get_list(arg=None): + """get list of messages""" + webnotes.form_dict['limit_start'] = int(webnotes.form_dict['limit_start']) + webnotes.form_dict['limit_page_length'] = int(webnotes.form_dict['limit_page_length']) + webnotes.form_dict['user'] = webnotes.session['user'] + + # set all messages as read + webnotes.conn.begin() + webnotes.conn.sql("""UPDATE `tabComment` + set docstatus = 1 where comment_doctype in ('My Company', 'Message') + and comment_docname = %s + """, webnotes.user.name) + webnotes.conn.commit() + + if webnotes.form_dict['contact'] == webnotes.session['user']: + # return messages + return webnotes.conn.sql("""select * from `tabComment` + where (owner=%(contact)s + or comment_docname=%(user)s + or (owner=comment_docname and ifnull(parenttype, "")!="Assignment")) + and comment_doctype ='Message' + order by creation desc + limit %(limit_start)s, %(limit_page_length)s""", webnotes.form_dict, as_dict=1) + else: + return webnotes.conn.sql("""select * from `tabComment` + where (owner=%(contact)s and comment_docname=%(user)s) + or (owner=%(user)s and comment_docname=%(contact)s) + or (owner=%(contact)s and comment_docname=%(contact)s) + and comment_doctype ='Message' + order by creation desc + limit %(limit_start)s, %(limit_page_length)s""", webnotes.form_dict, as_dict=1) + + +@webnotes.whitelist() +def get_active_users(arg=None): + return webnotes.conn.sql("""select name, + (select count(*) from tabSessions where user=tabProfile.name + and timediff(now(), lastupdate) < time("01:00:00")) as has_session + from tabProfile + where ifnull(enabled,0)=1 and + docstatus < 2 and + name not in ('Administrator', 'Guest') + order by first_name""", as_dict=1) + +@webnotes.whitelist() +def post(arg=None): + import webnotes + """post message""" + if not arg: + arg = {} + arg.update(webnotes.form_dict) + + if isinstance(arg, basestring): + import json + arg = json.loads(arg) + + from webnotes.model.doc import Document + d = Document('Comment') + d.parenttype = arg.get("parenttype") + d.comment = arg['txt'] + d.comment_docname = arg['contact'] + d.comment_doctype = 'Message' + d.save() + + import webnotes.utils + if webnotes.utils.cint(arg.get('notify')): + notify(arg) + +@webnotes.whitelist() +def delete(arg=None): + webnotes.conn.sql("""delete from `tabComment` where name=%s""", + webnotes.form_dict['name']); + +def notify(arg=None): + from webnotes.utils import cstr, get_fullname + from startup import get_url + + fn = get_fullname(webnotes.user.name) or webnotes.user.name + + url = get_url() + + message = '''You have a message from %s: + + %s + + To answer, please login to your erpnext account at \ + %s + ''' % (fn, arg['txt'], url, url) + + sender = webnotes.conn.get_value("Profile", webnotes.user.name, "email") \ + or webnotes.user.name + recipient = [webnotes.conn.get_value("Profile", arg["contact"], "email") \ + or arg["contact"]] + + from webnotes.utils.email_lib import sendmail + sendmail(recipient, sender, message, arg.get("subject") or "You have a message from %s" % (fn,)) + \ No newline at end of file diff --git a/core/page/messages/messages.txt b/core/page/messages/messages.txt new file mode 100644 index 0000000000..0949bd7fbb --- /dev/null +++ b/core/page/messages/messages.txt @@ -0,0 +1,32 @@ +[ + { + "creation": "2012-06-14 18:44:56", + "docstatus": 0, + "modified": "2013-04-03 14:48:57", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Page", + "module": "Core", + "name": "__common__", + "page_name": "messages", + "standard": "Yes", + "title": "Messages" + }, + { + "doctype": "Page Role", + "name": "__common__", + "parent": "messages", + "parentfield": "roles", + "parenttype": "Page", + "role": "All" + }, + { + "doctype": "Page", + "name": "messages" + }, + { + "doctype": "Page Role" + } +] \ No newline at end of file diff --git a/core/page/modules_setup/__init__.py b/core/page/modules_setup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/page/modules_setup/modules_setup.js b/core/page/modules_setup/modules_setup.js new file mode 100644 index 0000000000..e5de902520 --- /dev/null +++ b/core/page/modules_setup/modules_setup.js @@ -0,0 +1,51 @@ + +wn.pages['modules_setup'].onload = function(wrapper) { + wn.ui.make_app_page({ + parent: wrapper, + title: 'Show or Hide Modules', + single_column: true + }); + + wrapper.appframe.add_button("Update", function() { + wn.modules_setup.update(); + }) + + $('
Select modules to be shown.
').appendTo($(wrapper).find(".layout-main")); + $('
').appendTo($(wrapper).find(".layout-main")); + + wn.modules_setup.refresh_page(); +} + +wn.modules_setup = { + refresh_page: function() { + $('#modules-list').empty(); + + $.each(keys(wn.modules).sort(), function(i, m) { + if(m!="Setup") { + var $chk = $("") + .prependTo($('

'+m+'

').appendTo("#modules-list")); + if(wn.boot.hidden_modules.indexOf(m)==-1) { + $chk.attr("checked", true); + } + } + }); + }, + update: function() { + var ml = []; + $('#modules-list [data-module]:checkbox:not(:checked)').each(function() { + ml.push($(this).attr('data-module')); + }); + + wn.call({ + method: 'core.page.modules_setup.modules_setup.update', + args: { + ml: ml + }, + callback: function(r) { + if(r.exc) msgprint("There were errors.") + }, + btn: this + }); + } + +} \ No newline at end of file diff --git a/core/page/modules_setup/modules_setup.py b/core/page/modules_setup/modules_setup.py new file mode 100644 index 0000000000..e993d83ce3 --- /dev/null +++ b/core/page/modules_setup/modules_setup.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +import webnotes + +@webnotes.whitelist() +def update(ml): + """update modules""" + webnotes.conn.set_global('hidden_modules', ml) + webnotes.msgprint('Updated') + webnotes.clear_cache() \ No newline at end of file diff --git a/core/page/modules_setup/modules_setup.txt b/core/page/modules_setup/modules_setup.txt new file mode 100644 index 0000000000..556be2bfb6 --- /dev/null +++ b/core/page/modules_setup/modules_setup.txt @@ -0,0 +1,32 @@ +[ + { + "creation": "2012-10-04 18:45:29", + "docstatus": 0, + "modified": "2013-04-03 12:19:56", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Page", + "module": "Core", + "name": "__common__", + "page_name": "modules_setup", + "standard": "Yes", + "title": "Modules Setup" + }, + { + "doctype": "Page Role", + "name": "__common__", + "parent": "modules_setup", + "parentfield": "roles", + "parenttype": "Page", + "role": "System Manager" + }, + { + "doctype": "Page", + "name": "modules_setup" + }, + { + "doctype": "Page Role" + } +] \ No newline at end of file diff --git a/core/page/todo/__init__.py b/core/page/todo/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/page/todo/todo.css b/core/page/todo/todo.css new file mode 100644 index 0000000000..f67f729c4f --- /dev/null +++ b/core/page/todo/todo.css @@ -0,0 +1,51 @@ +.todoitem { + padding-bottom: 3px; + clear: both; +} + +.todoitem div { + float: left; + display: inline-block; + padding: 3px; +} + +.todoitem .label { + width: 50px; + margin-right: 11px; + margin-top: 3px; + text-align: center; +} + +.todoitem .todo-date { + margin-top: -2px; + margin-right: 7px; + color: #aaa; +} + +.todoitem .close { + margin-left: 5px; + font-size: 17px; +} + +.todoitem .close-span { + float: right; +} + +.todo-separator { + border-bottom: 1px solid #DEB85F; + margin-bottom: 5px; + clear: both; +} + +.todo-content { + padding-right: 15px; +} + +.todo-layout { + background-color: #FFFDC9; + min-height: 300px; +} + +.todoitem .popup-on-click { + margin: 0px 6px; +} \ No newline at end of file diff --git a/core/page/todo/todo.html b/core/page/todo/todo.html new file mode 100644 index 0000000000..e00f8ef35b --- /dev/null +++ b/core/page/todo/todo.html @@ -0,0 +1,11 @@ +
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/core/page/todo/todo.js b/core/page/todo/todo.js new file mode 100644 index 0000000000..64ab0a3a29 --- /dev/null +++ b/core/page/todo/todo.js @@ -0,0 +1,212 @@ +// ERPNext - web based ERP (http://erpnext.com) +// Copyright (C) 2012 Web Notes Technologies Pvt Ltd +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +wn.provide('erpnext.todo'); + +erpnext.todo.refresh = function() { + wn.call({ + method: 'core.page.todo.todo.get', + callback: function(r,rt) { + var todo_list = $('#todo-list div.todo-content'); + var assigned_todo_list = $('#assigned-todo-list div.todo-content'); + todo_list.empty(); + assigned_todo_list.empty(); + + var nothing_to_do = function() { + $('#todo-list div.todo-content') + .html('
Nothing to do :)
'); + } + + + if(r.message) { + for(var i in r.message) { + new erpnext.todo.ToDoItem(r.message[i]); + } + if (!todo_list.html()) { nothing_to_do(); } + } else { + nothing_to_do(); + } + } + }); +} + +erpnext.todo.ToDoItem = Class.extend({ + init: function(todo) { + label_map = { + 'High': 'label-important', + 'Medium': 'label-info', + 'Low':'' + } + todo.labelclass = label_map[todo.priority]; + todo.userdate = dateutil.str_to_user(todo.date) || ''; + + todo.fullname = ''; + if(todo.assigned_by) { + var assigned_by = wn.boot.user_info[todo.assigned_by] + todo.fullname = repl("[By %(fullname)s] ".bold(), { + fullname: (assigned_by ? assigned_by.fullname : todo.assigned_by), + }); + } + + var parent_list = "#todo-list"; + if(todo.owner !== user) { + var owner = wn.boot.user_info[todo.owner]; + todo.fullname = repl("[To %(fullname)s] ".bold(), { + fullname: (owner ? owner.fullname : todo.owner), + }); + } + parent_list += " div.todo-content"; + + if(todo.reference_name && todo.reference_type) { + todo.link = repl('\ + %(reference_type)s: %(reference_name)s', todo); + } else if(todo.reference_type) { + todo.link = repl('
\ + %(reference_type)s', todo); + } else { + todo.link = ''; + } + if(!todo.description) todo.description = ''; + todo.description_display = todo.description.replace(/\n\n/g, "
").trim(); + + $(parent_list).append(repl('\ +
\ +
%(priority)s
\ + \ +
\ +
%(userdate)s
\ + %(fullname)s:\ +
\ +
%(description_display)s\ + %(link)s\ +
\ + \ +
\ +
', todo)); + $todo = $(parent_list + ' div.todoitem:last'); + + if(todo.checked) { + $todo.find('.description').css('text-decoration', 'line-through'); + } + + if(!todo.reference_type) + $todo.find('.ref_link').toggle(false); + + $todo.find('.popup-on-click') + .data('todo', todo) + .click(function() { + erpnext.todo.make_dialog($(this).data('todo')); + return false; + }); + + $todo.find('.close') + .data('name', todo.name) + .click(function() { + $(this).parent().css('opacity', 0.5); + wn.call({ + method:'core.page.todo.todo.delete', + args: {name: $(this).data('name')}, + callback: function() { + erpnext.todo.refresh(); + } + }); + return false; + }) + } +}); + +erpnext.todo.make_dialog = function(det) { + if(!erpnext.todo.dialog) { + var dialog = new wn.ui.Dialog({ + width: 480, + title: 'To Do', + fields: [ + {fieldtype:'Text', fieldname:'description', label:'Description', + reqd:1}, + {fieldtype:'Date', fieldname:'date', label:'Event Date', reqd:1}, + {fieldtype:'Check', fieldname:'checked', label:'Completed'}, + {fieldtype:'Select', fieldname:'priority', label:'Priority', reqd:1, 'options':['Medium','High','Low'].join('\n')}, + {fieldtype:'Button', fieldname:'save', label:'Save (Ctrl+S)'} + ] + }); + + dialog.fields_dict.save.input.onclick = function() { + erpnext.todo.save(this); + } + erpnext.todo.dialog = dialog; + } + + if(det) { + erpnext.todo.dialog.set_values({ + date: det.date, + priority: det.priority, + description: det.description, + checked: det.checked + }); + erpnext.todo.dialog.det = det; + } + erpnext.todo.dialog.show(); + +} + +erpnext.todo.save = function(btn) { + var d = erpnext.todo.dialog; + var det = d.get_values(); + + if(!det) { + return; + } + + det.name = d.det.name || ''; + wn.call({ + method:'core.page.todo.todo.edit', + args: det, + btn: btn, + callback: function() { + erpnext.todo.dialog.hide(); + erpnext.todo.refresh(); + } + }); +} + +wn.pages.todo.onload = function(wrapper) { + // create app frame + wrapper.appframe = new wn.ui.AppFrame($(wrapper).find('.appframe-area'), 'To Do'); + wrapper.appframe.add_home_breadcrumb(); + wrapper.appframe.add_breadcrumb(wn.modules["To Do"].icon); + wrapper.appframe.add_button('Refresh', erpnext.todo.refresh, 'icon-refresh'); + wrapper.appframe.add_button('Add', function() { + erpnext.todo.make_dialog({ + date:get_today(), priority:'Medium', checked:0, description:''}); + }, 'icon-plus'); + wrapper.appframe.add_ripped_paper_effect(wrapper); + + // show report button for System Manager + if(wn.boot.profile.roles.indexOf("System Manager") !== -1) { + wrapper.appframe.add_button("Report", function() { wn.set_route("query-report", "todo"); }, + "icon-table"); + } + + // load todos + erpnext.todo.refresh(); + + // save on click + wrapper.save_action = function() { + if(erpnext.todo.dialog && erpnext.todo.dialog.display) { + erpnext.todo.dialog.fields_dict.save.input.click(); + } + }; +} \ No newline at end of file diff --git a/core/page/todo/todo.py b/core/page/todo/todo.py new file mode 100644 index 0000000000..f0de04ec9b --- /dev/null +++ b/core/page/todo/todo.py @@ -0,0 +1,64 @@ +# ERPNext - web based ERP (http://erpnext.com) +# Copyright (C) 2012 Web Notes Technologies Pvt Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import unicode_literals +import webnotes +from webnotes.model.doc import Document + +@webnotes.whitelist() +def get(arg=None): + """get todo list""" + return webnotes.conn.sql("""select name, owner, description, date, + priority, checked, reference_type, reference_name, assigned_by + from `tabToDo` where (owner=%s or assigned_by=%s) + order by field(priority, 'High', 'Medium', 'Low') asc, date asc""", + (webnotes.session['user'], webnotes.session['user']), as_dict=1) + +@webnotes.whitelist() +def edit(arg=None): + import markdown2 + args = webnotes.form_dict + + d = Document('ToDo', args.get('name') or None) + d.description = args['description'] + d.date = args['date'] + d.priority = args['priority'] + d.checked = args.get('checked', 0) + if not d.owner: d.owner = webnotes.session['user'] + d.save(not args.get('name') and 1 or 0) + + if args.get('name') and d.checked: + notify_assignment(d) + + return d.name + +@webnotes.whitelist() +def delete(arg=None): + name = webnotes.form_dict['name'] + d = Document('ToDo', name) + if d and d.name and d.owner != webnotes.session['user']: + notify_assignment(d) + webnotes.conn.sql("delete from `tabToDo` where name = %s", name) + +def notify_assignment(d): + doc_type = d.reference_type + doc_name = d.reference_name + assigned_by = d.assigned_by + + if doc_type and doc_name and assigned_by: + from webnotes.widgets.form import assign_to + assign_to.notify_assignment(assigned_by, d.owner, doc_type, doc_name) + \ No newline at end of file diff --git a/core/page/todo/todo.txt b/core/page/todo/todo.txt new file mode 100644 index 0000000000..716f721359 --- /dev/null +++ b/core/page/todo/todo.txt @@ -0,0 +1,32 @@ +[ + { + "creation": "2012-06-14 18:44:56", + "docstatus": 0, + "modified": "2013-04-03 14:44:34", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Page", + "module": "Core", + "name": "__common__", + "page_name": "todo", + "standard": "Yes", + "title": "To Do" + }, + { + "doctype": "Page Role", + "name": "__common__", + "parent": "todo", + "parentfield": "roles", + "parenttype": "Page", + "role": "All" + }, + { + "doctype": "Page", + "name": "todo" + }, + { + "doctype": "Page Role" + } +] \ No newline at end of file diff --git a/core/page/update_manager/update_manager.js b/core/page/update_manager/update_manager.js index 24a29853d1..94107638f6 100644 --- a/core/page/update_manager/update_manager.js +++ b/core/page/update_manager/update_manager.js @@ -29,20 +29,34 @@ wn.UpdateThisApp = Class.extend({ this.wrapper.appframe.add_button(wn._("Get Latest Updates"), function() { me.update_this_app(this); }, "icon-rss"); - this.wrapper.update_output = $('
')
-				.appendTo(this.body.append("
")); - this.wrapper.update_output.text(wn._('Click on "Get Latest Updates"')); + this.wrapper.update_output = $('
')
+				.appendTo(this.body);
+			this.wrapper.update_output.toggle(false);
+			
+			this.wrapper.progress_bar = $('
') + .appendTo(this.body); + this.wrapper.progress_bar.text(wn._('Click on "Get Latest Updates"')); } }, update_this_app: function(btn) { var me = this; + + me.wrapper.update_output.toggle(false); + me.wrapper.progress_bar.empty().toggle(true); + this.wrapper.progress_bar.html('
\ +
\ +
\ +
' + wn._("Update is in progress. This may take some time.") + '
'); + wn.call({ module: "core", page: "update_manager", method: "update_this_app", callback: function(r) { + me.wrapper.update_output.toggle(true); + me.wrapper.progress_bar.empty().toggle(false); me.wrapper.update_output.text(r.message); }, btn: btn, diff --git a/public/js/legacy/widgets/form/fields.js b/public/js/legacy/widgets/form/fields.js index 1b178b0167..95570e6b19 100644 --- a/public/js/legacy/widgets/form/fields.js +++ b/public/js/legacy/widgets/form/fields.js @@ -425,7 +425,9 @@ function DataField() { } DataField.prototype = new Field(); DataField.prototype.make_input = function() { var me = this; this.input = $a_input(this.input_area, this.df.fieldtype=='Password' ? 'password' : 'text'); - + + if(this.df.placeholder) $(this.input).attr("placeholder", this.df.placeholder); + this.get_value= function() { var v = this.input.value; if(this.validate) @@ -894,10 +896,16 @@ FloatField.prototype.format_input = function() { this.input.value=''; else { var format; - if(this.get_field_currency) + if(this.get_field_currency) { format = get_number_format(this.get_field_currency()); - this.input.value = - format_number(parseFloat(this.input.value), format); + this.input.value = + format_number(parseFloat(this.input.value), format); + } else { + var decimals = wn.boot.sysdefaults.float_precision ? + parseInt(wn.boot.sysdefaults.float_precision) : null; + + this.input.value = format_number(parseFloat(this.input.value), null, decimals); + } } } FloatField.prototype.onmake_input = function() { diff --git a/public/js/wn/app.js b/public/js/wn/app.js index 12ef61a387..1d19142c1b 100644 --- a/public/js/wn/app.js +++ b/public/js/wn/app.js @@ -77,6 +77,7 @@ wn.Application = Class.extend({ load_bootinfo: function() { if(wn.boot) { wn.control_panel = wn.boot.control_panel; + wn.modules = wn.boot.modules; this.set_globals(); this.sync_pages(); diff --git a/public/js/wn/form/formatters.js b/public/js/wn/form/formatters.js index 0d5c52f119..0041ad7c0d 100644 --- a/public/js/wn/form/formatters.js +++ b/public/js/wn/form/formatters.js @@ -11,7 +11,7 @@ wn.form.formatters = { parseInt(wn.boot.sysdefaults.float_precision) : null; return "
" + - format_number(flt(value, decimals), null, decimals) + "
"; + format_number(value, null, decimals) + "
"; }, Int: function(value) { return cint(value); diff --git a/public/js/wn/misc/user.js b/public/js/wn/misc/user.js index dbe411d7e5..4f267cce48 100644 --- a/public/js/wn/misc/user.js +++ b/public/js/wn/misc/user.js @@ -53,21 +53,43 @@ $.extend(wn.user, { modules_list.push(m); } }); - } else - // all modules - modules_list = keys(wn.modules); - + } + // filter hidden modules - if(wn.boot.modules_list) { - var allowed_list = JSON.parse(wn.boot.modules_list); - if(modules_list) { - var modules_list = $.map(modules_list, function(m) { - if(allowed_list.indexOf(m)!=-1) return m; else return null; - }); - } else { - var modules_list = allowed_list; - } + if(wn.boot.hidden_modules && modules_list) { + var hidden_list = JSON.parse(wn.boot.hidden_modules); + var modules_list = $.map(modules_list, function(m) { + if(hidden_list.indexOf(m)==-1) return m; else return null; + }); } + + if(!modules_list || !modules_list.length) { + // all modules + modules_list = keys(wn.modules).sort(); + } + + // hide based on permission + modules_list = $.map(modules_list, function(m) { + var type = wn.modules[m] && wn.modules[m].type; + var ret = null; + switch(type) { + case "module": + if(wn.boot.profile.allow_modules.indexOf(m)!=-1) + ret = m; + break; + case "page": + if(wn.boot.allowed_pages.indexOf(wn.modules[m].link)!=-1) + ret = m; + break; + case "view", "setup": + ret = m; + break; + default: + ret = null; + } + return ret; + }) + return modules_list; }, is_report_manager: function() { @@ -75,8 +97,6 @@ $.extend(wn.user, { } }) -// wn.session_alive is true if user shows mouse movement in 30 seconds - wn.session_alive = true; $(document).bind('mousemove', function() { wn.session_alive = true; diff --git a/public/js/wn/views/moduleview.js b/public/js/wn/views/moduleview.js index 8dbf5442a4..f9dff38a9c 100644 --- a/public/js/wn/views/moduleview.js +++ b/public/js/wn/views/moduleview.js @@ -2,7 +2,9 @@ // MIT Licensed. See license.txt wn.provide("wn.views.moduleview"); -wn.provide("wn.model.open_count_conditions") +wn.provide("wn.model.open_count_conditions"); +wn.provide("wn.module_page"); +wn.home_page = "desktop"; wn.views.moduleview.make = function(wrapper, module) { wrapper.module_view = new wn.views.moduleview.ModuleView(wrapper, module); diff --git a/public/js/wn/views/query_report.js b/public/js/wn/views/query_report.js index 67733a3a0a..e90b5d859b 100644 --- a/public/js/wn/views/query_report.js +++ b/public/js/wn/views/query_report.js @@ -120,6 +120,7 @@ wn.views.QueryReport = Class.extend({ $.each(wn.query_reports[this.report_name].filters || [], function(i, df) { var f = make_field(df, null, $filter_wrapper.get(0), null, 0, 1); f.df.single_select = 1; + f.df.placeholder = df.label; f.not_in_form = 1; f.with_label = 0; f.in_filter = 1; diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000000..cfba1a5ca0 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,25 @@ + + + + + + {{ title }} + + + + + + + + + {% if description -%} + + {%- endif %} + {% block header -%} + {%- endblock %} + + + {% block body %} + {% endblock %} + + \ No newline at end of file diff --git a/templates/css/login.css b/templates/css/login.css new file mode 100644 index 0000000000..4120807022 --- /dev/null +++ b/templates/css/login.css @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/templates/js/login.js b/templates/js/login.js new file mode 100644 index 0000000000..06ea4c5242 --- /dev/null +++ b/templates/js/login.js @@ -0,0 +1,97 @@ + +var login = {}; + +$(document).ready(function(wrapper) { + $('#login_btn').click(login.do_login) + + $('#password').keypress(function(ev){ + if(ev.which==13 && $('#password').val()) { + $('form').submit(function() { + login.do_login(); + return false; + }); + } + }); + $(document).trigger('login_rendered'); +}) + +// Login +login.do_login = function(){ + var args = {}; + if(window.is_sign_up) { + args.cmd = "core.doctype.profile.profile.sign_up"; + args.email = $("#login_id").val(); + args.full_name = $("#full_name").val(); + + if(!args.email || !valid_email(args.email) || !args.full_name) { + login.set_message("Valid email and name required."); + return false; + } + } else if(window.is_forgot) { + args.cmd = "reset_password"; + args.user = $("#login_id").val(); + + if(!args.user) { + login.set_message("Valid Login Id required."); + return false; + } + + } else { + args.cmd = "login" + args.usr = $("#login_id").val(); + args.pwd = $("#password").val(); + + if(!args.usr || !args.pwd) { + login.set_message("Both login and password required."); + return false; + } + } + + $('#login_btn').attr("disabled", "disabled"); + $("#login-spinner").toggle(true); + $('#login_message').toggle(false); + + $.ajax({ + type: "POST", + url: "server.py", + data: args, + dataType: "json", + success: function(data) { + $("input").val(""); + $("#login-spinner").toggle(false); + $('#login_btn').attr("disabled", false); + if(data.message=="Logged In") { + window.location.href = "app.html"; + } else if(data.message=="No App") { + window.location.href = "index"; + } else { + login.set_message(data.message); + } + } + }) + + return false; +} + +login.sign_up = function() { + $("#login_wrapper h3").html("Sign Up"); + $("#login-label").html("Email Id"); + $("#password-row, #sign-up-wrapper, #login_message").toggle(false); + $("#full-name-row").toggle(true); + $("#login_btn").html("Register"); + $("#forgot-wrapper").html("Login") + window.is_sign_up = true; +} + +login.show_forgot_password = function() { + $("#login_wrapper h3").html("Forgot"); + $("#login-label").html("Email Id"); + $("#password-row, #sign-up-wrapper, #login_message").toggle(false); + $("#login_btn").html("Send Password"); + $("#forgot-wrapper").html("Login") + window.is_forgot = true; +} + +login.set_message = function(message, color) { + $('#login_message').html(message).toggle(true); +} \ No newline at end of file diff --git a/templates/pages/404.html b/templates/pages/404.html new file mode 100644 index 0000000000..3c37a30f38 --- /dev/null +++ b/templates/pages/404.html @@ -0,0 +1,14 @@ +{% extends "app/website/templates/html/outer.html" %} + +{% set title="Not Found" %} + +{% block content %} +
+
+

Page missing or moved

+
+

We are very sorry for this, but the page you are looking for is missing + (this could be because of a typo in the address) or moved.

+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/error.html b/templates/pages/error.html new file mode 100644 index 0000000000..5ebe552bbc --- /dev/null +++ b/templates/pages/error.html @@ -0,0 +1,13 @@ +{% extends "lib/templates/base.html" %} + +{% set title="Error" %} + +{% block body %} +
+
+

Oops, a server error has occured

+
+
%(error)s
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/login.html b/templates/pages/login.html new file mode 100644 index 0000000000..285094b893 --- /dev/null +++ b/templates/pages/login.html @@ -0,0 +1,62 @@ +{% extends "lib/templates/base.html" %} + +{% block header %} + + {% include "lib/templates/css/login.css" %} +{% endblock %} + +{% set title="Login" %} + +{% block body %} +
+
+ +

Login

+
+ + + + + + + + + + + + + + + + + + + + +
+
+

+ Forgot Password

+

+ New user? Sign Up

+
+
+