Bläddra i källkod

Merge pull request #2872 from netchampfaris/kanban-improve-ux

Kanban Board enhancement and fixes frappe/erpnext#8005
version-14
Nabin Hait 8 år sedan
committed by GitHub
förälder
incheckning
2c984287ca
11 ändrade filer med 140 tillägg och 32 borttagningar
  1. +47
    -4
      frappe/desk/doctype/kanban_board/kanban_board.json
  2. +31
    -0
      frappe/desk/doctype/kanban_board/kanban_board.py
  3. +3
    -3
      frappe/desk/form/meta.py
  4. +2
    -0
      frappe/hooks.py
  5. +1
    -1
      frappe/public/css/kanban.css
  6. +1
    -1
      frappe/public/js/frappe/form/formatters.js
  7. +38
    -15
      frappe/public/js/frappe/list/list_sidebar.js
  8. +6
    -1
      frappe/public/js/frappe/ui/dialog.js
  9. +2
    -1
      frappe/public/js/frappe/views/kanban/kanban_board.js
  10. +8
    -5
      frappe/public/js/frappe/views/kanban/kanban_view.js
  11. +1
    -1
      frappe/public/less/kanban.less

+ 47
- 4
frappe/desk/doctype/kanban_board/kanban_board.json Visa fil

@@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:kanban_board_name", "autoname": "field:kanban_board_name",
@@ -22,6 +23,8 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Kanban Board Name", "label": "Kanban Board Name",
@@ -49,6 +52,8 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Reference DocType", "label": "Reference DocType",
@@ -77,6 +82,8 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Field Name", "label": "Field Name",
@@ -104,6 +111,8 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"length": 0, "length": 0,
@@ -130,6 +139,8 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Columns", "label": "Columns",
@@ -155,9 +166,11 @@
"columns": 0, "columns": 0,
"fieldname": "filters", "fieldname": "filters",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 0,
"hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Filters", "label": "Filters",
@@ -174,20 +187,49 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "private",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Private",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"idx": 0, "idx": 0,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-02-09 12:23:30.059564",
"modified_by": "Administrator",
"modified": "2017-03-14 23:02:13.267243",
"modified_by": "faris@erpnext.com",
"module": "Desk", "module": "Desk",
"name": "Kanban Board", "name": "Kanban Board",
"name_case": "", "name_case": "",
@@ -217,6 +259,7 @@
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 0, "track_changes": 0,


+ 31
- 0
frappe/desk/doctype/kanban_board/kanban_board.py Visa fil

@@ -11,10 +11,29 @@ from frappe.model.document import Document


class KanbanBoard(Document): class KanbanBoard(Document):
def validate(self): def validate(self):
self.validate_column_name()
def validate_column_name(self):
for column in self.columns: for column in self.columns:
if not column.column_name: if not column.column_name:
frappe.msgprint(frappe._("Column Name cannot be empty"), raise_exception=True) frappe.msgprint(frappe._("Column Name cannot be empty"), raise_exception=True)


def get_permission_query_conditions(user):
if not user: user = frappe.session.user

if user == "Administrator":
return ""

return """(`tabKanban Board`.private=0 or `tabKanban Board`.owner="{user}")""".format(user=user)

def has_permission(doc, ptype, user):
if doc.private == 0 or user == "Administrator":
return True

if user == doc.owner:
return True

return False


@frappe.whitelist() @frappe.whitelist()
def add_column(board_name, column_title): def add_column(board_name, column_title):
@@ -118,6 +137,18 @@ def quick_kanban_board(doctype, board_name, field_name):
doc.kanban_board_name = board_name doc.kanban_board_name = board_name
doc.reference_doctype = doctype doc.reference_doctype = doctype
doc.field_name = field_name doc.field_name = field_name

if doctype == 'Task':
project = frappe.new_doc('Project')
project.project_name = board_name
project.status = 'Open'
project.save()

doc.filters = '[["Task","project","=","{0}"]]'.format(board_name)

if doctype == 'Note':
doc.private = 1

doc.save() doc.save()
return doc return doc




+ 3
- 3
frappe/desk/form/meta.py Visa fil

@@ -176,12 +176,12 @@ class FormMeta(Meta):
self.load_kanban_column_fields() self.load_kanban_column_fields()


def load_kanban_boards(self): def load_kanban_boards(self):
kanban_boards = frappe.get_all(
'Kanban Board', fields=['name', 'filters', 'reference_doctype'], filters={'reference_doctype': self.name})
kanban_boards = frappe.get_list(
'Kanban Board', fields=['name', 'filters', 'reference_doctype', 'private'], 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): def load_kanban_column_fields(self):
values = frappe.get_all(
values = frappe.get_list(
'Kanban Board', fields=['field_name'], 'Kanban Board', fields=['field_name'],
filters={'reference_doctype': self.name}) filters={'reference_doctype': self.name})




+ 2
- 0
frappe/hooks.py Visa fil

@@ -78,6 +78,7 @@ permission_query_conditions = {
"ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions", "ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions",
"User": "frappe.core.doctype.user.user.get_permission_query_conditions", "User": "frappe.core.doctype.user.user.get_permission_query_conditions",
"Note": "frappe.desk.doctype.note.note.get_permission_query_conditions", "Note": "frappe.desk.doctype.note.note.get_permission_query_conditions",
"Kanban Board": "frappe.desk.doctype.kanban_board.kanban_board.get_permission_query_conditions",
"Contact": "frappe.geo.address_and_contact.get_permission_query_conditions_for_contact", "Contact": "frappe.geo.address_and_contact.get_permission_query_conditions_for_contact",
"Address": "frappe.geo.address_and_contact.get_permission_query_conditions_for_address" "Address": "frappe.geo.address_and_contact.get_permission_query_conditions_for_address"
} }
@@ -87,6 +88,7 @@ has_permission = {
"ToDo": "frappe.desk.doctype.todo.todo.has_permission", "ToDo": "frappe.desk.doctype.todo.todo.has_permission",
"User": "frappe.core.doctype.user.user.has_permission", "User": "frappe.core.doctype.user.user.has_permission",
"Note": "frappe.desk.doctype.note.note.has_permission", "Note": "frappe.desk.doctype.note.note.has_permission",
"Kanban Board": "frappe.desk.doctype.kanban_board.kanban_board.has_permission",
"Contact": "frappe.geo.address_and_contact.has_permission", "Contact": "frappe.geo.address_and_contact.has_permission",
"Address": "frappe.geo.address_and_contact.has_permission", "Address": "frappe.geo.address_and_contact.has_permission",
"Communication": "frappe.core.doctype.communication.communication.has_permission", "Communication": "frappe.core.doctype.communication.communication.has_permission",


+ 1
- 1
frappe/public/css/kanban.css Visa fil

@@ -2,7 +2,7 @@
min-height: calc(100vh - 252px); min-height: calc(100vh - 252px);
background-color: #fafbfc; background-color: #fafbfc;
display: flex; display: flex;
overflow: scroll;
overflow: auto;
} }
.kanban .kanban-column { .kanban .kanban-column {
flex: 0 0 230px; flex: 0 0 230px;


+ 1
- 1
frappe/public/js/frappe/form/formatters.js Visa fil

@@ -59,7 +59,7 @@ frappe.form.formatters = {
if(value) { if(value) {
return '<i class="octicon octicon-check" style="margin-right: 3px;"></i>'; return '<i class="octicon octicon-check" style="margin-right: 3px;"></i>';
} else { } else {
return '<i class="fa fa-circle-o text-extra-muted" style="margin-right: 3px; margin-bottom: -2px;"></i>';
return '<i class="fa fa-square-o text-extra-muted" style="margin-right: 3px; margin-bottom: -2px; font-size: 14px;"></i>';
} }
}, },
Link: function(value, docfield, options, doc) { Link: function(value, docfield, options, doc) {


+ 38
- 15
frappe/public/js/frappe/list/list_sidebar.js Visa fil

@@ -130,16 +130,18 @@ frappe.views.ListSidebar = Class.extend({
$('<li role="separator" class="divider"></li>').appendTo($dropdown); $('<li role="separator" class="divider"></li>').appendTo($dropdown);
divider = true; divider = true;
} }
$(`<li><a href="#${route}">${__(board.name)}</a></li>`).appendTo($dropdown);
$(`<li><a href="#${route}">
<span>${__(board.name)}</span>
${board.private ? '<i class="fa fa-lock fa-fw text-warning"></i>' : ''}
</a></li>`).appendTo($dropdown);
}); });


$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) var select_fields = frappe.get_meta(me.doctype)
.fields.filter(function(df) { .fields.filter(function(df) {
return df.fieldtype === 'Select';
}).map(function(df) {
return df.fieldname;
return df.fieldtype === 'Select' &&
df.fieldname !== 'kanban_column';
}); });


var fields = [ var fields = [
@@ -149,45 +151,61 @@ frappe.views.ListSidebar = Class.extend({
label: __('Kanban Board Name'), label: __('Kanban Board Name'),
reqd: 1 reqd: 1
} }
]
];


if(select_fields.length > 0) { if(select_fields.length > 0) {
fields = fields.concat([{ fields = fields.concat([{
fieldtype: 'Select', fieldtype: 'Select',
fieldname: 'field_name', fieldname: 'field_name',
label: __('Columns based on'), label: __('Columns based on'),
options: select_fields.join('\n'),
options: select_fields.map(df => df.label).join('\n'),
default: select_fields[0] default: select_fields[0]
}, },
{ {
fieldtype: 'Check', fieldtype: 'Check',
fieldname: 'custom_column', fieldname: 'custom_column',
label: __('Add Custom Column Field'),
label: __('Custom Column'),
default: 0, default: 0,
onchange: function(e) { onchange: function(e) {
var checked = d.get_value('custom_column'); var checked = d.get_value('custom_column');
if(checked) { if(checked) {
d.get_input('field_name').prop('disabled', true);
$(d.body).find('.frappe-control[data-fieldname="field_name"]').hide();
} else { } else {
d.get_input('field_name').prop('disabled', null);
$(d.body).find('.frappe-control[data-fieldname="field_name"]').show();
} }
} }
}]); }]);
} }


if(me.doctype === 'Task') {
fields[0].description = __('A new Project with this name will be created');
}

if(me.doctype === 'Note') {
fields[0].description = __('This Kanban Board will be private');
}

var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __('New Kanban Board'), title: __('New Kanban Board'),
fields: fields, fields: fields,
primary_action: function() {
var values = d.get_values();
primary_action_label: __('Save'),
primary_action: function(values) {

var custom_column = values.custom_column !== undefined ? var custom_column = values.custom_column !== undefined ?
values.custom_column : 1; values.custom_column : 1;
if(custom_column) {
var field_name = 'kanban_column';
} else {
var field_name =
select_fields
.find(df => df.label === values.field_name)
.fieldname;
}


me.add_custom_column_field(custom_column) me.add_custom_column_field(custom_column)
.then(function(custom_column) { .then(function(custom_column) {
var f = custom_column ?
'kanban_column' : values.field_name;
return me.make_kanban_board(values.board_name, f)
return me.make_kanban_board(values.board_name, field_name)
}) })
.then(function() { .then(function() {
d.hide(); d.hide();
@@ -231,11 +249,16 @@ frappe.views.ListSidebar = Class.extend({
field_name: field_name field_name: field_name
}, },
callback: function(r) { callback: function(r) {
var kb = r.message;
if(kb.filters) {
frappe.provide('frappe.kanban_filters');
frappe.kanban_filters[kb.kanban_board_name] = kb.filters;
}
frappe.set_route( frappe.set_route(
'List', 'List',
me.doctype, me.doctype,
'Kanban', 'Kanban',
r.message.kanban_board_name
kb.kanban_board_name
); );
} }
}); });


+ 6
- 1
frappe/public/js/frappe/ui/dialog.js Visa fil

@@ -79,7 +79,12 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({
.html(label) .html(label)
.click(function() { .click(function() {
me.primary_action_fulfilled = true; me.primary_action_fulfilled = true;
click.apply(me);
// get values and send it
// as first parameter to click callback
// if no values then return
var values = me.get_values();
if(!values) return;
click.apply(me, [values]);
}); });
}, },
make_head: function() { make_head: function() {


+ 2
- 1
frappe/public/js/frappe/views/kanban/kanban_board.js Visa fil

@@ -913,7 +913,8 @@ frappe.provide("frappe.views");
if (df.fieldname === board.field_name && df.fieldtype === "Select") { if (df.fieldname === board.field_name && df.fieldtype === "Select") {
if (action === "add") { if (action === "add") {
//add column_name to Select field's option field //add column_name to Select field's option field
df.options += "\n" + title;
if(!df.options.includes(title))
df.options += "\n" + title;
} else if (action === "delete") { } else if (action === "delete") {
var options = df.options.split("\n"); var options = df.options.split("\n");
var index = options.indexOf(title); var index = options.indexOf(title);


+ 8
- 5
frappe/public/js/frappe/views/kanban/kanban_view.js Visa fil

@@ -41,7 +41,13 @@ frappe.views.KanbanView = frappe.views.ListRenderer.extend({
var kb = this.meta.__kanban_boards.find( var kb = this.meta.__kanban_boards.find(
board => board.name === board_name board => board.name === board_name
); );
frappe.kanban_filters[board_name] = JSON.parse(kb && kb.filters || "[]");
frappe.kanban_filters[board_name] = JSON.parse(kb && kb.filters || '[]');
}
if(typeof frappe.kanban_filters[board_name] === 'string') {
frappe.kanban_filters[board_name] =
JSON.parse(
frappe.kanban_filters[board_name] || '[]'
)
} }
var filters = frappe.kanban_filters[board_name]; var filters = frappe.kanban_filters[board_name];
return filters; return filters;
@@ -49,14 +55,11 @@ frappe.views.KanbanView = frappe.views.ListRenderer.extend({
set_defaults: function() { set_defaults: function() {
this._super(); this._super();
this.no_realtime = true; this.no_realtime = true;
this.show_no_result = false;
this.page_title = __(this.get_board_name()); this.page_title = __(this.get_board_name());
}, },
get_board_name: function() { get_board_name: function() {
var route = frappe.get_route(); var route = frappe.get_route();
if(!route[3] || !this.meta.__kanban_boards.find(b => b.name === route[3])) {
frappe.throw(__(`Kanban Board <b>${route[3] || ''}</b> not found`));
return;
}
return route[3]; return route[3];
}, },
get_header_html: function() { get_header_html: function() {


+ 1
- 1
frappe/public/less/kanban.less Visa fil

@@ -4,7 +4,7 @@
min-height: ~"calc(100vh - 252px)"; min-height: ~"calc(100vh - 252px)";
background-color: @light-bg; background-color: @light-bg;
display: flex; display: flex;
overflow: scroll;
overflow: auto;


.kanban-column { .kanban-column {
flex: 0 0 230px; flex: 0 0 230px;


Laddar…
Avbryt
Spara