PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/web/plan.php

https://bitbucket.org/dhobsd/mtrack
PHP | 340 lines | 277 code | 34 blank | 29 comment | 17 complexity | 37afaf1f99575e207326c9bb0743ac62 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php # vim:ts=2:sw=2:et:
  2. /* For copyright and licensing terms, see the file named LICENSE */
  3. include '../inc/common.php';
  4. $pi = urldecode(mtrack_get_pathinfo());
  5. if (!strlen($pi)) {
  6. throw new Exception("no milestone specified");
  7. }
  8. MTrackACL::requireAllRights("Roadmap", 'modify');
  9. $MS = MTrackAPI::invoke('GET', "/milestone/$pi")->result;
  10. if (!$MS) {
  11. throw new Exception("no such milestone $pi");
  12. }
  13. mtrack_head("$MS->name - Planning");
  14. $MS = json_encode($MS);
  15. $milestones = MTrackAPI::invoke('GET', '/milestones')->result;
  16. $ALL_MS = array();
  17. foreach ($milestones as $M) {
  18. if ($M->id == $ms->mid) continue;
  19. $m = new stdclass;
  20. $m->id = $M->id;
  21. $m->label = $M->name;
  22. $ALL_MS[] = $m;
  23. }
  24. $ALL_MS = json_encode($ALL_MS);
  25. echo <<<HTML
  26. <div id='plan-banner'>
  27. <h1 id='milestone-name'></h1>
  28. <div id="plan-milestone-other-selector"></div>
  29. <div id='plan-rsrc-summary'>
  30. Allocation
  31. <span id='plan-rsrc-total'></span>
  32. hours
  33. </div>
  34. <br>
  35. <button id='newtkt' class='btn btn-primary'><i class='icon-white icon-plus'></i>New Ticket</button>
  36. </div>
  37. <div id="plan-milestone-area">
  38. <div id="plan-ticket-list"></div>
  39. <div id="plan-ticket-other"></div>
  40. <div id="plan-rsrc-list"></div>
  41. </div>
  42. <script type="text/template" id='ticket-template'>
  43. <div class='handle'>
  44. <div class='summary'>
  45. <% if (status == 'closed') { %><del><% } %>
  46. <% if (nsident) { %>#<%- nsident %><% } else { %>[NEW]<% } %> <%- summary %>
  47. <% if (status == 'closed') { %></del><% } %>
  48. </div>
  49. <% if (remaining) { %>
  50. <span class='loe'><%- remaining %></span>
  51. <% } %>
  52. <button class='btn btn-mini'><i class='icon-plus'></i></button>
  53. <% if (owner) { %>
  54. <img class='gravatar' src="${ABSWEB}avatar.php?u=<%- owner %>&amp;s=32">
  55. <% } %>
  56. </div>
  57. </script>
  58. <script type="text/template" id="rsrc-template">
  59. <div class='allocated-resource'>
  60. <img class='gravatar' src="${ABSWEB}avatar.php?u=<%- owner %>&amp;s=32">
  61. <%- owner %>
  62. <span class='loe'><%- total %></span>
  63. </div>
  64. </script>
  65. <div id='tktdialog' class='modal hide'>
  66. <div class='modal-header'>
  67. <a class='close' data-dismiss='modal'>&times;</a>
  68. <h3><span id='tkttitle'></span>
  69. <span id='tktsummary'></span>
  70. </h3>
  71. </div>
  72. <div class='modal-body'>
  73. <div id='tktdesc'></div>
  74. </div>
  75. <div class='modal-footer'>
  76. <button class='btn' id='edittktdesc'
  77. ><i class='icon-pencil'></i> Edit Description</button>
  78. <button class='btn' data-dismiss='modal'>Cancel</button>
  79. <button type='submit' class='btn btn-primary'>Save</button>
  80. </div>
  81. </form>
  82. </div>
  83. <script>
  84. $(document).ready(function () {
  85. var TheMilestone = new MTrackMilestone($MS);
  86. var ALL_MS = $ALL_MS;
  87. var OtherTicketList = null;
  88. var sel = $('<select/>', {
  89. "data-placeholder": "Select other milestone"
  90. });
  91. $('#plan-milestone-other-selector').append(sel);
  92. /* popover would be nice, but it craps out when used on a
  93. * position:fixed element */
  94. /*
  95. $('#plan-milestone-other-selector').popover({
  96. title: 'Source Milestone',
  97. placement: 'bottom',
  98. trigger: 'hover',
  99. content: 'Choose a second milestone to have its tickets display below. You may then drag tickets between the milestones to move them'
  100. });
  101. */
  102. sel.append("<option value=''></option>");
  103. _.each(ALL_MS, function (ms) {
  104. if (ms.id == TheMilestone.id) {
  105. return;
  106. }
  107. var opt = $("<option/>");
  108. opt.attr('value', ms.id);
  109. opt.text(ms.label);
  110. sel.append(opt);
  111. });
  112. sel.chosen({
  113. allow_single_deselect: true
  114. }).change(function() {
  115. var msid = sel.val();
  116. if (OtherTicketList) {
  117. delete OtherTicketList;
  118. $('#plan-ticket-other').empty();
  119. OtherTicketList = null;
  120. }
  121. if (msid) {
  122. var m = new MTrackMilestone({id: msid});
  123. m.fetch({success: function(model, response) {
  124. OtherTicketList = new MTrackPlanningTicketListView({
  125. el: '#plan-ticket-other',
  126. model: model
  127. });
  128. }});
  129. }
  130. $(document.body).animate({scrollTop: 0}, {
  131. duration: 300,
  132. easing: "easeOutQuint"
  133. });
  134. });
  135. /*
  136. new MTrackClickToEditTextField({
  137. model: TheMilestone,
  138. srcattr: "name",
  139. el: "#milestone-name",
  140. readonly: true,
  141. saveAfterEdit: true
  142. });
  143. TheMilestone.bind('change', function () {
  144. $('html head title').text(TheMilestone.get('name') + " - Planning");
  145. });
  146. */
  147. $('#milestone-name').text('Planning: ' + TheMilestone.get('name'));
  148. var plv = new MTrackPlanningTicketListView({
  149. model: TheMilestone,
  150. el: '#plan-ticket-list'
  151. });
  152. var alloc_timer = null;
  153. function update_allocation() {
  154. if (alloc_timer) {
  155. return;
  156. }
  157. alloc_timer = setTimeout(function () {
  158. $.ajax({
  159. url: ABSWEB + "api.php/milestone/" + TheMilestone.id + "/time/remaining",
  160. success: function(data) {
  161. var r = $('#plan-rsrc-total');
  162. r.text(data.total);
  163. r = $('#plan-rsrc-list');
  164. r.empty();
  165. var template = _.template($('#rsrc-template').html());
  166. _.each(_.keys(data.users).sort(), function(u) {
  167. var t = Math.round(100 * data.users[u]) / 100;
  168. var d = $(template({owner: u, total: t}));
  169. r.append(d);
  170. });
  171. r.append($(template({
  172. owner: 'Unassigned',
  173. total: data.unassigned
  174. })));
  175. r.popover({
  176. title: 'Resource Allocation',
  177. trigger: 'hover',
  178. placement: 'left',
  179. content: 'This area shows resource allocation broken out by user. It only includes tickets that have time remaining. The sum of remaining time for tickets that are not assigned is shown at the bottom of this list.'
  180. });
  181. },
  182. complete: function() {
  183. alloc_timer = null;
  184. }
  185. });
  186. }, 200);
  187. }
  188. TheMilestone.tickets.bind('all', function () {
  189. update_allocation();
  190. });
  191. var fake_ticket = new MTrackTicket;
  192. var wiki = null;
  193. var summary_editor = null;
  194. var on_ticket_save = null;
  195. /* set stuff up for ticket editing */
  196. function setup_ticket_editor() {
  197. var M = $('#tktdialog');
  198. summary_editor = new MTrackClickToEditTextField({
  199. model: fake_ticket,
  200. srcattr: "summary",
  201. el: "#tktsummary",
  202. placeholder: "Enter ticket summary"
  203. });
  204. //$('#tktdesc').html(T.get('description_html'));
  205. wiki = new MTrackWikiTextAreaView({
  206. model: fake_ticket,
  207. wikiContext: "ticket:",
  208. use_overlay: true,
  209. Caption: "Edit Description",
  210. OKLabel: "Accept Description",
  211. CancelLabel: "Abandon changes to description",
  212. srcattr: "description",
  213. renderedattr: "description_html",
  214. el: "#tktdesc"
  215. });
  216. wiki.bind('editstart', function () {
  217. setTimeout(function () {
  218. M.modal('hide');
  219. }, 1000);
  220. });
  221. wiki.bind('editend', function () {
  222. M.modal('show');
  223. });
  224. $('#edittktdesc').click(function () {
  225. wiki.edit();
  226. });
  227. $('#tktdialog button[type=submit]').click(function () {
  228. on_ticket_save();
  229. });
  230. };
  231. setup_ticket_editor();
  232. function show_ticket_editor(T, after) {
  233. var M = $('#tktdialog');
  234. fake_ticket.set({
  235. summary: T.get('summary'),
  236. description_html: T.get('description_html'),
  237. description: T.get('description')
  238. });//, {silent: true});
  239. //wiki.change();
  240. $('#tkttitle').text(
  241. T.id ?
  242. ( 'Edit Ticket #' + T.get('nsident') ) :
  243. 'New Ticket'
  244. );
  245. on_ticket_save = function() {
  246. // Apply changes from the clone
  247. T.save({
  248. summary: fake_ticket.get('summary'),
  249. description: fake_ticket.get('description'),
  250. description_html: fake_ticket.get('description_html')
  251. }, {
  252. success: function() {
  253. after.success();
  254. }
  255. });
  256. M.modal('hide');
  257. };
  258. M.modal();
  259. };
  260. $('#plan-ticket-list, #plan-ticket-other')
  261. .on('dblclick', 'div.handle', function() {
  262. var T = $(this.parentElement).data('plan-tkt');
  263. show_ticket_editor(T, {
  264. success: function () {
  265. }
  266. });
  267. });
  268. /* show nice data augments via popovers */
  269. $('#plan-ticket-list, #plan-ticket-other').popover({
  270. selector: 'div.handle',
  271. title: function() {
  272. var T = $(this.parentElement).data('plan-tkt');
  273. return T.get('summary');
  274. },
  275. placement: function() {
  276. var ele = this.\$element;
  277. return ele.parents().filter('#plan-ticket-list').length > 0 ?
  278. 'right' : 'left';
  279. },
  280. content: function() {
  281. var T = $(this.parentElement).data('plan-tkt');
  282. var html = '';
  283. if (T.get('owner')) {
  284. html += "<b>Owner:</b> " + T.get("owner") + "<br>";
  285. }
  286. html += "<b>Type:</b> " + T.get("classification") + "<br>";
  287. html += "<b>Status:</b> " + T.get("status");
  288. return html;
  289. }
  290. });
  291. $('#newtkt').click(function() {
  292. var T = new MTrackTicket;
  293. var m = {};
  294. m[TheMilestone.id] = TheMilestone.get('name');
  295. T.set({milestones: m, classification: 'enhancement'}, {silent: true});
  296. show_ticket_editor(T, {
  297. success: function () {
  298. // TODO: add to collection
  299. TheMilestone.tickets.add(T, {at: TheMilestone.tickets.length});
  300. }
  301. });
  302. });
  303. });
  304. </script>
  305. HTML;
  306. mtrack_foot();