diff --git a/module/execution/control.php b/module/execution/control.php
index 8da0e8c60a..e4a890573c 100644
--- a/module/execution/control.php
+++ b/module/execution/control.php
@@ -2638,7 +2638,7 @@ class execution extends control
$this->loadModel('product');
/* Get projects, executions and products. */
- $object = $this->project->getByID($objectID, $this->app->tab == 'project' ? 'project' : 'sprint,stage');
+ $object = $this->project->getByID($objectID, $this->app->tab == 'project' ? 'project' : 'sprint,stage,kanban');
$products = $this->product->getProducts($objectID);
$browseLink = $this->createLink($this->app->tab == 'project' ? 'projectstory' : 'execution', 'story', "objectID=$objectID");
diff --git a/module/execution/css/kanban.css b/module/execution/css/kanban.css
index ec349e5541..dc4cba5734 100644
--- a/module/execution/css/kanban.css
+++ b/module/execution/css/kanban.css
@@ -58,38 +58,8 @@
.region .kanban-lane-items {overflow: auto; padding-bottom: 10px;}
.region .kanban-item {position: relative}
-.region .kanban-item > .kanban-card > .title:hover {color: #2272eb}
-.region .kanban-item > .kanban-card > .info {margin-top: 5px; position: relative;}
-.region .kanban-item > .kanban-card > .info > .pri {height: 14px; border-width: 1px; font-size: 12px; line-height: 12px; min-width: 14px; padding: 0 1px;}
-.region .kanban-item > .kanban-card > .info > .estimate{margin-left: 4px; color: #999; background-color: rgb(255, 255, 255); padding: 3px 0px}
-.region .kanban-item > .kanban-card > .info > .time {margin-left: 4px; font-size: 12px; background: rgb(242, 242, 242)}
-.region .kanban-item > .kanban-card > .info > .user {position: absolute; right: 0; top: -2px}
-.region .kanban-item > .kanban-card > .info > .user > .avatar {display: inline-block; width: 24px; height: 24px; line-height: 24px; margin-right: 0px; margin-left: 2px}
-.region .kanban-item > .kanban-card > .info > .user > span {display: inline-block; color: transparent; width: 2px; height: 2px; background-color: #8990a2; position: sticky; border-radius: 50%; top: 3px; margin: 0 7px;}
-.region .kanban-item > .kanban-card > .info > .user > span:before,
-.region .kanban-item > .kanban-card > .info > .user > span:after {content: ''; display: block; position: absolute; width: 2px; height: 2px; background-color: #8990a2; top: 0; border-radius: 50%}
-.region .kanban-item > .kanban-card > .info > .user > span:before {left: -4px;}
-.region .kanban-item > .kanban-card > .info > .user > span:after {right: -4px;}
-.region .kanban-item > .kanban-card > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
-.region .kanban-item > .kanban-card > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
-.region .kanban-item:hover > .actions {opacity: 1;}
-.region .kanban-item .has-color .title,
-.region .kanban-item .has-color .actions .icon-more-v,
-.region .kanban-item .has-color .info > .label-pri,
-.region .kanban-item .has-color .info > .estimate,
-.region .kanban-item .has-color .info > .user .lable-teamSumCount,
-.region .kanban-item .has-color .info > .label-light {color: #FFFFFF;}
-.region .kanban-item .has-color .info > .label-pri {border-color: #FFFFFF;}
-.region .kanban-item .item-more {position: absolute; right: 6px; top: 0px;}
.kanban-card {height: auto !important; padding: 8px 14px !important}
-.kanban-card > .title {word-break: break-all;}
-.kanban-card > .info {position: relative; margin-top: 5px}
-.kanban-card > .info > .info + .info {margin-left: 8px}
-.kanban-card:hover > .actions {opacity: 1;}
-.kanban-card > .actions {position: absolute; top: 4px; right: 4px; opacity: 0;}
-.kanban-card > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
-.kanban-card > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
.cardcolor {width:40px; height: 14px; float: left; margin-left: 5px; margin-right: 5px; margin-top: 2px; border-radius: 2px;}
#cardcolormenu {padding:2px 2px; width: 100px;}
@@ -109,3 +79,27 @@
#kanbanScaleControl > .input-group-btn:last-child > .btn {border-radius: 0 16px 16px 0; border-left-color: transparent;}
#kanbanScaleControl > .input-group-btn > .btn:hover {border-color: #b8bfce;}
#kanbanActionMenu {top: 24px; right: 28px;}
+
+.kanban + .kanban {margin-top: 15px}
+.kanban-card > .title {display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis}
+.kanban-card > .infos {position: relative; margin-top: 5px}
+.kanban-card > .infos > .info + .info {margin-left: 8px}
+.kanban-card > .infos > .info-id,
+.kanban-card > .infos > .info-deadline,
+.kanban-card > .infos > .info-estimate {font-size: 12px; position: relative; top: 2px}
+.kanban-card > .infos > .label-pri {min-width: 16px; line-height: 14px; height: 16px; padding: 0}
+.kanban-card > .infos > .label-severity {transform: scale(.75)}
+.kanban-card > .infos > .avatar {position: absolute; right: 0; top: 0}
+.kanban-card > .actions {position: absolute; top: 4px; right: 4px; opacity: 0;}
+.kanban-card:hover > .actions {opacity: 1;}
+.kanban-card > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
+.kanban-card > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
+
+#kanban .kanban-card[data-scale-size="4"] {padding: 8px;}
+#kanban .kanban-card[data-scale-size="3"] {padding: 3px 4px;}
+#kanban .kanban-card[data-scale-size="3"] > .title {white-space: normal; max-height: 100%;}
+#kanban .kanban-card[data-scale-size="2"] {padding: 3px 4px; overflow: hidden;}
+#kanban .kanban-card[data-scale-size="2"] > .title {display: inline; line-height: 18px; white-space: normal; max-height: 100%; word-break: break-all;}
+#kanban .kanban-card[data-scale-size="2"] > .infos {display: inline-block; height: 18px; vertical-align: top; margin: 0;}
+#kanban .kanban-card[data-scale-size="2"] > .infos > .info-pri {transform: scale(.8); vertical-align: top;}
+#kanban .kanban-card[data-scale-size="2"] > .infos > .avatar {display: inline-block; transform: scale(.8); position: relative; top: -2px; vertical-align: top; margin-right: -1px;}
diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js
index 9094d0c3cf..5e6800e695 100644
--- a/module/execution/js/kanban.js
+++ b/module/execution/js/kanban.js
@@ -11,7 +11,7 @@ function changeKanbanScaleSize(newScaleSize)
$('#kanbanScaleControl .btn[data-type="+"]').attr('disabled', newScaleSize >= 4 ? 'disabled' : null);
$('#kanbanScaleControl .btn[data-type="-"]').attr('disabled', newScaleSize <= 1 ? 'disabled' : null);
- $('#kanbans').children('.kanban').each(function()
+ $('#kanban').children('.region').children("div[id^='kanban']").each(function()
{
var kanban = $(this).data('zui.kanban');
if(!kanban) return;
@@ -21,6 +21,12 @@ function changeKanbanScaleSize(newScaleSize)
return newScaleSize;
}
+/** Get card height */
+function getCardHeight()
+{
+ return [59, 59, 62, 62, 47][window.kanbanScaleSize];
+}
+
$('#type').change(function()
{
var type = $('#type').val();
@@ -70,7 +76,6 @@ function createColumnMenu(options)
var items = [];
if(privs.includes('setColumn')) items.push({label: kanbanLang.editColumn, icon: 'edit', url: createLink('kanban', 'setColumn', 'columnID=' + column.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}});
if(privs.includes('setWIP')) items.push({label: kanbanLang.setWIP, icon: 'alert', url: createLink('kanban', 'setWIP', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width' : '500px'}});
- if(privs.includes('deleteColumn') && column.$kanbanData.columns.length > 1) items.push({label: kanbanLang.deleteColumn, icon: 'trash', url: createLink('kanban', 'deleteColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}});
var bounds = options.$trigger[0].getBoundingClientRect();
items.$options = {x: bounds.right, y: bounds.top};
@@ -91,7 +96,7 @@ function createColumnCreateMenu(options)
{
if(priv.canCreateStory) items.push({label: storyLang.create, url: $.createLink('story', 'create', 'productID=' + productID)});
if(priv.canBatchCreateStory) items.push({label: executionLang.batchCreateStroy, url: $.createLink('story', 'batchcreate', 'productID=' + productID + '&branch=0&moduleID=0&storyID=0&executionID=' + executionID)});
- if(priv.canLinkStory) items.push({label: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID)});
+ if(priv.canLinkStory) items.push({label: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID + 'objectType=kanban'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}});
if(priv.canLinkStoryByPlane) items.push({label: executionLang.linkStoryByPlan, url: '#linkStoryByPlan', 'attrs' : {'data-toggle': 'modal'}});
}
else if(col.type == 'unconfirmed')
@@ -126,35 +131,327 @@ function handleFinishDrop()
$('.kanban').find('.can-drop-here').removeClass('can-drop-here');
}
-/**
+/* Define drag and drop rules */
+if(!window.kanbanDropRules)
+{
+ window.kanbanDropRules =
+ {
+ story:
+ {
+ backlog: ['ready'],
+ ready: ['backlog'],
+ },
+ bug:
+ {
+ 'unconfirmed': ['confirmed', 'fixing', 'fixed'],
+ 'confirmed': ['fixing', 'fixed'],
+ 'fixing': ['fixed'],
+ 'fixed': ['testing', 'tested', 'fixing'],
+ 'testing': ['tested', 'closed', 'fixing'],
+ 'tested': ['closed', 'fixing'],
+ 'closed': ['fixing'],
+ },
+ task:
+ {
+ 'wait': ['developing', 'developed', 'canceled', 'closed'],
+ 'developing': ['developed', 'pause'],
+ 'developed': ['canceled', 'closed'],
+ 'pause': ['developing'],
+ 'canceled': ['developing'],
+ 'closed': ['developing'],
+ }
+ }
+}
+
+/*
* Find drop columns
* @param {JQuery} $element Drag element
* @param {JQuery} $root Dnd root element
*/
function findDropColumns($element, $root)
{
- var $task = $element;
- var task = $task.data('task');
- //var status = task.status;
- var $col = $task.closest('.kanban-col');
- var col = $col.data();
- var lane = $col.closest('.kanban-lane').data('lane');
- var allStatusCanChange = statusChangeMap[status];
+ var $col = $element.closest('.kanban-col');
+ var col = $col.data();
+ var kanbanID = $root.data('id');
+ var kanbanRules = window.kanbanDropRules ? window.kanbanDropRules[kanbanID] : null;
- hideKanbanAction();
+ if(!kanbanRules) return $root.find('.kanban-lane-col:not([data-type="' + col.type + '"])');
- return $root.find('.kanban-lane-col:not([data-type="EMPTY"],[data-type=""])').filter(function()
+ var colRules = kanbanRules[col.type];
+ var lane = $col.closest('.kanban-lane').data('lane');
+ return $root.find('.kanban-lane-col').filter(function()
{
+ if(!colRules) return false;
+ if(colRules === true) return true;
+
var $newCol = $(this);
var newCol = $newCol.data();
+ if(newCol.id === col.id) return false;
+
var $newLane = $newCol.closest('.kanban-lane');
var newLane = $newLane.data('lane');
-
- $newCol.addClass('can-drop-here');
- return true;
+ var canDropHere = colRules.indexOf(newCol.type) > -1 && newLane.id === lane.id;
+ if(canDropHere) $newCol.addClass('can-drop-here');
+ return canDropHere;
});
}
+/**
+ * Render user avatar
+ * @param {String|{account: string, avatar: string}} user User account or user object
+ * @returns {string}
+ */
+function renderUserAvatar(user, objectType, objectID, size)
+{
+ var avatarSizeClass = 'avatar-' + (size || 'sm');
+ var $noPrivAndNoAssigned = $('
');
+ if(objectType == 'task')
+ {
+ if(!priv.canAssignTask && !user) return $noPrivAndNoAssigned;
+ var link = createLink('task', 'assignto', 'executionID=' + executionID + '&id=' + objectID, '', true);
+ }
+ if(objectType == 'story')
+ {
+ if(!priv.canAssignStory && !user) return $noPrivAndNoAssigned;
+ var link = createLink('story', 'assignto', 'id=' + objectID, '', true);
+ }
+ if(objectType == 'bug')
+ {
+ if(!priv.canAssignBug && !user) return $noPrivAndNoAssigned;
+ var link = createLink('bug', 'assignto', 'id=' + objectID, '', true);
+ }
+
+ if(!user) return $('');
+
+ if(typeof user === 'string') user = {account: user};
+ if(!user.avatar && window.userList && window.userList[user.account]) user = {avatar: userList[user.account].avatar, account: user.account};
+
+ var $noPrivAvatar = $('').avatar({user: user});
+ if(objectType == 'task' && !priv.canAssignTask) return $noPrivAvatar;
+ if(objectType == 'story' && !priv.canAssignStory) return $noPrivAvatar;
+ if(objectType == 'bug' && !priv.canAssignBug) return $noPrivAvatar;
+
+ return $('').avatar({user: user}).attr('data-toggle', 'modal').attr('data-width', '80%');
+}
+
+/**
+ * Render deadline
+ * @param {String|Date} deadline Deadline
+ * @returns {JQuery}
+ */
+function renderDeadline(deadline)
+{
+ if(deadline == '0000-00-00') return;
+
+ var date = $.zui.createDate(deadline);
+ var now = new Date();
+ now.setHours(0);
+ now.setMinutes(0);
+ now.setSeconds(0);
+ now.setMilliseconds(0);
+ var isEarlyThanToday = date.getTime() < now.getTime();
+ var deadlineDate = $.zui.formatDate(date, 'MM-dd');
+
+ return $('').text(deadlineLang + ' ' + deadlineDate).addClass(isEarlyThanToday ? 'text-red' : 'text-muted');
+}
+
+/**
+ * Render story item
+ * @param {Object} item Story item object
+ * @param {JQuery} $item Kanban item element
+ * @param {Object} col Column object
+ * @returns {JQuery} $item Kanban item element
+ */
+function renderStoryItem(item, $item, col)
+{
+ var scaleSize = window.kanbanScaleSize;
+ if(+$item.attr('data-scale-size') !== scaleSize) $item.empty().attr('data-scale-size', scaleSize);
+
+ if(scaleSize <= 3)
+ {
+ var $title = $item.find('.title');
+ if(!$title.length)
+ {
+ $title = $('' + (scaleSize <= 1 ? ' ' : '') + '')
+ .attr('href', $.createLink('story', 'view', 'storyID=' + item.id, '', true)).attr('data-toggle', 'modal').attr('data-width', '80%');
+ $title.appendTo($item);
+ }
+ $title.attr('title', item.title).find('.text').text(item.title);
+ }
+
+ if(scaleSize <= 2)
+ {
+ var idHtml = scaleSize <= 1 ? ('#' + item.id + '') : '';
+ var priHtml = '' + item.pri + '';
+ var hoursHtml = (item.estimate && scaleSize <= 1) ? ('' + item.estimate + 'h') : '';
+ var avatarHtml = renderUserAvatar(item.assignedTo, 'story', item.id);
+ var $infos = $item.find('.infos');
+ if(!$infos.length) $infos = $('');
+ $infos.html([idHtml, priHtml, hoursHtml].join(''));
+
+ $infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml);
+ if(scaleSize <= 1) $infos.appendTo($item);
+ else if(scaleSize === 2) $infos.prependTo($item);
+ else $infos.prependTo($item.find('.title'));
+ }
+ else if(scaleSize === 4)
+ {
+ $item.html(renderUserAvatar(item.assignedTo, 'story', item.id, 'md'));
+ }
+
+ if(scaleSize <= 1)
+ {
+ var $actions = $item.find('.actions');
+ if(!$actions.length && item.menus && item.menus.length)
+ {
+ $actions = $([
+ ''
+ ].join('')).appendTo($item);
+ }
+ }
+
+ return $item.attr('data-type', 'story').addClass('kanban-item-story');
+}
+
+/**
+ * Render bug item
+ * @param {Object} item Bug item object
+ * @param {JQuery} $item Kanban item element
+ * @param {Object} col Column object
+ * @returns {JQuery} $item Kanban item element
+ */
+function renderBugItem(item, $item, col)
+{
+ var scaleSize = window.kanbanScaleSize;
+ if(+$item.attr('data-scale-size') !== scaleSize) $item.empty().attr('data-scale-size', scaleSize);
+
+ if(scaleSize <= 3)
+ {
+ var $title = $item.find('.title');
+ if(!$title.length)
+ {
+ $title = $('' + (scaleSize <= 1 ? ' ' : '') + '')
+ .attr('href', $.createLink('bug', 'view', 'bugID=' + item.id, '', true)).attr('data-toggle', 'modal').attr('data-width', '80%');
+ $title.appendTo($item);
+ }
+ $title.attr('title', item.title).find('.text').text(item.title);
+ }
+
+ if(scaleSize <= 2)
+ {
+ var idHtml = scaleSize <= 1 ? ('#' + item.id + '') : '';
+ var severityHtml = scaleSize <= 1 ? ('') : '';
+ var priHtml = '' + item.pri + '';
+ var avatarHtml = renderUserAvatar(item.assignedTo, 'bug', item.id);
+
+ var $infos = $item.find('.infos');
+ if(!$infos.length) $infos = $('');
+ $infos.html([idHtml, severityHtml, priHtml].join(''));
+ if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline));
+ $infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml);
+
+ if(scaleSize <= 1) $infos.appendTo($item);
+ else if(scaleSize === 2) $infos.prependTo($item);
+ else $infos.prependTo($item.find('.title'));
+ }
+ else if(scaleSize === 4)
+ {
+ $item.html(renderUserAvatar(item.assignedTo, 'bug', item.id, 'md'));
+ }
+
+ if(scaleSize <= 1)
+ {
+ var $actions = $item.find('.actions');
+ if(!$actions.length && item.menus && item.menus.length)
+ {
+ $actions = $([
+ ''
+ ].join('')).appendTo($item);
+ }
+ }
+
+ return $item.attr('data-type', 'bug').addClass('kanban-item-bug');
+}
+
+/**
+ * Render task item
+ * @param {Object} item Task item object
+ * @param {JQuery} $item Kanban item element
+ * @param {Object} col Column object
+ * @returns {JQuery} $item Kanban item element
+ */
+function renderTaskItem(item, $item, col)
+{
+ var scaleSize = window.kanbanScaleSize;
+ if(+$item.attr('data-scale-size') !== scaleSize) $item.empty().attr('data-scale-size', scaleSize);
+
+ if(scaleSize <= 3)
+ {
+ var $title = $item.find('.title');
+ if(!$title.length)
+ {
+ $title = $('' + (scaleSize <= 1 ? ' ' : '') + '')
+ .attr('href', $.createLink('task', 'view', 'taskID=' + item.id, '', true)).attr('data-toggle', 'modal').attr('data-width', '80%');
+ $title.appendTo($item);
+ }
+ $title.attr('title', item.name).find('.text').text(item.name);
+ }
+
+ if(scaleSize <= 2)
+ {
+ var idHtml = scaleSize <= 1 ? ('#' + item.id + '') : '';
+ var priHtml = '' + item.pri + '';
+ var hoursHtml = (item.estimate && scaleSize <= 1) ? ('' + item.estimate + 'h') : '';
+ var avatarHtml = renderUserAvatar(item.assignedTo, 'task', item.id);
+
+ var $infos = $item.find('.infos');
+ if(!$infos.length) $infos = $('');
+ $infos.html([idHtml, priHtml, hoursHtml].join(''));
+ if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline));
+ $infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml);
+
+ if(scaleSize <= 1) $infos.appendTo($item);
+ else if(scaleSize === 2) $infos.prependTo($item);
+ else $infos.prependTo($item.find('.title'));
+ }
+ else if(scaleSize === 4)
+ {
+ $item.html(renderUserAvatar(item.assignedTo, 'task', item.id, 'md'));
+ }
+
+ if(scaleSize <= 1)
+ {
+ var $actions = $item.find('.actions');
+ if(!$actions.length && item.menus && item.menus.length)
+ {
+ $actions = $([
+ ''
+ ].join('')).appendTo($item);
+ }
+ }
+
+ $item.attr('data-type', 'task').addClass('kanban-item-task');
+
+ return $item;
+}
+
+/* Add column renderer */
+addColumnRenderer('story', renderStoryItem);
+addColumnRenderer('bug', renderBugItem);
+addColumnRenderer('task', renderTaskItem);
+
/**
* Render items count of a column.
*/
@@ -196,7 +493,7 @@ function renderHeaderCol($column, column, $header, kanbanData)
/* Render group header. */
var privs = kanbanData.actions;
var columnPrivs = kanbanData.columns[0].actions;
- var $actions = $('');
+ var $actions = $column.children('.actions');
if(privs.includes('sortGroup'))
{
@@ -213,14 +510,10 @@ function renderHeaderCol($column, column, $header, kanbanData)
}
}
- var regionID = $column.closest('.kanban').data('id');
- var groupID = $column.closest('.kanban-board').data('id');
- var laneID = column.$kanbanData.lanes[0].id ? column.$kanbanData.lanes[0].id : 0;
- var columnID = $column.closest('.kanban-col').data('id');
- var printMoreBtn = (columnPrivs.includes('setColumn') || columnPrivs.includes('setWIP') || columnPrivs.includes('deleteColumn'));
+ var printMoreBtn = (columnPrivs.includes('setColumn') || columnPrivs.includes('setWIP'));
/* Render more menu. */
- if((column.type == 'backlog' && hasStoryButton) || (column.type == 'wait' && hasTaskButton) || (column.type == 'unconfirmed' && hasBugButton))
+ if(((column.type == 'backlog' && hasStoryButton) || (column.type == 'wait' && hasTaskButton) || (column.type == 'unconfirmed' && hasBugButton)) && $actions.children('.text-primary').length == 0)
{
$actions.append([
'',
@@ -228,11 +521,10 @@ function renderHeaderCol($column, column, $header, kanbanData)
''
].join(''));
}
- if(printMoreBtn)
+ if(printMoreBtn && $actions.children('.btn').length == 0)
{
$actions.append(' ');
}
- $actions.appendTo($column);
}
/**
@@ -266,28 +558,6 @@ function renderLaneName($lane, lane, $kanban, columns, kanban)
}
}
-/**
- * Adjust add button postion in column
- */
-function adjustAddBtnPosition($kanban)
-{
- if(!$kanban)
- {
- $('.kanban').children('.kanban-board').each(function()
- {
- adjustAddBtnPosition($(this));
- });
- return;
- }
-
- $kanban.find('.kanban-lane-col:not([data-type="EMPTY"])').each(function()
- {
- var $col = $(this);
- var items = $col.children('.kanban-lane-items')[0];
- $col.toggleClass('has-scrollbar', items.scrollHeight > items.clientHeight);
- });
-}
-
/**
* Handle kanban action
*/
@@ -316,94 +586,6 @@ function processMinusBtn()
}
}
-/**
- * The function for rendering kanban item
- */
-function renderKanbanItem(item, $item)
-{
- var $title = $item.children('.title');
- var privs = item.actions;
- var printMoreBtn = (privs.includes('editCard') || privs.includes('archiveCard') || privs.includes('copyCard') || privs.includes('deleteCard') || privs.includes('moveCard') || privs.includes('setCardColor'));
- if(!$title.length)
- {
- if(privs.includes('viewCard')) $title = $('').appendTo($item).attr('href', createLink('kanban', 'viewCard', 'cardID=' + item.id, '', true));
- if(!privs.includes('viewCard')) $title = $('').appendTo($item);
- }
-
- $title.text(item.name).attr('title', item.name);
-
- if(printMoreBtn)
- {
- $(
- [
- ''
- ].join('')).appendTo($item);
- }
-
- var $info = $item.children('.info');
- if(!$info.length) $info = $(
- [
- '',
- '
',
- '
',
- '
',
- '
',
- '
'
- ].join('')).appendTo($item);
-
- $item.data('card', item);
-
- $info.children('.estimate').text(item.estimate + kanbancardLang.lblHour);
- if(item.estimate == 0) $info.children('.estimate').hide();
-
- $info.children('.pri')
- .attr('class', 'pri label-pri label-pri-' + item.pri)
- .text(item.pri);
-
- $item.css('background-color', item.color);
- $item.toggleClass('has-color', item.color != '#fff' && item.color != '');
- $item.find('.info > .label-light').css('background-color', item.color);
-
- var $time = $info.children('.time');
- if(item.end == '0000-00-00' && item.begin == '0000-00-00')
- {
- $time.hide();
- }
- else
- {
- var today = new Date();
- var begin = $.zui.createDate(item.begin);
- var end = $.zui.createDate(item.end);
- var needRemind = (begin.toLocaleDateString() == today.toLocaleDateString() || end.toLocaleDateString() == today.toLocaleDateString());
- if(item.end == '0000-00-00' && item.begin != '0000-00-00')
- {
- $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.beginAB).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + ' ' +kanbancardLang.beginAB).show();
- }
- else if(item.begin == '0000-00-00' && item.end != '0000-00-00')
- {
- $time.text($.zui.formatDate(end, 'MM/dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(end, 'yyyy/MM/dd') + ' ' + kanbancardLang.deadlineAB).show();
- }
- else if(item.begin != '0000-00-00' && item.end != '0000-00-00')
- {
- $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.to + ' ' + $.zui.formatDate(end, 'MM/dd')).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + kanbancardLang.to + $.zui.formatDate(end, 'yyyy/MM/dd')).show();
- }
-
- if(!$item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(210, 50, 61, 0.3)');
- if($item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(255, 255, 255, 0.3)');
- if(!needRemind) $time.css('background-color', 'rgba(0, 0, 0, 0.15)');
- }
-
- /* Display avatars of assignedTo. */
- var assignedTo = item.assignedTo.split(',');
- var $user = $info.children('.user');
- var title = [];
- for(i = 0; i < assignedTo.length; i++) title.push(users[assignedTo[i]]);
- $user.html(renderUsersAvatar(assignedTo, item.id)).attr('title', title);
-}
/* Define menu creators */
window.menuCreators =
{
@@ -429,9 +611,9 @@ function initKanban($kanban)
maxColWidth: 300,
createColumnText: kanbanLang.createColumn,
addItemText: '',
- itemRender: renderKanbanItem,
+ cardHeight: getCardHeight(),
+ cardsPerRow: window.kanbanScaleSize,
onAction: handleKanbanAction,
- onRenderKanban: adjustAddBtnPosition,
onRenderLaneName: renderLaneName,
onRenderHeaderCol: renderHeaderCol,
onRenderCount: renderCount,
@@ -511,13 +693,6 @@ $(function()
$.zui.ContextMenu.show(items, items.$options || {event: event});
});
- /* Adjust the add button position on window resize */
- $(window).on('resize', function(a)
- {
- adjustAddBtnPosition();
- });
-
-
/* Hide contextmenu when page scroll */
$(window).on('scroll', function()
{
diff --git a/module/execution/lang/en.php b/module/execution/lang/en.php
index 40578c1152..5abad60bc4 100644
--- a/module/execution/lang/en.php
+++ b/module/execution/lang/en.php
@@ -10,105 +10,106 @@
* @link http://www.zentao.net
*/
/* Fields. */
-$lang->execution->allExecutions = 'All ' . $lang->execution->common . 's';
-$lang->execution->allExecutionAB = 'Execution List';
-$lang->execution->id = $lang->executionCommon . ' ID';
-$lang->execution->type = $lang->executionCommon . 'Type';
-$lang->execution->name = $lang->executionCommon . 'Name';
-$lang->execution->code = $lang->executionCommon . 'Code';
-$lang->execution->projectName = 'Project';
-$lang->execution->execName = 'Execution Name';
-$lang->execution->execCode = 'Execution Code';
-$lang->execution->execType = 'Execution Type';
-$lang->execution->stage = 'Stage';
-$lang->execution->pri = 'Priority';
-$lang->execution->openedBy = 'OpenedBy';
-$lang->execution->openedDate = 'OpenedDate';
-$lang->execution->closedBy = 'ClosedBy';
-$lang->execution->closedDate = 'ClosedDate';
-$lang->execution->canceledBy = 'CanceledBy';
-$lang->execution->canceledDate = 'CanceledDate';
-$lang->execution->begin = 'Planned Begin';
-$lang->execution->end = 'Planned End';
-$lang->execution->dateRange = 'Duration';
-$lang->execution->realBeganAB = 'Actual Begin';
-$lang->execution->realEndAB = 'Actual End';
-$lang->execution->realBegan = 'Actual Begin';
-$lang->execution->realEnd = 'Actual End';
-$lang->execution->to = 'To';
-$lang->execution->days = ' Days';
-$lang->execution->day = ' Days';
-$lang->execution->workHour = ' Hours';
-$lang->execution->workHourUnit = 'H';
-$lang->execution->totalHours = ' Hours';
-$lang->execution->totalDays = ' Days';
-$lang->execution->status = $lang->executionCommon . 'Status';
-$lang->execution->execStatus = 'Status';
-$lang->execution->subStatus = 'Sub Status';
-$lang->execution->desc = $lang->executionCommon . 'Description';
-$lang->execution->execDesc = 'Description';
-$lang->execution->owner = 'Owner';
-$lang->execution->PO = "{$lang->executionCommon} Owner";
-$lang->execution->PM = "{$lang->executionCommon} Manager";
-$lang->execution->execPM = "Execution Manager";
-$lang->execution->QD = 'Test Manager';
-$lang->execution->RD = 'Release Manager';
-$lang->execution->release = 'Release';
-$lang->execution->acl = 'Access Control';
-$lang->execution->teamname = 'Team Name';
-$lang->execution->updateOrder = 'Rank';
-$lang->execution->order = "Rank {$lang->executionCommon}";
-$lang->execution->orderAB = "Rank";
-$lang->execution->products = "Link {$lang->productCommon}";
-$lang->execution->whitelist = 'Whitelist';
-$lang->execution->addWhitelist = 'Add Whitelist';
-$lang->execution->unbindWhitelist = 'Remove Whitelist';
-$lang->execution->totalEstimate = 'Estimates';
-$lang->execution->totalConsumed = 'Cost';
-$lang->execution->totalLeft = 'Left';
-$lang->execution->progress = ' Progress';
-$lang->execution->hours = 'Estimates: %s, Cost: %s, Left: %s.';
-$lang->execution->viewBug = 'Bugs';
-$lang->execution->noProduct = "No {$lang->productCommon} yet.";
-$lang->execution->createStory = "Create Story";
-$lang->execution->storyTitle = "Story Name";
-$lang->execution->all = "All {$lang->executionCommon}s";
-$lang->execution->undone = 'Unfinished ';
-$lang->execution->unclosed = 'Unclosed';
-$lang->execution->typeDesc = "OPS {$lang->executionCommon} has no {$lang->SRCommon}, Bug, Build, or Test features.";
-$lang->execution->mine = 'Mine: ';
-$lang->execution->involved = 'Mine';
-$lang->execution->other = 'Others';
-$lang->execution->deleted = 'Deleted';
-$lang->execution->delayed = 'Delayed';
-$lang->execution->product = $lang->execution->products;
-$lang->execution->readjustTime = "Adjust {$lang->executionCommon} Begin and End";
-$lang->execution->readjustTask = 'Adjust Task Begin and End';
-$lang->execution->effort = 'Effort';
-$lang->execution->storyEstimate = 'Story Estimate';
-$lang->execution->newEstimate = 'New Estimate';
-$lang->execution->reestimate = 'Reestimate';
-$lang->execution->selectRound = 'Select Round';
-$lang->execution->average = 'Average';
-$lang->execution->relatedMember = 'Team';
-$lang->execution->watermark = 'Exported by ZenTao';
-$lang->execution->burnXUnit = '(Date)';
-$lang->execution->burnYUnit = '(Hours)';
-$lang->execution->waitTasks = 'Waiting Tasks';
-$lang->execution->viewByUser = 'By User';
-$lang->execution->oneProduct = "Only one stage can be linked {$lang->productCommon}";
-$lang->execution->noLinkProduct = "Stage not linked {$lang->productCommon}";
-$lang->execution->recent = 'Recent visits: ';
-$lang->execution->copyNoExecution = 'There are no ' . $lang->executionCommon . 'available to copy.';
-$lang->execution->noTeam = 'No team members at the moment';
-$lang->execution->or = ' or ';
-$lang->execution->selectProject = 'Please select project';
-$lang->execution->unfoldClosed = 'Unfold Closed';
-$lang->execution->editName = 'Edit Name';
-$lang->execution->setWIP = 'WIP Settings';
-$lang->execution->sortColumn = 'Kanban Card Sorting';
-$lang->execution->batchCreateStroy = "Batch create {$lang->SRCommon}";
-$lang->execution->batchCreateTask = 'Batch create task';
+$lang->execution->allExecutions = 'All ' . $lang->execution->common . 's';
+$lang->execution->allExecutionAB = 'Execution List';
+$lang->execution->id = $lang->executionCommon . ' ID';
+$lang->execution->type = $lang->executionCommon . 'Type';
+$lang->execution->name = $lang->executionCommon . 'Name';
+$lang->execution->code = $lang->executionCommon . 'Code';
+$lang->execution->projectName = 'Project';
+$lang->execution->execName = 'Execution Name';
+$lang->execution->execCode = 'Execution Code';
+$lang->execution->execType = 'Execution Type';
+$lang->execution->stage = 'Stage';
+$lang->execution->pri = 'Priority';
+$lang->execution->openedBy = 'OpenedBy';
+$lang->execution->openedDate = 'OpenedDate';
+$lang->execution->closedBy = 'ClosedBy';
+$lang->execution->closedDate = 'ClosedDate';
+$lang->execution->canceledBy = 'CanceledBy';
+$lang->execution->canceledDate = 'CanceledDate';
+$lang->execution->begin = 'Planned Begin';
+$lang->execution->end = 'Planned End';
+$lang->execution->dateRange = 'Duration';
+$lang->execution->realBeganAB = 'Actual Begin';
+$lang->execution->realEndAB = 'Actual End';
+$lang->execution->realBegan = 'Actual Begin';
+$lang->execution->realEnd = 'Actual End';
+$lang->execution->to = 'To';
+$lang->execution->days = ' Days';
+$lang->execution->day = ' Days';
+$lang->execution->workHour = ' Hours';
+$lang->execution->workHourUnit = 'H';
+$lang->execution->totalHours = ' Hours';
+$lang->execution->totalDays = ' Days';
+$lang->execution->status = $lang->executionCommon . 'Status';
+$lang->execution->execStatus = 'Status';
+$lang->execution->subStatus = 'Sub Status';
+$lang->execution->desc = $lang->executionCommon . 'Description';
+$lang->execution->execDesc = 'Description';
+$lang->execution->owner = 'Owner';
+$lang->execution->PO = "{$lang->executionCommon} Owner";
+$lang->execution->PM = "{$lang->executionCommon} Manager";
+$lang->execution->execPM = "Execution Manager";
+$lang->execution->QD = 'Test Manager';
+$lang->execution->RD = 'Release Manager';
+$lang->execution->release = 'Release';
+$lang->execution->acl = 'Access Control';
+$lang->execution->teamname = 'Team Name';
+$lang->execution->updateOrder = 'Rank';
+$lang->execution->order = "Rank {$lang->executionCommon}";
+$lang->execution->orderAB = "Rank";
+$lang->execution->products = "Link {$lang->productCommon}";
+$lang->execution->whitelist = 'Whitelist';
+$lang->execution->addWhitelist = 'Add Whitelist';
+$lang->execution->unbindWhitelist = 'Remove Whitelist';
+$lang->execution->totalEstimate = 'Estimates';
+$lang->execution->totalConsumed = 'Cost';
+$lang->execution->totalLeft = 'Left';
+$lang->execution->progress = ' Progress';
+$lang->execution->hours = 'Estimates: %s, Cost: %s, Left: %s.';
+$lang->execution->viewBug = 'Bugs';
+$lang->execution->noProduct = "No {$lang->productCommon} yet.";
+$lang->execution->createStory = "Create Story";
+$lang->execution->storyTitle = "Story Name";
+$lang->execution->all = "All {$lang->executionCommon}s";
+$lang->execution->undone = 'Unfinished ';
+$lang->execution->unclosed = 'Unclosed';
+$lang->execution->typeDesc = "OPS {$lang->executionCommon} has no {$lang->SRCommon}, Bug, Build, or Test features.";
+$lang->execution->mine = 'Mine: ';
+$lang->execution->involved = 'Mine';
+$lang->execution->other = 'Others';
+$lang->execution->deleted = 'Deleted';
+$lang->execution->delayed = 'Delayed';
+$lang->execution->product = $lang->execution->products;
+$lang->execution->readjustTime = "Adjust {$lang->executionCommon} Begin and End";
+$lang->execution->readjustTask = 'Adjust Task Begin and End';
+$lang->execution->effort = 'Effort';
+$lang->execution->storyEstimate = 'Story Estimate';
+$lang->execution->newEstimate = 'New Estimate';
+$lang->execution->reestimate = 'Reestimate';
+$lang->execution->selectRound = 'Select Round';
+$lang->execution->average = 'Average';
+$lang->execution->relatedMember = 'Team';
+$lang->execution->watermark = 'Exported by ZenTao';
+$lang->execution->burnXUnit = '(Date)';
+$lang->execution->burnYUnit = '(Hours)';
+$lang->execution->waitTasks = 'Waiting Tasks';
+$lang->execution->viewByUser = 'By User';
+$lang->execution->oneProduct = "Only one stage can be linked {$lang->productCommon}";
+$lang->execution->noLinkProduct = "Stage not linked {$lang->productCommon}";
+$lang->execution->recent = 'Recent visits: ';
+$lang->execution->copyNoExecution = 'There are no ' . $lang->executionCommon . 'available to copy.';
+$lang->execution->noTeam = 'No team members at the moment';
+$lang->execution->or = ' or ';
+$lang->execution->selectProject = 'Please select project';
+$lang->execution->unfoldClosed = 'Unfold Closed';
+$lang->execution->editName = 'Edit Name';
+$lang->execution->setWIP = 'WIP Settings';
+$lang->execution->sortColumn = 'Kanban Card Sorting';
+$lang->execution->batchCreateStroy = "Batch create {$lang->SRCommon}";
+$lang->execution->batchCreateTask = 'Batch create task';
+$lang->execution->kanbanNoLinkProduct = "Kanban not linked {$lang->productCommon}";
/* Fields of zt_team. */
$lang->execution->root = 'Root';
diff --git a/module/execution/lang/zh-cn.php b/module/execution/lang/zh-cn.php
index f33c35aeac..f4bd6fc8a3 100644
--- a/module/execution/lang/zh-cn.php
+++ b/module/execution/lang/zh-cn.php
@@ -10,105 +10,106 @@
* @link http://www.zentao.net
*/
/* 字段列表。*/
-$lang->execution->allExecutions = '所有' . $lang->execution->common;
-$lang->execution->allExecutionAB = "{$lang->execution->common}列表";
-$lang->execution->id = $lang->executionCommon . '编号';
-$lang->execution->type = $lang->executionCommon . '类型';
-$lang->execution->name = $lang->executionCommon . '名称';
-$lang->execution->code = $lang->executionCommon . '代号';
-$lang->execution->projectName = '所属项目';
-$lang->execution->execName = "{$lang->execution->common}名称";
-$lang->execution->execCode = "{$lang->execution->common}代号";
-$lang->execution->execType = "{$lang->execution->common}类型";
-$lang->execution->stage = '阶段';
-$lang->execution->pri = '优先级';
-$lang->execution->openedBy = '由谁创建';
-$lang->execution->openedDate = '创建日期';
-$lang->execution->closedBy = '由谁关闭';
-$lang->execution->closedDate = '关闭日期';
-$lang->execution->canceledBy = '由谁取消';
-$lang->execution->canceledDate = '取消日期';
-$lang->execution->begin = '计划开始';
-$lang->execution->end = '计划完成';
-$lang->execution->dateRange = '起始日期';
-$lang->execution->realBeganAB = '实际开始';
-$lang->execution->realEndAB = '实际完成';
-$lang->execution->realBegan = '实际开始日期';
-$lang->execution->realEnd = '实际完成日期';
-$lang->execution->to = '至';
-$lang->execution->days = '可用工作日';
-$lang->execution->day = '天';
-$lang->execution->workHour = '工时';
-$lang->execution->workHourUnit = 'h';
-$lang->execution->totalHours = '可用工时';
-$lang->execution->totalDays = '可用工日';
-$lang->execution->status = $lang->executionCommon . '状态';
-$lang->execution->execStatus = "{$lang->execution->common}状态";
-$lang->execution->subStatus = '子状态';
-$lang->execution->desc = $lang->executionCommon . '描述';
-$lang->execution->execDesc = "{$lang->execution->common}描述";
-$lang->execution->owner = '负责人';
-$lang->execution->PO = $lang->productCommon . '负责人';
-$lang->execution->PM = $lang->executionCommon . '负责人';
-$lang->execution->execPM = "{$lang->execution->common}负责人";
-$lang->execution->QD = '测试负责人';
-$lang->execution->RD = '发布负责人';
-$lang->execution->release = '发布';
-$lang->execution->acl = '访问控制';
-$lang->execution->teamname = '团队名称';
-$lang->execution->updateOrder = '排序';
-$lang->execution->order = $lang->executionCommon . '排序';
-$lang->execution->orderAB = '排序';
-$lang->execution->products = '相关' . $lang->productCommon;
-$lang->execution->whitelist = '白名单';
-$lang->execution->addWhitelist = '添加白名单';
-$lang->execution->unbindWhitelist = '删除白名单';
-$lang->execution->totalEstimate = '预计';
-$lang->execution->totalConsumed = '消耗';
-$lang->execution->totalLeft = '剩余';
-$lang->execution->progress = '进度';
-$lang->execution->hours = '预计 %s 消耗 %s 剩余 %s';
-$lang->execution->viewBug = '查看bug';
-$lang->execution->noProduct = "无{$lang->executionCommon}";
-$lang->execution->createStory = "提{$lang->SRCommon}";
-$lang->execution->storyTitle = "{$lang->SRCommon}名称";
-$lang->execution->all = '所有';
-$lang->execution->undone = '未完成';
-$lang->execution->unclosed = '未关闭';
-$lang->execution->typeDesc = "运维{$lang->executionCommon}没有{$lang->SRCommon}、bug、版本、测试功能。";
-$lang->execution->mine = '我负责:';
-$lang->execution->involved = '我参与';
-$lang->execution->other = '其他';
-$lang->execution->deleted = '已删除';
-$lang->execution->delayed = '已延期';
-$lang->execution->product = $lang->execution->products;
-$lang->execution->readjustTime = "调整{$lang->executionCommon}起止时间";
-$lang->execution->readjustTask = '顺延任务的起止时间';
-$lang->execution->effort = '日志';
-$lang->execution->storyEstimate = '需求估算';
-$lang->execution->newEstimate = '新一轮估算';
-$lang->execution->reestimate = '重新估算';
-$lang->execution->selectRound = '选择轮次';
-$lang->execution->average = '平均值';
-$lang->execution->relatedMember = '相关成员';
-$lang->execution->watermark = '由禅道导出';
-$lang->execution->burnXUnit = '(日期)';
-$lang->execution->burnYUnit = '(工时)';
-$lang->execution->waitTasks = '待处理';
-$lang->execution->viewByUser = '按用户查看';
-$lang->execution->oneProduct = "阶段只能关联一个{$lang->productCommon}";
-$lang->execution->noLinkProduct = "阶段没有关联{$lang->productCommon}";
-$lang->execution->recent = '近期访问:';
-$lang->execution->copyNoExecution = '没有可用的' . $lang->executionCommon . '来复制';
-$lang->execution->noTeam = '暂时没有团队成员';
-$lang->execution->or = '或';
-$lang->execution->selectProject = '请选择项目';
-$lang->execution->unfoldClosed = '展开已结束';
-$lang->execution->editName = '编辑名称';
-$lang->execution->setWIP = '在制品数量设置(WIP)';
-$lang->execution->sortColumn = '看板列卡片排序';
-$lang->execution->batchCreateStroy = "批量新建{$lang->SRCommon}";
-$lang->execution->batchCreateTask = '批量建任务';
+$lang->execution->allExecutions = '所有' . $lang->execution->common;
+$lang->execution->allExecutionAB = "{$lang->execution->common}列表";
+$lang->execution->id = $lang->executionCommon . '编号';
+$lang->execution->type = $lang->executionCommon . '类型';
+$lang->execution->name = $lang->executionCommon . '名称';
+$lang->execution->code = $lang->executionCommon . '代号';
+$lang->execution->projectName = '所属项目';
+$lang->execution->execName = "{$lang->execution->common}名称";
+$lang->execution->execCode = "{$lang->execution->common}代号";
+$lang->execution->execType = "{$lang->execution->common}类型";
+$lang->execution->stage = '阶段';
+$lang->execution->pri = '优先级';
+$lang->execution->openedBy = '由谁创建';
+$lang->execution->openedDate = '创建日期';
+$lang->execution->closedBy = '由谁关闭';
+$lang->execution->closedDate = '关闭日期';
+$lang->execution->canceledBy = '由谁取消';
+$lang->execution->canceledDate = '取消日期';
+$lang->execution->begin = '计划开始';
+$lang->execution->end = '计划完成';
+$lang->execution->dateRange = '起始日期';
+$lang->execution->realBeganAB = '实际开始';
+$lang->execution->realEndAB = '实际完成';
+$lang->execution->realBegan = '实际开始日期';
+$lang->execution->realEnd = '实际完成日期';
+$lang->execution->to = '至';
+$lang->execution->days = '可用工作日';
+$lang->execution->day = '天';
+$lang->execution->workHour = '工时';
+$lang->execution->workHourUnit = 'h';
+$lang->execution->totalHours = '可用工时';
+$lang->execution->totalDays = '可用工日';
+$lang->execution->status = $lang->executionCommon . '状态';
+$lang->execution->execStatus = "{$lang->execution->common}状态";
+$lang->execution->subStatus = '子状态';
+$lang->execution->desc = $lang->executionCommon . '描述';
+$lang->execution->execDesc = "{$lang->execution->common}描述";
+$lang->execution->owner = '负责人';
+$lang->execution->PO = $lang->productCommon . '负责人';
+$lang->execution->PM = $lang->executionCommon . '负责人';
+$lang->execution->execPM = "{$lang->execution->common}负责人";
+$lang->execution->QD = '测试负责人';
+$lang->execution->RD = '发布负责人';
+$lang->execution->release = '发布';
+$lang->execution->acl = '访问控制';
+$lang->execution->teamname = '团队名称';
+$lang->execution->updateOrder = '排序';
+$lang->execution->order = $lang->executionCommon . '排序';
+$lang->execution->orderAB = '排序';
+$lang->execution->products = '相关' . $lang->productCommon;
+$lang->execution->whitelist = '白名单';
+$lang->execution->addWhitelist = '添加白名单';
+$lang->execution->unbindWhitelist = '删除白名单';
+$lang->execution->totalEstimate = '预计';
+$lang->execution->totalConsumed = '消耗';
+$lang->execution->totalLeft = '剩余';
+$lang->execution->progress = '进度';
+$lang->execution->hours = '预计 %s 消耗 %s 剩余 %s';
+$lang->execution->viewBug = '查看bug';
+$lang->execution->noProduct = "无{$lang->executionCommon}";
+$lang->execution->createStory = "提{$lang->SRCommon}";
+$lang->execution->storyTitle = "{$lang->SRCommon}名称";
+$lang->execution->all = '所有';
+$lang->execution->undone = '未完成';
+$lang->execution->unclosed = '未关闭';
+$lang->execution->typeDesc = "运维{$lang->executionCommon}没有{$lang->SRCommon}、bug、版本、测试功能。";
+$lang->execution->mine = '我负责:';
+$lang->execution->involved = '我参与';
+$lang->execution->other = '其他';
+$lang->execution->deleted = '已删除';
+$lang->execution->delayed = '已延期';
+$lang->execution->product = $lang->execution->products;
+$lang->execution->readjustTime = "调整{$lang->executionCommon}起止时间";
+$lang->execution->readjustTask = '顺延任务的起止时间';
+$lang->execution->effort = '日志';
+$lang->execution->storyEstimate = '需求估算';
+$lang->execution->newEstimate = '新一轮估算';
+$lang->execution->reestimate = '重新估算';
+$lang->execution->selectRound = '选择轮次';
+$lang->execution->average = '平均值';
+$lang->execution->relatedMember = '相关成员';
+$lang->execution->watermark = '由禅道导出';
+$lang->execution->burnXUnit = '(日期)';
+$lang->execution->burnYUnit = '(工时)';
+$lang->execution->waitTasks = '待处理';
+$lang->execution->viewByUser = '按用户查看';
+$lang->execution->oneProduct = "阶段只能关联一个{$lang->productCommon}";
+$lang->execution->noLinkProduct = "阶段没有关联{$lang->productCommon}";
+$lang->execution->recent = '近期访问:';
+$lang->execution->copyNoExecution = '没有可用的' . $lang->executionCommon . '来复制';
+$lang->execution->noTeam = '暂时没有团队成员';
+$lang->execution->or = '或';
+$lang->execution->selectProject = '请选择项目';
+$lang->execution->unfoldClosed = '展开已结束';
+$lang->execution->editName = '编辑名称';
+$lang->execution->setWIP = '在制品数量设置(WIP)';
+$lang->execution->sortColumn = '看板列卡片排序';
+$lang->execution->batchCreateStroy = "批量新建{$lang->SRCommon}";
+$lang->execution->batchCreateTask = '批量建任务';
+$lang->execution->kanbanNoLinkProduct = "看板没有关联{$lang->productCommon}";
/* Fields of zt_team. */
$lang->execution->root = '源ID';
diff --git a/module/execution/model.php b/module/execution/model.php
index fd0ac79003..1c73027ff7 100644
--- a/module/execution/model.php
+++ b/module/execution/model.php
@@ -312,6 +312,12 @@ class executionModel extends model
return false;
}
+ if($type == 'kanban' and empty($this->post->products[0]))
+ {
+ dao::$errors['message'][] = $this->lang->execution->kanbanNoLinkProduct;
+ return false;
+ }
+
$this->config->execution->create->requiredFields .= ',project';
}
diff --git a/module/execution/view/kanban.html.php b/module/execution/view/kanban.html.php
index babdc60730..594e7bcf7a 100644
--- a/module/execution/view/kanban.html.php
+++ b/module/execution/view/kanban.html.php
@@ -62,6 +62,17 @@ js::set('priv',
'canBatchCreateStory' => $canBatchCreateStory,
'canLinkStory' => $canLinkStory,
'canLinkStoryByPlane' => $canLinkStoryByPlane,
+ 'canAssignTask' => common::hasPriv('task', 'assignto'),
+ 'canAssignStory' => common::hasPriv('story', 'assignto'),
+ 'canFinishTask' => common::hasPriv('task', 'finish'),
+ 'canPauseTask' => common::hasPriv('task', 'pause'),
+ 'canCancelTask' => common::hasPriv('task', 'cancel'),
+ 'canCloseTask' => common::hasPriv('task', 'close'),
+ 'canActivateTask' => common::hasPriv('task', 'activate'),
+ 'canStartTask' => common::hasPriv('task', 'start'),
+ 'canAssignBug' => common::hasPriv('bug', 'assignto'),
+ 'canConfirmBug' => common::hasPriv('bug', 'confirmBug'),
+ 'canActivateBug' => common::hasPriv('bug', 'activate')
)
);
js::set('hasStoryButton', $hasStoryButton);
@@ -119,8 +130,6 @@ js::set('hasTaskButton', $hasTaskButton);
$actions .= "";
}
- $actions .= "";
-
echo $actions;
?>
diff --git a/module/kanban/model.php b/module/kanban/model.php
index 2c986eb41d..25a5f3a914 100644
--- a/module/kanban/model.php
+++ b/module/kanban/model.php
@@ -655,7 +655,12 @@ class kanbanModel extends model
$lanes = zget($laneGroup, $group->id, array());
if(!$lanes) continue;
- foreach($lanes as $lane) $lane->items = array();
+ foreach($lanes as $lane)
+ {
+ $this->refreshCards($lane);
+ $lane->items = isset($cardGroup[$lane->id]) ? $cardGroup[$lane->id] : array();
+ $lane->defaultCardType = $lane->type;
+ }
$group->columns = zget($columnGroup, $group->id, array());
$group->lanes = $lanes;
@@ -919,12 +924,60 @@ class kanbanModel extends model
*/
public function getCardGroupByExecution($executionID, $browseType = 'all', $orderBy = 'id_asc')
{
- $cards = array();
- if($browseType == 'all' or $browseType == 'story') $cards['story'] = $this->loadModel('story')->getExecutionStories($executionID, 0, 0, $orderBy);
- if($browseType == 'all' or $browseType == 'bug') $cards['bug'] = $this->loadModel('bug')->getExecutionBugs($executionID, 0, 0, '', 0, $orderBy);
- if($browseType == 'all' or $browseType == 'task') $cards['task'] = $this->loadModel('execution')->getKanbanTasks($executionID, $orderBy);
+ $cards = $this->dao->select('t1.*, t2.type as columnType')
+ ->from(TABLE_KANBANCELL)->alias('t1')
+ ->leftJoin(TABLE_KANBANCOLUMN)->alias('t2')->on('t1.column=t2.id')
+ ->where('t1.kanban')->eq($executionID)
+ ->beginIF($browseType != 'all')->andWhere('t1.type')->eq($browseType)->fi()
+ ->orderby($orderBy)
+ ->fetchgroup('lane', 'column');
- return $cards;
+ /* Get group objects. */
+ if($browseType == 'all' or $browseType == 'story') $objectGroup['story'] = $this->loadModel('story')->getExecutionStories($executionID);
+ if($browseType == 'all' or $browseType == 'bug') $objectGroup['bug'] = $this->loadModel('bug')->getExecutionBugs($executionID);
+ if($browseType == 'all' or $browseType == 'task') $objectGroup['task'] = $this->loadModel('execution')->getKanbanTasks($executionID, "id");
+
+ $cardGroup = array();
+
+ foreach($cards as $laneID => $cells)
+ {
+ foreach($cells as $columnID => $cell)
+ {
+ $cardIdList = array_filter(explode(',', $cell->cards));
+ $cardOrder = 1;
+ foreach($cardIdList as $cardID)
+ {
+ $cardData = array();
+ $objects = zget($objectGroup, $cell->type, array());
+ $object = zget($objects, $cardID, array());
+
+ if(empty($object)) continue;
+
+ $cardData['id'] = $object->id;
+ $cardData['order'] = $cardOrder++;
+ $cardData['pri'] = $object->pri ? $object->pri : '';
+ $cardData['estimate'] = $cell->type == 'bug' ? '' : $object->estimate;
+ $cardData['assignedTo'] = $object->assignedTo;
+ $cardData['deadline'] = $cell->type == 'story' ? '' : $object->deadline;
+ $cardData['severity'] = $cell->type == 'bug' ? $object->severity : '';
+ $cardData['acl'] = 'open';
+ $cardData['lane'] = $laneID;
+ $cardData['column'] = $cell->column;
+
+ if($cell->type == 'task')
+ {
+ $cardData['name'] = $object->name;
+ }
+ else
+ {
+ $cardData['title'] = $object->title;
+ }
+ $cardGroup[$laneID][$cell->columnType][] = $cardData;
+ }
+ }
+ }
+
+ return $cardGroup;
}
/**
@@ -1624,10 +1677,10 @@ class kanbanModel extends model
/**
* Add kanban cell for new lane.
- *
- * @param int $kanbanID
- * @param int $laneID
- * @param int $colID
+ *
+ * @param int $kanbanID
+ * @param int $laneID
+ * @param int $colID
* @param string $type story|task|bug|card
* @access public
* @return void
@@ -1733,7 +1786,7 @@ class kanbanModel extends model
$this->dao->insert(TABLE_KANBANLANE)->data($lane)->autoCheck()->exec();
if(dao::isError()) return false;
- $this->createRDColumn($regionID, $groupID, $this->dao->lastInsertId(), $type);
+ $this->createRDColumn($regionID, $groupID, $this->dao->lastInsertId(), $type, $executionID);
}
}
@@ -1748,7 +1801,7 @@ class kanbanModel extends model
* @access public
* @return bool
*/
- public function createRDColumn($regionID, $groupID, $laneID, $laneType)
+ public function createRDColumn($regionID, $groupID, $laneID, $laneType, $executionID)
{
$devColumnID = $testColumnID = $resolvingColumnID = 0;
if($laneType == 'story') $columnList = $this->lang->kanban->storyColumn;
@@ -1776,6 +1829,8 @@ class kanbanModel extends model
if($type == 'develop') $devColumnID = $this->dao->lastInsertId();
if($type == 'test') $testColumnID = $this->dao->lastInsertId();
if($type == 'resolving') $resolvingColumnID = $this->dao->lastInsertId();
+
+ $this->addKanbanCell($executionID, $laneID, $this->dao->lastInsertId(), $laneType);
}
}