grouptask.html.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. <?php
  2. /**
  3. * The group task view file of execution module of ZenTaoPMS.
  4. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  5. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  6. * @author Sun Guangming<sunguangming@easycorp.ltd>
  7. * @package execution
  8. * @link https://www.zentao.net
  9. */
  10. namespace zin;
  11. $isEn = $app->getClientLang() == 'en';
  12. $items = array();
  13. if(isset($lang->execution->groupFilter[$groupBy]))
  14. {
  15. foreach($lang->execution->groupFilter[$groupBy] as $filterKey => $name)
  16. {
  17. $items[] = li
  18. (
  19. setClass('nav-item'),
  20. a
  21. (
  22. ($filterKey == $filter) ? set('class', 'active') : null,
  23. span(setClass('text'), $name),
  24. ($filterKey == $filter) ? span(setClass('label size-sm rounded-full white'), $allCount) : null,
  25. set::href(createLink('execution', 'grouptask', "executionID={$executionID}&groupBy={$groupBy}&filter={$filterKey}")),
  26. set('data-app', $app->tab)
  27. )
  28. );
  29. }
  30. }
  31. else
  32. {
  33. $items[] = li
  34. (
  35. setClass('nav-item'),
  36. a
  37. (
  38. set('class', 'active'),
  39. span(setClass('text'), $lang->all),
  40. span(setClass('label size-sm rounded-full white'), $allCount),
  41. set::href(createLink('execution', 'grouptask', "executionID={$executionID}&groupBy={$groupBy}")),
  42. set('data-app', $app->tab)
  43. )
  44. );
  45. }
  46. $lang->task->statusList['changed'] = $lang->task->storyChange;
  47. featureBar
  48. (
  49. !empty($tasks) ? li
  50. (
  51. setClass('nav-item feature-actions'),
  52. a
  53. (
  54. setClass('btn ghost group-collapse-all'),
  55. span(setClass('text'), $lang->execution->treeLevel['root']),
  56. icon('fold-all')
  57. )
  58. ) : null,
  59. li
  60. (
  61. setClass('nav-item hidden feature-actions'),
  62. a
  63. (
  64. setClass('btn ghost group-expand-all'),
  65. span(setClass('text'), $lang->execution->treeLevel['all']),
  66. icon('unfold-all')
  67. )
  68. ),
  69. $items
  70. );
  71. $canCreate = !$isLimited && hasPriv('task', 'create');
  72. $canImportTask = hasPriv('task', 'importTask');
  73. $canImportBug = hasPriv('task', 'importBug');
  74. if(common::canModify('execution', $execution))
  75. {
  76. $createLink = $this->createLink('task', 'create', "executionID={$execution->id}") . ($app->tab == 'project' ? '#app=project' : '');
  77. if(commonModel::isTutorialMode())
  78. {
  79. $wizardParams = helper::safe64Encode("executionID={$execution->id}");
  80. $taskCreateLink = $this->createLink('tutorial', 'wizard', "module=task&method=create&params=$wizardParams");
  81. }
  82. $createItem = array('text' => $lang->task->create, 'url' => $createLink);
  83. if($canImportTask && $execution->multiple) $importTaskItem = array('text' => $lang->execution->importTask, 'url' => $this->createLink('execution', 'importTask', "execution={$execution->id}"), 'data-app' => $app->tab);
  84. if($canImportBug && $execution->lifetime != 'ops' && !in_array($execution->attribute, array('request', 'review')))
  85. {
  86. $importBugItem = array('text' => $lang->execution->importBug, 'url' => $this->createLink('execution', 'importBug', "execution={$execution->id}"), 'className' => 'importBug', 'data-app' => $app->tab);
  87. }
  88. }
  89. $importItems = !empty($importTaskItem) && empty($importBugItem) ? array($importTaskItem) : array();
  90. $importItems = empty($importTaskItem) && !empty($importBugItem) ? array($importBugItem) : $importItems;
  91. $importItems = !empty($importTaskItem) && !empty($importBugItem) ? array_filter(array($importTaskItem, $importBugItem)) : $importItems;
  92. toolbar
  93. (
  94. hasPriv('task', 'report') ? item(set(array
  95. (
  96. 'text' => $lang->task->report->common,
  97. 'icon' => 'bar-chart',
  98. 'class' => 'ghost',
  99. 'url' => createLink('task', 'report', "execution={$execution->id}&browseType={$browseType}"),
  100. 'data-app' => $app->tab
  101. ))) : null,
  102. hasPriv('task', 'export') ? item(set(array
  103. (
  104. 'text' => $lang->export,
  105. 'icon' => 'export',
  106. 'class' => 'ghost export',
  107. 'url' => createLink('task', 'export', "execution={$execution->id}&orderBy={$orderBy}&type={$browseType}"),
  108. 'data-toggle' => 'modal',
  109. 'data-size' => 'sm'
  110. ))) : null,
  111. !empty($importItems) ? dropdown(
  112. btn(setClass('btn ghost dropdown-toggle'), set::icon('import'), set::text($lang->import)),
  113. set::items($importItems),
  114. set::placement('bottom-end')
  115. ) : null,
  116. $canCreate && isset($createItem) ? item(set($createItem + array('class' => 'btn primary', 'icon' => 'plus'))) : null
  117. );
  118. $groupList = array();
  119. foreach($lang->execution->groups as $key => $value)
  120. {
  121. if(empty($key)) continue;
  122. $link = createLink('execution', 'grouptask', "executionID={$executionID}&groupBy={$key}");
  123. $groupList[] = array
  124. (
  125. 'text' => $value,
  126. 'url' => $link,
  127. 'active' => $key == $groupBy,
  128. 'data-app' => $app->tab
  129. );
  130. }
  131. $thead = function() use($lang, $groupList, $groupBy, $allCount, $isEn)
  132. {
  133. return h::tr
  134. (
  135. $allCount ? setClass('border-divider') : null,
  136. h::th
  137. (
  138. setClass('c-side text-left has-btn group-menu'),
  139. dropdown
  140. (
  141. btn
  142. (
  143. setClass('ghost btn square btn-default'),
  144. $lang->execution->groups[$groupBy]
  145. ),
  146. set::items($groupList)
  147. )
  148. ),
  149. h::th
  150. (
  151. setClass('c-id'),
  152. set::style(array('width' => '60px')),
  153. $lang->task->id
  154. ),
  155. h::th
  156. (
  157. setClass('c-pri'),
  158. set::style(array('width' => $isEn ? '40px' : '70px')),
  159. $lang->priAB
  160. ),
  161. h::th
  162. (
  163. setClass('c-name'),
  164. set::style(array('max-width' => '120px')),
  165. $lang->task->name
  166. ),
  167. h::th
  168. (
  169. setClass('c-status'),
  170. set::style(array('width' => '100px')),
  171. $lang->task->status
  172. ),
  173. h::th
  174. (
  175. setClass('c-user'),
  176. set::style(array('width' => '100px')),
  177. $lang->task->assignedTo
  178. ),
  179. h::th
  180. (
  181. setClass('c-user'),
  182. set::style(array('width' => $isEn ? '120px' : '100px')),
  183. $lang->task->finishedBy
  184. ),
  185. h::th
  186. (
  187. setClass('c-hours'),
  188. set::style(array('width' => $isEn ? '50px' : '60px')),
  189. $lang->task->estimateAB
  190. ),
  191. h::th
  192. (
  193. setClass('c-hours'),
  194. set::style(array('width' => $isEn ? '50px' : '60px')),
  195. $lang->task->consumedAB
  196. ),
  197. h::th
  198. (
  199. setClass('c-hours'),
  200. set::style(array('width' => $isEn ? '50px' : '60px')),
  201. $lang->task->leftAB
  202. ),
  203. h::th
  204. (
  205. setClass('c-progress'),
  206. set::style(array('width' => '60px')),
  207. $lang->task->progressAB
  208. ),
  209. h::th
  210. (
  211. setClass('c-type'),
  212. set::style(array('width' => '60px')),
  213. $lang->typeAB
  214. ),
  215. h::th
  216. (
  217. setClass('c-date'),
  218. set::style(array('width' => '100px')),
  219. $lang->task->deadline
  220. ),
  221. h::th
  222. (
  223. setClass('c-actions'),
  224. set::style(array('width' => $isEn ? '105px' : '100px')),
  225. $lang->actions
  226. )
  227. );
  228. };
  229. $tbody = function() use($tasks, $lang, $groupBy, $users, $groupByList, $execution, $members)
  230. {
  231. global $app;
  232. $tbody = array();
  233. $groupIndex = 1;
  234. foreach($tasks as $groupKey => $groupTasks)
  235. {
  236. $groupWait = 0;
  237. $groupDone = 0;
  238. $groupDoing = 0;
  239. $groupClosed = 0;
  240. $groupEstimate = 0.0;
  241. $groupConsumed = 0.0;
  242. $groupLeft = 0.0;
  243. $groupName = $groupKey;
  244. if($groupBy == 'story') $groupName = empty($groupName) ? $lang->task->noStory : zget($groupByList, $groupKey);
  245. if($groupBy == 'assignedTo' and $groupName == '') $groupName = $this->lang->task->noAssigned;
  246. $groupSum = 0;
  247. foreach($groupTasks as $taskKey => $task)
  248. {
  249. if($groupBy == 'story')
  250. {
  251. if(!$task->isParent)
  252. {
  253. $groupEstimate += $task->estimate;
  254. $groupConsumed += $task->consumed;
  255. if($task->status != 'cancel' && $task->status != 'closed') $groupLeft += $task->left;
  256. }
  257. }
  258. else
  259. {
  260. if(!$task->isParent)
  261. {
  262. $groupEstimate += $task->estimate;
  263. $groupConsumed += $task->consumed;
  264. if($groupBy == 'status' || ($task->status != 'cancel' && $task->status != 'closed')) $groupLeft += $task->left;
  265. }
  266. }
  267. if($task->status == 'wait') $groupWait++;
  268. if($task->status == 'doing') $groupDoing++;
  269. if($task->status == 'done') $groupDone++;
  270. if($task->status == 'closed') $groupClosed++;
  271. }
  272. $groupSum = count($groupTasks);
  273. $i = 0;
  274. foreach($groupTasks as $task)
  275. {
  276. $assignedToStyle = $task->assignedTo == $app->user->account ? "style='color:red'" : '';
  277. $tbody[] = h::tr
  278. (
  279. set(array('data-id' => $groupIndex)),
  280. ($groupIndex > 1 and $i == 0) ? setClass('divider-top') : null,
  281. $i == 0 ? h::td
  282. (
  283. $groupSum > 4 ? setClass('c-side text-left group-toggle text-top c-side-lg') : setClass('c-side text-left group-toggle text-top'),
  284. set::rowspan($groupSum),
  285. div
  286. (
  287. setClass('group-header'),
  288. a
  289. (
  290. setClass('groupBtn'),
  291. set::href('###'),
  292. icon('caret-down'),
  293. $groupName
  294. ),
  295. div
  296. (
  297. setClass('groupSummary small'),
  298. ($groupBy == 'assignedTo' and isset($members[$task->assignedTo])) ? html(sprintf($lang->execution->memberHoursAB, zget($users, $task->assignedTo), $members[$task->assignedTo]->totalHours)) : null,
  299. html(sprintf($lang->execution->groupSummaryAB, $groupSum, $groupWait, $groupDoing, $groupEstimate . $lang->execution->workHourUnit, $groupConsumed . $lang->execution->workHourUnit, $groupLeft . $lang->execution->workHourUnit))
  300. )
  301. )
  302. ) : null,
  303. h::td(sprintf('%03d', (string)$task->id)),
  304. h::td
  305. (
  306. span
  307. (
  308. setClass("pri-{$task->pri}"),
  309. $task->pri
  310. )
  311. ),
  312. h::td
  313. (
  314. setClass('c-name text-clip'),
  315. set('title', $task->name),
  316. !empty($task->mode) ? span(setClass('label gray-pale rounded-xl'), $lang->task->multipleAB) : null,
  317. (!$task->isParent && $task->parent > 0) ? span(setClass('label gray-pale rounded-xl'), $lang->task->childrenAB) : null,
  318. $task->isParent ? span(setClass('label gray-pale rounded-xl'), $lang->task->parentAB) : null,
  319. a(set::href(createLink('task', 'view', "task=$task->id")), $task->name, set('data-app', $app->tab))
  320. ),
  321. h::td
  322. (
  323. setClass('text-center'),
  324. span
  325. (
  326. setClass("status-{$task->status}"),
  327. zget($lang->task->statusList, $task->status, '')
  328. )
  329. ),
  330. h::td
  331. (
  332. setClass('text-center'),
  333. span
  334. (
  335. set(array('style' => $assignedToStyle)),
  336. $task->assignedToRealName
  337. )
  338. ),
  339. h::td
  340. (
  341. setClass('text-center'),
  342. zget($users, $task->finishedBy)
  343. ),
  344. h::td
  345. (
  346. setClass('text-right'),
  347. helper::formatHours($task->estimate) . $lang->execution->workHourUnit
  348. ),
  349. h::td
  350. (
  351. setClass('text-right'),
  352. helper::formatHours($task->consumed) . $lang->execution->workHourUnit
  353. ),
  354. h::td
  355. (
  356. setClass('text-right'),
  357. helper::formatHours($task->left) . $lang->execution->workHourUnit
  358. ),
  359. h::td
  360. (
  361. setClass('text-right'),
  362. $task->progress . '%'
  363. ),
  364. h::td
  365. (
  366. setClass('text-center'),
  367. zget($lang->task->typeList, $task->type)
  368. ),
  369. h::td
  370. (
  371. setClass('text-center'),
  372. isset($task->delay) ? setClass('delayed') : null,
  373. (substr((string)$task->deadline, 0, 4) > 0) ? substr((string)$task->deadline, 5, 6) : null
  374. ),
  375. common::canModify('execution', $execution) ? h::td
  376. (
  377. setClass('text-center'),
  378. common::hasPriv('task', 'assignTo') && common::hasDBPriv($task, 'task', 'assignto') ? btn
  379. (
  380. set
  381. (
  382. array
  383. (
  384. 'url' => createLink('task', 'assignTo', "executionID=$task->execution&taskID=$task->id"),
  385. 'data-toggle' => 'modal',
  386. 'class' => 'btn ghost toolbar-item text-primary square size-sm',
  387. 'icon' => 'hand-right',
  388. 'disabled' => !empty($task) && $task->status == 'closed'
  389. )
  390. )
  391. ) : null,
  392. common::hasPriv('task', 'edit') && common::hasDBPriv($task, 'task', 'edit')? btn
  393. (
  394. set
  395. (
  396. array
  397. (
  398. 'url' => createLink('task', 'edit', "taskID=$task->id"),
  399. 'class' => 'btn ghost toolbar-item text-primary square size-sm',
  400. 'icon' => 'edit',
  401. 'data-app' => $app->tab
  402. )
  403. )
  404. ) : null,
  405. common::hasPriv('task', 'delete') && common::hasDBPriv($task, 'task', 'delete') ? btn
  406. (
  407. set
  408. (
  409. array
  410. (
  411. 'url' => createLink('task', 'delete', "taskID={$task->id}"),
  412. 'data-confirm' => $task->isParent ? $lang->task->confirmDeleteParent : $lang->task->confirmDelete,
  413. 'class' => 'btn ghost toolbar-item text-primary square size-sm ajax-submit',
  414. 'icon' => 'trash'
  415. )
  416. )
  417. ) : null
  418. )
  419. : h::td()
  420. );
  421. $i ++;
  422. }
  423. if($i != 0)
  424. {
  425. $tbody[] = h::tr
  426. (
  427. $groupIndex > 1 ? setClass('hidden group-toggle group-summary divider-top') : setClass('hidden group-summary group-toggle'),
  428. set(array('data-id' => $groupIndex)),
  429. h::td
  430. (
  431. setClass('c-side'),
  432. div
  433. (
  434. setClass('summary-header'),
  435. a
  436. (
  437. setClass('summaryBtn'),
  438. set::href('###'),
  439. icon('caret-right'),
  440. $groupName
  441. )
  442. )
  443. ),
  444. h::td
  445. (
  446. set::colspan(13),
  447. div
  448. (
  449. setClass('table-row segments-list'),
  450. ($groupBy == 'assignedTo' and isset($members[$task->assignedTo])) ? html(sprintf($lang->execution->memberHours, zget($users, $task->assignedTo), $members[$task->assignedTo]->totalHours)) : null,
  451. ($groupBy == 'assignedTo' and $task->assignedTo and !isset($members[$task->assignedTo])) ? html(sprintf($lang->execution->memberHours, zget($users, $task->assignedTo), '0.0')) : null,
  452. ($groupBy == 'assignedTo' and empty($task->assignedTo)) ? div
  453. (
  454. setClass('table-col'),
  455. div
  456. (
  457. setClass('segments'),
  458. div
  459. (
  460. setClass('segment'),
  461. div
  462. (
  463. setClass('segment-title'),
  464. $groupName
  465. )
  466. )
  467. )
  468. ) : null,
  469. html(sprintf($lang->execution->countSummary, $groupSum, $groupDoing, $groupWait)),
  470. html(sprintf($lang->execution->timeSummary, $groupEstimate . $lang->execution->workHourUnit, $groupConsumed . $lang->execution->workHourUnit, $groupLeft . $lang->execution->workHourUnit))
  471. )
  472. )
  473. );
  474. }
  475. $groupIndex ++;
  476. }
  477. return $tbody;
  478. };
  479. if($tasks)
  480. {
  481. div
  482. (
  483. set::id('tasksTable'),
  484. h::table
  485. (
  486. setClass('table table-fixed condensed'),
  487. h::thead($thead()),
  488. h::tbody($tbody())
  489. )
  490. );
  491. }
  492. else
  493. {
  494. panel
  495. (
  496. div
  497. (
  498. setClass('table-empty-tip h-60 flex items-center justify-center'),
  499. span
  500. (
  501. setClass('text-gray'),
  502. $lang->task->noTask
  503. ),
  504. $canCreate ? btn
  505. (
  506. set::text($lang->task->create),
  507. set::icon('plus'),
  508. set::url(createLink('task', 'create', "execution={$executionID}" . (isset($moduleID) ? "&storyID=&moduleID={$moduleID}" : '')) . ($app->tab == 'project' ? '#app=project' : '')),
  509. setClass('btn primary-pale border-primary ml-2')
  510. ) : null
  511. )
  512. );
  513. }