* Finish task #47334.Add card view.

This commit is contained in:
liumengyi
2022-01-13 10:35:04 +08:00
parent 69ca909802
commit 1ea0bb4760
4 changed files with 420 additions and 187 deletions
+24 -30
View File
@@ -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;}
+318 -143
View File
@@ -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};
@@ -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 + '"><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()
{
+11 -2
View File
@@ -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
View File
@@ -654,7 +654,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;
@@ -918,12 +923,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;
}
/**
@@ -1616,10 +1669,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
@@ -1725,7 +1778,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);
}
}
@@ -1740,7 +1793,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;
@@ -1768,6 +1821,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);
}
}