diff --git a/frappe/desk/doctype/event/test_records.json b/frappe/desk/doctype/event/test_records.json
index aaadc881b8..41d5803083 100644
--- a/frappe/desk/doctype/event/test_records.json
+++ b/frappe/desk/doctype/event/test_records.json
@@ -3,18 +3,21 @@
"doctype": "Event",
"subject":"_Test Event 1",
"starts_on": "2014-01-01",
- "event_type": "Public"
+ "event_type": "Public",
+ "creation": "2014-01-01"
},
{
"doctype": "Event",
- "starts_on": "2014-01-01",
"subject":"_Test Event 2",
- "event_type": "Private"
+ "starts_on": "2014-01-01",
+ "event_type": "Private",
+ "creation": "2014-01-01"
},
{
"doctype": "Event",
- "starts_on": "2014-01-01",
"subject": "_Test Event 3",
- "event_type": "Private"
+ "starts_on": "2014-02-01",
+ "event_type": "Private",
+ "creation": "2014-02-01"
}
]
diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py
index 3924afd7a4..d0ee87a209 100644
--- a/frappe/desk/notifications.py
+++ b/frappe/desk/notifications.py
@@ -13,10 +13,12 @@ def get_notifications():
return
config = get_notification_config()
+
groups = config.get("for_doctype").keys() + config.get("for_module").keys()
cache = frappe.cache()
notification_count = {}
+ notification_percent = {}
for name in groups:
count = cache.hget("notification_count:" + name, frappe.session.user)
@@ -27,6 +29,7 @@ def get_notifications():
"open_count_doctype": get_notifications_for_doctypes(config, notification_count),
"open_count_module": get_notifications_for_modules(config, notification_count),
"open_count_other": get_notifications_for_other(config, notification_count),
+ "targets": get_notifications_for_targets(config, notification_percent),
"new_messages": get_new_messages()
}
@@ -111,6 +114,49 @@ def get_notifications_for_doctypes(config, notification_count):
return open_count_doctype
+def get_notifications_for_targets(config, notification_percent):
+ """Notifications for doc targets"""
+ can_read = frappe.get_user().get_can_read()
+ doc_target_percents = {}
+
+ # doc_target_percents = {
+ # "Company": {
+ # "Acme": 87,
+ # "RobotsRUs": 50,
+ # }, {}...
+ # }
+
+ for doctype in config.targets:
+ if doctype in can_read:
+ if doctype in notification_percent:
+ doc_target_percents[doctype] = notification_percent[doctype]
+ else:
+ doc_target_percents[doctype] = {}
+ d = config.targets[doctype]
+ condition = d["filters"]
+ target_field = d["target_field"]
+ value_field = d["value_field"]
+ try:
+ if isinstance(condition, dict):
+ doc_list = frappe.get_list(doctype, fields=["name", target_field, value_field],
+ filters=condition, limit_page_length = 100, ignore_ifnull=True)
+
+ except frappe.PermissionError:
+ frappe.clear_messages()
+ pass
+ except Exception as e:
+ if e.args[0]!=1412:
+ raise
+
+ else:
+ for doc in doc_list:
+ value = doc[value_field]
+ target = doc[target_field]
+ doc_target_percents[doctype][doc.name] = (value/target * 100) if value < target else 100
+
+ return doc_target_percents
+
+
def clear_notifications(user=None):
if frappe.flags.in_install:
return
@@ -163,7 +209,7 @@ def get_notification_config():
config = frappe._dict()
for notification_config in frappe.get_hooks().notification_config:
nc = frappe.get_attr(notification_config)()
- for key in ("for_doctype", "for_module", "for_other"):
+ for key in ("for_doctype", "for_module", "for_other", "targets"):
config.setdefault(key, {})
config[key].update(nc.get(key, {}))
return config
diff --git a/frappe/docs/assets/img/desk/bar_graph.png b/frappe/docs/assets/img/desk/bar_graph.png
new file mode 100644
index 0000000000..d25254af6d
Binary files /dev/null and b/frappe/docs/assets/img/desk/bar_graph.png differ
diff --git a/frappe/docs/assets/img/desk/line_graph.png b/frappe/docs/assets/img/desk/line_graph.png
new file mode 100644
index 0000000000..02c60c7c18
Binary files /dev/null and b/frappe/docs/assets/img/desk/line_graph.png differ
diff --git a/frappe/docs/user/en/guides/desk/making_graphs.md b/frappe/docs/user/en/guides/desk/making_graphs.md
new file mode 100644
index 0000000000..9234fa58b4
--- /dev/null
+++ b/frappe/docs/user/en/guides/desk/making_graphs.md
@@ -0,0 +1,61 @@
+# Making Graphs
+
+The Frappe UI **Graph** object enables you to render simple line and bar graphs for a discreet set of data points. You can also set special checkpoint values and summary stats.
+
+### Example: Line graph
+Here's is an example of a simple sales graph:
+
+ render_graph: function() {
+ $('.form-graph').empty();
+
+ var months = ['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
+ var values = [2410, 3100, 1700, 1200, 2700, 1600, 2740, 1000, 850, 1500, 400, 2013];
+
+ var goal = 2500;
+ var current_val = 2013;
+
+ new frappe.ui.Graph({
+ parent: $('.form-graph'),
+ width: 700,
+ height: 140,
+ mode: 'line-graph',
+
+ title: 'Sales',
+ subtitle: 'Monthly',
+ y_values: values,
+ x_points: months,
+
+ specific_values: [
+ {
+ name: "Goal",
+ line_type: "dashed", // "dashed" or "solid"
+ value: goal
+ },
+ ],
+ summary_values: [
+ {
+ name: "This month",
+ color: 'green', // Indicator colors: 'grey', 'blue', 'red',
+ // 'green', 'orange', 'purple', 'darkgrey',
+ // 'black', 'yellow', 'lightblue'
+ value: '₹ ' + current_val
+ },
+ {
+ name: "Goal",
+ color: 'blue',
+ value: '₹ ' + goal
+ },
+ {
+ name: "Completed",
+ color: 'green',
+ value: (current_val/goal*100).toFixed(1) + "%"
+ }
+ ]
+ });
+ },
+
+
+
+Setting the mode to 'bar-graph':
+
+
diff --git a/frappe/public/build.json b/frappe/public/build.json
index 75e4e76469..3b58de727b 100755
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -161,6 +161,7 @@
"public/js/frappe/query_string.js",
"public/js/frappe/ui/charts.js",
+ "public/js/frappe/ui/graph.js",
"public/js/frappe/misc/rating_icons.html",
"public/js/frappe/feedback.js"
diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css
index fa13c421fa..ebe34f0de2 100644
--- a/frappe/public/css/desk.css
+++ b/frappe/public/css/desk.css
@@ -508,6 +508,17 @@ fieldset[disabled] .form-control {
cursor: pointer;
margin-right: 10px;
}
+a.progress-small .progress-chart {
+ width: 60px;
+ margin-top: 4px;
+ float: right;
+}
+a.progress-small .progress {
+ margin-bottom: 0;
+}
+a.progress-small .progress-bar {
+ background-color: #98d85b;
+}
/* on small screens, show only icons on top */
@media (max-width: 767px) {
.module-view-layout .nav-stacked > li {
diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css
index d822b04975..844c2dc761 100644
--- a/frappe/public/css/form.css
+++ b/frappe/public/css/form.css
@@ -642,6 +642,92 @@ select.form-control {
box-shadow: none;
}
}
+/* goals */
+.goals-page-container {
+ background-color: #fafbfc;
+ padding-top: 1px;
+}
+.goals-page-container .goal-container {
+ background-color: #fff;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+ border-radius: 2px;
+ padding: 10px;
+ margin: 10px;
+}
+.graph-container .graphics {
+ margin-top: 10px;
+ padding: 10px 0px;
+}
+.graph-container .stats-group {
+ display: flex;
+ justify-content: space-around;
+ flex: 1;
+}
+.graph-container .stats-container {
+ display: flex;
+ justify-content: space-around;
+}
+.graph-container .stats-container .stats {
+ padding-bottom: 15px;
+}
+.graph-container .stats-container .stats-title {
+ color: #8D99A6;
+}
+.graph-container .stats-container .stats-value {
+ font-size: 20px;
+ font-weight: 300;
+}
+.graph-container .stats-container .stats-description {
+ font-size: 12px;
+ color: #8D99A6;
+}
+.graph-container .stats-container .graph-data .stats-value {
+ color: #98d85b;
+}
+.bar-graph .axis,
+.line-graph .axis {
+ font-size: 10px;
+ fill: #6a737d;
+}
+.bar-graph .axis line,
+.line-graph .axis line {
+ stroke: rgba(27, 31, 35, 0.1);
+}
+.data-points circle {
+ fill: #28a745;
+ stroke: #fff;
+ stroke-width: 2;
+}
+.data-points g.mini {
+ fill: #98d85b;
+}
+.data-points path {
+ fill: none;
+ stroke: #28a745;
+ stroke-opacity: 1;
+ stroke-width: 2px;
+}
+.line-graph .path {
+ fill: none;
+ stroke: #28a745;
+ stroke-opacity: 1;
+ stroke-width: 2px;
+}
+line.dashed {
+ stroke-dasharray: 5,3;
+}
+.tick.x-axis-label {
+ display: block;
+}
+.tick .specific-value {
+ text-anchor: start;
+}
+.tick .y-value-text {
+ text-anchor: end;
+}
+.tick .x-value-text {
+ text-anchor: middle;
+}
body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] {
height: 80px !important;
}
diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js
index fc3c31fce2..36baa6ca15 100644
--- a/frappe/public/js/frappe/form/dashboard.js
+++ b/frappe/public/js/frappe/form/dashboard.js
@@ -11,7 +11,7 @@ frappe.ui.form.Dashboard = Class.extend({
this.progress_area = this.wrapper.find(".progress-area");
this.heatmap_area = this.wrapper.find('.form-heatmap');
- this.chart_area = this.wrapper.find('.form-chart');
+ this.graph_area = this.wrapper.find('.form-graph');
this.stats_area = this.wrapper.find('.form-stats');
this.stats_area_row = this.stats_area.find('.row');
this.links_area = this.wrapper.find('.form-links');
@@ -43,9 +43,9 @@ frappe.ui.form.Dashboard = Class.extend({
this.frm.layout.show_message();
},
- add_comment: function(text, permanent) {
+ add_comment: function(text, alert_class, permanent) {
var me = this;
- this.set_headline_alert(text);
+ this.set_headline_alert(text, alert_class);
if(!permanent) {
setTimeout(function() {
me.clear_headline();
@@ -91,6 +91,7 @@ frappe.ui.form.Dashboard = Class.extend({
this.show();
},
+
format_percent: function(title, percent) {
var width = cint(percent) < 1 ? 1 : cint(percent);
var progress_class = "";
@@ -138,6 +139,11 @@ frappe.ui.form.Dashboard = Class.extend({
show = true;
}
+ if(this.data.graph) {
+ this.setup_graph();
+ show = true;
+ }
+
if(show) {
this.show();
}
@@ -383,13 +389,50 @@ frappe.ui.form.Dashboard = Class.extend({
},
//graphs
+ setup_graph: function() {
+ var me = this;
+
+ var method = this.data.graph_method;
+ var args = {
+ doctype: this.frm.doctype,
+ docname: this.frm.doc.name,
+ };
+
+ $.extend(args, this.data.graph_method_args);
+
+ frappe.call({
+ type: "GET",
+ method: method,
+ args: args,
+
+ callback: function(r) {
+ if(r.message) {
+ me.render_graph(r.message);
+ }
+ }
+ });
+ },
+
+ render_graph: function(args) {
+ var me = this;
+ this.graph_area.empty().removeClass('hidden');
+ $.extend(args, {
+ parent: me.graph_area,
+ width: 700,
+ height: 140,
+ mode: 'line-graph'
+ });
+
+ new frappe.ui.Graph(args);
+ },
+
setup_chart: function(opts) {
var me = this;
- this.chart_area.removeClass('hidden');
+ this.graph_area.removeClass('hidden');
$.extend(opts, {
- wrapper: me.wrapper.find('.form-chart'),
+ wrapper: me.graph_area,
padding: {
right: 30,
bottom: 30
diff --git a/frappe/public/js/frappe/form/templates/form_dashboard.html b/frappe/public/js/frappe/form/templates/form_dashboard.html
index b1865a9c94..c41929df73 100644
--- a/frappe/public/js/frappe/form/templates/form_dashboard.html
+++ b/frappe/public/js/frappe/form/templates/form_dashboard.html
@@ -5,7 +5,7 @@