* quick kb, some styling * card ordering fixed * AssignTo code cleanup * card ordering fixed * Empty column_name validation * filter autosave * column ordering * column indicator color * KB based on custom field * added add_custom_field methodversion-14
@@ -3,6 +3,7 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
import json | |||||
from frappe.utils import cstr | from frappe.utils import cstr | ||||
from frappe import _ | from frappe import _ | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -95,5 +96,11 @@ def create_custom_field(doctype, df): | |||||
"fieldtype": df.fieldtype, | "fieldtype": df.fieldtype, | ||||
"options": df.options, | "options": df.options, | ||||
"insert_after": df.insert_after, | "insert_after": df.insert_after, | ||||
"print_hide": df.print_hide | |||||
"print_hide": df.print_hide, | |||||
"hidden": df.hidden or 0 | |||||
}).insert() | }).insert() | ||||
@frappe.whitelist() | |||||
def add_custom_field(doctype, df): | |||||
df = json.loads(df) | |||||
return create_custom_field(doctype, df) |
@@ -148,7 +148,7 @@ | |||||
"read_only": 0, | "read_only": 0, | ||||
"remember_last_selected_value": 0, | "remember_last_selected_value": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 1, | |||||
"reqd": 0, | |||||
"search_index": 0, | "search_index": 0, | ||||
"set_only_once": 0, | "set_only_once": 0, | ||||
"unique": 0 | "unique": 0 | ||||
@@ -192,7 +192,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2017-01-10 04:51:19.413720", | |||||
"modified": "2017-01-18 13:53:44.283037", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Desk", | "module": "Desk", | ||||
"name": "Kanban Board", | "name": "Kanban Board", | ||||
@@ -8,8 +8,13 @@ import json | |||||
from frappe import _ | from frappe import _ | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class KanbanBoard(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() | @frappe.whitelist() | ||||
def add_column(board_name, column_title): | def add_column(board_name, column_title): | ||||
@@ -20,12 +25,12 @@ def add_column(board_name, column_title): | |||||
frappe.throw(_("Column <b>{0}</b> already exist.").format(column_title)) | frappe.throw(_("Column <b>{0}</b> already exist.").format(column_title)) | ||||
doc.append("columns", dict( | doc.append("columns", dict( | ||||
column_name=column_title, | |||||
color="" | |||||
column_name=column_title | |||||
)) | )) | ||||
doc.save() | doc.save() | ||||
return doc.columns | return doc.columns | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def archive_restore_column(board_name, column_title, status): | def archive_restore_column(board_name, column_title, status): | ||||
'''Set column's status to status''' | '''Set column's status to status''' | ||||
@@ -37,6 +42,7 @@ def archive_restore_column(board_name, column_title, status): | |||||
doc.save() | doc.save() | ||||
return doc.columns | return doc.columns | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def update_doc(doc): | def update_doc(doc): | ||||
'''Updates the doc when card is edited''' | '''Updates the doc when card is edited''' | ||||
@@ -56,13 +62,100 @@ def update_doc(doc): | |||||
} | } | ||||
return doc | return doc | ||||
@frappe.whitelist() | @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() | doc.save() | ||||
return doc | |||||
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 |
@@ -44,7 +44,8 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "color", | |||||
"default": "Active", | |||||
"fieldname": "status", | |||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
@@ -52,10 +53,10 @@ | |||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 1, | "in_list_view": 1, | ||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Color", | |||||
"label": "Status", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "\nRed\nBlue\nGreen", | |||||
"options": "Active\nArchived", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
@@ -73,8 +74,8 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"default": "Active", | |||||
"fieldname": "status", | |||||
"default": "darkgrey", | |||||
"fieldname": "indicator", | |||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
@@ -82,10 +83,10 @@ | |||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 1, | "in_list_view": 1, | ||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Status", | |||||
"label": "Indicator", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "Active\nArchived", | |||||
"options": "blue\norange\nred\ngreen\ndarkgrey\npurple\nyellow\nlightblue", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
@@ -137,7 +138,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 1, | "istable": 1, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2017-01-02 12:11:48.389715", | |||||
"modified": "2017-01-17 15:23:43.520379", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Desk", | "module": "Desk", | ||||
"name": "Kanban Board Column", | "name": "Kanban Board Column", | ||||
@@ -41,14 +41,14 @@ class FormMeta(Meta): | |||||
self.load_workflows() | self.load_workflows() | ||||
self.load_templates() | self.load_templates() | ||||
self.load_dashboard() | self.load_dashboard() | ||||
self.load_kanban_boards() | |||||
self.load_kanban_meta() | |||||
def as_dict(self, no_nulls=False): | def as_dict(self, no_nulls=False): | ||||
d = super(FormMeta, self).as_dict(no_nulls=no_nulls) | d = super(FormMeta, self).as_dict(no_nulls=no_nulls) | ||||
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", | for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", | ||||
"__linked_with", "__messages", "__print_formats", "__workflow_docs", | "__linked_with", "__messages", "__print_formats", "__workflow_docs", | ||||
"__form_grid_templates", "__listview_template", "__tree_js", | "__form_grid_templates", "__listview_template", "__tree_js", | ||||
"__dashboard", "__kanban_boards"): | |||||
"__dashboard", "__kanban_boards", "__kanban_column_fields"): | |||||
d[k] = self.get(k) | d[k] = self.get(k) | ||||
for i, df in enumerate(d.get("fields")): | for i, df in enumerate(d.get("fields")): | ||||
@@ -170,11 +170,24 @@ class FormMeta(Meta): | |||||
def load_dashboard(self): | def load_dashboard(self): | ||||
self.set('__dashboard', self.get_dashboard_data()) | 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): | def load_kanban_boards(self): | ||||
kanban_boards = frappe.get_all( | kanban_boards = frappe.get_all( | ||||
'Kanban Board', filters={'reference_doctype': self.name}) | 'Kanban Board', filters={'reference_doctype': self.name}) | ||||
self.set("__kanban_boards", kanban_boards, as_value=True) | 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): | def get_code_files_via_hooks(hook, name): | ||||
code_files = [] | code_files = [] | ||||
@@ -60,6 +60,10 @@ | |||||
.indicator-right.light-blue::after { | .indicator-right.light-blue::after { | ||||
background: #7CD6FD; | background: #7CD6FD; | ||||
} | } | ||||
.indicator.lightblue::before, | |||||
.indicator-right.lightblue::after { | |||||
background: #7CD6FD; | |||||
} | |||||
.modal-header .indicator { | .modal-header .indicator { | ||||
float: left; | float: left; | ||||
margin-top: 7.5px; | margin-top: 7.5px; | ||||
@@ -22,15 +22,24 @@ | |||||
font-weight: bold; | font-weight: bold; | ||||
font-size: 12px; | 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; | background: #ccc !important; | ||||
color: transparent; | color: transparent; | ||||
} | } | ||||
.kanban .sortable-ghost > .kanban-card * { | |||||
.kanban .sortable-ghost > .kanban-card:not(.add-card) * { | |||||
background: transparent !important; | background: transparent !important; | ||||
color: transparent !important; | color: transparent !important; | ||||
} | } | ||||
@@ -47,8 +56,7 @@ | |||||
color: #8D99A6; | color: #8D99A6; | ||||
} | } | ||||
.kanban .kanban-card.add-card:hover { | .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; | color: #36414C; | ||||
cursor: pointer; | cursor: pointer; | ||||
} | } | ||||
@@ -107,7 +115,7 @@ | |||||
outline: none; | outline: none; | ||||
} | } | ||||
.kanban .add-new-column a:hover { | .kanban .add-new-column a:hover { | ||||
color: #36414C; | |||||
color: #36414C !important; | |||||
} | } | ||||
.kanban .kanban-card-meta { | .kanban .kanban-card-meta { | ||||
margin-top: 8px; | margin-top: 8px; | ||||
@@ -117,3 +125,19 @@ | |||||
width: 16px; | width: 16px; | ||||
height: 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%; | |||||
} |
@@ -381,6 +381,10 @@ a.no-decoration:active { | |||||
.indicator-right.light-blue::after { | .indicator-right.light-blue::after { | ||||
background: #7CD6FD; | background: #7CD6FD; | ||||
} | } | ||||
.indicator.lightblue::before, | |||||
.indicator-right.lightblue::after { | |||||
background: #7CD6FD; | |||||
} | |||||
.modal-header .indicator { | .modal-header .indicator { | ||||
float: left; | float: left; | ||||
margin-top: 7.5px; | margin-top: 7.5px; | ||||
@@ -3,7 +3,7 @@ | |||||
frappe.db = { | frappe.db = { | ||||
get_value: function(doctype, filters, fieldname, callback) { | get_value: function(doctype, filters, fieldname, callback) { | ||||
frappe.call({ | |||||
return frappe.call({ | |||||
method: "frappe.client.get_value", | method: "frappe.client.get_value", | ||||
args: { | args: { | ||||
doctype: doctype, | doctype: doctype, | ||||
@@ -14,5 +14,19 @@ frappe.db = { | |||||
callback(r.message); | 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); | |||||
} | |||||
}); | |||||
} | } | ||||
} | } |
@@ -132,7 +132,7 @@ frappe.ui.form.AssignTo = Class.extend({ | |||||
frappe.ui.form.AssignToDialog = Class.extend({ | frappe.ui.form.AssignToDialog = Class.extend({ | ||||
init: function(opts){ | init: function(opts){ | ||||
var me = this | var me = this | ||||
$.extend(me,new frappe.ui.Dialog({ | |||||
$.extend(me, new frappe.ui.Dialog({ | |||||
title: __('Add to To Do'), | title: __('Add to To Do'), | ||||
fields: [ | fields: [ | ||||
{fieldtype: 'Link', fieldname:'assign_to', options:'User', | {fieldtype: 'Link', fieldname:'assign_to', options:'User', | ||||
@@ -152,11 +152,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ | |||||
{value:'High', label:__('High')}], | {value:'High', label:__('High')}], | ||||
'default':'Medium'}, | '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") | 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) { | if(args && assign_to) { | ||||
return frappe.call({ | return frappe.call({ | ||||
method: opts.method, | method: opts.method, | ||||
@@ -129,7 +129,112 @@ frappe.views.ListSidebar = Class.extend({ | |||||
}); | }); | ||||
$dropdown.find('.new-kanban-board').click(function() { | $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() { | setup_assigned_to_me: function() { | ||||
@@ -100,6 +100,7 @@ frappe.views.ListView = Class.extend({ | |||||
me.fields.push(d); | me.fields.push(d); | ||||
}); | }); | ||||
} | } | ||||
me.fields = me.fields.concat(me.meta.__kanban_column_fields); | |||||
}, | }, | ||||
set_columns: function() { | set_columns: function() { | ||||
var me = this; | var me = this; | ||||
@@ -1,15 +1,9 @@ | |||||
<div class="kanban-card-wrapper" data-name="{{name}}"> | <div class="kanban-card-wrapper" data-name="{{name}}"> | ||||
<div class="kanban-card content"> | <div class="kanban-card content"> | ||||
<button class="btn btn-default btn-xs kanban-card-edit"> | |||||
<span class="octicon octicon-pencil text-muted"></span> | |||||
</button> | |||||
<div class="kanban-card-title"> | <div class="kanban-card-title"> | ||||
{{ title }} | {{ title }} | ||||
</div> | </div> | ||||
<div class="kanban-card-meta"> | <div class="kanban-card-meta"> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="kanban-card edit-card-area"> | |||||
<textarea></textarea> | |||||
</div> | |||||
</div> | </div> |
@@ -1,7 +1,7 @@ | |||||
<div class="kanban-column" data-column-value="{{title}}"> | <div class="kanban-column" data-column-value="{{title}}"> | ||||
<div class="kanban-column-title"> | |||||
<div class="kanban-column-title indicator {{indicator}}"> | |||||
<span>{{ __(title) }}</span> | <span>{{ __(title) }}</span> | ||||
<div class="btn-group column-options"> | |||||
<div class="btn-group column-options dropdown pull-right"> | |||||
<a class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | <a class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||||
<button class="btn btn-default btn-xs"><span class="caret"></span></button> | <button class="btn btn-default btn-xs"><span class="caret"></span></button> | ||||
</a> | </a> | ||||
@@ -16,7 +16,7 @@ | |||||
<div class="kanban-card add-card"> | <div class="kanban-card add-card"> | ||||
<div class="kanban-card-title"> | <div class="kanban-card-title"> | ||||
<i class="octicon octicon-plus"></i> {{ __("Add a card") }} | |||||
<i class="octicon octicon-plus"></i> {{ __("Add " + doctype) }} | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="kanban-card new-card-area"> | <div class="kanban-card new-card-area"> | ||||
@@ -2,6 +2,8 @@ frappe.provide("frappe.views"); | |||||
(function () { | (function () { | ||||
var method_prefix = 'frappe.desk.doctype.kanban_board.kanban_board.'; | |||||
var store = fluxify.createStore({ | var store = fluxify.createStore({ | ||||
id: 'store', | id: 'store', | ||||
initialState: { | initialState: { | ||||
@@ -70,35 +72,16 @@ frappe.provide("frappe.views"); | |||||
}, | }, | ||||
save_filters: function (updater) { | save_filters: function (updater) { | ||||
var filters = JSON.stringify(this.cur_list.filter_list.get_filters()); | var filters = JSON.stringify(this.cur_list.filter_list.get_filters()); | ||||
frappe.call({ | |||||
method: "frappe.client.set_value", | |||||
args: { | |||||
doctype: 'Kanban Board', | |||||
name: this.board.name, | |||||
fieldname: 'filters', | |||||
value: filters | |||||
}, | |||||
callback: function () { | |||||
frappe.db.set_value( | |||||
'Kanban Board', this.board.name, | |||||
'filters', filters, | |||||
function() { | |||||
updater.set({ filters_modified: false }); | updater.set({ filters_modified: false }); | ||||
show_alert({ message: __("Filters saved"), indicator: 'green' }, 1); | |||||
} | |||||
}); | |||||
}, | |||||
change_card_column: function (updater, card, column_title) { | |||||
var state = this; | |||||
frappe.call({ | |||||
method: "frappe.client.set_value", | |||||
args: { | |||||
doctype: this.doctype, | |||||
name: card.name, | |||||
fieldname: this.board.field_name, | |||||
value: column_title | |||||
} | |||||
}).then(function (r) { | |||||
// var doc = r.message; | |||||
// var new_card = prepare_card(card, state, doc); | |||||
// fluxify.doAction('update_card', new_card); | |||||
}); | |||||
show_alert({ | |||||
message: __("Filters saved"), | |||||
indicator: 'green' | |||||
}, 1); | |||||
}); | |||||
}, | }, | ||||
add_card: function (updater, card_title, column_title) { | add_card: function (updater, card_title, column_title) { | ||||
var doc = frappe.model.get_new_doc(this.doctype); | var doc = frappe.model.get_new_doc(this.doctype); | ||||
@@ -149,7 +132,7 @@ frappe.provide("frappe.views"); | |||||
update_doc: function (updater, doc, card) { | update_doc: function (updater, doc, card) { | ||||
var state = this; | var state = this; | ||||
return frappe.call({ | return frappe.call({ | ||||
method: "frappe.desk.doctype.kanban_board.kanban_board.update_doc", | |||||
method: method_prefix + "update_doc", | |||||
args: { doc: doc }, | args: { doc: doc }, | ||||
freeze: true | freeze: true | ||||
}).then(function (r) { | }).then(function (r) { | ||||
@@ -158,31 +141,55 @@ frappe.provide("frappe.views"); | |||||
fluxify.doAction('update_card', updated_card); | fluxify.doAction('update_card', updated_card); | ||||
}); | }); | ||||
}, | }, | ||||
assign_to: function (updater, user, desc, card) { | |||||
var opts = { | |||||
method: "frappe.desk.form.assign_to.add", | |||||
doctype: this.doctype, | |||||
docname: card.name, | |||||
} | |||||
var args = { | |||||
description: desc | |||||
} | |||||
return frappe.ui.add_assignment(user, args, opts) | |||||
.then(function () { | |||||
card.assigned_list.push(user); | |||||
fluxify.doAction('update_card', card); | |||||
update_order: function(updater, order) { | |||||
return frappe.call({ | |||||
method: method_prefix + "update_order", | |||||
args: { | |||||
board_name: this.board.name, | |||||
order: order | |||||
} | |||||
}).then(function(r) { | |||||
var state = this; | |||||
var board = r.message[0]; | |||||
var updated_cards = r.message[1]; | |||||
var cards = update_cards_column(updated_cards); | |||||
var columns = prepare_columns(board.columns); | |||||
updater.set({ | |||||
cards: cards, | |||||
columns: columns | |||||
}); | }); | ||||
}); | |||||
}, | }, | ||||
update_order: function(updater, column_title, order) { | |||||
var board = store.getState().board.name; | |||||
update_column_order: function(updater, order) { | |||||
return frappe.call({ | return frappe.call({ | ||||
method: "frappe.desk.doctype.kanban_board.kanban_board.update_order", | |||||
method: method_prefix + "update_column_order", | |||||
args: { | args: { | ||||
board_name: board, | |||||
column_title: column_title, | |||||
board_name: this.board.name, | |||||
order: order | order: order | ||||
} | } | ||||
}).then(function(r) { | |||||
var board = r.message; | |||||
var columns = prepare_columns(board.columns); | |||||
updater.set({ | |||||
columns: columns | |||||
}); | |||||
}); | }); | ||||
}, | |||||
set_indicator: function(updater, column, color) { | |||||
return frappe.call({ | |||||
method: method_prefix + "set_indicator", | |||||
args: { | |||||
board_name: this.board.name, | |||||
column_name: column.title, | |||||
indicator: color | |||||
} | |||||
}).then(function(r) { | |||||
var board = r.message; | |||||
var columns = prepare_columns(board.columns); | |||||
updater.set({ | |||||
columns: columns | |||||
}); | |||||
}) | |||||
} | } | ||||
} | } | ||||
}); | }); | ||||
@@ -206,6 +213,7 @@ frappe.provide("frappe.views"); | |||||
self.$kanban_board.appendTo(self.wrapper); | self.$kanban_board.appendTo(self.wrapper); | ||||
self.$filter_area = self.cur_list.$page.find('.set-filters'); | self.$filter_area = self.cur_list.$page.find('.set-filters'); | ||||
bind_events(); | bind_events(); | ||||
setup_sortable(); | |||||
} | } | ||||
function make_columns() { | function make_columns() { | ||||
@@ -219,7 +227,22 @@ frappe.provide("frappe.views"); | |||||
function bind_events() { | function bind_events() { | ||||
bind_add_column(); | bind_add_column(); | ||||
bind_save_filter_button(); | |||||
bind_save_filter(); | |||||
} | |||||
function setup_sortable() { | |||||
var sortable = new Sortable(self.$kanban_board.get(0), { | |||||
group: 'columns', | |||||
animation: 150, | |||||
dataIdAttr: 'data-column-value', | |||||
filter: '.add-new-column', | |||||
handle: '.kanban-column-title', | |||||
onEnd: function(evt) { | |||||
var order = sortable.toArray(); | |||||
order = order.slice(1); | |||||
fluxify.doAction('update_column_order', order); | |||||
} | |||||
}); | |||||
} | } | ||||
function bind_add_column() { | function bind_add_column() { | ||||
@@ -261,21 +284,9 @@ frappe.provide("frappe.views"); | |||||
}); | }); | ||||
} | } | ||||
function bind_save_filter_button() { | |||||
self.$save_filter_btn = self.$filter_area.find('.save-filters'); | |||||
if (self.$save_filter_btn.length) return; | |||||
//make save filter button | |||||
self.$save_filter_btn = $('<button>', { | |||||
class: 'btn btn-xs btn-default text-muted save-filters', | |||||
text: __('Save filters') | |||||
}).on('click', function () { | |||||
fluxify.doAction('save_filters') | |||||
}).appendTo(self.$filter_area).hide(); | |||||
store.on('change:filters_modified', function (val) { | |||||
val ? self.$save_filter_btn.show() : | |||||
self.$save_filter_btn.hide(); | |||||
function bind_save_filter() { | |||||
store.on('change:filters_modified', function (modified) { | |||||
if(modified) fluxify.doAction('save_filters'); | |||||
}); | }); | ||||
self.cur_list.wrapper.on('render-complete', function () { | self.cur_list.wrapper.on('render-complete', function () { | ||||
@@ -337,8 +348,12 @@ frappe.provide("frappe.views"); | |||||
} | } | ||||
function make_dom() { | function make_dom() { | ||||
self.$kanban_column = $(frappe.render_template('kanban_column', | |||||
{ title: column.title })).appendTo(wrapper); | |||||
self.$kanban_column = $(frappe.render_template( | |||||
'kanban_column', { | |||||
title: column.title, | |||||
doctype: store.getState().doctype, | |||||
indicator: column.indicator | |||||
})).appendTo(wrapper); | |||||
self.$kanban_cards = self.$kanban_column.find('.kanban-cards'); | self.$kanban_cards = self.$kanban_column.find('.kanban-cards'); | ||||
} | } | ||||
@@ -350,7 +365,7 @@ frappe.provide("frappe.views"); | |||||
var order = column.order; | var order = column.order; | ||||
if(order) { | if(order) { | ||||
order = order.split('|'); | |||||
order = JSON.parse(order); | |||||
order.forEach(function(name) { | order.forEach(function(name) { | ||||
frappe.views.KanbanBoardCard(get_card(name), self.$kanban_cards); | frappe.views.KanbanBoardCard(get_card(name), self.$kanban_cards); | ||||
}); | }); | ||||
@@ -380,37 +395,24 @@ frappe.provide("frappe.views"); | |||||
onEnd: function (evt) { | onEnd: function (evt) { | ||||
wrapper.find('.kanban-card.add-card').fadeIn(100); | wrapper.find('.kanban-card.add-card').fadeIn(100); | ||||
wrapper.find('.kanban-cards').height('auto'); | wrapper.find('.kanban-cards').height('auto'); | ||||
var card_name = $(evt.item).data().name; | |||||
var card = get_card(card_name); | |||||
var board = store.getState().board.name; | |||||
// update order | // update order | ||||
var order = sortable.toArray(); | |||||
fluxify.doAction('update_order', column.title, order.join('|')); | |||||
var order = {} | |||||
wrapper.find('.kanban-column[data-column-value]') | |||||
.each(function() { | |||||
var col_name = $(this).data().columnValue; | |||||
order[col_name] = []; | |||||
$(this).find('.kanban-card-wrapper').each(function() { | |||||
var card_name = $(this).data().name; | |||||
order[col_name].push(card_name); | |||||
}); | |||||
}); | |||||
fluxify.doAction('update_order', order); | |||||
}, | }, | ||||
onAdd: function (evt) { | onAdd: function (evt) { | ||||
var card_name = $(evt.item).data().name; | |||||
var card = get_card(card_name); | |||||
fluxify.doAction('change_card_column', card, column.title); | |||||
// update order | |||||
var order = sortable.toArray(); | |||||
fluxify.doAction('update_order', column.title, order.join('|')); | |||||
}, | }, | ||||
}); | }); | ||||
} | } | ||||
function get_card_by_order(order) { | |||||
var board = store.getState().board.name; | |||||
filtered_cards.find(function(c) { | |||||
return c.kanban_column_order[board] === order; | |||||
}); | |||||
} | |||||
function get_card(name) { | |||||
return store.getState().cards.find(function (c) { | |||||
return c.name === name; | |||||
}); | |||||
} | |||||
function bind_add_card() { | function bind_add_card() { | ||||
var $wrapper = self.$kanban_column; | var $wrapper = self.$kanban_column; | ||||
var $btn_add = $wrapper.find('.add-card'); | var $btn_add = $wrapper.find('.add-card'); | ||||
@@ -451,14 +453,28 @@ frappe.provide("frappe.views"); | |||||
function bind_options() { | function bind_options() { | ||||
self.$kanban_column.find(".column-options .dropdown-menu") | self.$kanban_column.find(".column-options .dropdown-menu") | ||||
.on("click", "a", function (e) { | |||||
.on("click", "[data-action]", function (e) { | |||||
var $btn = $(this); | var $btn = $(this); | ||||
var action = $btn.data().action; | var action = $btn.data().action; | ||||
if (action === "archive") { | if (action === "archive") { | ||||
fluxify.doAction('archive_column', column); | fluxify.doAction('archive_column', column); | ||||
} else if (action === "indicator") { | |||||
var color = $btn.data().indicator; | |||||
fluxify.doAction('set_indicator', column, color); | |||||
} | } | ||||
}); | }); | ||||
get_column_indicators(function(indicators) { | |||||
var html = '<li class="button-group">' | |||||
html += indicators.reduce(function(prev, curr) { | |||||
return prev + '<div \ | |||||
data-action="indicator" data-indicator="'+curr+'"\ | |||||
class="btn btn-default btn-xs indicator ' + curr + '"></div>' | |||||
}, ""); | |||||
html += '</li>'; | |||||
self.$kanban_column.find(".column-options .dropdown-menu") | |||||
.append(html); | |||||
}); | |||||
} | } | ||||
init(); | init(); | ||||
@@ -472,7 +488,7 @@ frappe.provide("frappe.views"); | |||||
make_dom(); | make_dom(); | ||||
render_card_meta(); | render_card_meta(); | ||||
bind_edit_card(); | bind_edit_card(); | ||||
edit_card_title(); | |||||
// edit_card_title(); | |||||
} | } | ||||
function make_dom() { | function make_dom() { | ||||
@@ -501,9 +517,9 @@ frappe.provide("frappe.views"); | |||||
} | } | ||||
function setup_edit_card() { | function setup_edit_card() { | ||||
if (self.dialog) { | |||||
if (self.edit_dialog) { | |||||
refresh_dialog(); | refresh_dialog(); | ||||
self.dialog.show(); | |||||
self.edit_dialog.show(); | |||||
return; | return; | ||||
} | } | ||||
@@ -533,6 +549,7 @@ frappe.provide("frappe.views"); | |||||
refresh_dialog(); | refresh_dialog(); | ||||
make_timeline(); | make_timeline(); | ||||
edit_card_title(); | |||||
d.set_primary_action(__('Save'), function () { | d.set_primary_action(__('Save'), function () { | ||||
if (d.working) return; | if (d.working) return; | ||||
@@ -542,7 +559,7 @@ frappe.provide("frappe.views"); | |||||
fluxify.doAction('update_doc', doc, card) | fluxify.doAction('update_doc', doc, card) | ||||
.then(function (r) { | .then(function (r) { | ||||
d.working = false; | d.working = false; | ||||
// fluxify.doAction('update_card', card) | |||||
d.hide(); | |||||
}); | }); | ||||
}); | }); | ||||
d.show(); | d.show(); | ||||
@@ -555,10 +572,10 @@ frappe.provide("frappe.views"); | |||||
} | } | ||||
function set_dialog_fields() { | function set_dialog_fields() { | ||||
self.dialog.fields.forEach(function (df) { | |||||
self.edit_dialog.fields.forEach(function (df) { | |||||
var value = card.doc[df.fieldname]; | var value = card.doc[df.fieldname]; | ||||
if (value) { | if (value) { | ||||
self.dialog.set_value(df.fieldname, value); | |||||
self.edit_dialog.set_value(df.fieldname, value); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
@@ -586,17 +603,17 @@ frappe.provide("frappe.views"); | |||||
} | } | ||||
function make_edit_dialog(title, fields) { | function make_edit_dialog(title, fields) { | ||||
self.dialog = new frappe.ui.Dialog({ | |||||
self.edit_dialog = new frappe.ui.Dialog({ | |||||
title: title, | title: title, | ||||
fields: fields | fields: fields | ||||
}); | }); | ||||
return self.dialog; | |||||
return self.edit_dialog; | |||||
} | } | ||||
function make_assignees() { | function make_assignees() { | ||||
var d = self.dialog; | |||||
var html = get_assignees_html() + '<a class="strong add-assignment">\ | |||||
Assign <i class="octicon octicon-plus" style="margin-left: 2px;"></i></a>'; | |||||
var d = self.edit_dialog; | |||||
var html = get_assignees_html() + '<a class="add-assignment avatar avatar-small avatar-empty">\ | |||||
<i class="octicon octicon-plus text-muted" style="margin: 3px 0 0 5px;"></i></a>'; | |||||
d.$wrapper.find("[data-fieldname='assignees'] .control-input-wrapper").empty().append(html); | d.$wrapper.find("[data-fieldname='assignees'] .control-input-wrapper").empty().append(html); | ||||
d.$wrapper.find(".add-assignment").on("click", function () { | d.$wrapper.find(".add-assignment").on("click", function () { | ||||
@@ -615,27 +632,24 @@ frappe.provide("frappe.views"); | |||||
} | } | ||||
function show_assign_to_dialog() { | function show_assign_to_dialog() { | ||||
var ad = new frappe.ui.Dialog({ | |||||
title: __("Assign to"), | |||||
fields: [ | |||||
{ fieldtype: "Link", fieldname: "user", label: __("User"), options: "User" }, | |||||
{ fieldtype: "Small Text", fieldname: "description", label: __("Description") } | |||||
] | |||||
}) | |||||
ad.set_primary_action(__("Save"), function () { | |||||
var values = ad.get_values(); | |||||
fluxify.doAction('assign_to', values.user, values.description, card) | |||||
.then(function () { | |||||
refresh_dialog(); | |||||
ad.hide(); | |||||
}); | |||||
self.dialog = new frappe.ui.form.AssignToDialog({ | |||||
obj: self, | |||||
method: 'frappe.desk.form.assign_to.add', | |||||
doctype: card.doctype, | |||||
docname: card.name, | |||||
callback: function(r) { | |||||
var user = self.assign_to_dialog.get_values().assign_to; | |||||
card.assigned_list.push(user); | |||||
fluxify.doAction('update_card', card); | |||||
refresh_dialog(); | |||||
} | |||||
}); | }); | ||||
ad.show(); | |||||
self.assign_to_dialog = ad; | |||||
self.assign_to_dialog = self.dialog; | |||||
self.assign_to_dialog.show(); | |||||
} | } | ||||
function make_timeline() { | function make_timeline() { | ||||
var d = self.dialog; | |||||
var d = self.edit_dialog; | |||||
// timeline wrapper | // timeline wrapper | ||||
d.$wrapper.find('.modal-body').append('<div class="form-comments" style="padding:7px">'); | d.$wrapper.find('.modal-body').append('<div class="form-comments" style="padding:7px">'); | ||||
@@ -680,9 +694,53 @@ frappe.provide("frappe.views"); | |||||
} | } | ||||
function edit_card_title() { | function edit_card_title() { | ||||
var $edit_card_area = self.$card.find('.edit-card-area').hide(); | |||||
var $kanban_card_area = self.$card.find('.kanban-card.content'); | |||||
var $textarea = $edit_card_area.find('textarea').val(card.title); | |||||
var $card_title = self.edit_dialog.header.find('.modal-title'); | |||||
var $title_wrapper = $card_title.parent(); | |||||
$title_wrapper.addClass('edit-card-title').empty(); | |||||
var template = repl('<div class="h4">\ | |||||
<span>%(card_title)s</span>\ | |||||
<input type="text">\ | |||||
</div>', { card_title: card.title }); | |||||
$title_wrapper.html(template); | |||||
var $input = $title_wrapper.find('input').hide(); | |||||
var $span = $title_wrapper.find('span'); | |||||
$span.on('click', function() { | |||||
$input.show(); | |||||
$span.hide(); | |||||
$input.val(card.title); | |||||
$input.focus(); | |||||
}); | |||||
$input.on('blur', function() { | |||||
$input.hide(); | |||||
$span.show(); | |||||
}); | |||||
$input.keydown(function(e) { | |||||
if (e.which === 13) { | |||||
e.preventDefault(); | |||||
var new_title = $input.val(); | |||||
if (card.title === new_title) { | |||||
return; | |||||
} | |||||
get_doc().then(function () { | |||||
var tf = store.getState().card_meta.title_field.fieldname; | |||||
var doc = card.doc; | |||||
doc[tf] = new_title; | |||||
fluxify.doAction('update_doc', doc, card); | |||||
$span.html(new_title); | |||||
$input.trigger('blur'); | |||||
}) | |||||
} | |||||
}) | |||||
} | |||||
function edit_card_title_old() { | |||||
self.$card.find('.kanban-card-edit').on('click', function (e) { | self.$card.find('.kanban-card-edit').on('click', function (e) { | ||||
e.stopPropagation(); | e.stopPropagation(); | ||||
@@ -763,8 +821,8 @@ frappe.provide("frappe.views"); | |||||
if (df.fieldtype === "Text Editor" && !description_field) { | if (df.fieldtype === "Text Editor" && !description_field) { | ||||
description_field = df; | description_field = df; | ||||
} | } | ||||
if (df.fieldtype === "Date" && df.fieldname.indexOf("end") !== -1 && !due_date_field) { | |||||
due_date_field = df; | |||||
if (!due_date_field) { | |||||
due_date_field = get_date_field(meta.fields); | |||||
} | } | ||||
}); | }); | ||||
return { | return { | ||||
@@ -775,19 +833,22 @@ frappe.provide("frappe.views"); | |||||
} | } | ||||
} | } | ||||
function get_date_field(fields) { | |||||
var filtered = fields.filter(function(df) { | |||||
return df.fieldtype === 'Date' && | |||||
df.fieldname.indexOf('date') !== -1; | |||||
}); | |||||
var field = filtered.find(function(df) { | |||||
return df.fieldname.indexOf('end') !== -1; | |||||
}); | |||||
return field || filtered[0]; | |||||
} | |||||
function prepare_card(card, state, doc) { | function prepare_card(card, state, doc) { | ||||
var assigned_list = card._assign ? | var assigned_list = card._assign ? | ||||
JSON.parse(card._assign) : []; | JSON.parse(card._assign) : []; | ||||
var comment_count = card._comment_count || 0; | var comment_count = card._comment_count || 0; | ||||
if (card.kanban_column_order === null || card.kanban_column_order === '') { | |||||
var kanban_column_order = {}; | |||||
} else if (typeof card.kanban_column_order === 'string') { | |||||
kanban_column_order = JSON.parse(card.kanban_column_order); | |||||
} else if (typeof card.kanban_column_order === 'object') { | |||||
kanban_column_order = card.kanban_column_order; | |||||
} | |||||
if (doc) { | if (doc) { | ||||
card = Object.assign({}, card, doc); | card = Object.assign({}, card, doc); | ||||
} | } | ||||
@@ -799,7 +860,6 @@ frappe.provide("frappe.views"); | |||||
column: card[state.board.field_name], | column: card[state.board.field_name], | ||||
assigned_list: card.assigned_list || assigned_list, | assigned_list: card.assigned_list || assigned_list, | ||||
comment_count: card.comment_count || comment_count, | comment_count: card.comment_count || comment_count, | ||||
kanban_column_order: kanban_column_order, | |||||
doc: doc | doc: doc | ||||
}; | }; | ||||
} | } | ||||
@@ -809,9 +869,10 @@ frappe.provide("frappe.views"); | |||||
return { | return { | ||||
title: col.column_name, | title: col.column_name, | ||||
status: col.status, | status: col.status, | ||||
order: col.order | |||||
order: col.order, | |||||
indicator: col.indicator || 'darkgrey' | |||||
}; | }; | ||||
}) | |||||
}); | |||||
} | } | ||||
function modify_column_field_in_c11n(doc, board, title, action) { | function modify_column_field_in_c11n(doc, board, title, action) { | ||||
@@ -882,7 +943,7 @@ frappe.provide("frappe.views"); | |||||
args.status = action === 'archive' ? 'Archived' : 'Active'; | args.status = action === 'archive' ? 'Archived' : 'Active'; | ||||
} | } | ||||
return frappe.call({ | return frappe.call({ | ||||
method: 'frappe.desk.doctype.kanban_board.kanban_board.' + method, | |||||
method: method_prefix + method, | |||||
args: args | args: args | ||||
}); | }); | ||||
} | } | ||||
@@ -902,4 +963,34 @@ frappe.provide("frappe.views"); | |||||
}); | }); | ||||
} | } | ||||
})(); | |||||
function get_card(name) { | |||||
return store.getState().cards.find(function (c) { | |||||
return c.name === name; | |||||
}); | |||||
} | |||||
function update_cards_column(updated_cards) { | |||||
var cards = store.getState().cards; | |||||
cards.forEach(function(c) { | |||||
updated_cards.forEach(function(uc) { | |||||
if(uc.name === c.name) { | |||||
c.column = uc.column; | |||||
} | |||||
}); | |||||
}); | |||||
return cards; | |||||
} | |||||
function get_column_indicators(callback) { | |||||
frappe.model.with_doctype('Kanban Board Column', function() { | |||||
var meta = frappe.get_meta('Kanban Board Column'); | |||||
var indicators; | |||||
meta.fields.forEach(function(df) { | |||||
if(df.fieldname==='indicator') { | |||||
indicators = df.options.split("\n"); | |||||
} | |||||
}); | |||||
callback(indicators); | |||||
}); | |||||
} | |||||
})(); |
@@ -70,6 +70,11 @@ | |||||
background: @indicator-light-blue; | background: @indicator-light-blue; | ||||
} | } | ||||
.indicator.lightblue::before, | |||||
.indicator-right.lightblue::after { | |||||
background: @indicator-light-blue; | |||||
} | |||||
.modal-header .indicator { | .modal-header .indicator { | ||||
float: left; | float: left; | ||||
margin-top: 7.5px; | margin-top: 7.5px; | ||||
@@ -27,12 +27,27 @@ | |||||
font-size: 12px; | font-size: 12px; | ||||
.column-options { | .column-options { | ||||
position: absolute; | |||||
right: 0px; | |||||
.button-group { | |||||
display: flex; | |||||
padding: 12px 14px; | |||||
.btn.indicator { | |||||
flex: 1; | |||||
} | |||||
} | |||||
.indicator::before { | |||||
margin: 0; | |||||
} | |||||
} | |||||
&:hover { | |||||
cursor: pointer; | |||||
} | } | ||||
} | } | ||||
.sortable-ghost > .kanban-card { | |||||
.sortable-ghost > .kanban-card:not(.add-card) { | |||||
background: #ccc !important; | background: #ccc !important; | ||||
color: transparent; | color: transparent; | ||||
@@ -56,8 +71,7 @@ | |||||
color: @text-muted; | color: @text-muted; | ||||
&:hover { | &:hover { | ||||
background-color: #fff; | |||||
box-shadow: 0 1px 2px rgba(0,0,0,0.30); | |||||
box-shadow: none; | |||||
color: @text-color; | color: @text-color; | ||||
cursor: pointer; | cursor: pointer; | ||||
} | } | ||||
@@ -127,7 +141,7 @@ | |||||
} | } | ||||
.add-new-column a:hover { | .add-new-column a:hover { | ||||
color: @text-color; | |||||
color: @text-color !important; | |||||
} | } | ||||
.kanban-card-meta { | .kanban-card-meta { | ||||
@@ -139,4 +153,29 @@ | |||||
height: 16px; | height: 16px; | ||||
} | } | ||||
} | } | ||||
} | |||||
body[data-route*="Kanban"] { | |||||
.modal .add-assignment:hover { | |||||
// border-color: @text-color; | |||||
i { | |||||
color: @text-color !important; | |||||
} | |||||
} | |||||
} | |||||
.edit-card-title { | |||||
.h4 { | |||||
margin-top: 5px; | |||||
margin-bottom: 5px; | |||||
} | |||||
span:hover { | |||||
background-color: @light-yellow; | |||||
cursor: pointer; | |||||
} | |||||
input { | |||||
border: none; | |||||
outline: none; | |||||
width: 100%; | |||||
} | |||||
} | } |