This commit is contained in:
liyuchun
2021-09-07 09:00:13 +08:00
27 changed files with 791 additions and 608 deletions
+3 -3
View File
@@ -243,7 +243,7 @@ class html extends baseHTML
* @access public
* @return string
*/
static public function avatar($user, $size = '', $className = 'avatar-circle', $attrib = '', $tag = 'div')
static public function avatar($user, $size = '', $className = 'avatar-circle', $attrib = '', $tag = 'div', $hueDistance = 43, $saturation = '40%', $lightness = '60%')
{
$userObj = new stdClass();
@@ -273,8 +273,8 @@ class html extends baseHTML
if(!$hasImage)
{
$colorHue = (html::stringToCode($user->account) * 43) % 360;
$style .= "background: hsl($colorHue, 100%, 58%);";
$colorHue = (html::stringToCode($user->account) * $hueDistance) % 360;
$style .= "background: hsl($colorHue, $saturation, $lightness);";
}
if(!empty($style)) $style = "style='$style'";
+305
View File
@@ -0,0 +1,305 @@
<?php if($extView = $this->getExtViewFile(__FILE__)){include $extView; return helper::cd();}?>
<?php css::import($jsRoot . 'zui/kanban/min.css'); ?>
<?php js::import($jsRoot . 'zui/kanban/min.js'); ?>
<style>
#kanbanList .panel-heading {padding: 10px;}
#kanbanList .panel-body {padding: 0 10px 10px;}
#kanbanList .kanban {min-height: 120px;}
#kanbanList .kanban-item {margin-top: 0; border: 1px solid #ebebeb; border-radius: 2px;}
#kanbanList .kanban-item:hover {border: 1px solid #ccc;}
#kanbanList .kanban-item + .kanban-item {margin-top: 10px;}
#kanbanList .kanban-lane-items {padding: 10px;}
#kanbanList .kanban-header,
#kanbanList .kanban-lane {border-bottom: 3px solid #fff;}
#kanbanList .kanban-sub-lane {border-bottom: 0;}
#kanbanList .kanban-sub-lane + .kanban-sub-lane {border-top: 3px solid #fff;}
#kanbanList .kanban-col + .kanban-col {border-left: 3px solid #fff;}
#kanbanList .kanban-header-col {height: 72px; padding: 20px 5px;}
#kanbanList .kanban-header-col > .title {margin: 0; line-height: 32px; height: 32px}
#kanbanList .kanban-header-col > .title > .text {font-weight: bold; max-width: 200px; max-width: calc(100% - 50px);}
#kanbanList .kanban-header-col > .title > .icon, #kanbanList .kanban-header-col > .title > .count {top: -11px}
#kanbanList .kanban-header + .kanban-lane > .kanban-lane-name {margin-top: 0;}
#kanbanList .kanban-header {position: relative; border-bottom-width: 6px;}
#kanbanList .kanban-header:before {position: absolute; content: ' '; left: 0; top: 0; bottom: 0; background-color: #3dc6fd; width: 20px;}
#kanbanList .kanban-item.link-block {padding: 0;}
#kanbanList .kanban-item.link-block > a {padding: 10px; display: block;}
#kanbanList .kanban-item > .title {white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}
#kanbanList .kanban-item.link-block > a {padding: 10px; display: block;}
#kanbanList .kanban-item.has-progress {padding-right: 40px; position: relative;}
#kanbanList .kanban-item.has-progress > .progress-pie {position: absolute; right: 7px; top: 7px}
#kanbanList .kanban-item.has-left-border {border-left: 3px solid #838a9d;}
#kanbanList .kanban-item.has-left-border.border-left-green {border-left-color: #0bd986;}
#kanbanList .kanban-item.has-left-border.border-left-red {border-left-color: #ff5d5d;}
#kanbanList .kanban-item.has-left-border.border-left-blue {border-left-color: #0991ff;}
#kanbanList .kanban-header-col[data-type="doingProject"],
#kanbanList .kanban-header-col[data-type="doingProject"] + .kanban-header-col[data-type="doingExecution"] {padding: 38px 10px 0;}
#kanbanList .kanban-header-col[data-type="doingProject"]:after {content: attr(data-span-text); display: block; position: absolute; z-index: 10; left: 0; right: -100%; right: calc(-100% - 3px); top: 0; line-height: 36px; text-align: center; font-weight: bold; border-bottom: 3px solid #fff; background-color: #ededed;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-lane-items {height: 100%; display: flex; flex-direction: column; justify-content: center;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item {background-color: transparent; border: none; padding: 0; text-align: center;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item:hover {box-shadow: none;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item > .title {white-space: normal;}
#kanbanList .kanban-col[data-type="normalRelease"] .kanban-item {text-align: center;}
</style>
<script>
/**
* Check the given date whether it is earlier than today
* @param {Date|string} date Date or date string
* @returns {boolean}
*/
function isEarlierThanToday(date)
{
if(!window.todayBegin)
{
var now = new Date();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
now.setMilliseconds(0);
window.todayBegin = now.getTime();
}
return $.zui.createDate(date).getTime() < window.todayBegin;
}
/**
* Render product item
* @param {Object} item Product item object
* @param {JQuery} $item Kanban item element
* @param {Object} col Column object
* @returns {JQuery} $item Kanban item element
*/
function renderProductItem(item, $item)
{
var $title = $item.find('.title');
if(!$title.length)
{
if(window.userPrivs.product)
{
$title = $('<a class="title" />')
.attr('href', $.createLink('product', 'browse', 'productID=' + item._id));
}
else
{
$title = $('<div class="title" />');
}
$title.appendTo($item);
}
$title.text(item.name).attr('title', item.name);
return $item;
}
/**
* Render plan item
* @param {Object} item Plan item object
* @param {JQuery} $item Kanban item element
* @param {Object} col Column object
* @returns {JQuery} $item Kanban item element
*/
function renderPlanItem(item, $item)
{
var $title = $item.find('.title');
if(!$title.length)
{
if(window.userPrivs.productplan)
{
$item.addClass('link-block');
$title = $('<a class="title" />')
.attr('href', $.createLink('productplan', 'view', 'planID=' + item._id));
}
else
{
$title = $('<div class="title" />');
}
$title.appendTo($item);
}
$title.text(item.title).attr('title', item.title);
return $item;
}
/**
* Render project item
* @param {Object} item Project item object
* @param {JQuery} $item Kanban item element
* @param {Object} col Column object
* @returns {JQuery} $item Kanban item element
*/
function renderProjectItem(item, $item)
{
var $title = $item.find('.title');
if(!$title.length)
{
if(window.userPrivs.project)
{
$item.addClass('link-block');
$title = $('<a class="title" />')
.attr('href', $.createLink('project', 'index', 'projectID=' + item._id));
}
else
{
$title = $('<div class="title" />');
}
$title.appendTo($item);
}
$title.text(item.name).attr('title', item.name);
if(item.status === 'doing')
{
var progress = item.hours ? Math.round(item.hours.progress || 0) : 0;
var $progress = $item.find('.progress-pie');
if(!$progress.length)
{
$progress = $('<div class="progress-pie" data-doughnut-size="90" data-color="#3CB371" data-width="24" data-height="24" data-back-color="#e8edf3"><div class="progress-info"></div></div>').appendTo($item);
}
$progress.find('.progress-info').text(progress);
$progress.attr('data-value', progress).progressPie();
$item.addClass('has-progress');
}
return $item.addClass('has-left-border')
.toggleClass('border-left-green', item.status === 'doing' && !item.delay)
.toggleClass('border-left-red', item.status === 'doing' && !!item.delay)
.toggleClass('border-left-gray', item.status === 'closed')
.toggleClass('border-left-blue', item.status === 'wait');
}
/**
* Render execution item
* @param {Object} item Execution item object
* @param {JQuery} $item Kanban item element
* @param {Object} col Column object
* @returns {JQuery} $item Kanban item element
*/
function renderExecutionItem(item, $item)
{
var $title = $item.find('.title');
if(!$title.length)
{
if(window.userPrivs.project)
{
$item.addClass('link-block');
$title = $('<a class="title" />')
.attr('href', $.createLink('project', 'index', 'projectID=' + item._id));
}
else
{
$title = $('<div class="title" />');
}
$title.appendTo($item);
}
$title.text(item.name).attr('title', item.name);
var progress = item.progress || (item.hours ? item.hours.progress : undefined);
if(progress === undefined && window.hourList)
{
var hoursInfo = window.hourList[item._id];
progress = Math.round(hoursInfo.progress);
}
if(progress !== undefined)
{
var $progress = $item.find('.progress-pie');
if(!$progress.length)
{
$progress = $('<div class="progress-pie" data-doughnut-size="90" data-color="#3CB371" data-width="24" data-height="24" data-back-color="#e8edf3"><div class="progress-info"></div></div>').appendTo($item);
}
$progress.find('.progress-info').text(progress);
$progress.attr('data-value', progress).progressPie();
}
var isDelay = item.end && isEarlierThanToday(item.end);
return $item.addClass('has-progress has-left-border')
.toggleClass('border-left-green', !isDelay)
.toggleClass('border-left-red', !!isDelay);
}
/**
* Render release item
* @param {Object} item Release item object
* @param {JQuery} $item Kanban item element
* @param {Object} col Column object
* @returns {JQuery} $item Kanban item element
*/
function renderReleaseItem(item, $item)
{
var $title = $item.find('.title');
if(!$title.length)
{
if(window.userPrivs.release)
{
$item.addClass('link-block');
$title = $('<a class="title" />')
.attr('href', $.createLink('release', 'view', 'releaseID=' + item._id));
}
else
{
$title = $('<div class="title" />');
}
$title.appendTo($item);
}
$title.text(item.name).attr('title', item.name);
if(item.marker === '1')
{
if(!$title.find('.icon').length)
{
$title.append('&nbsp;<i class="icon icon-flag text-red"></i>');
}
}
else
{
$title.find('.icon').remove();
}
return $item;
}
/** All build-in columns renderers */
if(!window.columnRenderers) window.columnRenderers =
{
unclosedProduct: renderProductItem,
unexpiredPlan: renderPlanItem,
waitProject: renderProjectItem,
closedProject: renderProjectItem,
doingProject: renderProjectItem,
doingExecution: renderExecutionItem,
normalRelease: renderReleaseItem,
};
/** User privs map */
if(!window.userPrivs) window.userPrivs = {};
/**
* Add column renderer
* @params {string} columnType Column type
* @params {function} renderer Renderer function
*/
function addColumnRenderer(columnType, renderer)
{
if(typeof columnType === 'object') $.extend(window.columnRenderers[columnType], columnType);
else window.columnRenderers[columnType] = renderer;
}
/**
* Render kanban item
* @param {Object} item Kanban item object
* @param {JQuery} $item Kanban item element
* @param {Object} col Column object
* @returns {JQuery} $item Kanban item element
*/
function renderKanbanItem(item, $item, col)
{
var renderer = window.columnRenderers[col.type];
if(renderer) return renderer(item, $item, col);
return $item;
}
/* Set default options to kanban component */
$.extend($.fn.kanban.Constructor.DEFAULTS,
{
readonly: true,
maxColHeight: 260,
laneColClass: 'scrollbar-hover',
itemRender: renderKanbanItem,
useFlex: false,
onRenderHeaderCol: function($col, col)
{
if(col.type === 'doingProject') $col.attr('data-span-text', doingText);
}
});
</script>
+3
View File
@@ -736,6 +736,9 @@ class execution extends control
$stories = $this->story->getExecutionStories($executionID, 0, 0, $sort, $type, $param, 'story', '', $pager);
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
if(!empty($stories)) $stories = $this->story->mergeReviewer($stories);
$users = $this->user->getPairs('noletter');
/* Build the search form. */
+17 -1
View File
@@ -196,7 +196,7 @@
<th title='<?php echo $lang->story->taskCount?>' class='c-count'><?php echo $lang->story->taskCountAB;?></th>
<th title='<?php echo $lang->story->bugCount?>' class='c-count'><?php echo $lang->story->bugCountAB;?></th>
<th title='<?php echo $lang->story->caseCount?>' class='c-count'><?php echo $lang->story->caseCountAB;?></th>
<th class='c-actions-5 text-center {sorter:false}'><?php echo $lang->actions;?></th>
<th class='c-actions-7 text-center {sorter:false}'><?php echo $lang->actions;?></th>
</tr>
</thead>
<tbody id='storyTableList' class='sortable'>
@@ -256,6 +256,22 @@
{
$param = "executionID={$execution->id}&story={$story->id}&moduleID={$story->module}";
$story->reviewer = isset($story->reviewer) ? $story->reviewer : array();
$story->notReview = isset($story->notReview) ? $story->notReview : array();
if(common::hasPriv('story', 'review'))
{
$reviewDisabled = in_array($app->user->account, $story->notReview) and ($story->status == 'draft' or $story->status == 'changed') ? '' : 'disabled';
$story->from = 'execution';
common::printIcon('story', 'review', "story={$story->id}&from=story", $story, 'list', 'search', '', $reviewDisabled, false, "data-group=execution");
}
if(common::hasPriv('story', 'recall'))
{
$recallDisabled = empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) ? '' : 'disabled';
common::printIcon('story', 'recall', "story={$story->id}", $story, 'list', 'back', 'hiddenwin', $recallDisabled, '', '', $lang->story->recall);
}
$lang->task->create = $lang->execution->wbs;
$toTaskDisabled = strpos('draft,closed', $story->status) !== false ? 'disabled' : '';
if(commonModel::isTutorialMode())
+3
View File
@@ -86,18 +86,21 @@ if($config->systemMode == 'new') $lang->my->executionLink = 'Execution Default P
$lang->my->programLinkList = array();
$lang->my->programLinkList['program-browse'] = 'By default, you go to the program list, where you can view all of the programs';
$lang->my->programLinkList['program-project'] = 'By default, you go to the list of items in the most recent program, and you can view all items under the current program';
if($config->systemMode == 'new') $lang->my->programLinkList['program-kanban'] = 'Enter the Program Kanban by default, and check the progress of all Programs';
$lang->my->productLinkList = array();
$lang->my->productLinkList['product-index'] = 'The default access to the product home page, you can understand the company’s overall product status';
$lang->my->productLinkList['product-all'] = 'By default, you go to the product list, where you can view all the products';
$lang->my->productLinkList['product-dashboard'] = 'By default, go to the latest product dashboard to see the current product overview';
$lang->my->productLinkList['product-browse'] = 'By default, go to the list of requirements for the most recent product and see the requirements under the current product';
$lang->my->productLinkList['product-kanban'] = 'Enter the Product Kanban by default, and check the progress of all Products';
global $config;
$lang->my->projectLinkList = array();
$lang->my->projectLinkList['project-browse'] = 'By default, you go to the project list, where you can view all the projects';
$lang->my->projectLinkList['project-execution'] = 'Go to Project-Exection by default. You can check all information in Execution';
$lang->my->projectLinkList['project-index'] = 'By default, go to the most recent project dashboard to see the current project overview';
if($config->systemMode == 'new') $lang->my->projectLinkList['project-kanban'] = 'Enter the Project Kanban by default, and check the progress of all Projects';
$lang->my->executionLinkList = array();
if($config->systemMode == 'new')
+3
View File
@@ -93,18 +93,21 @@ if($config->systemMode == 'new') $lang->my->executionLink = 'Execution Default P
$lang->my->programLinkList = array();
$lang->my->programLinkList['program-browse'] = 'By default, you go to the program list, where you can view all of the programs';
$lang->my->programLinkList['program-project'] = 'By default, you go to the list of items in the most recent program, and you can view all items under the current program';
if($config->systemMode == 'new') $lang->my->programLinkList['program-kanban'] = 'Enter the Program Kanban by default, and check the progress of all Programs';
$lang->my->productLinkList = array();
$lang->my->productLinkList['product-index'] = 'The default access to the product home page, you can understand the company’s overall product status';
$lang->my->productLinkList['product-all'] = 'By default, you go to the product list, where you can view all the products';
$lang->my->productLinkList['product-dashboard'] = 'By default, go to the latest product dashboard to see the current product overview';
$lang->my->productLinkList['product-browse'] = 'By default, go to the list of requirements for the most recent product and see the requirements under the current product';
$lang->my->productLinkList['product-kanban'] = 'Enter the Product Kanban by default, and check the progress of all Products';
global $config;
$lang->my->projectLinkList = array();
$lang->my->projectLinkList['project-browse'] = 'By default, you go to the project list, where you can view all the projects';
$lang->my->projectLinkList['project-execution'] = 'Go to Project-Exection by default. You can check all information in Execution';
$lang->my->projectLinkList['project-index'] = 'By default, go to the most recent project dashboard to see the current project overview';
if($config->systemMode == 'new') $lang->my->projectLinkList['project-kanban'] = 'Enter the Project Kanban by default, and check the progress of all Projects';
$lang->my->executionLinkList = array();
if($config->systemMode == 'new')
+3
View File
@@ -86,18 +86,21 @@ if($config->systemMode == 'new') $lang->my->executionLink = 'Execution Default P
$lang->my->programLinkList = array();
$lang->my->programLinkList['program-browse'] = 'By default, you go to the program list, where you can view all of the programs';
$lang->my->programLinkList['program-project'] = 'By default, you go to the list of items in the most recent program, and you can view all items under the current program';
if($config->systemMode == 'new') $lang->my->programLinkList['program-kanban'] = 'Enter the Program Kanban by default, and check the progress of all Programs';
$lang->my->productLinkList = array();
$lang->my->productLinkList['product-index'] = 'The default access to the product home page, you can understand the company’s overall product status';
$lang->my->productLinkList['product-all'] = 'By default, you go to the product list, where you can view all the products';
$lang->my->productLinkList['product-dashboard'] = 'By default, go to the latest product dashboard to see the current product overview';
$lang->my->productLinkList['product-browse'] = 'By default, go to the list of requirements for the most recent product and see the requirements under the current product';
$lang->my->productLinkList['product-kanban'] = 'Enter the Product Kanban by default, and check the progress of all Products';
global $config;
$lang->my->projectLinkList = array();
$lang->my->projectLinkList['project-browse'] = 'By default, you go to the project list, where you can view all the projects';
$lang->my->projectLinkList['project-execution'] = 'Go to Project-Exection by default. You can check all information in Execution';
$lang->my->projectLinkList['project-index'] = 'By default, go to the most recent project dashboard to see the current project overview';
if($config->systemMode == 'new') $lang->my->projectLinkList['project-kanban'] = 'Enter the Project Kanban by default, and check the progress of all Projects';
$lang->my->executionLinkList = array();
if($config->systemMode == 'new')
+3
View File
@@ -86,18 +86,21 @@ if($config->systemMode == 'new') $lang->my->executionLink = 'Execution Default P
$lang->my->programLinkList = array();
$lang->my->programLinkList['program-browse'] = 'By default, you go to the program list, where you can view all of the programs';
$lang->my->programLinkList['program-project'] = 'By default, you go to the list of items in the most recent program, and you can view all items under the current program';
if($config->systemMode == 'new') $lang->my->programLinkList['program-kanban'] = 'Enter the Program Kanban by default, and check the progress of all Programs';
$lang->my->productLinkList = array();
$lang->my->productLinkList['product-index'] = 'The default access to the product home page, you can understand the company’s overall product status';
$lang->my->productLinkList['product-all'] = 'By default, you go to the product list, where you can view all the products';
$lang->my->productLinkList['product-dashboard'] = 'By default, go to the latest product dashboard to see the current product overview';
$lang->my->productLinkList['product-browse'] = 'By default, go to the list of requirements for the most recent product and see the requirements under the current product';
$lang->my->productLinkList['product-kanban'] = 'Enter the Product Kanban by default, and check the progress of all Products';
global $config;
$lang->my->projectLinkList = array();
$lang->my->projectLinkList['project-browse'] = 'By default, you go to the project list, where you can view all the projects';
$lang->my->projectLinkList['project-execution'] = 'Go to Project-Exection by default. You can check all information in Execution';
$lang->my->projectLinkList['project-index'] = 'By default, go to the most recent project dashboard to see the current project overview';
if($config->systemMode == 'new') $lang->my->projectLinkList['project-kanban'] = 'Enter the Project Kanban by default, and check the progress of all Projects';
$lang->my->executionLinkList = array();
if($config->systemMode == 'new')
+3
View File
@@ -93,18 +93,21 @@ if($config->systemMode == 'new') $lang->my->executionLink = '执行默认着陆
$lang->my->programLinkList = array();
$lang->my->programLinkList['program-browse'] = '默认进入项目集列表,可以查看所有的项目集';
$lang->my->programLinkList['program-project'] = '默认进入最近一个项目集的项目列表,可以查看当前项目集下所有项目';
if($config->systemMode == 'new') $lang->my->programLinkList['program-kanban'] = '默认进入项目集看板,可以可视化的查看到所有项目集的进展情况';
$lang->my->productLinkList = array();
$lang->my->productLinkList['product-index'] = '默认进入产品主页,可以了解公司整体的产品状况';
$lang->my->productLinkList['product-all'] = '默认进入产品列表,可以查看所有的产品';
$lang->my->productLinkList['product-dashboard'] = '默认进入最近一个产品仪表盘,可以查看当前产品概况';
$lang->my->productLinkList['product-browse'] = '默认进入最近一个产品的需求列表,可以查看当前产品下的需求信息';
$lang->my->productLinkList['product-kanban'] = '默认进入产品看板,可以可视化的查看到所有产品的进展情况';
global $config;
$lang->my->projectLinkList = array();
$lang->my->projectLinkList['project-browse'] = '默认进入项目列表,可以查看所有的项目';
$lang->my->projectLinkList['project-execution'] = '默认进入项目下所有执行列表,查看所有执行信息';
$lang->my->projectLinkList['project-index'] = '默认进入最近一个项目仪表盘,可以查看当前项目概况';
if($config->systemMode == 'new') $lang->my->projectLinkList['project-kanban'] = '默认进入项目看板,可以可视化的查看到所有项目的进展情况';
$lang->my->executionLinkList = array();
if($config->systemMode == 'new')
+3
View File
@@ -93,18 +93,21 @@ if($config->systemMode == 'new') $lang->my->executionLink = '執行預設着陸
$lang->my->programLinkList = array();
$lang->my->programLinkList['program-browse'] = '預設進入項目集列表,可以查看所有的項目集';
$lang->my->programLinkList['program-project'] = '預設進入最近一個項目集的項目列表,可以查看當前項目集下所有項目';
if($config->systemMode == 'new') $lang->my->programLinkList['program-kanban'] = '默認進入項目集看板,可以視覺化的查看到所有項目集的進展情况';
$lang->my->productLinkList = array();
$lang->my->productLinkList['product-index'] = '預設進入產品主頁,可以瞭解公司整體的產品狀況';
$lang->my->productLinkList['product-all'] = '預設進入產品列表,可以查看所有的產品';
$lang->my->productLinkList['product-dashboard'] = '預設進入最近一個產品儀表盤,可以查看當前產品概況';
$lang->my->productLinkList['product-browse'] = '預設進入最近一個產品的需求列表,可以查看當前產品下的需求信息';
$lang->my->productLinkList['product-kanban'] = '默認進入產品看板,可以視覺化的查看到所有產品的進展情况';
global $config;
$lang->my->projectLinkList = array();
$lang->my->projectLinkList['project-browse'] = '預設進入項目列表,可以查看所有的項目';
$lang->my->projectLinkList['project-execution'] = '預設進入項目下所有執行列表,查看所有執行信息';
$lang->my->projectLinkList['project-index'] = '預設進入最近一個項目儀表盤,可以查看當前項目概況';
if($config->systemMode == 'new') $lang->my->projectLinkList['project-kanban'] = '默認進入項目看板,可以視覺化的查看到所有項目的進展情况';
$lang->my->executionLinkList = array();
if($config->systemMode == 'new')
+7 -13
View File
@@ -1159,31 +1159,25 @@ class product extends control
$kanbanGroup = $this->product->getStats4Kanban();
extract($kanbanGroup);
$products = array();
$myProducts = array();
$otherProducts = array();
foreach($productList as $productID => $product)
{
if($product->status == 'normal' and $product->PO == $this->app->user->account)
{
$myProducts[$product->program][$productID] = $productID;
}
elseif($product->status == 'normal' and !($product->PO == $this->app->user->account))
{
$otherProducts[$product->program][$productID] = $productID;
}
if($product->status != 'normal') continue;
if($product->PO == $this->app->user->account) $myProducts[$product->program][] = $productID;
else $otherProducts[$product->program][] = $productID;
}
$products['myProducts'] = $myProducts;
$products['otherProducts'] = $otherProducts;
$kanbanList = array();
if(!empty($myProducts)) $kanbanList['my'] = $myProducts;
if(!empty($otherProducts)) $kanbanList['other'] = $otherProducts;
$this->view->title = $this->lang->product->kanban;
$this->view->products = $products;
$this->view->kanbanList = $kanbanList;
$this->view->programList = array(0 => $this->lang->product->emptyProgram) + $programList;
$this->view->productList = $productList;
$this->view->planList = $planList;
$this->view->projectList = $projectList;
$this->view->executionList = $executionList;
$this->view->projectProduct = $projectProduct;
$this->view->latestExecutions = $projectLatestExecutions;
$this->view->hourList = $hourList;
-30
View File
@@ -1,30 +0,0 @@
#kanban {padding: 10px;}
.table caption {background: #fff; border-top-left-radius: 2px; border-top-right-radius: 2px; margin-bottom: 0; font-weight: 500; padding-top: 0;}
.table th {border: 2px solid #fff;}
.table td {border-top: 5px solid #fff; border-bottom: 5px solid #fff; border-left: 2px solid #fff; border-right: 2px solid #fff;}
.table tbody tr:last-child td {border-bottom: unset;}
td.plan, td.release, td.project, td.execution, td.classicProject {vertical-align: top !important;}
td.program {padding-top: 10px !important; padding-bottom: 10px !important;}
.main-table tbody>tr>td:first-child, .main-table thead>tr>th:first-child {padding: 0 4px 10px 4px;}
.main-table tbody>tr>td {padding: 0 10px 10px;}
.table .program {writing-mode: vertical-lr; padding-left: 4px !important; color: #fff;}
td.product {padding-top: 10px !important;}
td.project, td.execution {padding: 0 0 10px 0 !important;}
td > div:not(.board) > .board-item:first-child {margin-top: 10px;}
td > div.board:first-child {margin-top: 5px;}
.fix-table-copy-wrapper thead > tr > th:first-child {background: none!important;}
.board-item {border: 1px solid #ebebeb; padding: 3px 10px; cursor: default; border-radius: 2px; background-color: #fff; text-align: left;}
.board-item:hover {border-color: #ccc;}
.board {padding: 0 10px;}
.board + .board {margin-top: 5px;}
.board-item + .board-item {margin-top: 10px;}
.project .board, .execution .board {padding-top: 5px; border-top: 2px solid #fff;}
.project .board:first-child, .execution .board:first-child {padding-top: 5px; border-top: unset;}
.release .board-item {display: flex; flex-flow: row nowrap; justify-content: flex-start; align-items: center;}
.emptyBoard {padding: 3px 10px; cursor: default;}
.project .table-row > .table-col:last-child, .execution .table-row > .table-col:last-child, .classicProject .table-row > .table-col:last-child{width: 24px; padding-top: 2px;}
.scroll {overflow-x: hidden; overflow-y: auto;}
+108
View File
@@ -0,0 +1,108 @@
/**
* Process kanban data
* @param {string} key Kanban key, used as kanban id
* @param {Object} programsData Programs data
* @returns {Object} kanban data
*/
function processKanbanData(key, programsData)
{
var kanbanId = key;
/* Generate columns */
var columns = [];
$.each(kanbanColumns, function(_, column)
{
columns.push($.extend({}, column,
{
kanban: kanbanId,
id: kanbanId + '-' + column.type,
}));
});
/* Format lanes data */
var lanes = [];
$.each(programsData, function(programId, programProducts)
{
var programName = programList[programId];
var subLanes = [];
programProducts.forEach(function(productID)
{
var product = productList[productID];
var items = {};
/* unclosed products */
var productItem = {id: 'product-' + productID, _id: productID, name: product.name};
items.unclosedProduct = [productItem];
/* plans */
items.unexpiredPlan = [];
var plans = planList[productID];
if(plans)
{
$.each(plans, function(planID, plan)
{
items.unexpiredPlan.push($.extend({}, plan, {id: 'plan-' + planID, _id: planID}));
});
}
/* doing projects */
if(kanbanColumns.doingProject)
{
items.doingProject = [];
var productProjects = projectProduct[productID];
if(productProjects)
{
$.each(productProjects, function(projectID)
{
var project = projectList[projectID];
if(!project) return;
var projectItem = $.extend({}, project, {id: 'project-' + projectID, _id: projectID});
items.doingProject.push(projectItem);
});
}
}
/* doing execution */
items.doingExecution = [];
var productProjects = projectProduct[productID];
if(productProjects)
{
$.each(productProjects, function(projectID)
{
var execution = latestExecutions[projectID];
if(!execution) return;
var executionItem = $.extend({}, execution, {id: 'execution-' + execution.id, _id: execution.id});
items.doingExecution.push(executionItem);
});
}
/* normal release */
items.normalRelease = [];
var releases = releaseList[productID];
if(releases)
{
$.each(releases, function(releaseID, release)
{
if(!release) return;
var releaseItem = $.extend({}, release, {id: 'release-' + releaseID, _id: releaseID});
items.normalRelease.push(releaseItem);
});
}
subLanes.push({id: kanbanId + '-' + programId + '-' + productID, items: items});
});
lanes.push({id: programId, kanban: kanbanId, name: programName, subLanes: subLanes});
});
return {id: kanbanId, columns: columns, lanes: lanes};
}
$(function()
{
/* Init all kanbans */
$.each(kanbanList, function(key, programsData)
{
$('#kanban-' + key).kanban({data: processKanbanData(key, programsData)});
});
});
+52 -216
View File
@@ -2,228 +2,64 @@
/**
* The html product kanban file of kanban method of product module of ZenTaoPMS.
*
* @copyright Copyright 2009-2015 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @copyright Copyright 2021-2021 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author Fangzhou Hu <hufangzhou@easycorp.ltd>
* @author Hao Sun <sunhao@easycorp.ltd>
* @package ZenTaoPMS
* @version $Id
*/
?>
<?php include '../../common/view/header.html.php';?>
<div id="kanban" class="main-table fade auto-fade-in" data-ride="table" data-checkable="false" data-group="true">
<?php if(empty($productList)):?>
<div class="table-empty-tip">
<p><span class="text-muted"><?php echo $lang->noData;?></span></p>
</div>
<?php else:?>
<?php foreach($products as $type => $programs):?>
<?php if(empty($programs)) continue;?>
<div class="cell">
<table class="table text-center" style="border-radius: 0; background: #efefef;">
<caption><?php echo $type == 'myProducts' ? $lang->product->myProduct : $lang->product->otherProduct;?></caption>
<thead>
<tr>
<?php
$rowspan = $config->systemMode == 'new' ? "rowspan='2'" : '';
$colspan = $config->systemMode == 'new' ? "colspan='2'" : '';
?>
<?php if($config->systemMode == 'new'):?>
<th <?php echo $rowspan;?> class="w-20px" style="border-right: 0; border-left: 0; background: #32C5FF;"></th>
<?php endif;?>
<th <?php echo $rowspan;?> style="border-left: 0px;"><?php echo $lang->product->unclosedProduct;?></th>
<th <?php echo $rowspan;?>><?php echo $lang->product->unexpiredPlan;?></th>
<th <?php echo $colspan;?>><?php echo $lang->product->doing;?></th>
<th <?php echo $rowspan;?>><?php echo $lang->product->normalRelease;?></th>
</tr>
<?php if($config->systemMode == 'new'):?>
<tr>
<th><?php echo $lang->product->doingProject;?></th>
<th><?php echo $lang->product->doingExecution;?></th>
</tr>
<?php endif;?>
</thead>
<tbody>
<?php $colorIndex = 0;?>
<?php foreach($programs as $programID => $programProduct):?>
<?php $i = 0;?>
<?php foreach($programProduct as $productID):?>
<?php
$scroll = '';
$doingCounts = isset($projectProduct[$productID]) ? count($projectProduct[$productID]) : 0;
$planCounts = isset($planList[$productID]) ? count($planList[$productID]) : 0;
$releaseCount = isset($releaseList[$productID]) ? count($releaseList[$productID]) : 0;
if(($doingCounts < $planCounts or $doingCounts < $releaseCount) and ($planCounts > 5 or $releaseCount > 5)) $scroll = 'scroll';
?>
<tr>
<?php if($i == 0 and $config->systemMode == 'new'):?>
<td rowspan="<?php echo count($programs[$programID])?>" class="program text-ellipsis" style="background: <?php echo $this->lang->product->kanbanColorList[$colorIndex];?>"><?php echo zget($programList, $programID);?></td>
<?php endif;?>
<td class="product">
<?php if(common::hasPriv('product', 'browse')):?>
<?php echo "<span>" . html::a($this->createLink('product', 'browse', "productID=$productID"), $productList[$productID]->name, '', "title={$productList[$productID]->name}") . '</span>';?>
<?php else:?>
<?php echo "<span title={$productList[$productID]->name}>{$productList[$productID]->name}</span>";?>
<?php endif;?>
</td>
<td class="plan">
<div class="<?php echo $scroll;?>">
<?php if(isset($planList[$productID])):?>
<?php foreach($planList[$productID] as $planID => $plan):?>
<div class="board-item text-ellipsis">
<?php if(common::hasPriv('productplan', 'view')):?>
<?php echo html::a($this->createLink('productplan', 'view', "planID=$planID"), $plan->title, '', "title={$plan->title}");?>
<?php else:?>
<?php echo "<span title={$plan->title}>{$plan->title}</span>"?>
<?php endif;?>
</div>
<?php endforeach;?>
<?php endif;?>
</div>
</td>
<?php if($config->systemMode == 'new'):?>
<td class="project">
<?php if(isset($projectProduct[$productID])):?>
<?php foreach($projectProduct[$productID] as $projectID => $project):?>
<?php if(!isset($projectList[$projectID])) continue;?>
<div class="board">
<?php $borderStyle = isset($project->delay) ? 'border-left: 3px solid red' : 'border-left: 3px solid #0bd986';?>
<div class="board-item" style="<?php echo $borderStyle;?>">
<div class="table-row">
<div class="table-col text-ellipsis">
<?php if(common::hasPriv('project', 'index')):?>
<?php echo html::a($this->createLink('project', 'index', "projectID=$projectID"), $projectList[$projectID]->name, '', "title={$projectList[$projectID]->name}");?>
<?php else:?>
<?php echo "<span title={$projectList[$projectID]->name}>{$projectList[$projectID]->name}</span>"?>
<?php endif;?>
</div>
<div class="table-col">
<div class="c-progress">
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo round($projectList[$projectID]->hours->progress);?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
<div class='progress-info'><?php echo round($projectList[$projectID]->hours->progress);?></div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endforeach;?>
<?php endif;?>
</td>
<td class="execution">
<?php if(isset($projectProduct[$productID])):?>
<?php foreach($projectProduct[$productID] as $projectID => $project):?>
<div class="board">
<?php
$borderStyle = '';
if(isset($latestExecutions[$projectID]))
{
$delay = helper::diffDate(helper::today(), $latestExecutions[$projectID]->end);
$borderStyle = $delay > 0 ? 'border-left: 3px solid red' : 'border-left: 3px solid #0bd986';
}
?>
<?php $boardStyle = isset($latestExecutions[$projectID]) ? 'board-item' : 'emptyBoard';?>
<?php $executionID = isset($latestExecutions[$projectID]) ? $latestExecutions[$projectID]->id : 0;?>
<?php $executionName = isset($latestExecutions[$projectID]) ? $latestExecutions[$projectID]->name : ''?>
<div class="<?php echo $boardStyle;?> text-ellipsis" style="<?php echo $borderStyle;?>">
<div class="table-row">
<div class="table-col text-ellipsis">
<?php if(isset($latestExecutions[$projectID])):?>
<?php if(common::hasPriv('execution', 'task')):?>
<?php echo html::a($this->createLink('execution', 'task', "executionID=$executionID"), $executionName, '', "title={$executionName}");?>
<?php else:?>
<?php echo "<span title=$executionName>$executionName</span>";?>
<?php endif;?>
<?php endif;?>
</div>
<div class="table-col">
<?php if(isset($latestExecutions[$projectID])):?>
<div class="c-progress">
<?php $hourList[$executionID] = isset($hourList[$executionID]) ? $hourList[$executionID] : $emptyHour; ?>
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo round($hourList[$executionID]->progress);?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
<div class='progress-info'><?php echo round($hourList[$executionID]->progress);?></div>
</div>
</div>
<?php endif;?>
</div>
</div>
</div>
</div>
<?php endforeach;?>
<?php endif;?>
</td>
<?php endif;?>
<?php if($config->systemMode == 'classic'):?>
<td class="classicProject">
<div>
<?php if(isset($executionList[$productID])):?>
<?php foreach($executionList[$productID] as $executionID => $execution):?>
<?php
$borderStyle = '';
$delay = helper::diffDate(helper::today(), $execution->end);
$borderStyle = $delay > 0 ? 'border-left: 3px solid red' : 'border-left: 3px solid #0bd986';
?>
<div class="board-item" style="<?php echo $borderStyle;?>">
<div class="table-row">
<div class="table-col text-ellipsis">
<?php if(common::hasPriv('execution', 'task')):?>
<?php echo html::a($this->createLink('execution', 'task', "executionID=$executionID"), $execution->name, '', "title={$execution->name} class='text-ellipsis'");?>
<?php else:?>
<?php echo "<span title={$execution->name}>{$execution->name}</span>"?>
<?php endif;?>
</div>
<div class="table-col">
<div class="c-progress">
<?php $hourList[$executionID] = isset($hourList[$executionID]) ? $hourList[$executionID] : $emptyHour; ?>
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo round($hourList[$executionID]->progress);?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
<div class='progress-info'><?php echo round($hourList[$executionID]->progress);?></div>
</div>
</div>
</div>
</div>
</div>
<?php endforeach;?>
<?php endif;?>
</div>
</td>
<?php endif;?>
<td class="release">
<div class="<?php echo $scroll;?>">
<?php if(isset($releaseList[$productID])):?>
<?php foreach($releaseList[$productID] as $releaseID => $release):?>
<div class="board-item">
<?php if(common::hasPriv('release', 'view')):?>
<?php echo html::a($this->createLink('release', 'view', "releaseID=$releaseID"), $release->name, '', "title={$release->name} class='text-ellipsis'");?>
<?php else:?>
<?php echo "<span title={$release->name}>{$release->name}</span>"?>
<?php endif;?>
<?php if($release->marker) echo "&nbsp;<span><i class='icon icon-flag red'></i></span>";?>
</div>
<?php endforeach;?>
<?php endif;?>
</div>
</td>
<?php $i ++;?>
</tr>
<?php endforeach;?>
<?php $colorIndex = $colorIndex == 9 ? 0 : ++ $colorIndex;?>
<?php endforeach;?>
</tbody>
</table>
</div>
<?php endforeach;?>
<?php endif;?>
<?php include '../../common/view/kanban.html.php';?>
<?php if(empty($kanbanList)):?>
<div class="table-empty-tip cell">
<p class="text-muted"><?php echo $lang->noData;?></p>
</div>
<script>
$(function()
<?php else:?>
<div id='kanbanList'>
<?php foreach($kanbanList as $type => $programs):?>
<div class='panel kanban-panel'>
<div class='panel-heading'>
<strong><?php echo $type == 'my' ? $lang->product->myProduct : $lang->product->otherProduct;?></strong>
</div>
<div class='panel-body'>
<div id='kanban-<?php echo $type;?>' class='kanban'></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
$kanbanColumns = array();
$kanbanColumns['unclosedProduct'] = array('name' => $lang->product->unclosedProduct, 'type' => 'unclosedProduct');
$kanbanColumns['unexpiredPlan'] = array('name' => $lang->product->unexpiredPlan, 'type' => 'unexpiredPlan');
if($config->systemMode == 'new')
{
$('.board').height($('.project .board').height());
$('.table div.scroll').each(function()
{
var count = $(this).parent().siblings('td.project').children('.board').length >= 5 ? $(this).parent().siblings('td.project').children('.board').length : 5;
var preHeight = $(this).parent().siblings('td.project').children('.board').length >= 5 ? $('.board').outerHeight(true) : $(this).find('.board-item').outerHeight(true);
$(this).css('max-height', preHeight * count);
})
})
</script>
$kanbanColumns['doingProject'] = array('name' => $lang->product->doingProject, 'type' => 'doingProject');
$kanbanColumns['doingExecution'] = array('name' => $lang->product->doingExecution, 'type' => 'doingExecution');
}
else
{
$kanbanColumns['doingExecution'] = array('name' => $lang->product->doing, 'type' => 'doingExecution');
}
$kanbanColumns['normalRelease'] = array('name' => $lang->product->normalRelease, 'type' => 'normalRelease');
$userPrivs = array();
$userPrivs['product'] = common::hasPriv('product', 'browse');
$userPrivs['productplan'] = common::hasPriv('productplan', 'view');
$userPrivs['project'] = common::hasPriv('project', 'index');
$userPrivs['execution'] = common::hasPriv('execution', 'task');
$userPrivs['release'] = common::hasPriv('release', 'view');
js::set('kanbanColumns', $kanbanColumns);
js::set('userPrivs', $userPrivs);
js::set('kanbanList', $kanbanList);
js::set('programList', $programList);
js::set('productList', $productList);
js::set('planList', $planList);
js::set('projectList', $projectList);
js::set('projectProduct', $projectProduct);
js::set('latestExecutions', $latestExecutions);
js::set('releaseList', $releaseList);
js::set('hourList', $hourList);
js::set('doingText', $lang->product->doing);
?>
<?php endif; ?>
<?php include '../../common/view/footer.html.php';?>
-5
View File
@@ -104,13 +104,8 @@ function computeEndDate(delta)
if(delta == 999)
{
$('#end').val(longTime);
$('#end').attr('disabled', 'disabled');
return false;
}
else
{
$('#end').removeAttr('disabled');
}
beginDate = convertStringToDate(beginDate);
if((delta == 7 || delta == 14) && (beginDate.getDay() == 1))
+4
View File
@@ -78,4 +78,8 @@ $(function()
$('#exchangeRate').val(exchangeRate);
$('#changeUnitTip').modal('hide');
})
/* If end is longtime, set the default date to today */
var today = $.zui.formatDate(new Date(), 'yyyy-MM-dd');
if($('#end').val() == longTime) $('#end').val(today).datetimepicker('update').val(longTime);
});
+109
View File
@@ -0,0 +1,109 @@
/**
* Process kanban data
* @param {string} key Kanban key, used as kanban id
* @param {Object} programsData Programs data
* @returns {Object} kanban data
*/
function processKanbanData(key, programsData)
{
var kanbanId = key;
/* Generate columns */
var columns = [];
$.each(kanbanColumns, function(_, column)
{
columns.push($.extend({}, column,
{
kanban: kanbanId,
id: kanbanId + '-' + column.type,
}));
});
/* Format lanes data */
var lanes = [];
$.each(programsData, function(programId, program)
{
var subLanes = [];
$.each(program.products, function(productID, product)
{
var items = {};
/* unclosed products */
var productItem = {id: 'product-' + productID, _id: productID, name: product.name};
items.unclosedProduct = [productItem];
/* plans */
items.unexpiredPlan = [];
var plans = product.plans;
if(plans)
{
$.each(plans, function(planID, plan)
{
items.unexpiredPlan.push($.extend({}, plan, {id: 'plan-' + planID, _id: planID}));
});
}
/* wait projects */
items.waitProject = [];
var waitProjects = product.projects && product.projects.wait;
if(waitProjects)
{
$.each(waitProjects, function(projectID, project)
{
var projectItem = $.extend({}, project, {id: 'project-' + projectID, _id: projectID});
items.waitProject.push(projectItem);
});
}
/* doing projects and executions */
items.doingProject = [];
items.doingExecution = [];
var doingProjects = product.projects && product.projects.doing;
if(doingProjects)
{
$.each(doingProjects, function(projectID, project)
{
var projectItem = $.extend({}, project, {id: 'project-' + projectID, _id: projectID});
items.doingProject.push(projectItem);
var execution = project.execution;
if(!execution) return;
var executionItem = $.extend({}, execution, {id: 'execution-' + execution.id, _id: execution.id});
items.doingExecution.push(executionItem);
});
}
/* normal release */
items.normalRelease = [];
var releases = product.releases;
if(releases)
{
$.each(releases, function(releaseID, release)
{
if(!release) return;
var releaseItem = $.extend({}, release, {id: 'release-' + releaseID, _id: releaseID});
items.normalRelease.push(releaseItem);
});
}
subLanes.push({id: kanbanId + '-' + programId + '-' + productID, items: items});
});
var programItem = $.extend({}, program, {id: programId, kanban: kanbanId, subLanes: subLanes});
lanes.push(programItem);
});
return {id: kanbanId, columns: columns, lanes: lanes};
}
$(function()
{
/* Init all kanbans */
$.each(kanbanGroup, function(key, programsData)
{
var $kanban = $('#kanban-' + key);
if(!$kanban.length) return;
$kanban.kanban({data: processKanbanData(key, programsData)});
});
});
+2 -3
View File
@@ -75,9 +75,8 @@
<?php echo html::input('begin', $program->begin, "class='form-control form-date' placeholder='" . $lang->project->begin . "' required");?>
<span class='input-group-addon'><?php echo $lang->project->to;?></span>
<?php
$disabledEnd = $program->end == LONG_TIME ? 'disabled' : '';
$end = $program->end == LONG_TIME ? $lang->program->longTime : $program->end;
echo html::input('end', $end, "class='form-control form-date' $disabledEnd placeholder='" . $lang->project->end . "' required");
$end = $program->end == LONG_TIME ? $lang->program->longTime : $program->end;
echo html::input('end', $end, "class='form-control form-date' placeholder='" . $lang->project->end . "' required");
?>
</div>
</td>
+39 -169
View File
@@ -1,182 +1,52 @@
<?php
/**
* The html template file of kanban method of program module of ZenTaoPMS.
* The html product kanban file of kanban method of product module of ZenTaoPMS.
*
* @copyright Copyright 2009-2015 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @copyright Copyright 2021-2021 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author Guangming Sun<sunguangming@cnezsoft.com>
* @author Hao Sun <sunhao@easycorp.ltd>
* @package ZenTaoPMS
* @version $Id
*/
?>
<?php include '../../common/view/header.html.php';?>
<div id="kanban" class="main-table fade auto-fade-in" data-ride="table" data-checkable="false" data-group="true">
<?php foreach($kanbanGroup as $type => $programGroup):?>
<?php if(empty($programGroup)) continue;?>
<?php $colorIndex = 0;?>
<div class="cell">
<div class='detail'>
<div class='detail-title'><?php echo $lang->program->kanban->typeList[$type];?></div>
<div class='detail-content'>
<table class="table no-margin table-grouped text-center" style='background: #efefef;'>
<thead>
<tr>
<th rowspan='2' class='w-20px' style='background: #32C5FF; border: none'></th>
<th rowspan='2'><?php echo $lang->program->kanban->openProducts;?></th>
<th rowspan='2'><?php echo $lang->program->kanban->unexpiredPlans;?></th>
<th rowspan='2'><?php echo $lang->program->kanban->waitingProjects;?></th>
<th colspan='2'><?php echo $lang->program->statusList['doing'];?></th>
<th rowspan='2'><?php echo $lang->program->kanban->normalReleases;?></th>
</tr>
<tr>
<th><?php echo $lang->program->kanban->doingProjects;?></th>
<th><?php echo $lang->program->kanban->doingExecutions;?></th>
</tr>
</thead>
<tbody>
<?php foreach($programGroup as $programID => $program):?>
<tr>
<td class='lane-name text-ellipsis' style='background: <?php echo $lang->program->kanban->laneColorList[$colorIndex];?>; color: #fff; border: none;' rowspan='<?php echo $program->rowspan;?>' title=<?php echo $program->name;?>><?php echo $program->name;?></td>
<?php $i = 0;?>
<?php if(!empty($program->products)):?>
<?php foreach($program->products as $productID => $product):?>
<?php
$scroll = '';
$doingCounts = isset($product->projects['doing']) ? count($product->projects['doing']) : 0;
$planCounts = isset($product->plans) ? count($product->plans) : 0;
$releaseCount = isset($product->releases) ? count($product->releases) : 0;
if(($doingCounts < $planCounts || $doingCounts < $releaseCount) and ($planCounts > 5 or $releaseCount > 5)) $scroll = 'scroll';
?>
<?php if($i != 0) echo '<tr>';?>
<td title=<?php echo $product->name;?> rowspan='<?php echo $product->rowspan;?>'><?php echo $product->name;?></td>
<td class='normal-plan' rowspan='<?php echo $product->rowspan;?>'>
<div class="<?php echo $scroll;?>">
<?php foreach($product->plans as $planID => $plan):?>
<div class='board-item'>
<div class='table-row'>
<div class='table-col text-ellipsis text-left'>
<?php echo html::a($this->createLink('productplan', 'view', "planID=$plan->id"), $plan->title);?>
</div>
</div>
</div>
<?php endforeach;?>
</div>
</td>
<td class='wait-project' rowspan='<?php echo $product->rowspan?>'>
<div class="<?php echo $scroll;?>">
<?php if(isset($product->projects['wait'])):?>
<?php foreach($product->projects['wait'] as $projectID => $project):?>
<div class='board-item' style='border-left: 3px solid #0991FF'>
<div class='table-row'>
<div class='table-col text-ellipsis text-left'>
<?php echo html::a($this->createLink('project', 'index', "projectID=$project->id"), $project->name);?>
</div>
</div>
</div>
<?php endforeach;?>
<?php endif;?>
</div>
</td>
<?php if(isset($product->projects['doing'])):?>
<?php $index = 0;?>
<?php foreach($product->projects['doing'] as $projectID => $project):?>
<?php if($index != 0) echo "<tr>";?>
<td class='doing-td project'>
<div class='board'>
<div class='board-item' <?php echo "style='border-left: 3px solid " . (isset($project->delay) ? 'red' : "#0BD986") . "'";?>>
<div class='table-row'>
<div class='table-col'>
<?php echo html::a($this->createLink('project', 'index', "projectID=$project->id"), $project->name);?>
</div>
<div class='table-col'>
<div class="c-progress">
<?php $projectProgress = isset($project->hours->progress) ? $project->hours->progress : 0;?>
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo round($projectProgress);?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
<div class='progress-info'><?php echo round($projectProgress);?></div>
</div>
</div>
</div>
</div>
</div>
</div>
</td>
<td class='doing-td execution'>
<?php if(!empty($project->execution)):?>
<div class='board'>
<div class='board-item' <?php echo "style='border-left: 3px solid " . (isset($project->execution->delay) ? 'red' : "#0BD986") . "'";?>>
<div class='table-row'>
<div class='table-col'>
<?php echo html::a($this->createLink('execution', 'task', "executionID={$project->execution->id}"), $project->execution->name);?>
</div>
<div class='table-col'>
<div class="c-progress">
<?php $executionProgress = isset($project->execution->hours->progress) ? $project->execution->hours->progress : 0;?>
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo round($executionProgress);?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
<div class='progress-info'><?php echo round($executionProgress);?></div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif;?>
</td>
<?php if($index == 0):?>
<td class='normal-release' rowspan=<?php echo $product->rowspan;?>>
<div class="<?php echo $scroll;?>">
<?php foreach($product->releases as $releaseID => $release):?>
<div class='board-item'>
<div class='table-row'>
<div class='table-col'>
<?php $flag = $release->marker ? " <icon class='icon icon-flag red' title='{$lang->release->marker}'></icon> " : '';?>
<?php echo html::a($this->createLink('release', 'view', "releaseID=$release->id"), $release->name . $flag);?>
</div>
</div>
</div>
<?php endforeach;?>
</div>
</td>
<?php endif;?>
<?php if($index != 0) echo "</tr>";?>
<?php $index ++;?>
<?php endforeach;?>
<?php else:?>
<td></td>
<td></td>
<td></td>
<?php endif;?>
<?php if($i != 0) echo '</tr>';?>
<?php $i ++;?>
<?php endforeach;?>
<?php else:?>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<?php endif;?>
</tr>
<?php $colorIndex ++;?>
<?php if($colorIndex > 9) $colorIndex = 0;?>
<?php endforeach;?>
</tbody>
</table>
</div>
<?php include '../../common/view/kanban.html.php';?>
<?php if(empty($kanbanGroup)):?>
<div class="table-empty-tip cell">
<p class="text-muted"><?php echo $lang->noData;?></p>
</div>
<?php else:?>
<div id='kanbanList'>
<?php foreach($kanbanGroup as $type => $programs):?>
<?php if(empty($programs)) continue;?>
<div class='panel kanban-panel'>
<div class='panel-heading'>
<strong><?php echo $lang->program->kanban->typeList[$type];?></strong>
</div>
<div class='panel-body'>
<div id='kanban-<?php echo $type;?>' class='kanban'></div>
</div>
</div>
<?php endforeach;?>
<?php endforeach; ?>
</div>
<script>
$(function()
{
$('.table div.scroll').each(function()
{
var count = $(this).closest('tr').find('td:first').attr('rowspan') >= 5 ? $(this).closest('tr').find('td:first').attr('rowspan') : 5;
var projectTdHeight = $(this).closest('tr').find('td.project .board').outerHeight(true) + 20;
var maxHeight = count > 5 ? projectTdHeight * count : projectTdHeight * count - 40;
$(this).css('max-height', maxHeight);
})
})
</script>
<?php
$kanbanColumns = array();
$kanbanColumns['unclosedProduct'] = array('name' => $lang->program->kanban->openProducts, 'type' => 'unclosedProduct');
$kanbanColumns['unexpiredPlan'] = array('name' => $lang->program->kanban->unexpiredPlans, 'type' => 'unexpiredPlan');
$kanbanColumns['waitProject'] = array('name' => $lang->program->kanban->waitingProjects, 'type' => 'waitProject');
$kanbanColumns['doingProject'] = array('name' => $lang->program->kanban->doingProjects, 'type' => 'doingProject');
$kanbanColumns['doingExecution'] = array('name' => $lang->program->kanban->doingExecutions, 'type' => 'doingExecution');
$kanbanColumns['normalRelease'] = array('name' => $lang->program->kanban->normalReleases, 'type' => 'normalRelease');
$userPrivs = array();
$userPrivs['product'] = common::hasPriv('product', 'browse');
$userPrivs['productplan'] = common::hasPriv('productplan', 'view');
$userPrivs['project'] = common::hasPriv('project', 'index');
$userPrivs['execution'] = common::hasPriv('execution', 'task');
$userPrivs['release'] = common::hasPriv('release', 'view');
js::set('kanbanColumns', $kanbanColumns);
js::set('userPrivs', $userPrivs);
js::set('kanbanGroup', $kanbanGroup);
js::set('doingText', $lang->program->statusList['doing']);
?>
<?php endif; ?>
<?php include '../../common/view/footer.html.php';?>
+62 -23
View File
@@ -1,27 +1,66 @@
/**
* Process kanban data
* @param {string} key Kanban key, used as kanban id
* @param {Object} programGroup Group data
* @returns {Object} kanban data
*/
function processKanbanData(key, programGroup)
{
var kanbanId = key;
/* Generate columns */
var columns = [];
$.each(kanbanColumns, function(_, column)
{
columns.push($.extend({}, column,
{
kanban: kanbanId,
id: kanbanId + '-' + column.type,
}));
});
/* Format lanes data */
var lanes = [];
$.each(programGroup, function(programId, statusMap)
{
var programName = programPairs[programId];
var items = {doingExecution: []};
/* Projects and executions */
['wait', 'doing', 'closed'].forEach(function(status)
{
var itemsList = [];
var statusProjects = statusMap[status];
if(statusProjects)
{
$.each(statusProjects, function(projectID, project)
{
itemsList.push($.extend({}, project, {id: 'project-' + projectID, _id: projectID}));
if(status === 'doing')
{
var execution = latestExecutions[projectID];
if(execution)
{
var executionItem = $.extend({}, execution, {id: 'execution-' + execution.id, _id: execution.id});
items.doingExecution = [executionItem];
}
}
});
}
items[status + 'Project'] = itemsList;
});
lanes.push({id: programId, kanban: kanbanId, name: programName, items: items});
});
return {id: kanbanId, columns: columns, lanes: lanes};
}
$(function()
{
$("div[class^='board-doing-']").height($('.board-doing-project').height());
$('.board-program').each(function()
/* Init all kanbans */
$.each(kanbanGroup, function(key, programGroup)
{
var boardWaitCount = $(this).find('.board-wait .board-item').length;
var boardClosedCount = $(this).find('.board-closed .board-item').length;
var boardDoingCount = $(this).find('.board-doing-project .board-item').length;
if((boardWaitCount > 5 || boardClosedCount > 5) && (boardWaitCount > boardDoingCount || boardClosedCount > boardDoingCount))
{
var boardHeight = 0;
if(boardDoingCount >= 5)
{
boardHeight = $('.board-doing-project').outerHeight(true) * boardDoingCount;
}
else
{
var boardHeight = $(this).find('.board-project .board-item').outerHeight(true) * 5;
}
$(this).find('.board-project').css("height", boardHeight);
$(this).find('.board-project').css("overflow", 'auto');
}
$('#kanban-' + key).kanban({data: processKanbanData(key, programGroup)});
});
})
});
+35 -131
View File
@@ -1,144 +1,48 @@
<?php
/**
* The project kanban view file of project module of ZenTaoPMS.
* The html product kanban file of kanban method of product module of ZenTaoPMS.
*
* @copyright Copyright 2009-2021 青岛易软天创网络科技有限公司 (QingDao Nature Easy Soft Network Technology Co,LTD www.cnezsoft.com)
* @author Qiyu Xie
* @package project
* @version $Id: kanban.html.php $
* @copyright Copyright 2021-2021 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package ZenTaoPMS
* @version $Id
*/
?>
<?php include '../../common/view/header.html.php';?>
<?php $colorIndex = 0;?>
<?php include '../../common/view/kanban.html.php';?>
<?php if(empty($kanbanGroup)):?>
<div class="table-empty-tip">
<p><span class="text-muted"><?php echo $lang->project->empty;?></span></p>
<div class="table-empty-tip cell">
<p class="text-muted"><?php echo $lang->project->empty;?></p>
</div>
<?php else:?>
<?php foreach($kanbanGroup as $type => $projectGroup):?>
<?php if(empty($projectGroup)) continue;?>
<div id="kanban" class="main-table fade auto-fade-in" data-ride="table" data-checkable="false" data-group="true">
<div class="cell">
<div class='detail'>
<div class='detail-title'><?php echo $lang->project->typeList[$type];?></div>
<div class='detail-content'>
<table class='table no-margin' style='background-color: #efefef;'>
<thead class='text-center'>
<tr>
<th rowspan='2' style="border-right: unset !important" class='w-20px'></th>
<th rowspan='2' style="border-left: unset !important"><?php echo $lang->project->waitProjects;?></th>
<th colspan='2'><?php echo $lang->project->statusList['doing'];?></th>
<th rowspan='2'><?php echo $lang->project->closedProjects;?></th>
</tr>
<tr>
<th><?php echo $lang->project->doingProjects;?></th>
<th><?php echo $lang->project->doingExecutions;?></th>
</tr>
</thead>
<tbody>
<?php foreach($projectGroup as $programID => $statusList):?>
<tr class='board-program'>
<td class='text-center' title='<?php echo zget($programPairs, $programID);?>' style='background: <?php echo $lang->project->laneColorList[$colorIndex];?>; color: #fff; padding-left: 2px; writing-mode: vertical-lr;'><?php echo zget($programPairs, $programID);?></td>
<?php foreach(array('wait','doing','closed') as $status):?>
<?php if($status == 'doing'):?>
<td class='board-doing'>
<?php if(isset($statusList[$status])):?>
<?php foreach($statusList[$status] as $project):?>
<div class='board-doing-project'>
<div class='board-item' <?php echo "style='border-left: 3px solid " . (isset($project->delay) ? 'red' : "#0BD986") . "'";?>>
<div class='table-row'>
<div class='table-col'>
<?php
if(common::hasPriv('project', 'index'))
{
echo html::a($this->createLink('project', 'index', "projectID=$project->id"), $project->name, '', "title='{$project->name}'");
}
else
{
echo "<span title='{$project->name}'>{$project->name}</span>";
}
?>
</div>
<div class='table-col'>
<div class="c-progress">
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo round($project->hours->progress);?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
<div class='progress-info'><?php echo round($project->hours->progress);?></div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endforeach;?>
<?php endif;?>
</td>
<td class='board-doing'>
<?php if(isset($statusList[$status])):?>
<?php foreach($statusList[$status] as $project):?>
<div class='board-doing-execution'>
<?php if(isset($latestExecutions[$project->id])):?>
<?php $execution = $latestExecutions[$project->id];?>
<div class='board-item' <?php echo "style='border-left: 3px solid " . (isset($execution->delay) ? 'red' : "#0BD986") . "'";?>>
<div class='table-row'>
<div class='table-col'>
<?php
if(common::hasPriv('execution', 'task'))
{
echo html::a($this->createLink('execution', 'task', "executionID=$execution->id"), $execution->name, '', "title='{$execution->name}'");
}
else
{
echo "<span title='{$execution->name}'>{$execution->name}</span>";
}
?>
</div>
<div class='table-col'>
<div class="c-progress">
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo round($execution->hours->progress);?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
<div class='progress-info'><?php echo round($execution->hours->progress);?></div>
</div>
</div>
</div>
</div>
</div>
<?php endif;?>
</div>
<?php endforeach;?>
<?php endif;?>
</td>
<?php else:?>
<td class='board-<?php echo $status;?>'>
<div class='board-project'>
<?php if(isset($statusList[$status])):?>
<?php foreach($statusList[$status] as $project):?>
<div class='board-item' <?php echo "style='border-left: 3px solid " . $lang->execution->statusColorList[$status] . "'";?>>
<?php
if(common::hasPriv('project', 'index'))
{
echo html::a($this->createLink('project', 'index', "projectID=$project->id"), $project->name, '', "title='{$project->name}'");
}
else
{
echo "<span title='{$project->name}'>{$project->name}</span>";
}
?>
</div>
<?php endforeach;?>
<?php endif;?>
</div>
</td>
<?php endif;?>
<?php endforeach;?>
</tr>
<?php $colorIndex ++;?>
<?php if($colorIndex > 9) $colorIndex = 0;?>
<?php endforeach;?>
</tbody>
</table>
</div>
<div id='kanbanList'>
<?php foreach($kanbanGroup as $type => $projectGroup):?>
<div class='panel kanban-panel'>
<div class='panel-heading'>
<strong><?php echo $lang->project->typeList[$type];?></strong>
</div>
<div class='panel-body'>
<div id='kanban-<?php echo $type;?>' class='kanban'></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endforeach;?>
<?php endif;?>
<?php
$kanbanColumns = array();
$kanbanColumns['waitProject'] = array('name' => $lang->project->waitProjects, 'type' => 'waitProject');
$kanbanColumns['doingProject'] = array('name' => $lang->project->doingProjects, 'type' => 'doingProject');
$kanbanColumns['doingExecution'] = array('name' => $lang->project->doingExecutions, 'type' => 'doingExecution');
$kanbanColumns['closedProject'] = array('name' => $lang->project->closedProjects, 'type' => 'closedProject');
$userPrivs = array();
$userPrivs['project'] = common::hasPriv('project', 'index');
$userPrivs['execution'] = common::hasPriv('execution', 'task');
js::set('kanbanColumns', $kanbanColumns);
js::set('userPrivs', $userPrivs);
js::set('kanbanGroup', $kanbanGroup);
js::set('latestExecutions', $latestExecutions);
js::set('programPairs', $programPairs);
js::set('doingText', $lang->project->statusList['doing']);
?>
<?php endif; ?>
<?php include '../../common/view/footer.html.php';?>
+1 -1
View File
@@ -199,5 +199,5 @@ $config->story->datatable->fieldList['caseCount']['name'] = $lang->story->ca
$config->story->datatable->fieldList['actions']['title'] = 'actions';
$config->story->datatable->fieldList['actions']['fixed'] = 'right';
$config->story->datatable->fieldList['actions']['width'] = '200';
$config->story->datatable->fieldList['actions']['width'] = $app->tab == 'project' ? '220' : '200';
$config->story->datatable->fieldList['actions']['required'] = 'yes';
+1 -1
View File
@@ -3769,7 +3769,7 @@ class storyModel extends model
common::printIcon('story', 'change', $vars . "&from=$story->from", $story, 'list', 'alter', '', '', false, "data-group=$story->from");
common::printIcon('story', 'review', $vars . "&from=$story->from", $story, 'list', 'search', '', '', false, "data-group=$story->from");
if($this->app->tab != 'project') common::printIcon('story', 'recall', $vars, $story, 'list', 'back', 'hiddenwin', '', '', '', $this->lang->story->recall);
common::printIcon('story', 'recall', $vars, $story, 'list', 'back', 'hiddenwin', '', '', '', $this->lang->story->recall);
common::printIcon('story', 'close', $vars, $story, 'list', '', '', 'iframe', true);
common::printIcon('story', 'edit', $vars . "&from=$story->from", $story, 'list', '', '', '', false, "data-group=$story->from");
if($story->type != 'requirement') common::printIcon('story', 'createCase', "productID=$story->product&branch=$story->branch&module=0&from=&param=0&$vars", $story, 'list', 'sitemap', '', '', false, "data-app='qa'");
+10 -10
View File
@@ -25,7 +25,7 @@
#task .task-desc > ul > li[data-target]:after {content: ' '; display: block; position: absolute; left: 0; top: -100%; width: 100%; height: 100%; border: 2px solid #F1A325; transition: top .5s; pointer-events: none; border-radius: 4px;}
#task .task-desc > ul > li[data-target]:before {font-family: ZentaoIcon; content: '\e836'; color: #333;}
#task .task-desc > ul > li[data-target].finish:after {top: 100%;}
#task .task-desc > ul > li[data-target].finish:before {content: '\e92f'; color: #00da88;}
#task .task-desc > ul > li[data-target].finish:before {content: '\e92f'; color: #43a047;}
#task .task-desc > ul > li[data-target].active {background: #FFF0D5;}
#task .task-desc > ul > li[data-target].active:after {top: 0%;}
#task .task-desc > ul > li[data-target].active:before {font-family: ZentaoIcon; color: #EA644A; content: '\e92c';}
@@ -35,27 +35,27 @@
#openTaskPage > div > .icon {display: inline-block; width: 30px; text-align: center;}
#openTaskPage:hover {background: #EBF2F9;}
#openTaskPage > .normal {top: 0;}
#openTaskPage.open {color: #00da88; background: #DDF4DF;}
#openTaskPage.open {color: #43a047; background: #DDF4DF;}
#openTaskPage.open:hover {color: #006af1; background: #EBF2F9;}
#openTaskPage > .opened, #openTaskPage > .reload, #openTaskPage.open > .reload {top: 36px;}
#openTaskPage.open > .normal, #openTaskPage.open:hover > .opened {top: -36px;}
#openTaskPage.open > .opened, #openTaskPage.open:hover > .reload {top: 0;}
#task.finish > .panel-heading {background: #00da88;}
#task.finish > .panel-heading {background: #43a047;}
#task.finish > .panel-heading .task-name {text-decoration: line-through;}
#task.finish.finish > .panel-heading > .pull-right {opacity: 1; transform: scale(1);}
#task .task-nav {color: #006af1; display: inline-block; background: #EBF2F9; padding: 1px 5px 0; transition: all .2s;}
#task .task-nav:hover {color: #fff; background: #006af1; cursor: pointer;}
#tasksProgress {background: #ddd; position: relative;}
#tasksProgress > .progress-bar {background: #006af1; height: 8px; border-radius: 4px; margin-bottom: 15px; transition: background .4s, width .4s;}
#tasksProgress.finish > .progress-bar {width: 100% !important; background: #00da88;}
#tasksProgress.finish > .progress-bar {width: 100% !important; background: #43a047;}
#tasksProgress .progress-text {position: absolute; right: 0; top: -25px; font-weight: bold; opacity: 0.7; color: #006af1;}
#tasksProgress.finish .progress-text {color: #00da88; opacity: 1;}
#tasksProgress.finish .progress-text {color: #43a047; opacity: 1;}
#tasks {background: #fff; box-shadow: 0 1px 1px rgba(0,0,0,.05), 0 2px 6px 0 rgba(0,0,0,0.045); border-radius: 6px;}
#tasks > li > a {padding: 8px 8px 8px 12px;}
#tasks > li > a > .pull-right {filter: alpha(opacity=0); opacity: 0; transition: opacity .4s, transform .4s; transform: scale(2.5, 2); color: #00da88;}
#tasks > li.finish > a, #tasks > li.finish.active > a {box-shadow: inset 4px 0 0 #00da88; border-left-color: #00da88; color: #808080;}
#tasks > li > a > .pull-right {filter: alpha(opacity=0); opacity: 0; transition: opacity .4s, transform .4s; transform: scale(2.5, 2); color: #43a047;}
#tasks > li.finish > a, #tasks > li.finish.active > a {box-shadow: inset 4px 0 0 #43a047; border-left-color: #43a047; color: #808080;}
#tasks > li.finish > a > .pull-right {filter: alpha(opacity=100); opacity: 1; transform: scale(1);}
#tasks > li.finish > a > .task-name {text-decoration: line-through;}
#tasks > li.active > a, #tasks > li.active.finish > a {box-shadow: inset 4px 0 0 #006af1; border-color: #ddd; border-left-color: #006af1; color: #006af1; background: #EBF2F9;}
@@ -63,16 +63,16 @@
#taskModalBack {position: absolute; top: 0; left: 0; bottom: 0; right: 0; background: #fff; transition: opacity .4s; opacity: 0; display: none;}
#taskModalBack.in {filter: alpha(opacity=60); opacity: 0.6;}
#taskModal {width: 500px; position: absolute; background: #00da88; color: #fff; text-align: center; overflow: hidden; transform: scale(.5) translate(0, 200px) rotateX(120deg); transitizon: transform .4s, opacity .4s; opacity: 0; transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); top: 0; padding: 30px; left: 50%; margin-left: -250px; top: 20%; box-shadow: 0 0 20px 0 rgba(0,0,0,.25); display: none;}
#taskModal {width: 500px; position: absolute; background: #43a047; color: #fff; text-align: center; overflow: hidden; transform: scale(.5) translate(0, 200px) rotateX(120deg); transition: transform .4s, opacity .4s; opacity: 0; transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); top: 0; padding: 30px; left: 50%; margin-left: -250px; top: 20%; box-shadow: 0 0 20px 0 rgba(0,0,0,.25); display: none;}
#taskModal.in {transform: scale(1) translate(0) rotateX(0); opacity: 1;}
#taskModal h3 {margin-bottom: 20px;}
#taskModal .start-icon > .icon-spin {-moz-animation: spin 10s infinite linear; -o-animation: spin 10s infinite linear; -webkit-animation: spin 10s infinite linear; animation: spin 10s infinite linear;}
#taskModal .start-icon {width: 60px; height: 60px; text-align: center; margin: 10px auto; position: relative;}
#taskModal .start-icon > .icon-front {font-size: 56px; color: #fff; line-height: 60px; width: 60px; height: 60px; position: absolute; left: 0; top: 0;}
#taskModal .btn-success {border-color: #DDF4DF;}
#taskModal .btn-success:hover {background: #00c178;}
#taskModal > .close {position: absolute; right: 15px; top: 10px;}
#taskModal > .finish-all {display: none;}
#taskModal > .finish {display: block;}
#taskModal.show-all > .finish-all {display: block;}
#taskModal.show-all > .finish {display: none;}
#taskModal .btn-outline {background-color: transparent; border: 1px solid #fff; color: #fff}
#taskModal .btn-outline:hover {background-color: #fff; color: #43a047}
+2 -2
View File
@@ -21,12 +21,12 @@
<div class='finish-all'>
<div class='start-icon'><i class='icon icon-check-circle icon-front'></i></div>
<h3><?php echo $lang->tutorial->congratulation;?></h3>
<button type='button' class='btn btn-success btn-reset-tasks'><i class='icon icon-restart'></i> <?php echo $lang->tutorial->restart;?></button> &nbsp; <a href='<?php echo helper::createLink('tutorial', 'quit');?>' class='btn btn-success'><i class='icon icon-signout'></i> <?php echo $lang->tutorial->exit;?></a>
<button type='button' class='btn btn-outline btn-reset-tasks'><i class='icon icon-restart'></i> <?php echo $lang->tutorial->restart;?></button> &nbsp; <a href='<?php echo helper::createLink('tutorial', 'quit');?>' class='btn btn-outline'><i class='icon icon-signout'></i> <?php echo $lang->tutorial->exit;?></a>
</div>
<div class='finish'>
<div class='start-icon'><i class='icon icon-check-circle icon-front'></i></div>
<h3><?php echo $lang->tutorial->congratulateTask;?></h3>
<button type='button' class='btn btn-success btn-next-task btn-task'><?php echo $lang->tutorial->nextTask;?> <i class='icon icon-angle-right'></i></button>
<button type='button' class='btn btn-outline btn-next-task btn-task'><?php echo $lang->tutorial->nextTask;?> <i class='icon icon-angle-right'></i></button>
</div>
</div>
</div>
+6
View File
@@ -0,0 +1,6 @@
/*!
* ZUI: ZUI Kanban View - v1.9.2 - 2021-09-06
* http://openzui.com
* GitHub: https://github.com/easysoft/zui.git
* Copyright (c) 2021 cnezsoft.com; Licensed MIT
*/.kanban{min-height:300px;overflow-x:auto}.kanban-header,.kanban-lane,.kanban-sub-lane{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;flex-direction:row;background-color:#f1f3f5;border-bottom:1px solid #d5d9de;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.no-flex .kanban-lane{padding-left:20px}.kanban-header{padding-left:20px;background-color:rgba(0,0,0,.07)}.kanban-col{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.not-use-flex .kanban-col{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none}.no-flex .kanban-col{float:left}.kanban-col+.kanban-col{border-left:1px solid #d5d9de}.kanban-header-col{position:relative;height:32px}.kanban-header-col>.title{display:block;margin:6px 32px;line-height:20px;text-align:center}.kanban-header-col>.title>.text{display:inline-block;max-width:100px;margin:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kanban-header-col>.title>.count,.kanban-header-col>.title>.icon{position:relative;top:-6px}.kanban-header-col[data-id=ADD]>.title{margin:6px;opacity:.7}.kanban-header-col[data-id=ADD]>.actions{display:none}.kanban-header-col>.actions{position:absolute;top:0;right:0}.kanban-header-col>.actions>.btn{min-width:32px}.kanban-header-col>.actions>.btn>.icon{opacity:.5}.kanban-lane{min-height:100px;margin-bottom:3px}.kanban-lane-name{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:20px;padding:10px 0;color:#fff;text-align:center;background-color:#3dc6fd;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none}.no-flex .kanban-lane-name{position:absolute;top:0;bottom:0;left:0}.kanban-header+.kanban-lane>.kanban-lane-name{margin-top:-33px}.kanban-lane-name .char{display:block;line-height:1;white-space:pre}.kanban-lane-name .char.rotate-90{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.kanban-lane-name.is-arab{font-size:12px;-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.kanban-lane-name.is-arab .char{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.kanban-sub-lanes{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;flex-direction:column;-webkit-box-flex:1;-webkit-flex:auto;-ms-flex:auto;flex:auto;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-pack:stretch;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column}.kanban-sub-lane{-webkit-box-flex:1;-webkit-flex:auto;-ms-flex:auto;flex:auto}.kanban-lane-col{max-height:300px;overflow:auto}.kanban-lane-col.drop-to{outline:3px solid rgba(255,190,57,.25);outline-offset:-3px}.kanban-lane-col.drop-to .kanban-lane-actions>.btn{background-color:rgba(255,190,57,.25);border:1px dotted #ff9800}.kanban-lane-col.drop-to .kanban-lane-actions>.btn>span{opacity:0}.kanban-lane-col[data-type=EMPTY]{background-color:#fff}.kanban-lane-items{padding:0 15px}.kanban-lane-actions{padding:10px 15px}.kanban-item{padding:10px;margin-top:10px;background:#fff;border:1px solid #fff;border-radius:4px;-webkit-transition:-webkit-box-shadow .2s,-webkit-transform .2s;-o-transition:box-shadow .2s,-o-transform .2s;transition:-webkit-box-shadow .2s,-webkit-transform .2s;transition:box-shadow .2s,transform .2s;transition:box-shadow .2s,transform .2s,-webkit-box-shadow .2s,-webkit-transform .2s,-o-transform .2s}.kanban-item.drag-from{background-color:#aaa;opacity:.2}.kanban-item:hover{border-color:rgba(0,0,0,.1);-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.09);box-shadow:0 4px 10px 0 rgba(0,0,0,.09)}.kanban-item.drag-shadow{border-color:rgba(0,0,0,.2);-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.09),0 4px 20px 0 rgba(0,0,0,.5);box-shadow:0 4px 10px 0 rgba(0,0,0,.09),0 4px 20px 0 rgba(0,0,0,.5);-webkit-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}
File diff suppressed because one or more lines are too long