From 97fc6031b0124236607009683aee41d23ef73de7 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Thu, 3 Aug 2023 10:45:02 +0800 Subject: [PATCH] * Refactor project view page. --- module/common/lang/de.php | 1 + module/common/lang/en.php | 1 + module/common/lang/fr.php | 1 + module/common/lang/zh-cn.php | 1 + module/project/config.php | 10 +- module/project/css/view.ui.css | 14 +- module/project/js/view.ui.js | 27 - module/project/lang/de.php | 1 + module/project/lang/en.php | 1 + module/project/lang/fr.php | 1 + module/project/lang/zh-cn.php | 5 + module/project/ui/browsebycard.html.php | 2 +- module/project/ui/view.html.php | 687 ++++++++---------------- 13 files changed, 249 insertions(+), 503 deletions(-) delete mode 100644 module/project/js/view.ui.js diff --git a/module/common/lang/de.php b/module/common/lang/de.php index 8ac9d0155e..f9f4a9dfd6 100644 --- a/module/common/lang/de.php +++ b/module/common/lang/de.php @@ -135,6 +135,7 @@ $lang->tutorialConfirm = 'Sie benutzen die Anleitung. Möchten Sie diese jetzt v $lang->levelExceeded = 'The level has exceeded the display range. For more information, please go to the web page or view it through search.'; $lang->noticeOkFile = 'Aus Sicherheitsgrü nden muss Ihr Adminkonto bestätigt werden. \n Bitte melden Sie sich an und erstellen Sie die Datei %s File.\n Hinweis:\n 1. Die Datei ist leer.\n 2. Wenn die Datei bereits existiert, löschen Sie sie und erstellen Sie eine neue Datei.'; $lang->noticeDrag = 'Click to add or drag to upload, no more than %s'; +$lang->allProgress = 'All Progress'; $lang->serviceAgreement = "Service Agreement"; $lang->privacyPolicy = "Privacy Policy"; diff --git a/module/common/lang/en.php b/module/common/lang/en.php index f20d742f90..bcde2e0c37 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -135,6 +135,7 @@ $lang->tutorialConfirm = 'You are using ZenTao tutorial. Do you want to quit rig $lang->levelExceeded = 'The level has exceeded the display range. For more information, please go to the web page or view it through search.'; $lang->noticeOkFile = 'For security reasons, your Admin account has to be confirmed. \n Please login ZenTao server and create %s File.\n Note:\n 1. File is blank.\n 2. If the file existed, delete it and then create a new one.'; $lang->noticeDrag = 'Click to add or drag to upload, no more than %s'; +$lang->allProgress = 'All Progress'; $lang->serviceAgreement = "Service Agreement"; $lang->privacyPolicy = "Privacy Policy"; diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php index 60ddf5de2c..4e69a69cf1 100644 --- a/module/common/lang/fr.php +++ b/module/common/lang/fr.php @@ -135,6 +135,7 @@ $lang->tutorialConfirm = 'Vous utilisez le didacticiel ZenTao. Voulez-vous quitt $lang->levelExceeded = 'The level has exceeded the display range. For more information, please go to the web page or view it through search.'; $lang->noticeOkFile = 'For security reasons, your Admin account has to be confirmed. \n Please login ZenTao server and create %s File.\n Note:\n 1. File is blank.\n 2. If the file existed, delete it and then create a new one.'; $lang->noticeDrag = 'Click to add or drag to upload, no more than %s'; +$lang->allProgress = 'All Progress'; $lang->serviceAgreement = "Service Agreement"; $lang->privacyPolicy = "Privacy Policy"; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index 1593ed1a6d..d23ad0cd89 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -135,6 +135,7 @@ $lang->tutorialConfirm = '检测到你尚未退出新手教程模式,是否现 $lang->levelExceeded = '层级已超过显示范围,更多信息请前往网页端查看或者是通过搜索方式查看。'; $lang->noticeOkFile = '为了安全起见,系统需要确认您的管理员身份。\n 请登录禅道所在的服务器,创建%s文件。\n 注意:\n 1. 文件内容为空。\n 2. 如果之前文件存在,删除之后重新创建。'; $lang->noticeDrag = '可点击添加或拖拽上传, 不超过 %s'; +$lang->allProgress = '总进度'; $lang->serviceAgreement = "服务协议"; $lang->privacyPolicy = "隐私政策"; diff --git a/module/project/config.php b/module/project/config.php index 3a4c83c60b..c040b2adb0 100644 --- a/module/project/config.php +++ b/module/project/config.php @@ -364,8 +364,8 @@ $config->project->view = new stdclass(); $config->project->view->operateList['main'] = array('start', 'activate', 'suspend', 'close'); $config->project->view->operateList['common'] = array('edit', 'delete'); -$config->project->statusLabelList['wait'] = 'wait lighter rounded-full'; -$config->project->statusLabelList['doing'] = 'doing primary-pale rounded-full'; -$config->project->statusLabelList['suspended'] = 'suspended gray text-white rounded-full'; -$config->project->statusLabelList['closed'] = 'closed success-pale rounded-full'; -$config->project->statusLabelList['delay'] = 'delay danger rounded-full'; +$config->project->statusLabelList['wait'] = 'wait lighter'; +$config->project->statusLabelList['doing'] = 'doing primary-pale'; +$config->project->statusLabelList['suspended'] = 'suspended gray text-white'; +$config->project->statusLabelList['closed'] = 'closed success-pale'; +$config->project->statusLabelList['delay'] = 'delay danger'; diff --git a/module/project/css/view.ui.css b/module/project/css/view.ui.css index 099901b695..a2289bde94 100644 --- a/module/project/css/view.ui.css +++ b/module/project/css/view.ui.css @@ -1,7 +1,9 @@ -.linked-products > .items-center {justify-content: space-between;} -#projectDashBoard .progress > .progress-bar {border-radius: 4px;} -.project-info .table-data-body {display: flex; flex-wrap: wrap;} -.project-info .table-data-body .table-data-tr {flex: 0 1 50%;} -.project-info .table-data .table-data-th {min-width: 36px;} -.project-statitic-hours .table-data .table-data-th {min-width: 60px; text-align: right;} .actions-menu {bottom: 12px;} + +.flex-col {flex-direction: column;} +.chart > div {margin: auto;} +.pie-chart {position: relative;} +.pie-chart .pie-chart-title {position: absolute; top: 50%; left: 50%; margin-top: -20px; margin-left: -16px} +.m-project-view #mainContent {display: flex; align-items: flex-start;} +#dynamicBlock {height: 50vh; display: flex; flex-direction: column; box-shadow: none;} +#dynamicBlock .panel-body {flex: 1 1 auto; overflow-y: auto;} diff --git a/module/project/js/view.ui.js b/module/project/js/view.ui.js deleted file mode 100644 index 7dcad045b2..0000000000 --- a/module/project/js/view.ui.js +++ /dev/null @@ -1,27 +0,0 @@ -$(function() -{ - setTimeout(function() - { - blocks.forEach(block => - { - if(block.domID) - { - block.content = {html: $(`#${block.domID}`).html()}; - $(`#${block.domID}`).remove(); - delete(block.domID); - } - }); - - const $dashboard = $('#projectDashBoard').data('zui.Dashboard') - $dashboard.render({blocks: blocks}); - - $('#projectDashBoard .dashboard-block').attr('draggable', false); - $('#projectDashBoard .dashboard-block-header').hide(); - - $('.actions-menu').css('width', $('#mainContent').width() / 3 * 2); - - const historyHeight = $('#projectDashBoard .history').height(); - const blockHeight = $('#projectDashBoard .dashboard-blocks > .dashboard-block-cell').height(); - if(historyHeight < blockHeight * 2) $('.actions-menu').removeClass('fixed'); - }, 10); -}); diff --git a/module/project/lang/de.php b/module/project/lang/de.php index 4f753d076b..d9d25670a2 100644 --- a/module/project/lang/de.php +++ b/module/project/lang/de.php @@ -160,6 +160,7 @@ $lang->project->leftHours = 'Left Hours'; $lang->project->children = "Child {$lang->projectCommon}"; $lang->project->parent = 'Parent Program'; $lang->project->allStories = 'All Stories'; +$lang->project->allProgress = 'All Progress'; $lang->project->doneStories = 'Finished Stories'; $lang->project->doneProjects = 'Finished'; $lang->project->allInput = 'Total Input'; diff --git a/module/project/lang/en.php b/module/project/lang/en.php index 4f753d076b..1b0bc8a046 100644 --- a/module/project/lang/en.php +++ b/module/project/lang/en.php @@ -135,6 +135,7 @@ $lang->project->estimate = 'Estimates'; $lang->project->consume = 'Cost'; $lang->project->surplus = 'Left'; $lang->project->progress = 'Progress'; +$lang->project->allProgress = 'All Progress'; $lang->project->weekProgress = 'This Week Progress'; $lang->project->dateRange = 'Plan Duration'; $lang->project->to = ' to '; diff --git a/module/project/lang/fr.php b/module/project/lang/fr.php index 4f753d076b..d9d25670a2 100644 --- a/module/project/lang/fr.php +++ b/module/project/lang/fr.php @@ -160,6 +160,7 @@ $lang->project->leftHours = 'Left Hours'; $lang->project->children = "Child {$lang->projectCommon}"; $lang->project->parent = 'Parent Program'; $lang->project->allStories = 'All Stories'; +$lang->project->allProgress = 'All Progress'; $lang->project->doneStories = 'Finished Stories'; $lang->project->doneProjects = 'Finished'; $lang->project->allInput = 'Total Input'; diff --git a/module/project/lang/zh-cn.php b/module/project/lang/zh-cn.php index a54e69e490..5eb97851ee 100644 --- a/module/project/lang/zh-cn.php +++ b/module/project/lang/zh-cn.php @@ -135,6 +135,7 @@ $lang->project->estimate = '预计'; $lang->project->consume = '消耗'; $lang->project->surplus = '剩余'; $lang->project->progress = '进度'; +$lang->project->allProgress = '总进度'; $lang->project->weekProgress = '本周进度'; $lang->project->dateRange = '计划起止日期'; $lang->project->to = '至'; @@ -338,6 +339,10 @@ $lang->project->multipleList['0'] = '否'; $lang->project->acls['private'] = '私有'; $lang->project->acls['open'] = '公开'; +$lang->project->shortAclList['private'] = '私有'; +$lang->project->shortAclList['open'] = '公开'; +$lang->project->shortAclList['program'] = '项目集内公开'; + $lang->project->subAclList['private'] = "私有 (只有{$lang->projectCommon}负责人、团队成员和干系人可访问)"; $lang->project->subAclList['open'] = "公开 (有{$lang->projectCommon}视图权限即可访问)"; $lang->project->subAclList['program'] = "项目集内公开(所有上级项目集负责人和干系人、{$lang->projectCommon}负责人、团队成员和干系人可访问)"; diff --git a/module/project/ui/browsebycard.html.php b/module/project/ui/browsebycard.html.php index bed6caed5e..96a0843fbc 100644 --- a/module/project/ui/browsebycard.html.php +++ b/module/project/ui/browsebycard.html.php @@ -155,7 +155,7 @@ if(!empty($projectStats)) ), span ( - setClass("project-status label {$statusLabel}"), + setClass("project-status label rounded-full {$statusLabel}"), $lang->project->statusList[$status] ) ), diff --git a/module/project/ui/view.html.php b/module/project/ui/view.html.php index 3f597a809a..f1bfd63aae 100644 --- a/module/project/ui/view.html.php +++ b/module/project/ui/view.html.php @@ -10,31 +10,232 @@ declare(strict_types=1); */ namespace zin; -$blocks = array( - array( - 'id' => 1, - 'size' => 'sm', - 'domID' => 'dynamicBlock', +$programDom = null; +if($project->grade > 1) +{ + foreach($programList as $programID => $name) + { + if(common::hasPriv('program', 'product')) + { + $programList[$programID] = html::a + ( + $this->createLink('program', 'product', "programID={$programID}"), + $name + ); + } + } + + $programDom = div + ( + icon('program mr-2'), + html($programList ? implode('/ ', $programList) : '') + ); +} + +$totalEstimate = $workhour->totalConsumed + $workhour->totalLeft; +$progress = 0; +if($project->model == 'waterfall') +{ + $progress = $this->project->getWaterfallProgress($project->id); +} +elseif($totalEstimate > 0) +{ + $progress = floor($workhour->totalConsumed / $totalEstimate * 1000) / 1000 * 100; +} + +$status = $this->processStatus('project', $project); + +div +( + setClass('flex-auto canvas flex p-4 w-2/3'), + div + ( + setClass('text-center w-1/3 flex flex-col justify-center items-center'), + div + ( + set('class', 'chart pie-chart'), + echarts + ( + set::color(array('#2B80FF', '#E3E4E9')), + set::series + ( + array + ( + array + ( + 'type' => 'pie', + 'radius' => array('80%', '90%'), + 'itemStyle' => array('borderRadius' => '40'), + 'label' => array('show' => false), + 'data' => array($progress, 100 - $progress) + ) + ) + ) + )->size(120, 120), + div + ( + set::class('pie-chart-title text-center'), + div(span(set::class('text-2xl font-bold'), $progress . '%')), + div + ( + span + ( + setClass('text-sm text-gray'), + $lang->allProgress, + icon + ( + 'help ml-1', + toggle::tooltip(array('title' => '')), + setClass('text-light') + ) + ) + ) + ) + ), + div + ( + setClass('border w-3/4 flex justify-center items-center pl-4 py-2'), + div + ( + setClass('w-1/3'), + div + ( + setClass('article-h1'), + $statData->storyCount + ), + span + ( + setClass('text-gray'), + $lang->story->common + ) + ), + div + ( + setClass('w-1/3'), + div + ( + setClass('article-h1'), + $statData->taskCount + ), + span + ( + setClass('text-gray'), + $lang->task->common + ) + ), + div + ( + setClass('w-1/3'), + div + ( + setClass('article-h1'), + $statData->bugCount + ), + span + ( + setClass('text-gray'), + $lang->bug->common + ) + ), + ) ), - array( - 'id' => 2, - 'size' => 'sm', - 'domID' => 'memberBlock', + div + ( + setClass('flex-none w-2/3'), + div + ( + setClass('flex items-center'), + label + ( + setClass('rounded-full'), + $project->id + ), + span + ( + setClass('article-h2 ml-2 clip'), + $project->name + ), + !empty($config->setCode) ? label + ( + setClass('label lighter-pale ml-2'), + $project->code + ) : null, + label + ( + setClass('label warning-pale ring-warning rounded-full ml-2'), + $lang->project->projectTypeList[$project->hasProduct] + ), + $project->deleted ? label + ( + setClass('danger-outline text-danger flex-none ml-2'), + $lang->project->deleted + ) : null, + isset($project->delay) ? label + ( + setClass("label ml-2 flex-none {$config->project->statusLabelList['delay']}"), + $lang->project->delayed + ) : label + ( + setClass("ml-2 flex-none {$config->project->statusLabelList[$project->status]}"), + $status + ), + span + ( + setClass('ml-2 text-gray'), + $lang->project->shortAclList[$project->acl], + icon + ( + 'help', + set('data-toggle', 'tooltip'), + set('data-title', $lang->project->subAclList[$project->acl]), + set('data-placement', 'right'), + set('data-type', 'white'), + set('data-class-name', 'text-gray border border-light'), + setClass('ml-2 text-gray'), + ) + ) + ), + div + ( + setClass('flex mt-4'), + div + ( + setClass('clip w-1/2 text-secondary'), + $programDom + ), + ), + div + ( + set::class('detail-content mt-4'), + html($project->desc), + ), ), - array( - 'id' => 3, - 'size' => 'xl', - 'domID' => 'basicBlock', - ), - array( - 'id' => 4, - 'size' => 'smWide', - 'domID' => 'historyBlock', - ) ); -jsVar('blocks', $blocks); -jsVar('confirmDeleteTip', $lang->project->confirmDelete); +panel +( + setClass('canvas w-1/3 ml-4'), + setID('dynamicBlock'), + to::heading + ( + div + ( + set('class', 'panel-title'), + $lang->execution->latestDynamic, + ) + ), + to::headingActions + ( + common::hasPriv('project', 'dynamic') ? btn + ( + setClass('ghost text-gray'), + set::url(createLink('project', 'dynamic', "projectID={$projectID}&type=all")), + $lang->more + ) : null + ), + set::bodyClass('pt-0'), + dynamic() +); /* Construct suitable actions for the current project. */ $operateMenus = array(); @@ -60,13 +261,6 @@ foreach($config->project->view->operateList['common'] as $operate) $commonActions[] = $settings; } -dashboard -( - setID('projectDashBoard'), - set::blocks($blocks), - set::blockMenu(false), -); - div ( setClass('w-2/3 text-center fixed actions-menu'), @@ -78,440 +272,5 @@ div set::object($project) ) ); - -div -( - setID('dynamicBlock'), - setClass('hidden'), - panel - ( - to::heading - ( - div - ( - set('class', 'panel-title'), - $lang->execution->latestDynamic, - ) - ), - to::headingActions - ( - common::hasPriv('project', 'dynamic') ? btn - ( - setClass('ghost text-gray'), - set::url(createLink('project', 'dynamic', "projectID={$projectID}&type=all")), - $lang->more - ) : null - ), - set::bodyClass('pt-0'), - dynamic() - ) -); - -/* Related members. */ -$membersDom = array(); -foreach(array('PM', 'PO', 'QD', 'RD') as $field) -{ - if(empty($project->$field)) continue; - - $membersDom[] = div - ( - setClass('flex-initial w-1/2 py-1'), - icon('person', setClass('mr-2')), - zget($users, $project->$field), - span - ( - setClass('text-gray ml-2'), - "( {$lang->project->$field} )" - ) - ); - - unset($teamMembers[$project->$field]); -} -$memberCount = count($membersDom); -foreach($teamMembers as $teamMember) -{ - if($memberCount >= 10) break; - - $membersDom[] = div - ( - setClass('flex-initial w-1/2 py-1'), - icon('person', setClass('mr-2')), - zget($users, $teamMember->account), - ); - $memberCount ++; -} - -if(common::hasPriv('project', 'manageMembers')) -{ - $membersDom[] = div - ( - setClass('flex-initial w-1/2 py-1'), - a - ( - setClass('ghost text-gray'), - icon('plus', setClass('bg-primary-50 text-primary mr-2')), - span($lang->project->manageMembers), - set::href(createLink('project', 'manageMembers', "projectID={$projectID}")) - ) - ); -} - -div -( - setID('memberBlock'), - setClass('hidden'), - panel - ( - to::heading - ( - div - ( - set('class', 'panel-title'), - $lang->execution->relatedMember - ) - ), - to::headingActions - ( - common::hasPriv('project', 'team') ? btn - ( - setClass('ghost text-gray'), - set::url(createLink('project', 'team', "projectID={$projectID}")), - $lang->more - ) : null - ), - set::bodyClass('flex flex-wrap pt-0'), - $membersDom - ) -); - -/* History list. */ -div -( - setID('historyBlock'), - setClass('hidden'), - div - ( - history() - ) -); - -/* Basic info. */ -$programDom = null; -if($project->grade > 1) -{ - foreach($programList as $programID => $name) - { - if(common::hasPriv('program', 'product')) - { - $programList[$programID] = html::a - ( - $this->createLink('program', 'product', "programID={$programID}"), - $name - ); - } - } - - $programDom = div - ( - icon('program mr-2'), - html($programList ? implode('/ ', $programList) : '') - ); -} - -$productsDom = array(); -if(!empty($project->hasProduct)) -{ - foreach($products as $productID => $product) - { - foreach($product->branches as $branchID) - { - $branchName = isset($branchGroups[$productID][$branchID]) ? '/' . $branchGroups[$productID][$branchID] : ''; - $productsDom[] = div - ( - setClass('flex-initial w-1/2 py-1'), - icon('product mr-2'), - a - ( - set::href(createLink('product', 'browse', "productID=$productID&branch=$branchID")), - span($product->name . $branchName) - ) - ); - } - } -} - -$plansDom = array(); -foreach($products as $productID => $product) -{ - foreach($product->plans as $planIDList) - { - $planIDList = explode(',', $planIDList); - foreach($planIDList as $planID) - { - if(isset($planGroup[$productID][$planID])) - { - $plansDom[] = div - ( - setClass('mt-2 clip'), - hasPriv('productplan', 'view') ? a - ( - set::href(createLink('productplan', 'view', "planID={$planID}")), - icon('calendar text-gray mr-1'), - $product->name . '/' . $planGroup[$productID][$planID] - ) : span - ( - $product->name . '/' . $planGroup[$productID][$planID] - ) - ); - } - } - } -} - -$basicInfo = null; -if(empty($project->hasProduct) && !empty($config->URAndSR) && $project->model !== 'kanban' && isset($lang->project->menu->storyGroup)) -{ - $basicInfo = tableData - ( - set::useTable(false), - item - ( - set::name($lang->story->common), - $statData->storyCount - ), - item - ( - set::name($lang->requirement->common), - $statData->requirementCount - ), - item - ( - set::name($lang->task->common), - $statData->taskCount - ), - item - ( - set::name($lang->bug->common), - $statData->bugCount - ), - item - ( - set::name($lang->project->budget), - $statData->budget - ), - ); -} -else -{ - $basicInfo = tableData - ( - set::useTable(false), - item - ( - set::name($lang->story->common), - $statData->storyCount - ), - item - ( - set::name($lang->task->common), - $statData->taskCount - ), - item - ( - set::name($lang->bug->common), - $statData->bugCount - ), - item - ( - set::name($lang->project->budget), - $statData->budget - ), - ); -} - -$totalEstimate = $workhour->totalConsumed + $workhour->totalLeft; -$progress = 0; -if($project->model == 'waterfall') -{ - $progress = $this->project->getWaterfallProgress($project->id); -} -elseif($totalEstimate > 0) -{ - $progress = floor($workhour->totalConsumed / $totalEstimate * 1000) / 1000 * 100; -} - -$aclList = $project->parent ? $lang->project->subAclList : $lang->project->aclList; -div -( - setID('basicBlock'), - setClass('hidden'), - sectionList - ( - section - ( - setClass('border-b pb-4'), - div - ( - label - ( - setClass('text-dark'), - $project->id - ), - !empty($config->setCode) ? label - ( - setClass('dark-outline text-dark ml-2'), - $project->code - ) : null, - span(setClass('article-h2 ml-2'), $project->name), - ), - $project->desc ? div - ( - setClass('mt-2'), - $project->desc - ) : null, - div - ( - setClass('mt-2'), - $config->vision == 'rnd' ? label - ( - setClass('secondary-pale ring-secondary text-primary'), - zget($lang->project->projectTypeList, $project->hasProduct) - ) : null, - $project->deleted ? label - ( - setClass('danger-outline text-danger ml-2'), - $lang->project->deleted - ) : null, - $project->lifetime ? label - ( - setClass('secondary-outline text-primary ml-2'), - zget($lang->execution->lifeTimeList, $project->lifetime, '') - ) : null, - isset($project->delay) ? label - ( - setClass('danger-outline text-danger ml-2'), - $lang->project->delayed - ) : null, - label - ( - setClass("status-{$project->status} ml-2"), - $this->processStatus('project', $project) - ), - ) - ), - section - ( - setClass('border-b pb-4'), - set::title($lang->project->parent), - $programDom - ), - !empty($project->hasProduct) ? section - ( - setClass('border-b pb-4 linked-products'), - set::title($lang->project->manageProducts), - to::actions(btn - ( - setClass('ghost text-gray'), - set::url(createLink('project', 'manageproducts', "projectID={$project->id}")), - $lang->more - )), - div - ( - setClass('flex flex-wrap'), - $productsDom - ) - ) : null, - section - ( - setClass('border-b pb-4'), - set::title($lang->execution->linkPlan), - $plansDom - ), - section - ( - setClass('border-b pb-4'), - set::title($lang->execution->lblStats), - div - ( - setClass('flex flex-nowrap items-center'), - span($lang->project->progress . ' ' . $progress . $lang->percent), - div - ( - setClass('progress flex-auto ml-2 h-2'), - div - ( - setClass('progress-bar'), - set('role', 'progressbar'), - setStyle(array('width' => $progress . $lang->percent)), - ) - ) - ), - div - ( - setClass('pt-1 project-info project-statitic-hours'), - tableData - ( - set::useTable(false), - item - ( - set::name($lang->project->begin), - $project->begin - ), - item - ( - set::name($lang->project->realBeganAB), - helper::isZeroDate($project->realBegan) ? '' : $project->realBegan - ), - item - ( - set::name($lang->project->end), - $project->end = $project->end == LONG_TIME ? $this->lang->project->longTime : $project->end - ), - item - ( - set::name($lang->project->realEndAB), - helper::isZeroDate($project->realEnd) ? '' : $project->realEnd - ), - item - ( - set::name($lang->execution->totalEstimate), - (float)$workhour->totalEstimate . $lang->execution->workHour - ), - item - ( - set::name($lang->execution->totalDays), - (float)$project->days . $lang->execution->day - ), - item - ( - set::name($lang->execution->totalConsumed), - (float)$workhour->totalConsumed . $lang->execution->workHour - ), - item - ( - set::name($lang->execution->totalHours), - (float)$workhour->totalHours . $lang->execution->workHour - ), - item - ( - set::name($lang->execution->totalLeft), - (float)$workhour->totalLeft . $lang->execution->workHour - ), - ) - ) - ), - section - ( - setClass('border-b pb-4 project-info'), - set::title($lang->execution->basicInfo), - $basicInfo - ), - section - ( - set::title($lang->project->acl), - p($aclList[$project->acl]) - ) - ) -); - +/* ====== Render page ====== */ render();