* optimize UI for kanban and fix drop rules not work. See merge request easycorp/zentaopms!2511
477 lines
16 KiB
JavaScript
477 lines
16 KiB
JavaScript
$(document).on('click', '.task-toggle', function(e)
|
|
{
|
|
var $toggle = $(this);
|
|
var id = $(this).data('id');
|
|
var isCollapsed = $toggle.toggleClass('collapsed').hasClass('collapsed');
|
|
$toggle.closest('[data-ride="table"]').find('tr.parent-' + id).toggle(!isCollapsed);
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
});
|
|
|
|
$(function()
|
|
{
|
|
$('#productplanList tbody tr').each(function()
|
|
{
|
|
var $content = $(this).find('td.content');
|
|
var content = $content.find('div').html();
|
|
if(content.indexOf('<br') >= 0 || content.indexOf('<img') >= 0)
|
|
{
|
|
$content.append("<a href='###' class='more'><i class='icon icon-chevron-double-down'></i></a>");
|
|
}
|
|
});
|
|
|
|
$('#createExecutionButton').on('click', function()
|
|
{
|
|
var projectID = $('#project').val();
|
|
var planID = $('#planID').val();
|
|
if(!projectID)
|
|
{
|
|
alert(projectNotEmpty);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
$.apps.open(createLink('execution', 'create', 'projectID=' + projectID + '&executionID=©ExecutionID=&planID=' + planID + '&confirm=&productID=' + productID), 'project')
|
|
}
|
|
$('#projects').modal('hide');
|
|
});
|
|
|
|
$('.switchButton').click(function()
|
|
{
|
|
var viewType = $(this).attr('data-type');
|
|
$.cookie('viewType', viewType, {expires:config.cookieLife, path:config.webRoot});
|
|
window.location.reload();
|
|
});
|
|
|
|
$('#branch').change(function()
|
|
{
|
|
var branchID = $(this).val();
|
|
var link = createLink('productplan', 'browse', "productID=" + productID + '&branch=' + branchID);
|
|
location.href = link;
|
|
});
|
|
|
|
if(viewType == 'kanban')
|
|
{
|
|
$('#branch_chosen .chosen-single span').prepend('<i class="icon-delay"></i>');
|
|
$('#kanban').kanban(
|
|
{
|
|
data: kanbanData,
|
|
minColWidth: 290,
|
|
maxColWidth: 290,
|
|
maxColHeight: 460,
|
|
minColHeight: 190,
|
|
cardHeight: 80,
|
|
itemRender: renderKanbanItem,
|
|
virtualize: true,
|
|
droppable:
|
|
{
|
|
target: findDropColumns,
|
|
finish: handleFinishDrop
|
|
}
|
|
});
|
|
|
|
resetLaneHeight();
|
|
|
|
$('#kanban').on('scroll', function()
|
|
{
|
|
$.zui.ContextMenu.hide();
|
|
});
|
|
|
|
/* Init contextmenu. */
|
|
$('#kanban').on('click', '[data-contextmenu]', function(event)
|
|
{
|
|
var $trigger = $(this);
|
|
var menuType = $trigger.data('contextmenu');
|
|
var menuCreator = window.menuCreators[menuType];
|
|
if(!menuCreator) return;
|
|
|
|
var options = $.extend({event: event, $trigger: $trigger}, $trigger.data());
|
|
var items = menuCreator(options);
|
|
if(!items || !items.length) return;
|
|
|
|
$.zui.ContextMenu.show(items, items.$options || {event: event});
|
|
});
|
|
}
|
|
|
|
/* Hide contextmenu when page scroll. */
|
|
$(window).on('scroll', function()
|
|
{
|
|
$.zui.ContextMenu.hide();
|
|
});
|
|
});
|
|
|
|
/* Define menu creators. */
|
|
window.menuCreators = {card: createCardMenu};
|
|
|
|
/**
|
|
* Create card menu.
|
|
*
|
|
* @param object options
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
function createCardMenu(options)
|
|
{
|
|
var card = options.$trigger.closest('.kanban-item').data('item');
|
|
var privs = card.actions;
|
|
if(!privs.length) return [];
|
|
|
|
var items = [];
|
|
if(privs.includes('createExecution'))
|
|
{
|
|
var className = '';
|
|
var executionLink = systemMode == 'new' ? '#projects' : createLink('execution', 'create', "projectID=0&executionID=0©ExecutionID=0&plan=" + card.id + "&confirm=no&productID=" + productID);
|
|
var today = new Date();
|
|
var end = $.zui.createDate(card.end);
|
|
if(end.getTime() < today.getTime())
|
|
{
|
|
className = 'disabled';
|
|
}
|
|
else if(card.status == 'done' || card.status == 'closed')
|
|
{
|
|
className = 'disabled';
|
|
}
|
|
else if(product.type != 'normal')
|
|
{
|
|
var branchStatus = branchStatusList[card.branch];
|
|
if(branchStatus == 'closed') className = 'disabled';
|
|
}
|
|
|
|
if(systemMode == 'new')
|
|
{
|
|
items.push({label: productplanLang.createExecution, icon: 'plus', url: executionLink, className: className, attrs: {'data-toggle': 'modal', 'onclick': 'getPlanID(this,' + card.branch + ')', 'data-id': card.id}});
|
|
}
|
|
else
|
|
{
|
|
items.push({label: productplanLang.createExecution, icon: 'plus', url: executionLink, className: className});
|
|
}
|
|
}
|
|
|
|
if(privs.includes('linkStory')) items.push({label: productplanLang.linkStory, icon: 'link', url: createLink('productplan', 'view', "planID=" + card.id + "&type=story&orderBy=id_desc&link=true")});
|
|
if(privs.includes('linkBug')) items.push({label: productplanLang.linkBug, icon: 'bug', url: createLink('productplan', 'view', "planID=" + card.id + "&type=bug&orderBy=id_desc&link=true")});
|
|
if(privs.includes('edit')) items.push({label: productplanLang.edit, icon: 'edit', url: createLink('productplan', 'edit', "planID=" + card.id)});
|
|
if(privs.includes('start')) items.push({label: productplanLang.start, icon: 'start', url: createLink('productplan', 'start', "planID=" + card.id), attrs: {'target': 'hiddenwin'}});
|
|
if(privs.includes('finish')) items.push({label: productplanLang.finish, icon: 'checked', url: createLink('productplan', 'finish', "planID=" + card.id), attrs: {'target': 'hiddenwin'}});
|
|
if(privs.includes('close')) items.push({label: productplanLang.close, icon: 'off', url: createLink('productplan', 'close', "planID=" + card.id, '', true), className: 'iframe', attrs: {'data-toggle': 'modal'}});
|
|
if(privs.includes('activate')) items.push({label: productplanLang.activate, icon: 'magic', url: createLink('productplan', 'activate', "planID=" + card.id), attrs: {'target': 'hiddenwin'}});
|
|
if(privs.includes('delete')) items.push({label: productplanLang.delete, icon: 'trash', url: createLink('productplan', 'delete', "planID=" + card.id), attrs: {'target': 'hiddenwin'}});
|
|
|
|
var bounds = options.$trigger[0].getBoundingClientRect();
|
|
items.$options = {x: bounds.right, y: bounds.top};
|
|
return items;
|
|
}
|
|
|
|
$(document).on('click', 'td.content .more', function(e)
|
|
{
|
|
var $toggle = $(this);
|
|
if($toggle.hasClass('open'))
|
|
{
|
|
$toggle.removeClass('open');
|
|
$toggle.closest('.content').find('div').css('height', '25px');
|
|
$toggle.css('padding-top', 0);
|
|
$toggle.find('i').removeClass('icon-chevron-double-up').addClass('icon-chevron-double-down');
|
|
}
|
|
else
|
|
{
|
|
$toggle.addClass('open');
|
|
$toggle.closest('.content').find('div').css('height', 'auto');
|
|
$toggle.css('padding-top', ($toggle.closest('.content').find('div').height() - $toggle.height()) / 2);
|
|
$toggle.find('i').removeClass('icon-chevron-double-down').addClass('icon-chevron-double-up');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Find drop columns.
|
|
*
|
|
* @param object $element Drag element
|
|
* @param object $root Dnd root element
|
|
* @access public
|
|
* @return object|bool
|
|
*/
|
|
function findDropColumns($element, $root)
|
|
{
|
|
var $col = $element.closest('.kanban-col');
|
|
var col = $col.data();
|
|
var lane = $col.closest('.kanban-lane').data();
|
|
var kanbanRules = window.kanbanDropRules ? window.kanbanDropRules : null;
|
|
|
|
if(!kanbanRules) return $root.find('.kanban-lane[data-id="' + lane.id + '"] .kanban-lane-col:not([data-type="project"],[data-type="' + col.type + '"])');
|
|
|
|
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');
|
|
return colRules.indexOf(newCol.type) > -1 && newLane.id === lane.id;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reset lane height according to window height.
|
|
*/
|
|
function resetLaneHeight()
|
|
{
|
|
if(product.type == 'normal')
|
|
{
|
|
var windowHeight = $(window).height();
|
|
var mainHeader = $('#mainHeader').outerHeight();
|
|
var marginMenu = $('#mainMenu').outerHeight();
|
|
var headerHeight = $('#kanban > .kanban-board > .kanban-header').outerHeight();
|
|
|
|
maxHeight = windowHeight - headerHeight - mainHeader - marginMenu - 80;
|
|
$('.kanban-lane').css('height', maxHeight);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle finish drop card.
|
|
*
|
|
* @param object event
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
function handleFinishDrop(event)
|
|
{
|
|
var $card = $(event.element); // The drag card.
|
|
var $dragCol = $card.closest('.kanban-lane-col');
|
|
var $dropCol = $(event.target);
|
|
|
|
/* Get d-n-d(drag and drop) infos. */
|
|
var card = $card.data('item');
|
|
var fromColType = $dragCol.data('type');
|
|
var toColType = $dropCol.data('type');
|
|
var kanbanID = $card.closest('.kanban').data('id');
|
|
|
|
changeCardColType(card, fromColType, toColType, kanbanID);
|
|
}
|
|
|
|
/**
|
|
* Change column type for a card.
|
|
*
|
|
* @param object card
|
|
* @param string fromColType The column type before change
|
|
* @param string toColType The column type after change
|
|
* @param int kanbanID
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
function changeCardColType(card, fromColType, toColType, kanbanID)
|
|
{
|
|
if(typeof card == 'undefined') return false;
|
|
var objectID = card.id;
|
|
var privs = card.actions;
|
|
var showIframe = false;
|
|
var link = '';
|
|
|
|
if(toColType == 'doing')
|
|
{
|
|
if(fromColType == 'wait' && privs.includes('start'))
|
|
{
|
|
link = createLink('productplan', 'start', 'planID=' + objectID);
|
|
showIframe = false;
|
|
}
|
|
else if((fromColType == 'done' || fromColType == 'closed') && privs.includes('activate'))
|
|
{
|
|
link = createLink('productplan', 'activate', 'planID=' + objectID);
|
|
showIframe = false;
|
|
}
|
|
}
|
|
else if(toColType == 'done')
|
|
{
|
|
if(fromColType == 'doing')
|
|
{
|
|
link = createLink('productplan', 'finish', 'planID=' + objectID);
|
|
showIframe = false;
|
|
}
|
|
}
|
|
else if(toColType == 'closed')
|
|
{
|
|
if(fromColType != 'closed')
|
|
{
|
|
link = createLink('productplan', 'close', 'planID=' + objectID, '', true);
|
|
showIframe = true;
|
|
}
|
|
}
|
|
|
|
if(showIframe)
|
|
{
|
|
var modalTrigger = new $.zui.ModalTrigger({type: 'iframe', width: '70%', url: link});
|
|
modalTrigger.show();
|
|
}
|
|
else if(!showIframe && link)
|
|
{
|
|
hiddenwin.location.href = link;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The function for rendering kanban item.
|
|
*
|
|
* @param object item
|
|
* @param object $item
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
function renderKanbanItem(item, $item)
|
|
{
|
|
var privs = item.actions;
|
|
var printMoreBtn = (privs.includes('createExecution') || privs.includes('linkStory') || privs.includes('linkBug') || privs.includes('edit') || privs.includes('start') || privs.includes('finish') || privs.includes('close') || privs.includes('activate') || privs.includes('delete'));
|
|
|
|
/* Output header information. */
|
|
var $header = $item.children('.header');
|
|
if(!$header.length) $header = $(
|
|
[
|
|
'<div class="header">',
|
|
'</div>'
|
|
].join('')).appendTo($item);
|
|
|
|
var $titleBox = $item.children('.titleBox');
|
|
if(!$titleBox.length) $titleBox = $(
|
|
[
|
|
'<div class="titleBox">',
|
|
'</div>'
|
|
].join('')).appendTo($header);
|
|
|
|
/* Print plan name. */
|
|
var $title = $titleBox.children('.title');
|
|
if(!$title.length)
|
|
{
|
|
if(privs.includes('view')) $title = $('<a class="title"></a>').appendTo($titleBox).attr('href', createLink('productplan', 'view', 'cardID=' + item.id));
|
|
if(!privs.includes('view')) $title = $('<a class="title"></a>').appendTo($titleBox);
|
|
}
|
|
$title.text(item.title).attr('title', item.title);
|
|
|
|
/* Determine whether to print an expired label. */
|
|
var today = new Date();
|
|
var begin = $.zui.createDate(item.begin);
|
|
var end = $.zui.createDate(item.end);
|
|
if(item.delay && (item.status == 'wait' || item.status == 'doing'))
|
|
{
|
|
$expired = $titleBox.children('.expired');
|
|
if(!$expired.length)
|
|
{
|
|
$('<span class="expired label label-danger label-badge">' + productplanLang.expired + '</span>').appendTo($titleBox);
|
|
}
|
|
}
|
|
|
|
if(printMoreBtn)
|
|
{
|
|
$(
|
|
[
|
|
'<div class="actions" title="' + productplanLang.more + '">',
|
|
'<a data-contextmenu="card" data-id="' + item.id + '">',
|
|
'<i class="icon icon-ellipsis-v"></i>',
|
|
'</a>',
|
|
'</div>'
|
|
].join('')).appendTo($header);
|
|
}
|
|
|
|
/* Output plan date information. */
|
|
var $dateBox = $item.children('.dateBox');
|
|
if(!$dateBox.length) $dateBox = $(
|
|
[
|
|
'<div class="dateBox">',
|
|
'<span class="time label label-outline"></span>',
|
|
'</div>'
|
|
].join('')).appendTo($item);
|
|
|
|
var $time = $dateBox.children('.time');
|
|
var beginTimeShort = $.zui.formatDate(begin, 'MM-dd');
|
|
var beginTimeLong = $.zui.formatDate(begin, 'yyyy-MM-dd');
|
|
var endTimeShort = $.zui.formatDate(end, 'MM-dd');
|
|
var endTimeLong = $.zui.formatDate(end, 'yyyy-MM-dd')
|
|
var to = productplanLang.to;
|
|
var undetermined = productplanLang.future;
|
|
var undetermindeDate = '2030-01-01';
|
|
if(item.begin != undetermindeDate && item.end != undetermindeDate)
|
|
{
|
|
$time.text(beginTimeShort + ' ' + to + ' ' + endTimeShort).attr('title', beginTimeLong + to + endTimeLong).show();
|
|
}
|
|
else if(item.begin != undetermindeDate && item.end == undetermindeDate)
|
|
{
|
|
$time.text(beginTimeShort + ' ' + to + ' ' + undetermined).attr('title', beginTimeLong + to).show();
|
|
}
|
|
else if(item.begin == undetermindeDate && item.end != undetermindeDate)
|
|
{
|
|
$time.text(undetermined + ' ' + to + ' ' + endTimeShort).attr('title', to + endTimeLong).show();
|
|
}
|
|
else if(item.begin != '2030-01-01' && item.end == '2030-01-01')
|
|
{
|
|
$time.text($.zui.formatDate(begin, 'MM-dd') + ' ' + productplanLang.to + ' ' + productplanLang.future).attr('title', $.zui.formatDate(begin, 'yyyy-MM-dd') + productplanLang.to).show();
|
|
}
|
|
else if(item.begin == '2030-01-01' && item.end != '2030-01-01')
|
|
{
|
|
$time.text(productplanLang.future + ' ' + productplanLang.to + ' ' + $.zui.formatDate(end, 'MM-dd')).attr('title', $.zui.formatDate(end, 'yyyy-MM-dd') + productplanLang.to).show();
|
|
}
|
|
else
|
|
{
|
|
$time.text(undetermined).attr('title', undetermined).show();
|
|
}
|
|
|
|
/* Output plan desc information. */
|
|
var $desc = $item.children('.desc');
|
|
if(!$desc.length)
|
|
{
|
|
$("<div class='desc c-name'"+ " title='" + item.desc + "'>" + item.desc + '</div>').appendTo($item);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get planID.
|
|
*
|
|
* @param object obj
|
|
* @param int branch
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
function getPlanID(obj, branch)
|
|
{
|
|
var planID = $(obj).attr("data-id");
|
|
$('#planID').val(planID);
|
|
|
|
link = createLink('productplan', 'ajaxGetProjects', 'productID=' + productID + '&branch=' + branch);
|
|
$.get(link, function(projects)
|
|
{
|
|
$('#project').replaceWith(projects);
|
|
$("#project_chosen").remove();
|
|
$("#project").chosen();
|
|
|
|
var projectList = $("#project").val();
|
|
if(!projectList)
|
|
{
|
|
$("#project").attr('disabled', true);
|
|
$("#project").trigger('chosen:updated');
|
|
$(".tips").removeClass('hidden');
|
|
|
|
var locateLink = createLink('product', 'project', 'status=all&productID=' + productID + '&branch=' + branch);
|
|
var locateButton = "<a href=" + locateLink + " class='btn btn-primary' data-app='product'>" + enterProjectList + "</a>";
|
|
$("#projects .btn-primary").replaceWith(locateButton);
|
|
}
|
|
else
|
|
{
|
|
$(".tips").addClass('hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
if(!window.kanbanDropRules)
|
|
{
|
|
window.kanbanDropRules =
|
|
{
|
|
wait: ['doing', 'closed'],
|
|
doing: ['done', 'closed'],
|
|
done: ['doing', 'closed'],
|
|
closed: ['doing']
|
|
}
|
|
}
|