PageRenderTime 409ms CodeModel.GetById 122ms app.highlight 166ms RepoModel.GetById 107ms app.codeStats 1ms

/web/plan.php

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