Merge branch '20.x' of https://github.com/easysoft/zentaopms into 20.x
This commit is contained in:
@@ -1118,7 +1118,7 @@ class block extends control
|
||||
$productID = isset($this->session->product) ? 0 : $this->session->product;
|
||||
if($productID && !array_key_exists($productID, $products)) $productID = 0;
|
||||
|
||||
$this->view->plans = $this->loadModel('programplan')->getDataForGantt($this->session->program, $productID, 0, 'task');
|
||||
$this->view->plans = $this->loadModel('programplan')->getDataForGantt($this->session->program, $productID, 0, 'task', false);
|
||||
$this->view->products = $products;
|
||||
$this->view->productID = $productID;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Delete block.
|
||||
*
|
||||
* @param int $index
|
||||
*
|
||||
* @param int $index
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
@@ -26,8 +26,8 @@ function deleteBlock(index)
|
||||
|
||||
/**
|
||||
* Sort blocks.
|
||||
*
|
||||
* @param array $orders format is {'blockid' : 1, 'block1' : 2}
|
||||
*
|
||||
* @param array $orders format is {'blockid' : 1, 'block1' : 2}
|
||||
* @param function $callback
|
||||
* @access public
|
||||
* @return void
|
||||
@@ -65,7 +65,7 @@ function refreshBlock($panel, afterRefresh)
|
||||
else
|
||||
{
|
||||
$panel.children('.panel-move-handler,style,script').remove();
|
||||
$panel.find('.panel-body,.empty-tip').replaceWith($data);
|
||||
$panel.find('.panel-body,.empty-tip').first().replaceWith($data);
|
||||
}
|
||||
$panel.find('.progress-pie').progressPie();
|
||||
if($.isFunction(afterRefresh))
|
||||
@@ -78,6 +78,7 @@ function refreshBlock($panel, afterRefresh)
|
||||
});
|
||||
}
|
||||
$panel.find('.tablesorter').sortTable();
|
||||
$panel.find('.chosen').chosen();
|
||||
initTableHeader($panel);
|
||||
$(".sparkline").sparkline();
|
||||
}).fail(function()
|
||||
@@ -121,7 +122,7 @@ function initTableHeader($wrapper)
|
||||
|
||||
if(!$table.length || !$table.children('thead').length || ($panel.find('#assigntomeBlock').length && $panel.find('#assigntomeBlock > div').length > 1)) return;
|
||||
var isFixed = $panel.find('.panel-body').height() < $table.outerHeight();
|
||||
|
||||
|
||||
$panel.toggleClass('with-fixed-header', isFixed);
|
||||
var $header = $panel.children('.table-header-fixed').toggle(isFixed);
|
||||
if(!isFixed)
|
||||
@@ -194,7 +195,7 @@ function checkRefreshProgress($dashboard, doneCallback)
|
||||
* @param index $index
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
*/
|
||||
function hiddenBlock(index)
|
||||
{
|
||||
$.getJSON(createLink('block', 'delete', 'index=' + index + '&module=' + module + '&type=hidden'), function(data)
|
||||
@@ -240,7 +241,7 @@ $(function()
|
||||
{
|
||||
resizeBlock(e.element.data('id'), isSideCol ? 4 : 8);
|
||||
});
|
||||
|
||||
|
||||
e.element.toggleClass('block-sm', isSideCol);
|
||||
}
|
||||
}).on('click', '.refresh-panel', function()
|
||||
|
||||
@@ -1,60 +1,79 @@
|
||||
<div class="panel-body">
|
||||
<div id='cmmiGantt'>
|
||||
<?php echo html::select('cmmiGanttProductID', $products, $productID, "class='form-control chosen'"); ?>
|
||||
<?php if(empty($plans['data'])): ?>
|
||||
<div class='empty-tip'><?php echo $lang->programplan->noData;?></div>
|
||||
<?php else:?>
|
||||
<div class='gantt clearfix'>
|
||||
<div class='gantt-plans pull-left'></div>
|
||||
<div class='gantt-container scrollbar-hover'>
|
||||
<div class='gantt-canvas'></div>
|
||||
<div class='gantt-canvas'>
|
||||
<div class='gantt-today'><div><?php echo $lang->programplan->today; ?></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
</div>
|
||||
<style>
|
||||
.block-cmmigantt > .panel-body {overflow: visible!important;}
|
||||
#cmmiGantt {position: relative;}
|
||||
#cmmiGanttProductID_chosen {position: absolute; top: -39px; left: 120px; width: 150px!important;}
|
||||
.block-cmmigantt > .panel-body {overflow: visible!important}
|
||||
#cmmiGantt {position: relative}
|
||||
#cmmiGanttProductID_chosen {position: absolute; top: -39px; left: 120px; width: 150px!important}
|
||||
[lang="zh-cn"] #cmmiGanttProductID_chosen {left: 85px}
|
||||
.gantt-plans {padding: 20px 0 12px}
|
||||
.gantt-plan {margin-top: 10px; line-height: 20px;}
|
||||
.gantt-container {position: absolute; left: 100px; top: 0; right: 0; bottom: -10px; overflow-x: auto; padding-top: 20px;}
|
||||
.gantt-plans {padding: 20px 0 22px; max-height: 380px; overflow: hidden;}
|
||||
.gantt-plan {margin-top: 10px; line-height: 20px}
|
||||
.gantt-container {position: absolute; left: 100px; top: 0; right: 0; bottom: -10px; overflow: auto; padding-top: 20px; max-height: 380px;}
|
||||
.gantt-canvas {border: 1px solid #dddee4; border-style: solid dotted; position: relative}
|
||||
.gantt-row {height: 50px; position: relative; z-index: 1}
|
||||
.gantt-row:hover {background-color: rgba(0,0,0,.05);}
|
||||
.gantt-row:hover {background-color: rgba(0,0,0,.05)}
|
||||
.gantt-bar {height: 15px; background: #dddee4; position: absolute; left: 0; top: 10px}
|
||||
.gantt-bar-progress {height: 15px; margin-bottom: 5px}
|
||||
.gantt-task-info {white-space: nowrap; width: 100%; overflow: visible;}
|
||||
.gantt-col {position: absolute; z-index: 0; top: 0; border-right: 1px dotted #dddee4;}
|
||||
.gantt-task-info {white-space: nowrap; width: 100%; overflow: visible}
|
||||
.gantt-col {position: absolute; z-index: 0; top: 0; border-right: 1px dotted #dddee4}
|
||||
.gantt-col-time {position: absolute; top: -18px; left: 0}
|
||||
.gantt-today {position: absolute; top: -16px; left: 0; bottom: 0; border-left: #00da88 dotted 1px;}
|
||||
.gantt-today > div {position: absolute; top: 0; left: 0; font-size: 12px; line-height: 14px; padding: 0 3px; background: #00da88; color: #fff; white-space: nowrap; z-index: 10;}
|
||||
</style>
|
||||
<script>
|
||||
function initCmmiGanttBlock()
|
||||
{
|
||||
var ganttData = <?php echo $plans; ?>;
|
||||
if(!ganttData.data) ganttData = {data: []};
|
||||
/* Init product select control */
|
||||
var $cmmiGanttProductID = $('#cmmiGanttProductID');
|
||||
$cmmiGanttProductID.on('change', function()
|
||||
{
|
||||
$.get(createLink('product', 'ajaxSetState', 'productID=' + $cmmiGanttProductID.val()), function()
|
||||
{
|
||||
refreshBlock($cmmiGanttProductID.closest('.panel'));
|
||||
});
|
||||
});
|
||||
|
||||
var plans = [];
|
||||
var tasks = [];
|
||||
var plansMap = {};
|
||||
<?php if(!empty($plans['data'])): ?>
|
||||
var ganttData = <?php echo json_encode($plans['data']); ?>;
|
||||
if(!ganttData) return;
|
||||
|
||||
var plans = [];
|
||||
var tasks = [];
|
||||
var plansMap = {};
|
||||
var startDatetime = Number.MAX_SAFE_INTEGER;
|
||||
var endDatetime = 0;
|
||||
var minTimeGap = Number.MAX_SAFE_INTEGER;
|
||||
var $gantt = $('#cmmiGantt');
|
||||
var ONE_DAY = 24 * 3600 * 1000;
|
||||
var endDatetime = 0;
|
||||
var minTimeGap = Number.MAX_SAFE_INTEGER;
|
||||
var $gantt = $('#cmmiGantt');
|
||||
var ONE_DAY = 24 * 3600 * 1000;
|
||||
var TIME_GAP_STEP = 7;
|
||||
var MIN_COL_WIDTH = 60;
|
||||
|
||||
$.each(ganttData.data, function(index, item)
|
||||
$.each(ganttData, function(index, item)
|
||||
{
|
||||
plansMap[item.id] = item;
|
||||
if(item.type === 'plan' && item.parent === '0')
|
||||
{
|
||||
item.startDatetime = createDatetime(item.start_date);
|
||||
item.endDatetime = createDatetime(item.deadline);
|
||||
startDatetime = Math.min(startDatetime, item.startDatetime);
|
||||
endDatetime = Math.max(endDatetime, item.endDatetime);
|
||||
minTimeGap = Math.min(minTimeGap, endDatetime - startDatetime);
|
||||
item.tasks = [];
|
||||
item.endDatetime = createDatetime(item.deadline);
|
||||
startDatetime = Math.min(startDatetime, item.startDatetime);
|
||||
endDatetime = Math.max(endDatetime, item.endDatetime);
|
||||
minTimeGap = Math.min(minTimeGap, endDatetime - startDatetime);
|
||||
item.tasks = [];
|
||||
item.completeTasks = [];
|
||||
item.progress = 0;
|
||||
item.progress = 0;
|
||||
plans.push(item);
|
||||
}
|
||||
else if(item.type === 'task')
|
||||
@@ -73,15 +92,16 @@
|
||||
plan.tasks.push(task);
|
||||
});
|
||||
|
||||
var $plans = $gantt.find('.gantt-plans');
|
||||
var $plans = $gantt.find('.gantt-plans');
|
||||
var $ganttContainer = $gantt.find('.gantt-container');
|
||||
var $ganttCanvas = $gantt.find('.gantt-canvas');
|
||||
var themeColor = $.getThemeColor('primary');
|
||||
var days = Math.ceil((endDatetime - startDatetime) / ONE_DAY);
|
||||
var canvasHeight = plans.length * 50 + 10;
|
||||
var $ganttCanvas = $gantt.find('.gantt-canvas');
|
||||
var themeColor = $.getThemeColor('primary');
|
||||
var canvasHeight = plans.length * 50 + 10;
|
||||
|
||||
var days = Math.ceil((endDatetime - startDatetime) / ONE_DAY);
|
||||
minTimeGap = Math.max(1, Math.ceil(minTimeGap / ONE_DAY));
|
||||
|
||||
// Update gantt plans and bars
|
||||
/* Update gantt plans and bars */
|
||||
$.each(plans, function(index, plan)
|
||||
{
|
||||
plan.progress = !plan.tasks.length ? 0 : plan.progress / plan.tasks.length;
|
||||
@@ -101,24 +121,26 @@
|
||||
$ganttCanvas.append($row);
|
||||
});
|
||||
|
||||
// Layout gantt container
|
||||
/* Layout gantt container */
|
||||
$ganttContainer.css('left', $plans.width() + 15);
|
||||
$ganttCanvas.css('height', canvasHeight);
|
||||
|
||||
var $cmmiGanttProductID = $('#cmmiGanttProductID');
|
||||
if(!$cmmiGanttProductID.data('chosen')) $cmmiGanttProductID.chosen();
|
||||
$cmmiGanttProductID.on('change', function()
|
||||
{
|
||||
$.get(createLink('product', 'ajaxSetState', 'productID=' + $cmmiGanttProductID.val()), function()
|
||||
{
|
||||
refreshBlock($cmmiGanttProductID.closest('.panel'));
|
||||
});
|
||||
});
|
||||
|
||||
/* Layout gantt */
|
||||
layoutGantt();
|
||||
$(window).on('resize', layoutGantt);
|
||||
setTimeout(layoutGantt, 100);
|
||||
|
||||
/* Bind events */
|
||||
$ganttContainer.on('scroll', function()
|
||||
{
|
||||
$plans.scrollTop($ganttContainer.scrollTop());
|
||||
});
|
||||
|
||||
/**
|
||||
* Layout gantt
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function layoutGantt()
|
||||
{
|
||||
var minWidth = $ganttContainer.width();
|
||||
@@ -133,8 +155,8 @@
|
||||
var $col = $('<div class="gantt-col"></div>');
|
||||
$col.css(
|
||||
{
|
||||
left: i * colWidth,
|
||||
width: colWidth,
|
||||
left: i * colWidth,
|
||||
width: colWidth,
|
||||
height: canvasHeight
|
||||
});
|
||||
var colTime = startDatetime + i * timeGap * ONE_DAY;
|
||||
@@ -147,21 +169,32 @@
|
||||
var $planRow = $gantt.find('.gantt-row[data-id="' + plan.id + '"]');
|
||||
$planRow.find('.gantt-bar').css(
|
||||
{
|
||||
left: Math.floor((plan.startDatetime - startDatetime) * pxPerMs),
|
||||
left: Math.floor((plan.startDatetime - startDatetime) * pxPerMs),
|
||||
width: Math.floor((plan.endDatetime - plan.startDatetime) * pxPerMs)
|
||||
});
|
||||
});
|
||||
|
||||
$gantt.find('.gantt-today').css('left', (Date.now() - startDatetime) * pxPerMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create date from string
|
||||
*
|
||||
* @param {String} dateStr like '2020-08-02'
|
||||
* @return {Number} Date timestramp
|
||||
*/
|
||||
function createDatetime(dateStr)
|
||||
{
|
||||
dateStr = dateStr.split('-');
|
||||
var year = Number.parseInt(dateStr[0].length > 3 ? dateStr[0] : dateStr[2], 10);
|
||||
dateStr = dateStr.split('-');
|
||||
var year = Number.parseInt(dateStr[0].length > 3 ? dateStr[0] : dateStr[2], 10);
|
||||
var month = Number.parseInt(dateStr[1], 10);
|
||||
var day = Number.parseInt(dateStr[2].length > 3 ? dateStr[0] : dateStr[2], 10);
|
||||
var day = Number.parseInt(dateStr[2].length > 3 ? dateStr[0] : dateStr[2], 10);
|
||||
return new Date(year, month - 1, day).getTime();
|
||||
}
|
||||
|
||||
<?php endif;?>
|
||||
}
|
||||
|
||||
initCmmiGanttBlock();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
@@ -8,13 +8,12 @@ $maxConsumed = 0;
|
||||
<div class='panel-body'>
|
||||
<div class='table-row'>
|
||||
<div class='table-col chart-titles'>
|
||||
|
||||
<?php foreach($programs as $program):?>
|
||||
<?php
|
||||
$maxTeamCount = max($program->teamCount, $maxTeamCount);
|
||||
$maxConsumed = max($program->consumed, $maxConsumed);
|
||||
?>
|
||||
<div class='chart-title text-ellipsis'><strong><?php echo $program->name;?></strong></div>
|
||||
<div class='chart-title text-ellipsis'><span><?php echo $program->name;?></span></div>
|
||||
<?php endforeach;?>
|
||||
</div>
|
||||
<div class='table-col chart-rows'>
|
||||
@@ -47,26 +46,26 @@ $maxConsumed = 0;
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.block-programteam .panel-body {margin-top: -10px;}
|
||||
.chart-titles {width: 110px; padding-right: 10px; padding-top: 30px; position: relative; z-index: 1;}
|
||||
.chart-rows {position: relative; z-index: 0;}
|
||||
.chart-title {width: 100px; line-height: 20px; padding: 5px 0; height: 30px;}
|
||||
.chart-title > strong {background: #fff;}
|
||||
.chart-col-title {line-height: 20px; margin-bottom: 10px; padding: 0 10px;}
|
||||
.program-team, .program-consumed {padding: 0;}
|
||||
.program-team {border-right: 1px solid #e5e8ec;}
|
||||
.program-team > .chart-col-title {text-align: right;}
|
||||
.chart-col-item {height: 30px; padding: 5px 0; position: relative;}
|
||||
.chart-col-text {width: 50px; position: absolute; top: 5px; line-height: 20px; left: 0;}
|
||||
.program-team .chart-col-item {padding-left: 50px; position: relative;}
|
||||
.program-team .chart-col-item:before {position: absolute; content: ' '; z-index: 0; left: -100px; top: 14px; right: 0; border-top: 1px dotted #e5e8ec;}
|
||||
.program-consumed .chart-col-item {padding-right: 50px;}
|
||||
.chart-col-item .progress {background: none; border-radius: 0; overflow: visible; margin: 6px 0 0 0; position: relative; z-index: 1;}
|
||||
.chart-col-item .progress > .progress-bar {border-radius: 0; position: relative; min-width: 1px;}
|
||||
.program-team .chart-col-item .progress > .progress-bar {float: right; background-color: #39cfff;}
|
||||
.chart-col-item .progress .progress-text {position: absolute; top: -6px; color: #333; width: 50px; line-height: 20px; white-space: nowrap; overflow: visible;}
|
||||
.program-team .progress .progress-text {text-align: right; left: -50px; padding-right: 5px;}
|
||||
.program-consumed .progress .progress-text {text-align: left; right: -50px; padding-left: 5px;}
|
||||
.block-programteam .panel-body {margin-top: -10px}
|
||||
.chart-titles {width: 110px; padding-right: 10px; padding-top: 30px; position: relative; z-index: 1}
|
||||
.chart-rows {position: relative; z-index: 0}
|
||||
.chart-title {width: 100px; line-height: 20px; padding: 5px 0; height: 30px}
|
||||
.chart-title > span {background: #fff}
|
||||
.chart-col-title {line-height: 20px; margin-bottom: 10px; padding: 0 10px}
|
||||
.program-team, .program-consumed {padding: 0}
|
||||
.program-team {border-right: 1px solid #e5e8ec}
|
||||
.program-team > .chart-col-title {text-align: right}
|
||||
.chart-col-item {height: 30px; padding: 5px 0; position: relative}
|
||||
.chart-col-text {width: 50px; position: absolute; top: 5px; line-height: 20px; left: 0}
|
||||
.program-team .chart-col-item {padding-left: 50px; position: relative}
|
||||
.program-team .chart-col-item:before {position: absolute; content: ' '; z-index: 0; left: -100px; top: 14px; right: 0; border-top: 1px dotted #e5e8ec}
|
||||
.program-consumed .chart-col-item {padding-right: 50px}
|
||||
.chart-col-item .progress {background: none; border-radius: 0; overflow: visible; margin: 6px 0 0 0; position: relative; z-index: 1}
|
||||
.chart-col-item .progress > .progress-bar {border-radius: 0; position: relative; min-width: 1px}
|
||||
.program-team .chart-col-item .progress > .progress-bar {float: right; background-color: #39cfff}
|
||||
.chart-col-item .progress .progress-text {position: absolute; top: -6px; color: #333; width: 50px; line-height: 20px; white-space: nowrap; overflow: visible}
|
||||
.program-team .progress .progress-text {text-align: right; left: -50px; padding-right: 5px}
|
||||
.program-consumed .progress .progress-text {text-align: left; right: -50px; padding-left: 5px}
|
||||
</style>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
|
||||
@@ -347,6 +347,7 @@ class programModel extends model
|
||||
->setIF($this->post->acl != 'custom', 'whitelist', '')
|
||||
->setIF($this->post->acl == 'custom' and !isset($_POST['whitelist']), 'whitelist', '')
|
||||
->setDefault('team', $this->post->name)
|
||||
->setDefault('isCat', 0)
|
||||
->join('whitelist', ',')
|
||||
->stripTags($this->config->program->editor->edit['id'], $this->config->allowedTags)
|
||||
->remove('products, branch, uid, plans')
|
||||
|
||||
@@ -106,10 +106,11 @@ class programplanModel extends model
|
||||
* @param int $productID
|
||||
* @param int $baselineID
|
||||
* @param string $selectCustom
|
||||
* @param bool $returnJson
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDataForGantt($programID, $productID, $baselineID = 0, $selectCustom = '')
|
||||
public function getDataForGantt($programID, $productID, $baselineID = 0, $selectCustom = '', $returnJson = true)
|
||||
{
|
||||
$this->loadModel('stage');
|
||||
|
||||
@@ -240,7 +241,7 @@ class programplanModel extends model
|
||||
$datas['data'][$index]->taskProgress = $progress;
|
||||
}
|
||||
|
||||
return json_encode($datas);
|
||||
return $returnJson ? json_encode($datas) : $datas;
|
||||
}
|
||||
|
||||
public function getTotalPercent($plan)
|
||||
|
||||
Reference in New Issue
Block a user