gantt.html.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. <?php
  2. /**
  3. * The gantt of programplan module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package programplan
  9. * @version $Id: gantt.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php chdir(__DIR__);?>
  14. <?php include $app->getModuleRoot() . 'common/view/gantt.html.php';?>
  15. <?php if(isset($project) and $project->model == 'ipd') js::set('reviewPoints', json_encode($reviewPoints));?>
  16. <?php js::set('projectID', $projectID);?>
  17. <style>
  18. .submitBtn{display:none}
  19. .gantt_row_task:hover .submitBtn{ display: inline-block;}
  20. .editDeadline {display: none; line-height: normal !important; padding: 2px 6px; color: #fff !important;}
  21. td:hover > .editDeadline {display: inline-block !important;}
  22. .gantt_tree_content table {width: 100%;}
  23. .gantt_tree_content table td {padding: 0 !important;}
  24. #ganttView {height: 600px;}
  25. #mainContent:before {background: #fff;}
  26. .checkbox-primary {margin-top: 0px; margin-left: 10px;}
  27. form {display: block; margin-top: 0em; margin-block-end: 1em;}
  28. .gantt_task_content span.task-label, .gantt_task_content span.label-pri{display: none;}
  29. .gantt_task_content .icon-seal{display: none;}
  30. #ganttPris > span {display: inline-block; line-height: 20px; min-width: 20px; border-radius: 2px;}
  31. .gantt_task_line.gantt_selected {box-shadow: 0 1px 1px rgba(0,0,0,.05), 0 2px 6px 0 rgba(0,0,0,.045)}
  32. .gantt_link_arrow_right {border-left-color: #2196F3;}
  33. .gantt_link_arrow_left {border-right-color: #2196F3;}
  34. .icon-confirm {color: #18a6fd; font-size: 18px;}
  35. .icon-seal{font-size: 18px;}
  36. .gantt_task_link .gantt_line_wrapper div{background-color: #2196F3;}
  37. .gantt_critical_link .gantt_line_wrapper>div {background-color: #e63030 !important;}
  38. .gantt_critical_link.start_to_start .gantt_link_arrow_right {border-left-color: #e63030 !important;}
  39. .gantt_critical_link.finish_to_start .gantt_link_arrow_right {border-left-color: #e63030 !important;}
  40. .gantt_critical_link.start_to_finish .gantt_link_arrow_left {border-right-color: #e63030 !important;}
  41. .gantt_critical_link.finish_to_finish .gantt_link_arrow_left {border-right-color: #e63030 !important;}
  42. .gantt_task_link.start_to_start .gantt_line_wrapper div { background-color: #DD55EA; }
  43. .gantt_task_link.start_to_start:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #DD55EA; }
  44. .gantt_task_link.start_to_start .gantt_link_arrow_right { border-left-color: #DD55EA; }
  45. .gantt_task_link.finish_to_start .gantt_line_wrapper div { background-color: #7576ba; }
  46. .gantt_task_link.finish_to_start:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #7576ba; }
  47. .gantt_task_link.finish_to_start .gantt_link_arrow_right { border-left-color: #7576ba; }
  48. .gantt_task_link.finish_to_finish .gantt_line_wrapper div { background-color: #55d822; }
  49. .gantt_task_link.finish_to_finish:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #55d822; }
  50. .gantt_task_link.finish_to_finish .gantt_link_arrow_left { border-right-color: #55d822; }
  51. .gantt_task_link.start_to_finish .gantt_line_wrapper div { background-color: #975000; }
  52. .gantt_task_link.start_to_finish:hover .gantt_line_wrapper div { box-shadow: 0 0 5px 0px #975000; }
  53. .gantt_task_link.start_to_finish .gantt_link_arrow_left { border-right-color: #975000; }
  54. .gantt_grid_head_owner_id {text-align: left}
  55. .gantt_critical_task{background:#e63030 !important; border-color:#9d3a3a !important;}
  56. .gantt_marker .gantt_marker_content {left: -15px; background-color: #f51e1e;}
  57. .gantt_row{cursor: pointer;}
  58. #ganttDownload, #ganttHeader {display: none;}
  59. #ganttContainer {margin-top: 40px;}
  60. #mainContent {padding: 10px 20px 20px 20px;}
  61. #mainContent:before {background: #fff;}
  62. #mainContent.loading {overflow: hidden}
  63. #mainContent.loading #ganttView {overflow: hidden}
  64. #mainContent.loading #ganttHeader {display: block; padding-bottom: 20px; margin: 0; height: 46px;}
  65. #mainContent.loading #ganttDownload {display: inline}
  66. #mainContent.loading #ganttContainer {padding: 40px;}
  67. #mainContent.loading .scrollVer_cell,
  68. #mainContent.loading .scrollVer_cell {display: none;}
  69. .gantt-fullscreen #header,
  70. .gantt-fullscreen #mainMenu,
  71. .gantt-fullscreen #footer {display: none!important;}
  72. .gantt-fullscreen #mainContent {position: fixed; top: 0; right: 0; bottom: 0; left: 0}
  73. .gantt_grid_head_cell.gantt_grid_head_text{overflow:visible;}
  74. .gantt_grid_head_cell, .gantt_scale_cell{color:#000000!important;}
  75. .gantt_tree_content{color:#838A9D;}
  76. .gantt_row > div:first-child .gantt_tree_content{color:#3C4353;}
  77. .gantt_task_line.gantt_task_inline_color{border:0px;}
  78. .gantt_grid_scale, .gantt_task_scale, .gantt_task_vscroll{background-color: #F2F7FF;}
  79. #myCover {display:none;left:12px!important;z-index:10!important;top:9px!important;height:unset!important;}
  80. .button-group{position: relative;}
  81. .flax{display: flex; margin-bottom: 10px;}
  82. .switchBtn > i {padding-left: 7px;}
  83. #mainContent > .pull-left > .btn-group > .text{display: block;margin-top: 7px;}
  84. #mainContent > .pull-left > .btn-group > a > .text{overflow: hidden;display: block;}
  85. #mainContent > .pull-right > .button-group .text{margin-left: 0px;}
  86. .pull-right .icon-plus.icon-sm:before{vertical-align: 4%;}
  87. #ganttView .gantt_resizer{min-width: unset !important;}
  88. div.delayed {background: #FFEBEB; color: #FB2B2B;}
  89. </style>
  90. <?php js::set('customUrl', $this->createLink('programplan', 'ajaxCustom'));?>
  91. <?php js::set('dateDetails', $dateDetails);?>
  92. <?php js::set('module', $app->rawModule);?>
  93. <?php js::set('method', $app->rawMethod);?>
  94. <?php js::set('ganttType', $ganttType);?>
  95. <?php js::set('showFields', $this->config->programplan->ganttCustom->ganttFields);?>
  96. <?php js::set('canGanttEdit', common::hasPriv('programplan', 'ganttEdit'));?>
  97. <?php js::set('zooming', isset($zooming) ? $zooming : 'day');?>
  98. <?php js::set('canEditDeadline', common::hasPriv('review', 'edit'));?>
  99. <div id='mainContent' class='main-content load-indicator' data-loading='<?php echo $lang->programplan->exporting;?>'>
  100. <?php if($this->app->getModuleName() == 'programplan'):?>
  101. <div class='btn-toolbar pull-left'>
  102. <div class='btn-group'>
  103. <?php if($project->stageBy == 'product'):?>
  104. <?php $viewName = $productID != 0 ? zget($productList,$productID) : $lang->product->allProduct;?>
  105. <a href='javascript:;' class='btn btn-link btn-limit text-ellipsis' data-toggle='dropdown' style="max-width: 120px;"><span class='text' title='<?php echo $viewName;?>'><?php echo $viewName;?></span> <span class='caret'></span></a>
  106. <ul class='dropdown-menu' style='max-height:240px; max-width: 300px; overflow-y:auto'>
  107. <?php
  108. $class = '';
  109. if($productID == 0) $class = 'class="active"';
  110. foreach($productList as $key => $productName)
  111. {
  112. $class = $productID == $key ? 'class="active"' : '';
  113. echo "<li $class>" . html::a($this->createLink('programplan', 'browse', "projectID=$projectID&productID=$key&type=gantt"), $productName, '', "title='{$productName}' class='text-ellipsis'") . "</li>";
  114. }
  115. ?>
  116. </ul>
  117. <?php else:?>
  118. <?php echo "<span class='text'>{$lang->programplan->gantt}</span>";?>
  119. <?php endif;?>
  120. </div>
  121. </div>
  122. <div class="pull-right btn-toolbar flax">
  123. <div class="btn-group">
  124. <?php echo html::a('',"<i class='icon-gantt-alt'></i> &nbsp;", '', "class='btn btn-icon text-primary switchBtn' title='{$lang->programplan->gantt}'");?>
  125. <?php echo html::a($this->createLink('project', 'execution', "status=all&projectID=$projectID"),"<i class='icon-list'></i> &nbsp;", '', "class='btn btn-icon switchBtn' title='{$lang->project->bylist}'");?>
  126. </div>
  127. <a href='javascript:fullScreen();' id='fullScreenBtn' class='btn btn-link'><i class='icon icon-fullscreen'></i> <?php echo $lang->programplan->full;?></a>
  128. <div class='button-group'>
  129. <button class='btn btn-link' data-toggle='dropdown'><i class='icon icon-export'></i> <span class='text'><?php echo $this->lang->export;?></span> <span class='caret'></span></button>
  130. <ul class='dropdown-menu' id='exportActionMenu'>
  131. <li><a href='javascript:exportGantt()'><?php echo $lang->execution->gantt->exportImg;?></a></li>
  132. <li><a href="javascript:exportGantt('pdf')"><?php echo $lang->execution->gantt->exportPDF;?></a></li>
  133. </ul>
  134. </div>
  135. <?php
  136. echo html::a(helper::createLink('programplan', 'ajaxcustom', '', '', true), '<i class="icon icon-cog-outline"></i> ' . $lang->settings, '', "class='iframe btn btn-link' data-width='45%'");
  137. if(common::hasPriv('programplan', 'create') and empty($product->deleted)) echo html::a($this->createLink('programplan', 'create', "projectID=$projectID"), "<i class='icon icon-sm icon-plus'> </i> " . $this->lang->programplan->create, '', "class='btn btn-primary'");
  138. ?>
  139. </div>
  140. <?php endif;?>
  141. <div id='ganttContainer'>
  142. <div class='gantt' id='ganttView'></div>
  143. </div>
  144. <?php $fileName = "gantt-export-{$projectID}";?>
  145. <a id='ganttDownload' target='hiddenwin' download='<?php echo "{$fileName}.png";?>'></a>
  146. <?php
  147. $typeHtml = '<div class="btn-group">';
  148. $typeHtml .= '<button class="btn btn-link" data-toggle="dropdown"><span class="text">' . $lang->programplan->ganttBrowseType[$ganttType] . '</span> <span class="caret"></span></button>';
  149. $typeHtml .= '<ul class="dropdown-menu">';
  150. foreach($lang->programplan->ganttBrowseType as $browseType => $typeName)
  151. {
  152. if($app->rawModule == 'review' and $app->rawMethod == 'assess')
  153. {
  154. $typeHtml .= '<li ' . ($ganttType == $browseType ? "class='active'" : '') . '>' . html::a($this->createLink('review', 'assess', "reivewID=$reviewID&from=&type=$browseType"), $typeName) . '</li>';
  155. }
  156. else
  157. {
  158. $typeHtml .= '<li ' . ($ganttType == $browseType ? "class='active'" : '') . '>' . html::a($this->createLink('programplan', 'browse', "projectID=$projectID&productID=$productID&type=$browseType"), $typeName) . '</li>';
  159. }
  160. }
  161. $typeHtml .= '</ul></div>';
  162. ?>
  163. </div>
  164. <div id="myCover">
  165. <div class="gantt_control">
  166. <div class='btn btn-link' id='ganttPris'>
  167. <strong><?php echo $lang->task->pri . " : "?></strong>
  168. <?php foreach($lang->execution->gantt->progressColor as $pri => $color):?>
  169. <?php if($pri <= 4):?>
  170. <span style="background:<?php echo $color?>"><?php echo $pri;?></span> &nbsp;
  171. <?php endif;?>
  172. <?php endforeach;?>
  173. </div>
  174. </div>
  175. <div id="gantt_here"></div>
  176. </div>
  177. <script>
  178. var scriptLoadedMap = {};
  179. var loadingPrefixText = '<?php echo $lang->programplan->exporting;?>';
  180. //After that you have to redefine the getFullscreenElement() method to return a custom root node that will be expanded to full screen:
  181. gantt.ext.fullscreen.getFullscreenElement = function() {
  182. return document.getElementById("myCover");
  183. }
  184. gantt.init("gantt_here");
  185. // before gantt is expanded to full screen
  186. gantt.attachEvent("onBeforeExpand",function(){
  187. $('#myCover').css('display', 'unset');
  188. $('#mainContent .pull-right').css('opacity', '0');
  189. $('.btn-toolbar').css('display', 'none');
  190. return true;
  191. });
  192. // when gantt exited the full screen mode
  193. gantt.attachEvent("onCollapse", function (){
  194. $('#myCover').css('display', 'none');
  195. $('#mainContent .pull-right').css('opacity', '1');
  196. $('.btn-toolbar').css('display', 'flex');
  197. });
  198. /**
  199. * Get remote script for export.
  200. *
  201. * @param string $url
  202. * @param function $successCallback
  203. * @param function $errorCallback
  204. * @access public
  205. * @return void
  206. */
  207. function getRemoteScript(url, successCallback, errorCallback)
  208. {
  209. if(scriptLoadedMap[url]) return successCallback && successCallback();
  210. $.getScript(url, function()
  211. {
  212. scriptLoadedMap[url] = true;
  213. if(successCallback) successCallback();
  214. }).fail(function()
  215. {
  216. if(errorCallback) errorCallback('Cannot load "' + url + '".');
  217. });
  218. }
  219. /**
  220. * Update export progress.
  221. *
  222. * @param int $progress
  223. * @access public
  224. * @return void
  225. */
  226. function updateProgress(progress)
  227. {
  228. var progressText = loadingPrefixText;
  229. if(progress < 1) progressText += Math.floor(progress * 100) + '%';
  230. $('#mainContent').attr('data-loading', progressText);
  231. }
  232. /**
  233. * Draw gantt to canvas.
  234. *
  235. * @param string $exportType
  236. * @param function $successCallback
  237. * @param function $errorCallback
  238. * @access public
  239. * @return void
  240. */
  241. function drawGanttToCanvas(exportType, successCallback, errorCallback)
  242. {
  243. updateProgress(0);
  244. exportType = exportType || 'image';
  245. var $ganttView = $('#ganttView');
  246. var oldHeight = $ganttView.css('height');
  247. var $ganttContainer = $('#ganttContainer');
  248. var $ganttDataArea = $ganttView.find('.gantt_data_area');
  249. var $ganttDridData = $ganttView.find('.gantt_grid_data');
  250. var ganttRowHeight = $ganttView.find('.gantt_row').first().outerHeight() || 25;
  251. var ganttHeight = $ganttView.find('.gantt_grid_scale').outerHeight() + <?php echo !empty($plans['data']) ? count($plans['data']) : 0;?> * ganttRowHeight;
  252. <?php if($selectCustom == 'task'):?>
  253. var ganttWidth = $ganttDridData.width() - 100;
  254. <?php else:?>
  255. var ganttWidth = $ganttDataArea.outerWidth() + $ganttDridData.outerWidth();
  256. <?php endif;?>
  257. $ganttContainer.css(
  258. {
  259. height: ganttHeight + $('#ganttHeader').outerHeight() + 80,
  260. width: ganttWidth + 93
  261. });
  262. $ganttView.css('height', ganttHeight);
  263. gantt.render();
  264. updateProgress(0.1);
  265. getRemoteScript('<?php echo $jsRoot . 'html2canvas/min.js';?>', function()
  266. {
  267. updateProgress(0.2);
  268. var afterFinish = function(canvas)
  269. {
  270. $ganttContainer.css(
  271. {
  272. width: '',
  273. height: ''
  274. });
  275. $ganttView.css('height', oldHeight);
  276. if(canvas)
  277. {
  278. try
  279. {
  280. canvas.removeNode(true)
  281. }
  282. catch(err)
  283. {
  284. canvas.remove()
  285. };
  286. }
  287. };
  288. var delayTime = Math.max(1000, Math.floor(10 * (ganttHeight * ganttWidth) / 100000));
  289. var progressTimer;
  290. if(delayTime > 1500)
  291. {
  292. var startProgress = 0.2;
  293. var deltaProgress = 0.5 / Math.floor(delayTime/1000);
  294. progressTimer = setInterval(function()
  295. {
  296. startProgress += deltaProgress;
  297. updateProgress(Math.min(0.7, startProgress));
  298. }, 1000);
  299. }
  300. setTimeout(function()
  301. {
  302. if(progressTimer) clearInterval(progressTimer);
  303. updateProgress(0.7);
  304. html2canvas($ganttContainer[0], {logging: false}).then(function(canvas)
  305. {
  306. var isExportPDF = exportType === 'pdf';
  307. updateProgress(isExportPDF ? 0.8 : 0.9);
  308. canvas.onerror = function()
  309. {
  310. afterFinish(canvas);
  311. if(errorCallback) errorCallback('Cannot convert image to blob.');
  312. };
  313. if(isExportPDF)
  314. {
  315. var width = canvas.width;
  316. var height = canvas.height;
  317. getRemoteScript('<?php echo $jsRoot . 'pdfjs/min.js';?>', function()
  318. {
  319. updateProgress(0.8);
  320. var pdf = new jsPDF(
  321. {
  322. format: [width, height],
  323. unit: 'px',
  324. orientation: width > height ? 'l' : 'p'
  325. });
  326. pdf.addImage(canvas, 0, 0, pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight());
  327. pdf.save('<?php echo "$fileName.pdf"?>');
  328. if(successCallback) successCallback(pdf);
  329. afterFinish();
  330. }, function(error)
  331. {
  332. afterFinish(canvas);
  333. if(errorCallback) errorCallback(error);
  334. });
  335. }
  336. else
  337. {
  338. canvas.toBlob(function(blob)
  339. {
  340. updateProgress(0.95);
  341. var imageUrl = URL.createObjectURL(blob);
  342. if(navigator.msSaveBlob)
  343. {
  344. navigator.msSaveOrOpenBlob(blob, '<?php echo $fileName; ?>.png');
  345. }
  346. else
  347. {
  348. $('#ganttDownload').attr({href: imageUrl})[0].click();
  349. }
  350. if(successCallback) successCallback(imageUrl);
  351. afterFinish(canvas);
  352. });
  353. }
  354. }).catch(function(error)
  355. {
  356. afterFinish();
  357. if(errorCallback) errorCallback('Cannot draw graphic to canvas.');
  358. });
  359. }, delayTime);
  360. }, errorCallback);
  361. }
  362. /**
  363. * Export gantt.
  364. *
  365. * @param string $exportType
  366. * @access public
  367. * @return void
  368. */
  369. function exportGantt(exportType)
  370. {
  371. var $mainContent = $('#mainContent');
  372. $mainContent.addClass('loading').css('height', Math.max(200, Math.floor($(window).height() - $('#footer').outerHeight() - $('#header').outerHeight() - $('#mainMenu').outerHeight() - 38)));
  373. $('#ganttExportDate').text(new Date().format('yyyy-MM-dd hh:mm:ss'));
  374. var afterFinish = function(url)
  375. {
  376. setTimeout(function()
  377. {
  378. $mainContent.css('height', '').removeClass('loading');
  379. }, 300);
  380. updateProgress(1);
  381. };
  382. drawGanttToCanvas(exportType, afterFinish, function(errorText)
  383. {
  384. afterFinish();
  385. $.zui.messager.danger('<?php echo $lang->programplan->exportFail;?>' + (errorText || ''));
  386. });
  387. }
  388. /**
  389. * Get by id for gantt.
  390. *
  391. * @param array $list
  392. * @param string $id
  393. * @access public
  394. * @return string
  395. */
  396. function getByIdForGantt(list, id)
  397. {
  398. for (var i = 0; i < list.length; i++)
  399. {
  400. if (list[i].key == id) return list[i].label || "";
  401. }
  402. return id;
  403. }
  404. /**
  405. * Zoom tasks.
  406. *
  407. * @param value
  408. * @access public
  409. * @return void
  410. */
  411. function zoomTasks(value)
  412. {
  413. switch(value)
  414. {
  415. case "day":
  416. gantt.config.min_column_width = 70;
  417. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'day', step: 1, format: '%m-%d'}];
  418. gantt.config.scale_height = 22 * gantt.config.scales.length;
  419. break;
  420. case "week":
  421. gantt.config.min_column_width = 70;
  422. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'week', step: 1, format: "<?php echo $lang->execution->gantt->zooming['week'];?> #%W"}, {unit:"day", step:1, date:"%D"}]
  423. gantt.config.scale_height = 22 * gantt.config.scales.length;
  424. break;
  425. case "month":
  426. gantt.config.min_column_width = 70;
  427. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'month', step: 1, format: '%M'}, {unit:"week", step:1, date:"<?php echo $lang->execution->gantt->zooming['week'];?> #%W"}];
  428. gantt.config.scale_height = 22 * gantt.config.scales.length;
  429. break;
  430. }
  431. gantt.render();
  432. $('.gantt_grid_head_cell .sort').addClass(value);
  433. }
  434. window.onload = function () {
  435. zoomTasks(zooming);
  436. }
  437. /**
  438. * Update criticalPath
  439. *
  440. * @access public
  441. * @return void
  442. */
  443. function updateCriticalPath()
  444. {
  445. gantt.config.highlight_critical_path = !gantt.config.highlight_critical_path;
  446. if(gantt.config.highlight_critical_path)
  447. {
  448. $('#criticalPath').html(<?php echo json_encode($lang->programplan->hideCriticalPath);?>);
  449. gantt.config.highlight_critical_path = true;
  450. }
  451. else
  452. {
  453. $('#criticalPath').html(<?php echo json_encode($lang->programplan->showCriticalPath);?>);
  454. gantt.config.highlight_critical_path = false;
  455. }
  456. gantt.render();
  457. }
  458. $("#fullScreenBtn").on("click", function()
  459. {
  460. $("#mainContent").css("z-index", "5");
  461. $("#ganttView").css("z-index", "5");
  462. });
  463. function exitHandler()
  464. {
  465. if (module == 'review' && method == 'assess' && !document.fullscreenElement && !document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement)
  466. {
  467. location.reload();
  468. }
  469. }
  470. /* Validate task drag. */
  471. function validateResources(id)
  472. {
  473. var task = gantt.getTask(id);
  474. var from = new Date(task.start_date),
  475. to = new Date(task.end_date);
  476. var status = task.status;
  477. var type = task.type;
  478. var statusLang = "<?php echo $lang->task->statusList['wait'];?>";
  479. flag = true;
  480. /* Check status. */
  481. if(status !== statusLang && type != 'point')
  482. {
  483. if(type == 'task')
  484. {
  485. tipMsg = "<?php echo $lang->programplan->error->taskDrag;?>";
  486. }
  487. else
  488. {
  489. tipMsg = "<?php echo $lang->programplan->error->planDrag;?>";
  490. }
  491. new $.zui.Messager(tipMsg.replace('%s', status), {
  492. type: 'danger',
  493. icon: 'exclamation-sign'
  494. }).show();
  495. gantt.refreshData();
  496. return false;
  497. }
  498. task.begin = from;
  499. task.deadline = (new Date(to.valueOf() - 1));;
  500. gantt.updateTask(task.id);
  501. var itemID;
  502. if(type == 'task')
  503. {
  504. itemID = task.id.split("-")[1];
  505. }
  506. else if(type == 'plan')
  507. {
  508. itemID = task.id;
  509. }
  510. else if(type == 'point')
  511. {
  512. itemID = task.id.split("-")[2];
  513. }
  514. /* Check data. */
  515. var postData = {
  516. 'id' : itemID,
  517. 'startDate' : from.toLocaleDateString('en-CA'),
  518. 'endDate' : to.toLocaleDateString('en-CA'),
  519. 'type' : type
  520. };
  521. var link = createLink('programplan', 'ajaxResponseGanttDragEvent');
  522. /* Sync Close. */
  523. $.ajax({
  524. url: link,
  525. dataType: "json",
  526. async: false,
  527. data: postData,
  528. type: "post",
  529. success: function(response)
  530. {
  531. if(response.result == 'fail' && response.message)
  532. {
  533. new $.zui.Messager(response.message, {
  534. type: 'danger',
  535. icon: 'exclamation-sign'
  536. }).show();
  537. flag = false;
  538. }
  539. }
  540. });
  541. return flag;
  542. }
  543. $(function()
  544. {
  545. document.addEventListener('fullscreenchange', exitHandler);
  546. var layout;
  547. // Set gantt view height
  548. var resizeGanttView = function()
  549. {
  550. if(gantt.getState().fullscreen) return false;
  551. $('#ganttView').css('height', Math.max(200, Math.floor($(window).height() - $('#footer').outerHeight() - $('#header').outerHeight() - $('#mainMenu').outerHeight() - 120)));
  552. };
  553. var ganttData = $.parseJSON(<?php echo json_encode(json_encode($plans));?>);
  554. if(!ganttData) ganttData = {};
  555. if(!ganttData.data) ganttData.data = [];
  556. <?php
  557. $userList = array();
  558. if(!empty($users))
  559. {
  560. foreach($users as $account => $realname)
  561. {
  562. $user = array();
  563. $user['key'] = $account;
  564. $user['label'] = $realname;
  565. $userList[] = $user;
  566. }
  567. }
  568. ?>
  569. gantt.serverList("userList", <?php echo json_encode($userList);?>);
  570. gantt.config.readonly = canGanttEdit ? false : true;
  571. gantt.config.details_on_dblclick = false;
  572. gantt.config.order_branch = ganttType == 'assignedTo' ? false : true;
  573. gantt.config.drag_progress = false;
  574. gantt.config.drag_links = false;
  575. gantt.config.drag_move = ganttType == 'assignedTo' ? false : true;
  576. gantt.config.drag_resize = ganttType == 'assignedTo' ? false : true;
  577. gantt.config.smart_rendering = true;
  578. gantt.config.smart_scales = true;
  579. gantt.config.static_background = true;
  580. gantt.config.show_task_cells = false;
  581. gantt.config.row_height = 32;
  582. gantt.config.min_column_width = 40;
  583. gantt.config.details_on_create = false;
  584. gantt.config.scales = [{unit: "year", step: 1, format: "%Y"}, {unit: 'day', step: 1, format: '%m-%d'}];
  585. gantt.config.scale_height = 18 * gantt.config.scales.length;
  586. gantt.config.duration_unit = "day";
  587. gantt.config.columns = [];
  588. gantt.config.columns.push({name: 'text', width: '*', tree: true, resize: true, min_width:120, width:200});
  589. gantt.config.columns.push({name:"custom", label:"", align: "left", width: 40, template: getSubmitBtn});
  590. if(showFields.indexOf('PM') != -1) gantt.config.columns.push({name: 'owner_id', align: 'left', resize: true, width: 80, template: function(task){return getByIdForGantt(gantt.serverList('userList'), task.owner_id)}})
  591. if(showFields.indexOf('status') != -1) gantt.config.columns.push({name: 'status', align: 'center', resize: true, width: 80});
  592. gantt.config.columns.push({name: 'begin', align: 'center', resize: true, width: 80});
  593. if(showFields.indexOf('deadline') != -1) gantt.config.columns.push({name: 'deadline', align: 'center', resize: true, width: 140, template: getDeadlineBtn});
  594. gantt.config.columns.push({name: 'duration', align: 'center', resize: true, width: 60});
  595. if(showFields.indexOf('estimate') != -1) gantt.config.columns.push({name: 'estimate', align: 'center', resize: true, width: 60});
  596. if(showFields.indexOf('progress') != -1) gantt.config.columns.push({name: 'percent', align: 'center', resize: true, width:70, template: function(plan){ if(plan.percent) return Math.round(plan.percent) + '%';}});
  597. if(showFields.indexOf('taskProgress') != -1) gantt.config.columns.push({name: 'taskProgress', align: 'center', resize: true, width: 60});
  598. if(showFields.indexOf('realBegan') != -1) gantt.config.columns.push({name: 'realBegan', align: 'center', resize: true, width: 80});
  599. if(showFields.indexOf('realEnd') != -1) gantt.config.columns.push({name: 'realEnd', align: 'center', resize: true, width: 80});
  600. if(showFields.indexOf('consumed') != -1) gantt.config.columns.push({name: 'consumed', align: 'center', resize: true, width: 60});
  601. if(showFields.indexOf('delay') != -1)
  602. {
  603. gantt.config.columns.push({name: 'delay', align: 'center', resize: true, width: 60, template:function(item)
  604. {
  605. if(item.delayDays > 0) return "<div class='delayed'>" + item.delay + "</div>";
  606. return item.delay;
  607. }});
  608. }
  609. if(showFields.indexOf('delayDays') != -1) gantt.config.columns.push({name: 'delayDays', align: 'center', resize: false, width: 60});
  610. gantt.config.layout =
  611. {
  612. css: "gantt_container",
  613. cols:
  614. [
  615. {
  616. width:600,
  617. rows:
  618. [
  619. {view: "grid", scrollX: "gridScroll", scrollable: true, scrollY: "scrollVer"},
  620. {view: "scrollbar", id: "gridScroll", group:"horizontal"}
  621. ]
  622. },
  623. {resizer: true, width: 1},
  624. {
  625. rows:
  626. [
  627. {view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer"},
  628. {view: "scrollbar", id: "scrollHor", group:"horizontal"}
  629. ]
  630. },
  631. {view: "scrollbar", id: "scrollVer"}
  632. ]
  633. };
  634. function getDeadlineBtn(task)
  635. {
  636. var date = task.deadline;
  637. if(task.type == 'point' && canEditDeadline && (!task.rawStatus || task.rawStatus == 'fail' || task.rawStatus == 'draft')) return "<table><tr><td><span class='deadline'>" + gridDateToStr(new Date(date.valueOf())) + '</span> <a class="btn btn-primary editDeadline" title="<?php echo $lang->programplan->edit;?>"><i class="icon-common-edit icon-edit"></i> <?php echo $lang->programplan->edit;?></a></td></tr></table>';
  638. return date;
  639. }
  640. function getSubmitBtn(task)
  641. {
  642. if(task.type == 'point' && !task.rawStatus) return '<button class="btn btn-link submitBtn" title="<?php echo $lang->programplan->submit;?>"><i class="icon-confirm"></i></button>';
  643. }
  644. gantt.templates.task_end_date = function(data)
  645. {
  646. return gantt.templates.task_date(new Date(date.valueOf() - 1));
  647. }
  648. var gridDateToStr = gantt.date.date_to_str("%Y-%m-%d");
  649. gantt.templates.grid_date_format = function(date, column)
  650. {
  651. if(column === "end_date")
  652. {
  653. return gridDateToStr(new Date(date.valueOf() - 1));
  654. }
  655. else
  656. {
  657. return gridDateToStr(date);
  658. }
  659. }
  660. endField = gantt.config.columns.pop();
  661. endField.resize = false;
  662. gantt.config.columns.push(endField);
  663. gantt.locale.labels.column_text = <?php echo json_encode($typeHtml);?>;
  664. gantt.locale.labels.column_owner_id = "<?php echo $lang->programplan->PMAB;?>";
  665. gantt.locale.labels.column_status = "<?php echo $lang->statusAB;?>";
  666. gantt.locale.labels.column_percent = "<?php echo $lang->programplan->percentAB;?>";
  667. gantt.locale.labels.column_taskProgress = "<?php echo $lang->programplan->taskProgress;?>";
  668. gantt.locale.labels.column_begin = "<?php echo $lang->programplan->begin;?>";
  669. gantt.locale.labels.column_start_date = "<?php echo $lang->programplan->begin;?>";
  670. gantt.locale.labels.column_deadline = "<?php echo $lang->programplan->end;?>";
  671. gantt.locale.labels.column_end_date = "<?php echo $lang->programplan->end;?>";
  672. gantt.locale.labels.column_realBegan = "<?php echo $lang->programplan->realBegan;?>";
  673. gantt.locale.labels.column_realEnd = "<?php echo $lang->programplan->realEnd;?>";
  674. gantt.locale.labels.column_duration = "<?php echo $lang->programplan->duration;?>";
  675. gantt.locale.labels.column_estimate = "<?php echo $lang->programplan->estimate;?>";
  676. gantt.locale.labels.column_consumed = "<?php echo $lang->programplan->consumed;?>";
  677. gantt.locale.labels.column_delay = "<?php echo $lang->programplan->delay;?>";
  678. gantt.locale.labels.column_delayDays = "<?php echo $lang->programplan->delayDays;?>";
  679. if((module == 'review' && method == 'assess') || dateDetails) gantt.config.show_chart = false;
  680. var date2Str = gantt.date.date_to_str(gantt.config.task_date);
  681. var today = new Date();
  682. var todayTips = "<?php echo $lang->programplan->today;?>";
  683. gantt.addMarker({
  684. start_date: today,
  685. css: "today",
  686. text: todayTips,
  687. title: todayTips + ": " + date2Str(today)
  688. });
  689. gantt.templates.task_class = function(start, end, task){return 'pri-' + (task.pri || 0);};
  690. gantt.templates.rightside_text = function(start, end, task)
  691. {
  692. if(typeof task.owner_id == 'undefined') return;
  693. if(task.type == 'point') return "<span class='status-" + task.rawStatus + "'>" + task.status + '</span>';
  694. return getByIdForGantt(gantt.serverList('userList'), task.owner_id);
  695. };
  696. gantt.templates.link_class = function(link)
  697. {
  698. var types = gantt.config.links;
  699. if(link.type == types.finish_to_start) return 'finish_to_start';
  700. if(link.type == types.start_to_start) return 'start_to_start';
  701. if(link.type == types.finish_to_finish) return 'finish_to_finish';
  702. if(link.type == types.start_to_finish) return 'start_to_finish';
  703. };
  704. gantt.templates.tooltip_text = function (start, end, task)
  705. {
  706. if(task.type != 'point') return task.text;
  707. };
  708. gantt.attachEvent('onTemplatesReady', function()
  709. {
  710. $('#fullScreenBtn').click(function()
  711. {
  712. if(module == 'review' && method == 'assess')
  713. {
  714. gantt.config.layout = layout;
  715. gantt.init('ganttView');
  716. }
  717. gantt.expand();
  718. });
  719. });
  720. gantt.attachEvent("onBeforeTaskMove", function(id, mode, e)
  721. {
  722. task = gantt.getTask(id);
  723. if(task.type == 'point') return false;
  724. });
  725. var isGanttExpand = false;
  726. var delayTimer = null;
  727. var handleFullscreen = function()
  728. {
  729. if(isGanttExpand)
  730. {
  731. $('body').addClass('gantt-fullscreen');
  732. $('#ganttView').css('height', $(window).height() - 60);
  733. isGanttExpand = false;
  734. }
  735. else
  736. {
  737. $('body').removeClass('gantt-fullscreen');
  738. resizeGanttView();
  739. }
  740. delayTimer = null;
  741. };
  742. var delayHandleFullscreen = function()
  743. {
  744. if(delayTimer) clearTimeout(delayTimer);
  745. delayTimer = setTimeout(handleFullscreen, 50);
  746. };
  747. gantt.attachEvent('onBeforeExpand', function()
  748. {
  749. $('body').addClass('gantt-fullscreen');
  750. isGanttExpand = true;
  751. return true;
  752. });
  753. if(document.addEventListener)
  754. {
  755. document.addEventListener('webkitfullscreenchange', delayHandleFullscreen, false);
  756. document.addEventListener('mozfullscreenchange', delayHandleFullscreen, false);
  757. document.addEventListener('fullscreenchange', delayHandleFullscreen, false);
  758. document.addEventListener('MSFullscreenChange', delayHandleFullscreen, false);
  759. }
  760. resizeGanttView();
  761. $(window).resize(resizeGanttView);
  762. gantt.templates.grid_folder = function(item) {
  763. return "";
  764. };
  765. gantt.templates.grid_file = function(item) {
  766. return "";
  767. };
  768. gantt.init('ganttView');
  769. gantt.parse(ganttData);
  770. gantt.showDate(new Date());
  771. // Show task in modal on click task
  772. var taskModalTrigger = new $.zui.ModalTrigger({type: 'iframe', width: '95%'});
  773. gantt.attachEvent('onTaskClick', function(id, e)
  774. {
  775. var editBtn = $(e.srcElement);
  776. if(editBtn.hasClass('icon-common-edit'))
  777. {
  778. editBtn = editBtn.parent();
  779. }
  780. if(editBtn.hasClass('editDeadline'))
  781. {
  782. var parentID = id.split("-")[0];
  783. var stageEndDate = $("div[data-task-id='" + parentID + "']").find('div[data-column-name="end_date"] > .gantt_tree_content').text();
  784. var reviewID = id;
  785. var deadlineDate = editBtn.prev().text();
  786. editBtn.datetimepicker(
  787. {
  788. weekStart: 1,
  789. todayBtn: 1,
  790. autoclose: 1,
  791. todayHighlight: 1,
  792. startView: 2,
  793. minView: 2,
  794. forceParse: 0,
  795. format: "yyyy-mm-dd",
  796. startDate: new Date(),
  797. endDate: new Date(stageEndDate),
  798. initialDate: new Date(deadlineDate),
  799. })
  800. .on('changeDate', function(ev){
  801. var year = ev.date.getFullYear();
  802. var month = ev.date.getMonth() + 1;
  803. var day = ev.date.getUTCDate();
  804. var formattedDate = year + '-' + (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day;
  805. $.post(createLink('review', 'ajaxChangeTRDeadline'), {'deadline' : formattedDate, 'id' : reviewID , 'projectID' : projectID}, function()
  806. {
  807. location.reload();
  808. });
  809. })
  810. .on('show', function(){
  811. editBtn.css('display', 'inline-block');
  812. })
  813. .on('hide', function(){
  814. editBtn.css('display', 'none');
  815. });
  816. editBtn.datetimepicker('show');
  817. return false;
  818. }
  819. if(editBtn.hasClass('gantt_close') || editBtn.hasClass('gantt_open')) return false;
  820. var task = gantt.getTask(id);
  821. if(task.type == 'point' && task.rawStatus) location.href = createLink('review', 'view', 'reviewID=' + task.reviewID);
  822. if(task.type == 'plan' && !task.isParent) window.parent.$.apps.open(createLink('execution', 'task', 'id=' + task.id), 'execution');
  823. if(editBtn.hasClass('icon-confirm'))
  824. {
  825. var pointAttr = JSON.parse(reviewPoints);
  826. var category = id.split("-")[1];
  827. if(pointAttr[category]['disabled'])
  828. {
  829. new $.zui.Messager(pointAttr[category]['message'], {
  830. type: 'danger',
  831. icon: 'exclamation-sign'
  832. }).show();
  833. return false;
  834. }
  835. else
  836. {
  837. location.href = createLink('review', 'create', 'projectID=' + projectID);
  838. }
  839. }
  840. if(ganttType == 'assignedTo')
  841. {
  842. taskID = id;
  843. }
  844. else
  845. {
  846. /* The id of task item is like executionID-taskID. e.g. 1507-37829, 37829 is task id. */
  847. var position = id.indexOf('-');
  848. if(position < 0) return;
  849. var taskID = parseInt(id.substring(position + 1));
  850. }
  851. if(!isNaN(taskID) && taskID > 0) taskModalTrigger.show({url: createLink('task', 'view', 'taskID=' + taskID, 'html', true)});
  852. });
  853. gantt.attachEvent("onBeforeRowDragEnd", function(id, parent, tindex)
  854. {
  855. var tasks = gantt.getChildren(parent);
  856. var task = gantt.getTask(id);
  857. var link = createLink('programplan', 'ajaxResponseGanttMoveEvent');
  858. //prevent moving to another position.
  859. if(task.parent != parent || id.indexOf('-') == -1)
  860. {
  861. return false;
  862. }
  863. else
  864. {
  865. $.ajax({
  866. url: link,
  867. dataType: "json",
  868. data: {id: id, tasks: tasks},
  869. type: "post",
  870. success: function(result){}
  871. });
  872. }
  873. return true;
  874. });
  875. // Make folder can open or close by click
  876. $('#ganttView').on('click', '.gantt_close,.gantt_open', function()
  877. {
  878. var $task = $(this).closest('.gantt_row_task');
  879. var task = gantt.getTask($task.attr('task_id'));
  880. if(task) gantt[task.$open ? 'close' : 'open'](task.id);
  881. });
  882. $(".example .checkbox-primary").on('click', function()
  883. {
  884. var stageCustom = [];
  885. $("input[name='stageCustom[]']:checked").each(function()
  886. {
  887. var custom = $(this).val();
  888. stageCustom.push(custom);
  889. });
  890. if(stageCustom.length == 0) stageCustom = 0;
  891. $.ajax({
  892. url: customUrl,
  893. dataType: "json",
  894. data: {stageCustom: stageCustom},
  895. type: "post",
  896. success: function(result)
  897. {
  898. window.location.reload();
  899. }
  900. });
  901. });
  902. $('#ganttContainer').mouseleave(function()
  903. {
  904. setTimeout(function(){$('.gantt_tooltip').remove()}, 100);
  905. });
  906. /* Link attachEvent onAfterTaskDrag */
  907. gantt.attachEvent("onBeforeTaskChanged", function(id, mode, task)
  908. {
  909. return validateResources(id);
  910. });
  911. gantt.attachEvent("onRowDragStart", function(id, target, e) {
  912. //any custom logic here
  913. var task = gantt.getTask(id);
  914. if(task.type != 'task') return false;
  915. return true;
  916. });
  917. });
  918. </script>