Merge branch 'sprint/182_liumengyi_kanban' into 'kanban'
* Finish task #47334.Add card view. See merge request easycorp/zentaopms!1372
This commit is contained in:
@@ -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");
|
||||
|
||||
|
||||
@@ -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;}
|
||||
|
||||
+319
-144
@@ -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 = $('<div class="avatar has-text ' + avatarSizeClass + ' avatar-circle" title="' + noAssigned + '" style="background: #ccc"><i class="icon icon-person"></i></div>');
|
||||
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 $('<a class="avatar has-text ' + avatarSizeClass + ' avatar-circle iframe" title="' + noAssigned + '" style="background: #ccc" href="' + link + '" data-toggle="modal" data-width="80%"><i class="icon icon-person"></i></a>');
|
||||
|
||||
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 = $('<div class="avatar has-text ' + avatarSizeClass + ' avatar-circle" />').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 $('<a class="avatar has-text ' + avatarSizeClass + ' avatar-circle iframe" title="' + user.account + '" href="' + link + '"/>').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 $('<span class="info info-deadline"/>').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 = $('<a class="title iframe" data-width="95%">' + (scaleSize <= 1 ? '<i class="icon icon-lightbulb text-muted"></i> ' : '') + '<span class="text"></span></a>')
|
||||
.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 ? ('<span class="info info-id text-muted">#' + item.id + '</span>') : '';
|
||||
var priHtml = '<span class="info info-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>';
|
||||
var hoursHtml = (item.estimate && scaleSize <= 1) ? ('<span class="info info-estimate text-muted">' + item.estimate + 'h</span>') : '';
|
||||
var avatarHtml = renderUserAvatar(item.assignedTo, 'story', item.id);
|
||||
var $infos = $item.find('.infos');
|
||||
if(!$infos.length) $infos = $('<div class="infos"></div>');
|
||||
$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 = $([
|
||||
'<div class="actions">',
|
||||
'<a data-contextmenu="story" data-col="' + col.type + '">',
|
||||
'<i class="icon icon-ellipsis-v"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].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 = $('<a class="title iframe" data-width="95%">' + (scaleSize <= 1 ? '<i class="icon icon-bug text-muted"></i> ' : '') + '<span class="text"></span></a>')
|
||||
.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 ? ('<span class="info info-id text-muted">#' + item.id + '</span>') : '';
|
||||
var severityHtml = scaleSize <= 1 ? ('<span class="info info-severity label-severity" data-severity="' + item.severity + '" title="' + item.severity + '"></span>') : '';
|
||||
var priHtml = '<span class="info info-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>';
|
||||
var avatarHtml = renderUserAvatar(item.assignedTo, 'bug', item.id);
|
||||
|
||||
var $infos = $item.find('.infos');
|
||||
if(!$infos.length) $infos = $('<div class="infos"></div>');
|
||||
$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 = $([
|
||||
'<div class="actions">',
|
||||
'<a data-contextmenu="bug" data-col="' + col.type + '">',
|
||||
'<i class="icon icon-ellipsis-v"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].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 = $('<a class="title iframe" data-width="95%">' + (scaleSize <= 1 ? '<i class="icon icon-checked text-muted"></i> ' : '') + '<span class="text"></span></a>')
|
||||
.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 ? ('<span class="info info-id text-muted">#' + item.id + '</span>') : '';
|
||||
var priHtml = '<span class="info info-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>';
|
||||
var hoursHtml = (item.estimate && scaleSize <= 1) ? ('<span class="info info-estimate text-muted">' + item.estimate + 'h</span>') : '';
|
||||
var avatarHtml = renderUserAvatar(item.assignedTo, 'task', item.id);
|
||||
|
||||
var $infos = $item.find('.infos');
|
||||
if(!$infos.length) $infos = $('<div class="infos"></div>');
|
||||
$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 = $([
|
||||
'<div class="actions">',
|
||||
'<a data-contextmenu="task" data-col="' + col.type + '">',
|
||||
'<i class="icon icon-ellipsis-v"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].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 = $('<div class="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([
|
||||
'<a data-contextmenu="columnCreate" data-type="' + column.type + '" data-kanban="' + kanban.id + '" data-parent="' + (column.parentType || '') + '" class="text-primary">',
|
||||
@@ -228,11 +521,10 @@ function renderHeaderCol($column, column, $header, kanbanData)
|
||||
'</a>'
|
||||
].join(''));
|
||||
}
|
||||
if(printMoreBtn)
|
||||
if(printMoreBtn && $actions.children('.btn').length == 0)
|
||||
{
|
||||
$actions.append(' <button class="btn btn-link action" title="' + kanbanLang.moreAction + '" data-contextmenu="column" data-column="' + column.id + '"><i class="icon icon-ellipsis-v"></i></button>');
|
||||
}
|
||||
$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 = $('<a class="title iframe" data-toggle="modal" data-width="80%"></a>').appendTo($item).attr('href', createLink('kanban', 'viewCard', 'cardID=' + item.id, '', true));
|
||||
if(!privs.includes('viewCard')) $title = $('<p class="title"></p>').appendTo($item);
|
||||
}
|
||||
|
||||
$title.text(item.name).attr('title', item.name);
|
||||
|
||||
if(printMoreBtn)
|
||||
{
|
||||
$(
|
||||
[
|
||||
'<div class="actions" title="' + kanbanLang.more + '">',
|
||||
'<a data-contextmenu="card" data-id="' + item.id + '">',
|
||||
'<i class="icon icon-ellipsis-v"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].join('')).appendTo($item);
|
||||
}
|
||||
|
||||
var $info = $item.children('.info');
|
||||
if(!$info.length) $info = $(
|
||||
[
|
||||
'<div class="info">',
|
||||
'<span class="pri"></span>',
|
||||
'<span class="estimate label label-light"></span>',
|
||||
'<span class="time label label-light"></span>',
|
||||
'<div class="user"></div>',
|
||||
'</div>'
|
||||
].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()
|
||||
{
|
||||
|
||||
+100
-99
@@ -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';
|
||||
|
||||
+100
-99
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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 .= "</ul>";
|
||||
}
|
||||
|
||||
$actions .= "</div>";
|
||||
|
||||
echo $actions;
|
||||
?>
|
||||
</div>
|
||||
|
||||
+67
-12
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user