batchedit.ui.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. window.renderRowData = function($row, index, row)
  2. {
  3. const executionID = row.execution;
  4. let members = [];
  5. let teamAccounts = executionTeams[executionID] != undefined ? executionTeams[executionID] : [];
  6. $.each(teamAccounts, function(index, teamAccount)
  7. {
  8. members[teamAccount] = users[teamAccount];
  9. });
  10. $row.attr('data-parent', row.parent);
  11. let taskMembers = [];
  12. if(row.mode != '' && teams[row.id] != undefined)
  13. {
  14. teamAccounts = teams[row.id];
  15. $.each(teamAccounts, function(index, teamAccount)
  16. {
  17. taskMembers[teamAccount.account] = users[teamAccount.account];
  18. });
  19. }
  20. else
  21. {
  22. if(row.status == 'closed') members['closed'] = 'Closed';
  23. taskMembers = members;
  24. }
  25. const taskUsers = [];
  26. let disabled = false;
  27. $row.find('.form-batch-input[data-name="assignedTo"]').empty();
  28. if(teams[row.id] != undefined && ((row.mode == 'linear' && row.status != 'done') || taskMembers[currentUser] == undefined)) disabled = true;
  29. if(row.status == 'closed') disabled = true;
  30. if(row.assignedTo && taskMembers[row.assignedTo] == undefined) taskMembers[row.assignedTo] = users[row.assignedTo];
  31. for(let account in taskMembers) taskUsers.push({value: account, text: taskMembers[account]});
  32. if(parentTasks[row.parent] != undefined && taskDateLimit == 'limit')
  33. {
  34. const parentTask = parentTasks[row.parent];
  35. $row.find('[id^="estStarted"]').on('inited', function(e, info)
  36. {
  37. if(parentTask.estStarted == '')
  38. {
  39. row.estStarted = '';
  40. if(parentTasks[row.id] != undefined) parentTasks[row.id].estStarted = '';
  41. info[0].render({readonly: true});
  42. }
  43. });
  44. $row.find('[id^="deadline"]').on('inited', function(e, info)
  45. {
  46. if(parentTask.deadline == '')
  47. {
  48. row.deadline = '';
  49. if(parentTasks[row.id] != undefined) parentTasks[row.id].deadline = '';
  50. info[0].render({readonly: true});
  51. }
  52. });
  53. }
  54. $row.find('[data-name="assignedTo"]').find('.picker-box').on('inited', function(e, info)
  55. {
  56. const $assignedTo = info[0];
  57. const manageLink = noSprintPairs[row.project] != undefined ? manageLinkList['project'].replace('{projectID}', row.project) : manageLinkList['execution'].replace('{executionID}', row.execution);
  58. const pickerToolbar = manageLink && teams[row.id] == undefined ? [{'className': 'text-primary manageTeamBtn', 'key': 'manageTeam', 'text': manageTeamMemberText, 'icon': 'plus-solid-circle', 'url': manageLink, 'data-toggle': 'modal', 'data-size': 'lg', 'data-dismiss': 'pick'}] : '';
  59. if(!pickerToolbar) $row.find('.taskAssignedToBox').removeClass('taskAssignedToBox');
  60. if(pickerToolbar) $row.find('.taskAssignedToBox').attr('data-object', noSprintPairs[row.project] != undefined ? row.project : row.execution);
  61. $assignedTo.render({items: taskUsers, disabled: disabled, toolbar: pickerToolbar});
  62. });
  63. if(row.status == 'wait') $row.find('[data-name="status"]').find('.picker-box').on('inited', function(e, info) { info[0].render({items: noPauseStatusList}); });
  64. if(teams[row.id] != undefined || row.isParent > 0)
  65. {
  66. $row.find('.form-batch-input[data-name="estimate"]').attr('disabled', 'disabled');
  67. $row.find('.form-batch-input[data-name="consumed"]').attr('disabled', 'disabled');
  68. $row.find('.form-batch-input[data-name="left"]').attr('disabled', 'disabled');
  69. }
  70. if(moduleGroup[executionID] != undefined)
  71. {
  72. $row.find('[data-name="module"]').find('.picker-box').on('inited', function(e, info)
  73. {
  74. let $module = info[0];
  75. let modules = moduleGroup[executionID];
  76. $module.render({items: modules});
  77. $module.$.setValue(row.module);
  78. });
  79. }
  80. $row.find('[data-name="story"]').find('.picker-box').on('inited', function(e, info)
  81. {
  82. const $storyPicker = info[0];
  83. if(stories.length > 0)
  84. {
  85. const storyItems = stories[row.module] != undefined ? stories[row.module] : [];
  86. $storyPicker.render({items: storyItems});
  87. $storyPicker.$.setValue(row.story);
  88. }
  89. else
  90. {
  91. const getStoryLink = $.createLink('task', 'ajaxGetStories', 'executionID=' + row.execution + '&moduleID=' + row.module);
  92. $.getJSON(getStoryLink, function(executionStories)
  93. {
  94. $storyPicker.render({items: executionStories});
  95. $storyPicker.$.setValue(row.story);
  96. });
  97. }
  98. });
  99. }
  100. window.clickSubmit = async function(e)
  101. {
  102. if(!nonStoryChildTasks) return true;
  103. const $taskBatchForm = $('#taskBatchEditForm' + executionID);
  104. const $taskBatchFormTrs = $taskBatchForm.find('tbody tr');
  105. var confirmID = '';
  106. for(let i = 0; i < $taskBatchFormTrs.length; i++)
  107. {
  108. const $currentTr = $($taskBatchFormTrs[i]);
  109. const taskID = $currentTr.find('.form-batch-control[data-name=id]').find('input[name^=id]').val();
  110. const storyID = $currentTr.find('.form-batch-control[data-name=story]').find('input[name^=story]').val();
  111. if(tasks[taskID].story == storyID) continue;
  112. if(!storyID && tasks[taskID].parent <= 0) continue;
  113. if(tasks[taskID].parent > 0)
  114. {
  115. if(storyID) confirmID = confirmID.replace('ID' + taskID + ', ', '');
  116. continue;
  117. }
  118. if(typeof childTasks[taskID] != 'object' || typeof nonStoryChildTasks[taskID] != 'object') continue;
  119. const nonStoryChildTaskIdList = Object.keys(nonStoryChildTasks[taskID]);
  120. if(nonStoryChildTaskIdList.length == 0) continue;
  121. for(let j = 0; j < nonStoryChildTaskIdList.length; j++) confirmID += 'ID' + nonStoryChildTaskIdList[j].toString() + ', ';
  122. }
  123. if(confirmID.length == 0) return true;
  124. if(confirmID.endsWith(', ')) confirmID = confirmID.slice(0, -2);
  125. let confirmTip = syncStoryToChildrenTip.replace('%s', confirmID);
  126. zui.Modal.confirm(confirmTip).then((res) =>
  127. {
  128. $taskBatchForm.find('[name=syncChildren]').remove();
  129. $taskBatchForm.append('<input type="hidden" name="syncChildren" value="' + (res ? '1' : '0') + '" />');
  130. const formData = new FormData($taskBatchForm[0]);
  131. const confirmURL = $taskBatchForm.attr('action');
  132. $.ajaxSubmit({url: confirmURL, data: formData});
  133. });
  134. return false;
  135. };
  136. window.statusChange = function(event)
  137. {
  138. const $currentTr = $(event.target).closest('tr');
  139. const status = $(event.target).val();
  140. const $assignedToPicker = $currentTr.find('[name^=assignedTo]').zui('picker');
  141. let hasClosed = false;
  142. let assignedToItems = JSON.parse(JSON.stringify($assignedToPicker.options.items));
  143. if(status == 'closed')
  144. {
  145. for(let i = 0; i < assignedToItems.length; i++)
  146. {
  147. if(assignedToItems[i].value == 'closed') hasClosed = true;
  148. }
  149. if(!hasClosed) assignedToItems.push({key: "closed", keys: "closed c", text : "Closed", value : 'closed'});
  150. $assignedToPicker.render({items: assignedToItems, disabled: true});
  151. $assignedToPicker.$.setValue('closed');
  152. }
  153. else
  154. {
  155. for(let i = 0; i < assignedToItems.length; i++)
  156. {
  157. if(assignedToItems[i].value == 'closed')
  158. {
  159. assignedToItems.splice(i, 1);
  160. $assignedToPicker.render({items: assignedToItems, disabled: false});
  161. $assignedToPicker.$.setValue('');
  162. }
  163. }
  164. }
  165. }
  166. function checkBatchEstStartedAndDeadline(event)
  167. {
  168. if(taskDateLimit != 'limit') return;
  169. const $currentRow = $(event.target).closest('tr');
  170. const taskID = $currentRow.find('[name^=id]').val();
  171. const parentID = tasks[taskID].parent;
  172. const field = $(event.target).closest('.form-batch-control').data('name');
  173. const estStarted = $currentRow.find('[name^=estStarted]').val();
  174. const deadline = $currentRow.find('[name^=deadline]').val();
  175. const parentTask = parentTasks[parentID] ? parentTasks[parentID] : {estStarted: '', deadline: ''};
  176. if(field == 'estStarted')
  177. {
  178. const $estStartedTd = $currentRow.find('td[data-name=estStarted]');
  179. $estStartedTd.find('.date-tip').remove();
  180. const $childrenEstStarted = $(event.target).closest('tbody').find('tr[data-parent="' + taskID + '"]').find('[name^=estStarted]');
  181. $childrenEstStarted.each(function()
  182. {
  183. let $childDatePicker = $(this).zui('datePicker');
  184. $childDatePicker.render({readonly: estStarted.length == 0});
  185. if(estStarted.length == 0) $childDatePicker.$.setValue('');
  186. checkBatchEstStartedAndDeadline({target: this});
  187. });
  188. if(estStarted.length > 0)
  189. {
  190. let $datetip = $('<div class="date-tip"></div>');
  191. let parentEstStarted = typeof tasks[parentID] == 'undefined' || $(event.target).closest('tbody').find('[name="estStarted[' + parentID + ']"]').length == 0 ? parentTask.estStarted : $(event.target).closest('tbody').find('[name="estStarted[' + parentID + ']"]').val();
  192. if(parentEstStarted.length > 0 && estStarted < parentEstStarted) $datetip.append('<div class="form-tip text-danger">' + overParentEstStartedLang.replace('%s', parentEstStarted) + '</div>');
  193. let childEstStarted = childrenDateLimit[taskID] ? childrenDateLimit[taskID].estStarted : '';
  194. $childrenEstStarted.each(function()
  195. {
  196. if(childEstStarted.length == 0 || ($(this).val().length > 0 && $(this).val() < childEstStarted)) childEstStarted = $(this).val();
  197. });
  198. if(childEstStarted.length > 0 && estStarted > childEstStarted)
  199. {
  200. $datetip.append('<div class="form-tip text-warning">' + overChildEstStartedLang.replace('%s', childEstStarted) + '<span class="ignore-date ignore-child underline">' + ignoreLang + '</span></div>');
  201. $datetip.off('click', '.ignore-child').on('click', '.ignore-child', function(e){ignoreTip(e)});
  202. }
  203. $estStartedTd.append($datetip);
  204. }
  205. }
  206. if(field == 'deadline')
  207. {
  208. const $deadlineTd = $currentRow.find('td[data-name=deadline]');
  209. $deadlineTd.find('.date-tip').remove();
  210. const $childrenDeadline = $(event.target).closest('tbody').find('tr[data-parent="' + taskID + '"]').find('[name^=deadline]');
  211. $childrenDeadline.each(function()
  212. {
  213. let $childDatePicker = $(this).zui('datePicker');
  214. $childDatePicker.render({readonly: deadline.length == 0});
  215. if(deadline.length == 0) $childDatePicker.$.setValue('');
  216. checkBatchEstStartedAndDeadline({target: this});
  217. });
  218. if(deadline.length > 0)
  219. {
  220. let $datetip = $('<div class="date-tip"></div>');
  221. let parentDeadline = typeof tasks[parentID] == 'undefined' || $(event.target).closest('tbody').find('[name="deadline[' + parentID + ']"]').length == 0 ? parentTask.deadline : $(event.target).closest('tbody').find('[name="deadline[' + parentID + ']"]').val();
  222. if(parentDeadline.length > 0 && deadline > parentDeadline) $datetip.append('<div class="form-tip text-danger">' + overParentDeadlineLang.replace('%s', parentDeadline) + '</div>');
  223. let childDeadline = childrenDateLimit[taskID] ? childrenDateLimit[taskID].deadline : '';
  224. $childrenDeadline.each(function()
  225. {
  226. if(childDeadline.length == 0 || ($(this).val().length > 0 && $(this).val() > childDeadline)) childDeadline = $(this).val();
  227. });
  228. if(childDeadline.length > 0 && deadline < childDeadline)
  229. {
  230. $datetip.append('<div class="form-tip text-warning">' + overChildDeadlineLang.replace('%s', childDeadline) + '<span class="ignore-date ignore-child underline">' + ignoreLang + '</span></div>');
  231. $datetip.off('click', '.ignore-child').on('click', '.ignore-child', function(e){ignoreTip(e)});
  232. }
  233. $deadlineTd.append($datetip);
  234. }
  235. }
  236. }
  237. /**
  238. * Get select of stories.
  239. *
  240. * @access public
  241. * @return void
  242. */
  243. function setStories(event)
  244. {
  245. const $module = $(event.target);
  246. const $currentRow = $module.closest('tr');
  247. const moduleID = $module.val();
  248. const getStoryLink = $.createLink('task', 'ajaxGetStories', 'executionID=' + executionID + '&moduleID=' + moduleID);
  249. let $row = $currentRow;
  250. while($row.length)
  251. {
  252. const $storyPicker = $row.find('[name^=story]').zui('picker');
  253. const storyID = $row.find('[name^=story]').val();
  254. $.getJSON(getStoryLink, function(stories)
  255. {
  256. $storyPicker.render({items: stories})
  257. $storyPicker.$.setValue(storyID);
  258. });
  259. $row = $row.next('tr');
  260. if(!$row.find('td[data-name="module"][data-ditto="on"]').length) break;
  261. }
  262. }
  263. /**
  264. * Set preview.
  265. *
  266. * @access public
  267. * @return void
  268. */
  269. function setStoryRelated(event)
  270. {
  271. let $story = $(event.target).closest('td').find('input[name^=story]');
  272. let $currentRow = $(event.target).closest('tr');
  273. let storyID = $story.val();
  274. let link = $.createLink('story', 'ajaxGetInfo', 'storyID=' + storyID + '&pageType=batch');
  275. let $row = $currentRow;
  276. while($row.length)
  277. {
  278. const $module = $row.find('input[name^="module"]');
  279. if(storyID > 0)
  280. {
  281. $.getJSON(link, function(data)
  282. {
  283. const storyInfo = data['storyInfo'];
  284. if(!$module.val()) $module.zui('picker').$.setValue(parseInt(storyInfo.moduleID), true);
  285. });
  286. }
  287. $row = $row.next('tr');
  288. if(!$row.find('td[data-name="story"][data-ditto="on"]').length) break;
  289. }
  290. }
  291. window.computeForParent = function(e)
  292. {
  293. const $this = $(e.target);
  294. const field = $this.attr('data-name');
  295. const parentID = $this.closest('tr').attr('data-parent');
  296. if(parentID == '0') return;
  297. const $parent = $('input[name="id[' + parentID + ']"]');
  298. if($parent.length == 0) return;
  299. const oldParentHour = parseFloat($parent.closest('tr').find('input[data-name="' + field + '"]').val());
  300. let parentHour = 0;
  301. $('tr[data-parent="' + parentID + '"]').each(function()
  302. {
  303. parentHour += parseFloat($(this).find('input[data-name="' + field + '"]').val());
  304. });
  305. if(oldParentHour < parentHour) $parent.closest('tr').find('input[data-name="' + field + '"]').val(parentHour.toFixed(2));
  306. }