Commits

Rich Manalang [Atlassian]  committed b3bf98b

working

  • Participants
  • Parent commits db65732

Comments (0)

Files changed (15)

 rsa*
 log/
 tmp/
+public/js/people.js
 use Rack::Flash
 use OmniAuth::Builder do
   provider :JIRA, CONSUMER_KEY, "",
-    :client_options => { :site => "http://localhost:2990/jira", :private_key_file => 'rsa.pem' }
+    :client_options => { :site => "http://rmanalang.staff.sf.atlassian.com:2990/jira", :private_key_file => 'rsa.pem' }
 end
 
 before do
     @jira = JIRA::Client.new(CONSUMER_KEY,'', session[:access_token].consumer.options.merge({:rest_base_path => '/rest/api/2'}))
     # @jira.consumer.http.set_debug_output($stderr)
     @jira.set_access_token(session[:access_token].token, session['access_token'].secret)
+
+    @client = OAuth::AccessToken.new(session[:access_token].consumer, session[:access_token].token, session['access_token'].secret)
+    @client.consumer.http.set_debug_output($stderr)
   end
 
   if !@jira.nil? and @project.nil?
 end
 
 get '/signout' do
-  session[:current_user] = nil
-  session[:access_token] = nil
+  session.clear
   @current_user = nil
   redirect "/"
 end
 
+get '/places' do
+  content_type :json
+  uri = URI.parse("http://gd.geobytes.com/AutoCompleteCity?q=#{CGI::escape(params[:q])}")
+  Net::HTTP.get_response(uri).body
+end
+
 get '/trips' do
+  content_type :json
   @trips = @project.issues
   @trips.to_json
 end
   @trip.to_json  
 end
 
+post '/trip/:key/complete_booking' do
+  content_type :json
+  resp = @client.post('/rest/api/2/issue/'+params[:key]+'/transitions',
+    request.env["rack.input"].read,
+    { "Content-Type" => "application/json" }
+  )
+  resp.body
+end
+
 %w(get post).each do |method|
   send(method, "/auth/:provider/callback") do
     session[:current_user] = request.env['omniauth.auth'].info

File client/app/app.coffee

     .format(2) + 
     " AUD"
 
-class User extends Backbone.Model
-  url: =>
-    '/jira/rest/api/2/user?username=' + @id
-  initialize: (options) ->
-    @fetch(options).done (d) =>
-      options.callback d
-
 class TripModel extends Backbone.Model
   url: '/trip'
 
 
   model: TripModel
 
-  comparator: (updatedOn) =>
-    @get 'customfield_10000'
+  comparator: (a,b) =>
+    a.get('customfield_10000') < b.get('customfield_10000')
 
   parse: (d) ->
     _.map d, (i) =>
   render: ->
     @$el.html @.template()
     $('div.date').datepicker()
+
+    $('.place.from').typeahead
+      property: "customfield_10002"
+      source: (typeahead, query) ->
+        return if query.length < 3
+        $.ajax
+          url: "/places?q=#{query}"
+          success: (data) =>
+            typeahead.process(data)
+
+    $('.place.to').typeahead
+      property: "customfield_10003"
+      source: (typeahead, query) ->
+        return if query.length < 3
+        $.ajax
+          url: "/places?q=#{query}"
+          success: (data) =>
+            typeahead.process(data)
+
     $('#approver').typeahead
-      source: [
-        "Jay Simons"
-        "Mike Cannon-Brookes"
-        "Scott Farquar"
-        "Daniel Freeman"
-        "Rich Manalang"
-      ]
+      property: "assignee"
+      source: (typeahead, query) ->
+        _.map people.items, (o) ->
+          o.label
 
     $('#cost, #curr').on('change keyup', =>)
     @
     @model.on 'error', @showInvalid
     rslt = @model.save trip,
       success: =>
-        console.log arguments
         @render()
         @showSavedToast()
         @collection.trigger 'add', @model
 
   initialize: (options) ->
     @collection.on 'add', @addOne
+    @collection.on 'change', @render, @
     @render()
 
   render: ->
     @addAll()
 
   addOne: (model) =>
-    console.log('adding one')
     myTripView = new MyTripView({model: model})
     myTripView.render();
     @$el.find('#mytrips-rows').prepend myTripView.el
     @collection.each @addOne
 
 
+class PendingTripsView extends Backbone.View
+  el: $ '#pendingtrips-rows'
+
+  template: _.template $('#pendingtrips-template').html()
+
+  initialize: (options)->
+    @render()
+
+  events:
+    "click .complete-booking": "completeBooking"
+
+  completeBooking: (e) ->
+    el = $(e.currentTarget)
+    trip = el.data()
+    $.ajax
+      url: "/trip/#{trip.key}/complete_booking"
+      type: "post"
+      dataType: "json"
+      processData: false
+      data:
+        JSON.stringify
+          transition:
+            id: 31
+      success: =>
+        el.removeClass('btn-success').addClass('btn-inverse').addClass('disabled').html('Booked')
+        @collection.get(trip.id).set('status', {name: "Booked"}, {silent: true})
+        @collection.trigger 'change'
+    false
+
+  render: ->
+    @collection.filter (d) ->
+      d.attributes.status.name == "Approved"
+    .sort (a,b) ->
+      a.get('customfield_10000') > b.get('customfield_10000')
+    .each (model) =>
+      @$el.append @template(model)
+
 class FlashView extends Backbone.View
   tagName: "div"
   
 
 window.trips = new TripCollection
 
-addTripView = new AddTripView({collection: trips})
+addTripView = new AddTripView 
+  collection: trips
+
 trips.fetch().done ->
-  myTripsView = new MyTripsView({collection: trips})
+
+  myTripsView = new MyTripsView 
+    collection: trips
+
+  pendingTripsView = new PendingTripsView 
+    collection: trips
 
 appRouter = new AppRouter
 Backbone.history.start()

File public/css/styles.css

     min-height:1000px;
 }
 body {
-    background: url(../img/clouds.png) 50% 333px repeat-x,
-                url(../img/bg.jpg) 0 0 repeat-x;
+    background: url(../img/clouds.png) 50% 83px repeat-x,
+                url(../img/bg.jpg) 0 -250px repeat-x;
 }
 .hidden {
   display:none;
 }
 .status-btn {
   width:100px;
+}
+.logos {
+  margin-left:0;
+}
+.logos .thumbnail {
+  border:0;
+  box-shadow:none;
+}
+.status {
+  width:100px;
 }

File public/img/airbnb.png

Added
New image

File public/img/airbnblogo.png

Added
New image

File public/img/orbitz.jpg

Old
Old image
New
New image

File public/js/app.js

-var AddTripView, AppRouter, FlashView, MyTripView, MyTripsView, TripCollection, TripModel, User, addTripView, appRouter, getRate, statusButton,
-  __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+var AddTripView, AppRouter, FlashView, MyTripView, MyTripsView, PendingTripsView, TripCollection, TripModel, addTripView, appRouter, getRate, statusButton,
   __hasProp = Object.prototype.hasOwnProperty,
-  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; },
+  __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
 statusButton = function(status) {
   switch (status) {
   return $('#costin').html((parseFloat($('#cost').val() * rate)).round(2).format(2) + " AUD");
 };
 
-User = (function(_super) {
-
-  __extends(User, _super);
-
-  function User() {
-    this.url = __bind(this.url, this);
-    User.__super__.constructor.apply(this, arguments);
-  }
-
-  User.prototype.url = function() {
-    return '/jira/rest/api/2/user?username=' + this.id;
-  };
-
-  User.prototype.initialize = function(options) {
-    var _this = this;
-    return this.fetch(options).done(function(d) {
-      return options.callback(d);
-    });
-  };
-
-  return User;
-
-})(Backbone.Model);
-
 TripModel = (function(_super) {
 
   __extends(TripModel, _super);
 
   TripCollection.prototype.model = TripModel;
 
-  TripCollection.prototype.comparator = function(updatedOn) {
-    return this.get('customfield_10000');
+  TripCollection.prototype.comparator = function(a, b) {
+    return a.get('customfield_10000') < b.get('customfield_10000');
   };
 
   TripCollection.prototype.parse = function(d) {
     var _this = this;
     this.$el.html(this.template());
     $('div.date').datepicker();
+    $('.place.from').typeahead({
+      property: "customfield_10002",
+      source: function(typeahead, query) {
+        var _this = this;
+        if (query.length < 3) return;
+        return $.ajax({
+          url: "/places?q=" + query,
+          success: function(data) {
+            return typeahead.process(data);
+          }
+        });
+      }
+    });
+    $('.place.to').typeahead({
+      property: "customfield_10003",
+      source: function(typeahead, query) {
+        var _this = this;
+        if (query.length < 3) return;
+        return $.ajax({
+          url: "/places?q=" + query,
+          success: function(data) {
+            return typeahead.process(data);
+          }
+        });
+      }
+    });
     $('#approver').typeahead({
-      source: ["Jay Simons", "Mike Cannon-Brookes", "Scott Farquar", "Daniel Freeman", "Rich Manalang"]
+      property: "assignee",
+      source: function(typeahead, query) {
+        return _.map(people.items, function(o) {
+          return o.label;
+        });
+      }
     });
     $('#cost, #curr').on('change keyup', function() {});
     return this;
     this.model.on('error', this.showInvalid);
     rslt = this.model.save(trip, {
       success: function() {
-        console.log(arguments);
         _this.render();
         _this.showSavedToast();
         return _this.collection.trigger('add', _this.model);
 
   MyTripsView.prototype.initialize = function(options) {
     this.collection.on('add', this.addOne);
+    this.collection.on('change', this.render, this);
     return this.render();
   };
 
 
   MyTripsView.prototype.addOne = function(model) {
     var myTripView;
-    console.log('adding one');
     myTripView = new MyTripView({
       model: model
     });
 
 })(Backbone.View);
 
+PendingTripsView = (function(_super) {
+
+  __extends(PendingTripsView, _super);
+
+  function PendingTripsView() {
+    PendingTripsView.__super__.constructor.apply(this, arguments);
+  }
+
+  PendingTripsView.prototype.el = $('#pendingtrips-rows');
+
+  PendingTripsView.prototype.template = _.template($('#pendingtrips-template').html());
+
+  PendingTripsView.prototype.initialize = function(options) {
+    return this.render();
+  };
+
+  PendingTripsView.prototype.events = {
+    "click .complete-booking": "completeBooking"
+  };
+
+  PendingTripsView.prototype.completeBooking = function(e) {
+    var el, trip,
+      _this = this;
+    el = $(e.currentTarget);
+    trip = el.data();
+    $.ajax({
+      url: "/trip/" + trip.key + "/complete_booking",
+      type: "post",
+      dataType: "json",
+      processData: false,
+      data: JSON.stringify({
+        transition: {
+          id: 31
+        }
+      }),
+      success: function() {
+        el.removeClass('btn-success').addClass('btn-inverse').addClass('disabled').html('Booked');
+        _this.collection.get(trip.id).set('status', {
+          name: "Booked"
+        }, {
+          silent: true
+        });
+        return _this.collection.trigger('change');
+      }
+    });
+    return false;
+  };
+
+  PendingTripsView.prototype.render = function() {
+    var _this = this;
+    return this.collection.filter(function(d) {
+      return d.attributes.status.name === "Approved";
+    }).sort(function(a, b) {
+      return a.get('customfield_10000') > b.get('customfield_10000');
+    }).each(function(model) {
+      return _this.$el.append(_this.template(model));
+    });
+  };
+
+  return PendingTripsView;
+
+})(Backbone.View);
+
 FlashView = (function(_super) {
 
   __extends(FlashView, _super);
 });
 
 trips.fetch().done(function() {
-  var myTripsView;
-  return myTripsView = new MyTripsView({
+  var myTripsView, pendingTripsView;
+  myTripsView = new MyTripsView({
+    collection: trips
+  });
+  return pendingTripsView = new PendingTripsView({
     collection: trips
   });
 });

File public/js/libs/bootstrap-typeahead.js

+/* =============================================================
+ * bootstrap-typeahead.js v2.0.0
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+!function( $ ){
+
+  "use strict"
+
+  var Typeahead = function ( element, options ) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.typeahead.defaults, options)
+    this.matcher = this.options.matcher || this.matcher
+    this.sorter = this.options.sorter || this.sorter
+    this.highlighter = this.options.highlighter || this.highlighter
+    this.$menu = $(this.options.menu).appendTo('body')
+    this.source = this.options.source
+    this.onselect = this.options.onselect
+    this.strings = true
+    this.shown = false
+    this.listen()
+  }
+
+  Typeahead.prototype = {
+
+    constructor: Typeahead
+
+  , select: function () {
+      var val = JSON.parse(this.$menu.find('.active').attr('data-value'))
+        , text
+
+      if (!this.strings) text = val[this.options.property]
+      else text = val
+
+      this.$element.val(text)
+
+      if (typeof this.onselect == "function")
+          this.onselect(val)
+
+      return this.hide()
+    }
+
+  , show: function () {
+      var pos = $.extend({}, this.$element.offset(), {
+        height: this.$element[0].offsetHeight
+      })
+
+      this.$menu.css({
+        top: pos.top + pos.height
+      , left: pos.left
+      })
+
+      this.$menu.show()
+      this.shown = true
+      return this
+    }
+
+  , hide: function () {
+      this.$menu.hide()
+      this.shown = false
+      return this
+    }
+
+  , lookup: function (event) {
+      var that = this
+        , items
+        , q
+        , value
+
+      this.query = this.$element.val()
+
+      if (typeof this.source == "function") {
+        value = this.source(this, this.query)
+        if (value) this.process(value)
+      } else {
+        this.process(this.source)
+      }
+    }
+
+  , process: function (results) {
+      var that = this
+        , items
+        , q
+
+      if (results.length && typeof results[0] != "string")
+          this.strings = false
+
+      this.query = this.$element.val()
+
+      if (!this.query) {
+        return this.shown ? this.hide() : this
+      }
+
+      items = $.grep(results, function (item) {
+        if (!that.strings)
+          item = item[that.options.property]
+        if (that.matcher(item)) return item
+      })
+
+      items = this.sorter(items)
+
+      if (!items.length) {
+        return this.shown ? this.hide() : this
+      }
+
+      return this.render(items.slice(0, this.options.items)).show()
+    }
+
+  , matcher: function (item) {
+      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+    }
+
+  , sorter: function (items) {
+      var beginswith = []
+        , caseSensitive = []
+        , caseInsensitive = []
+        , item
+        , sortby
+
+      while (item = items.shift()) {
+        if (this.strings) sortby = item
+        else sortby = item[this.options.property]
+
+        if (!sortby.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+        else if (~sortby.indexOf(this.query)) caseSensitive.push(item)
+        else caseInsensitive.push(item)
+      }
+
+      return beginswith.concat(caseSensitive, caseInsensitive)
+    }
+
+  , highlighter: function (item) {
+      return item.replace(new RegExp('(' + this.query + ')', 'ig'), function ($1, match) {
+        return '<strong>' + match + '</strong>'
+      })
+    }
+
+  , render: function (items) {
+      var that = this
+
+      items = $(items).map(function (i, item) {
+        i = $(that.options.item).attr('data-value', JSON.stringify(item))
+        if (!that.strings)
+            item = item[that.options.property]
+        i.find('a').html(that.highlighter(item))
+        return i[0]
+      })
+
+      items.first().addClass('active')
+      this.$menu.html(items)
+      return this
+    }
+
+  , next: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , next = active.next()
+
+      if (!next.length) {
+        next = $(this.$menu.find('li')[0])
+      }
+
+      next.addClass('active')
+    }
+
+  , prev: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , prev = active.prev()
+
+      if (!prev.length) {
+        prev = this.$menu.find('li').last()
+      }
+
+      prev.addClass('active')
+    }
+
+  , listen: function () {
+      this.$element
+        .on('blur',     $.proxy(this.blur, this))
+        .on('keypress', $.proxy(this.keypress, this))
+        .on('keyup',    $.proxy(this.keyup, this))
+
+      if ($.browser.webkit || $.browser.msie) {
+        this.$element.on('keydown', $.proxy(this.keypress, this))
+      }
+
+      this.$menu
+        .on('click', $.proxy(this.click, this))
+        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+    }
+
+  , keyup: function (e) {
+      e.stopPropagation()
+      e.preventDefault()
+
+      switch(e.keyCode) {
+        case 40: // down arrow
+        case 38: // up arrow
+          break
+
+        case 9: // tab
+        case 13: // enter
+          if (!this.shown) return
+          this.select()
+          break
+
+        case 27: // escape
+          this.hide()
+          break
+
+        default:
+          this.lookup()
+      }
+
+  }
+
+  , keypress: function (e) {
+      e.stopPropagation()
+      if (!this.shown) return
+
+      switch(e.keyCode) {
+        case 9: // tab
+        case 13: // enter
+        case 27: // escape
+          e.preventDefault()
+          break
+
+        case 38: // up arrow
+          e.preventDefault()
+          this.prev()
+          break
+
+        case 40: // down arrow
+          e.preventDefault()
+          this.next()
+          break
+      }
+    }
+
+  , blur: function (e) {
+      var that = this
+      e.stopPropagation()
+      e.preventDefault()
+      setTimeout(function () { that.hide() }, 150)
+    }
+
+  , click: function (e) {
+      e.stopPropagation()
+      e.preventDefault()
+      this.select()
+    }
+
+  , mouseenter: function (e) {
+      this.$menu.find('.active').removeClass('active')
+      $(e.currentTarget).addClass('active')
+    }
+
+  }
+
+
+  /* TYPEAHEAD PLUGIN DEFINITION
+   * =========================== */
+
+  $.fn.typeahead = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('typeahead')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.typeahead.defaults = {
+    source: []
+  , items: 8
+  , menu: '<ul class="typeahead dropdown-menu"></ul>'
+  , item: '<li><a href="#"></a></li>'
+  , onselect: null
+  , property: 'value'
+  }
+
+  $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+  * ================== */
+
+  $(function () {
+    $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+      var $this = $(this)
+      if ($this.data('typeahead')) return
+      e.preventDefault()
+      $this.typeahead($this.data())
+    })
+  })
+
+}( window.jQuery );

File views/index.slim

       == slim :'partials/my_trip'
     script#booktrip-template(type="text/html")
       == slim :'partials/book_trip'
+    script#pendingtrips-template(type="text/html")
+      == slim :'partials/pending_trips'
     script#flash-template(type="text/html")
       == slim :'partials/flash'
 
       #404.content.hidden
         h1 Dude, where's my page? (404)
 
+    script(src='./js/people.js')
     script(src="./js/libs/jquery.min.js")
     script(src="./js/libs/underscore.min.js")
     script(src="./js/libs/backbone.min.js")
-    //- script(src="./js/libs/backbone-localstorage.js")
     script(src="./js/libs/backbone.validations.js")
     script(src="./js/libs/backbone_router_filter.js")
     script(src='./js/libs/bootstrap.min.js')
+    script(src='./js/libs/bootstrap-typeahead.js')
     script(src='./js/libs/sugar.min.js')
     script(src='./js/libs/bootstrap-datepicker.js')
     script(src="./js/libs/plugins.js")

File views/partials/add_trip.slim

         .input-prepend
           span.add-on
             i.icon-plane
-          input.span7#descr(name="summary" type="text" placeholder="Describe your trip here...")
+          input.span7#descr(name="summary" type="text" placeholder="Describe your trip here..." autocomplete="off")
     .row
       .span4.customfield_10002.control-group
         label
         .input-prepend
           span.add-on
             i.icon-map-marker
-          input.place.span4(name="customfield_10002" type="text" placeholder="San Francisco")
+          input.place.from.span4(name="customfield_10002" type="text" placeholder="San Francisco" autocomplete="off")
       .span4.customfield_10003.control-group
         label 
           b To
         .input-prepend
           span.add-on
             i.icon-map-marker
-          input.place.span4(name="customfield_10003" type="text" placeholder="Sydney")
+          input.place.to.span4(name="customfield_10003" type="text" placeholder="Sydney" autocomplete="off")
     .row
       .span4.customfield_10000.control-group
         label
         .input-prepend
           span.add-on
             i.icon-user
-          input.span4#approver(name="assignee" type="text" placeholder="Daniel Freeman" data-provide="typeahead")
+          input.span4#approver(name="assignee" type="text" autocomplete="off" placeholder="Daniel Freeman" data-provide="typeahead")
     .row
       .span8
         input.btn.btn-primary.btn-large(type="submit" value="Submit for approval")

File views/partials/book_trip.slim

 b.pointer
 .flash
-.row
+.row-fluid
   .span12
     .page-header
       h1 
         | Book Your Trip 
         small Policies and procedures for booking your trip
-
     p
-      | Once your travel request is approved, you may proceed with booking your trip. Atlassian has contracts with two travel agencies to help you with your bookings.
-    ul.thumbnails
-      li.span6
-        .thumbnail
+      | Once your travel request is approved, you may proceed with booking your trip. 
+      | Atlassian has contracts with 
+      a(href="http://expedia.com" target="_blank") Expedia
+      |  and 
+      a(href="http://orbitz.com" target="_blank") Orbitz
+      |  to help you with your travel needs. 
+      | Please use these agencies to book your flights, hotel, and/or rental car. 
+      | In addition, lots of Atlassians have had great luck booking a room or flat 
+      | through 
+      a(href="http://airbnb.com" target="_blank") Airbnb
+      | .
+    ul.thumbnails.row-fluid.logos
+      li.span4
+        a.thumbnail(href="http://travelocity.com" target="_blank")
           img(src="./img/travelocity.png")
-      li.span6
-        .thumbnail
-          img(src="./img/orbitz.jpg")
+      li.span4
+        a.thumbnail(href="http://orbitz.com" target="_blank")
+          img(src="./img/orbitz.jpg")
+      li.span4
+        a.thumbnail(href="http://airbnb.com" target="_blank")
+          img(src="./img/airbnb.png")
+    p
+      | Once you've booked your trip, please 
+      | update the travel request below to close out your request.
+
+.row-fluid
+  .span12
+    h3
+      | Trips Pending Booking
+
+.row-fluid
+  form#completebooking-form.span12
+    table.table.table-bordered.table-striped
+      thead
+        tr
+          th Description
+          th Trip
+          th Dates
+          th Amount
+          th.status 
+      tbody#pendingtrips-rows
+
+  p
+    | If you have questions or comments about this process, please email 
+    a(href="mailto:travel@atlassian.com") travel@atlassian.com
+    |.

File views/partials/my_trip.slim

   |  (<%- Math.ceil((Date.create(customfield_10001).getTime() - Date.create(customfield_10000).getTime())/(1000*60*60*24)) %>d)
 td <%- parseInt(customfield_10004).format() %> <%- customfield_10005.value %>
 td <%- assignee.displayName %>
-td
+td.status
   button.btn.disabled.status-btn(class="<%- statusButton(status.name) %>")
     | <%- status.name %>

File views/partials/my_trips.slim

 .flash
 .row-fluid.padded-bottom
   .span10
-    h3 My Trips
+    h2 My Trips
   .span2
     a.btn.btn-primary.pull-right(href="#/trip/new")
       i.icon-plus-sign.icon-white
           th Dates
           th Amount
           th Approver
-          th Status
+          th.status Status
       tbody#mytrips-rows

File views/partials/pending_trips.slim

+tr
+  td <%- attributes.summary %>
+  td 
+    | <%- attributes.customfield_10002 %> &rarr; 
+    br
+    | <%- attributes.customfield_10003 %>
+  td 
+    | <%- Date.create(attributes.customfield_10000).format("{Weekday}, {Mon} {d}") %> 
+    | to 
+    br
+    | <%- Date.create(attributes.customfield_10001).format("{Weekday}, {Mon} {d}") %>
+    |  (<%- Math.ceil((Date.create(attributes.customfield_10001).getTime() - Date.create(attributes.customfield_10000).getTime())/(1000*60*60*24)) %>d)
+  td <%- parseInt(attributes.customfield_10004).format() %> <%- attributes.customfield_10005.value %>
+  td.status
+    .complete-booking.button.btn.status-btn.btn-success(data-id="<%- id %>" data-key="<%- attributes.key %>")
+      | Complete Booking