diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 11198be9f2..cb91181e42 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +import json from frappe.utils import cstr from frappe import _ from frappe.model.document import Document @@ -95,5 +96,11 @@ def create_custom_field(doctype, df): "fieldtype": df.fieldtype, "options": df.options, "insert_after": df.insert_after, - "print_hide": df.print_hide + "print_hide": df.print_hide, + "hidden": df.hidden or 0 }).insert() + +@frappe.whitelist() +def add_custom_field(doctype, df): + df = json.loads(df) + return create_custom_field(doctype, df) \ No newline at end of file diff --git a/frappe/desk/doctype/kanban_board/kanban_board.json b/frappe/desk/doctype/kanban_board/kanban_board.json index 6fb71b2ef7..62a0fbad8f 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.json +++ b/frappe/desk/doctype/kanban_board/kanban_board.json @@ -148,7 +148,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -192,7 +192,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-01-10 04:51:19.413720", + "modified": "2017-01-18 13:53:44.283037", "modified_by": "Administrator", "module": "Desk", "name": "Kanban Board", diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index c01d6d35e7..3ecaf9e2c6 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -8,8 +8,13 @@ import json from frappe import _ from frappe.model.document import Document + class KanbanBoard(Document): - pass + def validate(self): + for column in self.columns: + if not column.column_name: + frappe.msgprint(frappe._("Column Name cannot be empty"), raise_exception=True) + @frappe.whitelist() def add_column(board_name, column_title): @@ -20,12 +25,12 @@ def add_column(board_name, column_title): frappe.throw(_("Column {0} already exist.").format(column_title)) doc.append("columns", dict( - column_name=column_title, - color="" + column_name=column_title )) doc.save() return doc.columns + @frappe.whitelist() def archive_restore_column(board_name, column_title, status): '''Set column's status to status''' @@ -37,6 +42,7 @@ def archive_restore_column(board_name, column_title, status): doc.save() return doc.columns + @frappe.whitelist() def update_doc(doc): '''Updates the doc when card is edited''' @@ -56,13 +62,100 @@ def update_doc(doc): } return doc + @frappe.whitelist() -def update_order(board_name, column_title, order): - '''Save the order of cards in a column''' - doc = frappe.get_doc('Kanban Board', board_name) +def update_order(board_name, order): + '''Save the order of cards in columns''' + board = frappe.get_doc('Kanban Board', board_name) + doctype = board.reference_doctype + fieldname = board.field_name + order_dict = json.loads(order) - for col in doc.columns: - if column_title == col.column_name: - col.order = order + updated_cards = [] + for col_name, cards in order_dict.iteritems(): + order_list = [] + for card in cards: + column = frappe.get_value( + doctype, + {'name': card}, + fieldname + ) + if column != col_name: + frappe.set_value(doctype, card, fieldname, col_name) + updated_cards.append(dict( + name=card, + column=col_name + )) + + for column in board.columns: + if column.column_name == col_name: + column.order = json.dumps(cards) + + board.save() + return board, updated_cards + + +@frappe.whitelist() +def quick_kanban_board(doctype, board_name, field_name): + '''Create new KanbanBoard quickly with default options''' + doc = frappe.new_doc('Kanban Board') + options = frappe.get_value('DocField', dict( + parent=doctype, + fieldname=field_name + ), 'options') + + columns = [] + if options: + columns = options.split('\n') + + for column in columns: + doc.append("columns", dict( + column_name=column + )) + + doc.kanban_board_name = board_name + doc.reference_doctype = doctype + doc.field_name = field_name doc.save() - return doc \ No newline at end of file + return doc + + +@frappe.whitelist() +def update_column_order(board_name, order): + '''Set the order of columns in Kanban Board''' + board = frappe.get_doc('Kanban Board', board_name) + order = json.loads(order) + old_columns = board.columns + new_columns = [] + + for col in order: + for column in old_columns: + if col == column.column_name: + new_columns.append(column) + old_columns.remove(column) + + new_columns.extend(old_columns) + + board.columns = [] + for col in new_columns: + board.append("columns", dict( + column_name=col.column_name, + status=col.status, + order=col.order, + indicator=col.indicator, + )) + + board.save() + return board + +@frappe.whitelist() +def set_indicator(board_name, column_name, indicator): + '''Set the indicator color of column''' + board = frappe.get_doc('Kanban Board', board_name) + + for column in board.columns: + if column.column_name == column_name: + column.indicator = indicator + + board.save() + return board \ No newline at end of file diff --git a/frappe/desk/doctype/kanban_board_column/kanban_board_column.json b/frappe/desk/doctype/kanban_board_column/kanban_board_column.json index a23dc1a201..04a1483abf 100644 --- a/frappe/desk/doctype/kanban_board_column/kanban_board_column.json +++ b/frappe/desk/doctype/kanban_board_column/kanban_board_column.json @@ -44,7 +44,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "color", + "default": "Active", + "fieldname": "status", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, @@ -52,10 +53,10 @@ "in_filter": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Color", + "label": "Status", "length": 0, "no_copy": 0, - "options": "\nRed\nBlue\nGreen", + "options": "Active\nArchived", "permlevel": 0, "precision": "", "print_hide": 0, @@ -73,8 +74,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "Active", - "fieldname": "status", + "default": "darkgrey", + "fieldname": "indicator", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, @@ -82,10 +83,10 @@ "in_filter": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Status", + "label": "Indicator", "length": 0, "no_copy": 0, - "options": "Active\nArchived", + "options": "blue\norange\nred\ngreen\ndarkgrey\npurple\nyellow\nlightblue", "permlevel": 0, "precision": "", "print_hide": 0, @@ -137,7 +138,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-01-02 12:11:48.389715", + "modified": "2017-01-17 15:23:43.520379", "modified_by": "Administrator", "module": "Desk", "name": "Kanban Board Column", diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index fe59e2971b..8072a0392b 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -41,14 +41,14 @@ class FormMeta(Meta): self.load_workflows() self.load_templates() self.load_dashboard() - self.load_kanban_boards() + self.load_kanban_meta() def as_dict(self, no_nulls=False): d = super(FormMeta, self).as_dict(no_nulls=no_nulls) for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", "__linked_with", "__messages", "__print_formats", "__workflow_docs", "__form_grid_templates", "__listview_template", "__tree_js", - "__dashboard", "__kanban_boards"): + "__dashboard", "__kanban_boards", "__kanban_column_fields"): d[k] = self.get(k) for i, df in enumerate(d.get("fields")): @@ -170,11 +170,24 @@ class FormMeta(Meta): def load_dashboard(self): self.set('__dashboard', self.get_dashboard_data()) + + def load_kanban_meta(self): + self.load_kanban_boards() + self.load_kanban_column_fields() def load_kanban_boards(self): kanban_boards = frappe.get_all( 'Kanban Board', filters={'reference_doctype': self.name}) self.set("__kanban_boards", kanban_boards, as_value=True) + + def load_kanban_column_fields(self): + values = frappe.get_all( + 'Kanban Board', fields=['field_name'], + filters={'reference_doctype': self.name}) + + fields = [x['field_name'] for x in values] + fields = list(set(fields)) + self.set("__kanban_column_fields", fields, as_value=True) def get_code_files_via_hooks(hook, name): code_files = [] diff --git a/frappe/public/css/indicator.css b/frappe/public/css/indicator.css index 9516f9f7c5..bd283dd627 100644 --- a/frappe/public/css/indicator.css +++ b/frappe/public/css/indicator.css @@ -60,6 +60,10 @@ .indicator-right.light-blue::after { background: #7CD6FD; } +.indicator.lightblue::before, +.indicator-right.lightblue::after { + background: #7CD6FD; +} .modal-header .indicator { float: left; margin-top: 7.5px; diff --git a/frappe/public/css/kanban.css b/frappe/public/css/kanban.css index a6aa15f25f..4b32a3a47d 100644 --- a/frappe/public/css/kanban.css +++ b/frappe/public/css/kanban.css @@ -22,15 +22,24 @@ font-weight: bold; font-size: 12px; } -.kanban .kanban-column-title .column-options { - position: absolute; - right: 0px; +.kanban .kanban-column-title .column-options .button-group { + display: flex; + padding: 12px 14px; +} +.kanban .kanban-column-title .column-options .button-group .btn.indicator { + flex: 1; } -.kanban .sortable-ghost > .kanban-card { +.kanban .kanban-column-title .column-options .indicator::before { + margin: 0; +} +.kanban .kanban-column-title:hover { + cursor: pointer; +} +.kanban .sortable-ghost > .kanban-card:not(.add-card) { background: #ccc !important; color: transparent; } -.kanban .sortable-ghost > .kanban-card * { +.kanban .sortable-ghost > .kanban-card:not(.add-card) * { background: transparent !important; color: transparent !important; } @@ -47,8 +56,7 @@ color: #8D99A6; } .kanban .kanban-card.add-card:hover { - background-color: #fff; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + box-shadow: none; color: #36414C; cursor: pointer; } @@ -107,7 +115,7 @@ outline: none; } .kanban .add-new-column a:hover { - color: #36414C; + color: #36414C !important; } .kanban .kanban-card-meta { margin-top: 8px; @@ -117,3 +125,19 @@ width: 16px; height: 16px; } +body[data-route*="Kanban"] .modal .add-assignment:hover i { + color: #36414C !important; +} +.edit-card-title .h4 { + margin-top: 5px; + margin-bottom: 5px; +} +.edit-card-title span:hover { + background-color: #fffce7; + cursor: pointer; +} +.edit-card-title input { + border: none; + outline: none; + width: 100%; +} diff --git a/frappe/public/css/website.css b/frappe/public/css/website.css index ea29902446..07df70b47f 100644 --- a/frappe/public/css/website.css +++ b/frappe/public/css/website.css @@ -381,6 +381,10 @@ a.no-decoration:active { .indicator-right.light-blue::after { background: #7CD6FD; } +.indicator.lightblue::before, +.indicator-right.lightblue::after { + background: #7CD6FD; +} .modal-header .indicator { float: left; margin-top: 7.5px; diff --git a/frappe/public/js/frappe/db.js b/frappe/public/js/frappe/db.js index f222c5b8f8..608ff4fe41 100644 --- a/frappe/public/js/frappe/db.js +++ b/frappe/public/js/frappe/db.js @@ -3,7 +3,7 @@ frappe.db = { get_value: function(doctype, filters, fieldname, callback) { - frappe.call({ + return frappe.call({ method: "frappe.client.get_value", args: { doctype: doctype, @@ -14,5 +14,19 @@ frappe.db = { callback(r.message); } }); + }, + set_value: function(doctype, docname, fieldname, value, callback) { + return frappe.call({ + method: "frappe.client.set_value", + args: { + doctype: doctype, + name: docname, + fieldname: fieldname, + value: value + }, + callback: function(r) { + callback(r.message); + } + }); } } diff --git a/frappe/public/js/frappe/form/footer/assign_to.js b/frappe/public/js/frappe/form/footer/assign_to.js index a4dca37538..11a7f4df98 100644 --- a/frappe/public/js/frappe/form/footer/assign_to.js +++ b/frappe/public/js/frappe/form/footer/assign_to.js @@ -132,7 +132,7 @@ frappe.ui.form.AssignTo = Class.extend({ frappe.ui.form.AssignToDialog = Class.extend({ init: function(opts){ var me = this - $.extend(me,new frappe.ui.Dialog({ + $.extend(me, new frappe.ui.Dialog({ title: __('Add to To Do'), fields: [ {fieldtype: 'Link', fieldname:'assign_to', options:'User', @@ -152,11 +152,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ {value:'High', label:__('High')}], 'default':'Medium'}, ], - primary_action: function() { - var assign_to = opts.obj.dialog.fields_dict.assign_to.get_value(); - var args = opts.obj.dialog.get_values(); - frappe.ui.add_assignment(assign_to, args, opts, opts.obj.dialog); - }, + primary_action: function() { frappe.ui.add_assignment(opts, me) }, primary_action_label: __("Add") })); @@ -184,7 +180,9 @@ frappe.ui.form.AssignToDialog = Class.extend({ }); -frappe.ui.add_assignment = function(assign_to, args, opts, dialog) { +frappe.ui.add_assignment = function(opts, dialog) { + var assign_to = opts.obj.dialog.fields_dict.assign_to.get_value(); + var args = opts.obj.dialog.get_values(); if(args && assign_to) { return frappe.call({ method: opts.method, diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 5b2d830baf..19bc5b5d77 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -129,7 +129,112 @@ frappe.views.ListSidebar = Class.extend({ }); $dropdown.find('.new-kanban-board').click(function() { - frappe.new_doc('Kanban Board', {reference_doctype: me.doctype}); + // frappe.new_doc('Kanban Board', {reference_doctype: me.doctype}); + var select_fields = frappe.get_meta(me.doctype) + .fields.filter(function(df) { + return df.fieldtype === 'Select'; + }).map(function(df) { + return df.fieldname; + }); + + var fields = [ + { + fieldtype: 'Data', + fieldname: 'board_name', + label: __('Kanban Board Name'), + reqd: 1 + } + ] + + if(select_fields.length > 0) { + fields = fields.concat([{ + fieldtype: 'Select', + fieldname: 'field_name', + label: __('Columns based on'), + options: select_fields.join('\n'), + default: select_fields[0] + }, + { + fieldtype: 'Check', + fieldname: 'custom_column', + label: __('Add Custom Column Field'), + default: 0, + onchange: function(e) { + var checked = d.get_value('custom_column'); + if(checked) { + d.get_input('field_name').prop('disabled', true); + } else { + d.get_input('field_name').prop('disabled', null); + } + } + }]); + } + + var d = new frappe.ui.Dialog({ + title: __('New Kanban Board'), + fields: fields, + primary_action: function() { + var values = d.get_values(); + var custom_column = values.custom_column !== undefined ? + values.custom_column : 1; + + me.add_custom_column_field(custom_column) + .then(function(custom_column) { + console.log(custom_column) + var f = custom_column ? + 'kanban_column' : values.field_name; + console.log(f) + return me.make_kanban_board(values.board_name, f) + }) + .then(function() { + d.hide(); + }, function(err) { + msgprint(err); + }); + } + }); + d.show(); + }); + }, + add_custom_column_field: function(flag) { + var me = this; + return new Promise(function(resolve, reject) { + if(!flag) resolve(false); + frappe.call({ + method: 'frappe.custom.doctype.custom_field.custom_field.add_custom_field', + args: { + doctype: me.doctype, + df: { + label: 'Kanban Column', + fieldname: 'kanban_column', + fieldtype: 'Select', + hidden: 1 + } + } + }).success(function() { + resolve(true); + }).error(function(err) { + reject(err); + }); + }); + }, + make_kanban_board: function(board_name, field_name) { + var me = this; + return frappe.call({ + method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board', + args: { + doctype: me.doctype, + board_name: board_name, + field_name: field_name + }, + callback: function(r) { + frappe.set_route( + 'List', + me.doctype, + 'Kanban', + r.message.kanban_board_name + ); + } }); }, setup_assigned_to_me: function() { diff --git a/frappe/public/js/frappe/list/listview.js b/frappe/public/js/frappe/list/listview.js index d0de4641a9..14a4995716 100644 --- a/frappe/public/js/frappe/list/listview.js +++ b/frappe/public/js/frappe/list/listview.js @@ -100,6 +100,7 @@ frappe.views.ListView = Class.extend({ me.fields.push(d); }); } + me.fields = me.fields.concat(me.meta.__kanban_column_fields); }, set_columns: function() { var me = this; diff --git a/frappe/public/js/frappe/views/kanban/kanban_card.html b/frappe/public/js/frappe/views/kanban/kanban_card.html index 58716a0e6d..94b6054e1c 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_card.html +++ b/frappe/public/js/frappe/views/kanban/kanban_card.html @@ -1,15 +1,9 @@
-
{{ title }}
-
- -
\ No newline at end of file diff --git a/frappe/public/js/frappe/views/kanban/kanban_column.html b/frappe/public/js/frappe/views/kanban/kanban_column.html index 66a56efa6e..f9c7ac268d 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_column.html +++ b/frappe/public/js/frappe/views/kanban/kanban_column.html @@ -1,7 +1,7 @@
-
+
{{ __(title) }} -
+