diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 276aff6..624d531 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,3 +14,5 @@ //= require jquery_ujs //= require twitter/bootstrap/bootstrap-button //= require utils +//= require bootstrap-datepicker +//= require bootstrap-timepicker diff --git a/app/assets/javascripts/bootstrap-datepicker.js b/app/assets/javascripts/bootstrap-datepicker.js new file mode 100755 index 0000000..bf3a56d --- /dev/null +++ b/app/assets/javascripts/bootstrap-datepicker.js @@ -0,0 +1,474 @@ +/* ========================================================= + * bootstrap-datepicker.js + * http://www.eyecon.ro/bootstrap-datepicker + * ========================================================= + * Copyright 2012 Stefan Petre + * + * 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( $ ) { + + // Picker object + + var Datepicker = function(element, options){ + this.element = $(element); + this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy'); + this.picker = $(DPGlobal.template) + .appendTo('body') + .on({ + click: $.proxy(this.click, this)//, + //mousedown: $.proxy(this.mousedown, this) + }); + this.isInput = this.element.is('input'); + this.component = this.element.is('.date') ? this.element.find('.add-on') : false; + + if (this.isInput) { + this.element.on({ + focus: $.proxy(this.show, this), + //blur: $.proxy(this.hide, this), + keyup: $.proxy(this.update, this) + }); + } else { + if (this.component){ + this.component.on('click', $.proxy(this.show, this)); + } else { + this.element.on('click', $.proxy(this.show, this)); + } + } + + this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0; + if (typeof this.minViewMode === 'string') { + switch (this.minViewMode) { + case 'months': + this.minViewMode = 1; + break; + case 'years': + this.minViewMode = 2; + break; + default: + this.minViewMode = 0; + break; + } + } + this.viewMode = options.viewMode||this.element.data('date-viewmode')||0; + if (typeof this.viewMode === 'string') { + switch (this.viewMode) { + case 'months': + this.viewMode = 1; + break; + case 'years': + this.viewMode = 2; + break; + default: + this.viewMode = 0; + break; + } + } + this.startViewMode = this.viewMode; + this.weekStart = options.weekStart||this.element.data('date-weekstart')||0; + this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1; + this.onRender = options.onRender; + this.fillDow(); + this.fillMonths(); + this.update(); + this.showMode(); + }; + + Datepicker.prototype = { + constructor: Datepicker, + + show: function(e) { + this.picker.show(); + this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); + this.place(); + $(window).on('resize', $.proxy(this.place, this)); + if (e ) { + e.stopPropagation(); + e.preventDefault(); + } + if (!this.isInput) { + } + var that = this; + $(document).on('mousedown', function(ev){ + if ($(ev.target).closest('.datepicker').length == 0) { + that.hide(); + } + }); + this.element.trigger({ + type: 'show', + date: this.date + }); + }, + + hide: function(){ + this.picker.hide(); + $(window).off('resize', this.place); + this.viewMode = this.startViewMode; + this.showMode(); + if (!this.isInput) { + $(document).off('mousedown', this.hide); + } + //this.set(); + this.element.trigger({ + type: 'hide', + date: this.date + }); + }, + + set: function() { + var formated = DPGlobal.formatDate(this.date, this.format); + if (!this.isInput) { + if (this.component){ + this.element.find('input').prop('value', formated); + } + this.element.data('date', formated); + } else { + this.element.prop('value', formated); + } + }, + + setValue: function(newDate) { + if (typeof newDate === 'string') { + this.date = DPGlobal.parseDate(newDate, this.format); + } else { + this.date = new Date(newDate); + } + this.set(); + this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0); + this.fill(); + }, + + place: function(){ + var offset = this.component ? this.component.offset() : this.element.offset(); + this.picker.css({ + top: offset.top + this.height, + left: offset.left + }); + }, + + update: function(newDate){ + this.date = DPGlobal.parseDate( + typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')), + this.format + ); + this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0); + this.fill(); + }, + + fillDow: function(){ + var dowCnt = this.weekStart; + var html = ''; + while (dowCnt < this.weekStart + 7) { + html += ''+DPGlobal.dates.daysMin[(dowCnt++)%7]+''; + } + html += ''; + this.picker.find('.datepicker-days thead').append(html); + }, + + fillMonths: function(){ + var html = ''; + var i = 0 + while (i < 12) { + html += ''+DPGlobal.dates.monthsShort[i++]+''; + } + this.picker.find('.datepicker-months td').append(html); + }, + + fill: function() { + var d = new Date(this.viewDate), + year = d.getFullYear(), + month = d.getMonth(), + currentDate = this.date.valueOf(); + this.picker.find('.datepicker-days th:eq(1)') + .text(DPGlobal.dates.months[month]+' '+year); + var prevMonth = new Date(year, month-1, 28,0,0,0,0), + day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth()); + prevMonth.setDate(day); + prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + nextMonth.setDate(nextMonth.getDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName, + prevY, + prevM; + while(prevMonth.valueOf() < nextMonth) { + if (prevMonth.getDay() === this.weekStart) { + html.push(''); + } + clsName = this.onRender(prevMonth); + prevY = prevMonth.getFullYear(); + prevM = prevMonth.getMonth(); + if ((prevM < month && prevY === year) || prevY < year) { + clsName += ' old'; + } else if ((prevM > month && prevY === year) || prevY > year) { + clsName += ' new'; + } + if (prevMonth.valueOf() === currentDate) { + clsName += ' active'; + } + html.push(''+prevMonth.getDate() + ''); + if (prevMonth.getDay() === this.weekEnd) { + html.push(''); + } + prevMonth.setDate(prevMonth.getDate()+1); + } + this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + var currentYear = this.date.getFullYear(); + + var months = this.picker.find('.datepicker-months') + .find('th:eq(1)') + .text(year) + .end() + .find('span').removeClass('active'); + if (currentYear === year) { + months.eq(this.date.getMonth()).addClass('active'); + } + + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.picker.find('.datepicker-years') + .find('th:eq(1)') + .text(year + '-' + (year + 9)) + .end() + .find('td'); + year -= 1; + for (var i = -1; i < 11; i++) { + html += ''+year+''; + year += 1; + } + yearCont.html(html); + }, + + click: function(e) { + e.stopPropagation(); + e.preventDefault(); + var target = $(e.target).closest('span, td, th'); + if (target.length === 1) { + switch(target[0].nodeName.toLowerCase()) { + case 'th': + switch(target[0].className) { + case 'switch': + this.showMode(1); + break; + case 'prev': + case 'next': + this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call( + this.viewDate, + this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) + + DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1) + ); + this.fill(); + this.set(); + break; + } + break; + case 'span': + if (target.is('.month')) { + var month = target.parent().find('span').index(target); + this.viewDate.setMonth(month); + } else { + var year = parseInt(target.text(), 10)||0; + this.viewDate.setFullYear(year); + } + if (this.viewMode !== 0) { + this.date = new Date(this.viewDate); + this.element.trigger({ + type: 'changeDate', + date: this.date, + viewMode: DPGlobal.modes[this.viewMode].clsName + }); + } + this.showMode(-1); + this.fill(); + this.set(); + break; + case 'td': + if (target.is('.day') && !target.is('.disabled')){ + var day = parseInt(target.text(), 10)||1; + var month = this.viewDate.getMonth(); + if (target.is('.old')) { + month -= 1; + } else if (target.is('.new')) { + month += 1; + } + var year = this.viewDate.getFullYear(); + this.date = new Date(year, month, day,0,0,0,0); + this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0); + this.fill(); + this.set(); + this.element.trigger({ + type: 'changeDate', + date: this.date, + viewMode: DPGlobal.modes[this.viewMode].clsName + }); + } + break; + } + } + }, + + mousedown: function(e){ + e.stopPropagation(); + e.preventDefault(); + }, + + showMode: function(dir) { + if (dir) { + this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir)); + } + this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); + } + }; + + $.fn.datepicker = function ( option, val ) { + return this.each(function () { + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option === 'object' && option; + if (!data) { + $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options)))); + } + if (typeof option === 'string') data[option](val); + }); + }; + + $.fn.datepicker.defaults = { + onRender: function(date) { + return ''; + } + }; + $.fn.datepicker.Constructor = Datepicker; + + var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'Month', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'FullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'FullYear', + navStep: 10 + }], + dates:{ + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + }, + isLeapYear: function (year) { + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) + }, + getDaysInMonth: function (year, month) { + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] + }, + parseFormat: function(format){ + var separator = format.match(/[.\/\-\s].*?/), + parts = format.split(/\W+/); + if (!separator || !parts || parts.length === 0){ + throw new Error("Invalid date format."); + } + return {separator: separator, parts: parts}; + }, + parseDate: function(date, format) { + var parts = date.split(format.separator), + date = new Date(), + val; + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + date.setMilliseconds(0); + if (parts.length === format.parts.length) { + var year = date.getFullYear(), day = date.getDate(), month = date.getMonth(); + for (var i=0, cnt = format.parts.length; i < cnt; i++) { + val = parseInt(parts[i], 10)||1; + switch(format.parts[i]) { + case 'dd': + case 'd': + day = val; + date.setDate(val); + break; + case 'mm': + case 'm': + month = val - 1; + date.setMonth(val - 1); + break; + case 'yy': + year = 2000 + val; + date.setFullYear(2000 + val); + break; + case 'yyyy': + year = val; + date.setFullYear(val); + break; + } + } + date = new Date(year, month, day, 0 ,0 ,0); + } + return date; + }, + formatDate: function(date, format){ + var val = { + d: date.getDate(), + m: date.getMonth() + 1, + yy: date.getFullYear().toString().substring(2), + yyyy: date.getFullYear() + }; + val.dd = (val.d < 10 ? '0' : '') + val.d; + val.mm = (val.m < 10 ? '0' : '') + val.m; + var date = []; + for (var i=0, cnt = format.parts.length; i < cnt; i++) { + date.push(val[format.parts[i]]); + } + return date.join(format.separator); + }, + headTemplate: ''+ + ''+ + '‹'+ + ''+ + '›'+ + ''+ + '', + contTemplate: '' + }; + DPGlobal.template = ''; + +}( window.jQuery ); \ No newline at end of file diff --git a/app/assets/javascripts/bootstrap-timepicker.js b/app/assets/javascripts/bootstrap-timepicker.js new file mode 100644 index 0000000..5972e3c --- /dev/null +++ b/app/assets/javascripts/bootstrap-timepicker.js @@ -0,0 +1,1097 @@ +/*! + * Timepicker Component for Twitter Bootstrap + * + * Copyright 2013 Joris de Wit + * + * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +(function($, window, document, undefined) { + 'use strict'; + + // TIMEPICKER PUBLIC CLASS DEFINITION + var Timepicker = function(element, options) { + this.widget = ''; + this.$element = $(element); + this.defaultTime = options.defaultTime; + this.disableFocus = options.disableFocus; + this.disableMousewheel = options.disableMousewheel; + this.isOpen = options.isOpen; + this.minuteStep = options.minuteStep; + this.modalBackdrop = options.modalBackdrop; + this.orientation = options.orientation; + this.secondStep = options.secondStep; + this.showInputs = options.showInputs; + this.showMeridian = options.showMeridian; + this.showSeconds = options.showSeconds; + this.template = options.template; + this.appendWidgetTo = options.appendWidgetTo; + this.showWidgetOnAddonClick = options.showWidgetOnAddonClick; + + this._init(); + }; + + Timepicker.prototype = { + + constructor: Timepicker, + _init: function() { + var self = this; + + if (this.showWidgetOnAddonClick && (this.$element.parent().hasClass('input-append') || this.$element.parent().hasClass('input-prepend'))) { + this.$element.parent('.input-append, .input-prepend').find('.add-on').on({ + 'click.timepicker': $.proxy(this.showWidget, this) + }); + this.$element.on({ + 'focus.timepicker': $.proxy(this.highlightUnit, this), + 'click.timepicker': $.proxy(this.highlightUnit, this), + 'keydown.timepicker': $.proxy(this.elementKeydown, this), + 'blur.timepicker': $.proxy(this.blurElement, this), + 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) + }); + } else { + if (this.template) { + this.$element.on({ + 'focus.timepicker': $.proxy(this.showWidget, this), + 'click.timepicker': $.proxy(this.showWidget, this), + 'blur.timepicker': $.proxy(this.blurElement, this), + 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) + }); + } else { + this.$element.on({ + 'focus.timepicker': $.proxy(this.highlightUnit, this), + 'click.timepicker': $.proxy(this.highlightUnit, this), + 'keydown.timepicker': $.proxy(this.elementKeydown, this), + 'blur.timepicker': $.proxy(this.blurElement, this), + 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) + }); + } + } + + if (this.template !== false) { + this.$widget = $(this.getTemplate()).on('click', $.proxy(this.widgetClick, this)); + } else { + this.$widget = false; + } + + if (this.showInputs && this.$widget !== false) { + this.$widget.find('input').each(function() { + $(this).on({ + 'click.timepicker': function() { $(this).select(); }, + 'keydown.timepicker': $.proxy(self.widgetKeydown, self), + 'keyup.timepicker': $.proxy(self.widgetKeyup, self) + }); + }); + } + + this.setDefaultTime(this.defaultTime); + }, + + blurElement: function() { + this.highlightedUnit = null; + this.updateFromElementVal(); + }, + + clear: function() { + this.hour = ''; + this.minute = ''; + this.second = ''; + this.meridian = ''; + + this.$element.val(''); + }, + + decrementHour: function() { + if (this.showMeridian) { + if (this.hour === 1) { + this.hour = 12; + } else if (this.hour === 12) { + this.hour--; + + return this.toggleMeridian(); + } else if (this.hour === 0) { + this.hour = 11; + + return this.toggleMeridian(); + } else { + this.hour--; + } + } else { + if (this.hour <= 0) { + this.hour = 23; + } else { + this.hour--; + } + } + }, + + decrementMinute: function(step) { + var newVal; + + if (step) { + newVal = this.minute - step; + } else { + newVal = this.minute - this.minuteStep; + } + + if (newVal < 0) { + this.decrementHour(); + this.minute = newVal + 60; + } else { + this.minute = newVal; + } + }, + + decrementSecond: function() { + var newVal = this.second - this.secondStep; + + if (newVal < 0) { + this.decrementMinute(true); + this.second = newVal + 60; + } else { + this.second = newVal; + } + }, + + elementKeydown: function(e) { + switch (e.keyCode) { + case 9: //tab + case 27: // escape + this.updateFromElementVal(); + break; + case 37: // left arrow + e.preventDefault(); + this.highlightPrevUnit(); + break; + case 38: // up arrow + e.preventDefault(); + switch (this.highlightedUnit) { + case 'hour': + this.incrementHour(); + this.highlightHour(); + break; + case 'minute': + this.incrementMinute(); + this.highlightMinute(); + break; + case 'second': + this.incrementSecond(); + this.highlightSecond(); + break; + case 'meridian': + this.toggleMeridian(); + this.highlightMeridian(); + break; + } + this.update(); + break; + case 39: // right arrow + e.preventDefault(); + this.highlightNextUnit(); + break; + case 40: // down arrow + e.preventDefault(); + switch (this.highlightedUnit) { + case 'hour': + this.decrementHour(); + this.highlightHour(); + break; + case 'minute': + this.decrementMinute(); + this.highlightMinute(); + break; + case 'second': + this.decrementSecond(); + this.highlightSecond(); + break; + case 'meridian': + this.toggleMeridian(); + this.highlightMeridian(); + break; + } + + this.update(); + break; + } + }, + + getCursorPosition: function() { + var input = this.$element.get(0); + + if ('selectionStart' in input) {// Standard-compliant browsers + + return input.selectionStart; + } else if (document.selection) {// IE fix + input.focus(); + var sel = document.selection.createRange(), + selLen = document.selection.createRange().text.length; + + sel.moveStart('character', - input.value.length); + + return sel.text.length - selLen; + } + }, + + getTemplate: function() { + var template, + hourTemplate, + minuteTemplate, + secondTemplate, + meridianTemplate, + templateContent; + + if (this.showInputs) { + hourTemplate = ''; + minuteTemplate = ''; + secondTemplate = ''; + meridianTemplate = ''; + } else { + hourTemplate = ''; + minuteTemplate = ''; + secondTemplate = ''; + meridianTemplate = ''; + } + + templateContent = ''+ + ''+ + ''+ + ''+ + ''+ + (this.showSeconds ? + ''+ + '' + : '') + + (this.showMeridian ? + ''+ + '' + : '') + + ''+ + ''+ + ' '+ + ''+ + ' '+ + (this.showSeconds ? + ''+ + '' + : '') + + (this.showMeridian ? + ''+ + '' + : '') + + ''+ + ''+ + ''+ + ''+ + ''+ + (this.showSeconds ? + ''+ + '' + : '') + + (this.showMeridian ? + ''+ + '' + : '') + + ''+ + '
   
'+ hourTemplate +':'+ minuteTemplate +':'+ secondTemplate +' '+ meridianTemplate +'
  
'; + + switch(this.template) { + case 'modal': + template = ''; + break; + case 'dropdown': + template = ''; + break; + } + + return template; + }, + + getTime: function() { + if (this.hour === '') { + return ''; + } + + return this.hour + ':' + (this.minute.toString().length === 1 ? '0' + this.minute : this.minute) + (this.showSeconds ? ':' + (this.second.toString().length === 1 ? '0' + this.second : this.second) : '') + (this.showMeridian ? ' ' + this.meridian : ''); + }, + + hideWidget: function() { + if (this.isOpen === false) { + return; + } + + this.$element.trigger({ + 'type': 'hide.timepicker', + 'time': { + 'value': this.getTime(), + 'hours': this.hour, + 'minutes': this.minute, + 'seconds': this.second, + 'meridian': this.meridian + } + }); + + if (this.template === 'modal' && this.$widget.modal) { + this.$widget.modal('hide'); + } else { + this.$widget.removeClass('open'); + } + + $(document).off('mousedown.timepicker, touchend.timepicker'); + + this.isOpen = false; + // show/hide approach taken by datepicker + this.$widget.detach(); + }, + + highlightUnit: function() { + this.position = this.getCursorPosition(); + if (this.position >= 0 && this.position <= 2) { + this.highlightHour(); + } else if (this.position >= 3 && this.position <= 5) { + this.highlightMinute(); + } else if (this.position >= 6 && this.position <= 8) { + if (this.showSeconds) { + this.highlightSecond(); + } else { + this.highlightMeridian(); + } + } else if (this.position >= 9 && this.position <= 11) { + this.highlightMeridian(); + } + }, + + highlightNextUnit: function() { + switch (this.highlightedUnit) { + case 'hour': + this.highlightMinute(); + break; + case 'minute': + if (this.showSeconds) { + this.highlightSecond(); + } else if (this.showMeridian){ + this.highlightMeridian(); + } else { + this.highlightHour(); + } + break; + case 'second': + if (this.showMeridian) { + this.highlightMeridian(); + } else { + this.highlightHour(); + } + break; + case 'meridian': + this.highlightHour(); + break; + } + }, + + highlightPrevUnit: function() { + switch (this.highlightedUnit) { + case 'hour': + if(this.showMeridian){ + this.highlightMeridian(); + } else if (this.showSeconds) { + this.highlightSecond(); + } else { + this.highlightMinute(); + } + break; + case 'minute': + this.highlightHour(); + break; + case 'second': + this.highlightMinute(); + break; + case 'meridian': + if (this.showSeconds) { + this.highlightSecond(); + } else { + this.highlightMinute(); + } + break; + } + }, + + highlightHour: function() { + var $element = this.$element.get(0), + self = this; + + this.highlightedUnit = 'hour'; + + if ($element.setSelectionRange) { + setTimeout(function() { + if (self.hour < 10) { + $element.setSelectionRange(0,1); + } else { + $element.setSelectionRange(0,2); + } + }, 0); + } + }, + + highlightMinute: function() { + var $element = this.$element.get(0), + self = this; + + this.highlightedUnit = 'minute'; + + if ($element.setSelectionRange) { + setTimeout(function() { + if (self.hour < 10) { + $element.setSelectionRange(2,4); + } else { + $element.setSelectionRange(3,5); + } + }, 0); + } + }, + + highlightSecond: function() { + var $element = this.$element.get(0), + self = this; + + this.highlightedUnit = 'second'; + + if ($element.setSelectionRange) { + setTimeout(function() { + if (self.hour < 10) { + $element.setSelectionRange(5,7); + } else { + $element.setSelectionRange(6,8); + } + }, 0); + } + }, + + highlightMeridian: function() { + var $element = this.$element.get(0), + self = this; + + this.highlightedUnit = 'meridian'; + + if ($element.setSelectionRange) { + if (this.showSeconds) { + setTimeout(function() { + if (self.hour < 10) { + $element.setSelectionRange(8,10); + } else { + $element.setSelectionRange(9,11); + } + }, 0); + } else { + setTimeout(function() { + if (self.hour < 10) { + $element.setSelectionRange(5,7); + } else { + $element.setSelectionRange(6,8); + } + }, 0); + } + } + }, + + incrementHour: function() { + if (this.showMeridian) { + if (this.hour === 11) { + this.hour++; + return this.toggleMeridian(); + } else if (this.hour === 12) { + this.hour = 0; + } + } + if (this.hour === 23) { + this.hour = 0; + + return; + } + this.hour++; + }, + + incrementMinute: function(step) { + var newVal; + + if (step) { + newVal = this.minute + step; + } else { + newVal = this.minute + this.minuteStep - (this.minute % this.minuteStep); + } + + if (newVal > 59) { + this.incrementHour(); + this.minute = newVal - 60; + } else { + this.minute = newVal; + } + }, + + incrementSecond: function() { + var newVal = this.second + this.secondStep - (this.second % this.secondStep); + + if (newVal > 59) { + this.incrementMinute(true); + this.second = newVal - 60; + } else { + this.second = newVal; + } + }, + + mousewheel: function(e) { + if (this.disableMousewheel) { + return; + } + + e.preventDefault(); + e.stopPropagation(); + + var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail, + scrollTo = null; + + if (e.type === 'mousewheel') { + scrollTo = (e.originalEvent.wheelDelta * -1); + } + else if (e.type === 'DOMMouseScroll') { + scrollTo = 40 * e.originalEvent.detail; + } + + if (scrollTo) { + e.preventDefault(); + $(this).scrollTop(scrollTo + $(this).scrollTop()); + } + + switch (this.highlightedUnit) { + case 'minute': + if (delta > 0) { + this.incrementMinute(); + } else { + this.decrementMinute(); + } + this.highlightMinute(); + break; + case 'second': + if (delta > 0) { + this.incrementSecond(); + } else { + this.decrementSecond(); + } + this.highlightSecond(); + break; + case 'meridian': + this.toggleMeridian(); + this.highlightMeridian(); + break; + default: + if (delta > 0) { + this.incrementHour(); + } else { + this.decrementHour(); + } + this.highlightHour(); + break; + } + + return false; + }, + + // This method was adapted from bootstrap-datepicker. + place : function() { + if (this.isInline) { + return; + } + var widgetWidth = this.$widget.outerWidth(), widgetHeight = this.$widget.outerHeight(), visualPadding = 10, windowWidth = + $(window).width(), windowHeight = $(window).height(), scrollTop = $(window).scrollTop(); + + var zIndex = parseInt(this.$element.parents().filter(function() {}).first().css('z-index'), 10) + 10; + var offset = this.component ? this.component.parent().offset() : this.$element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.$element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.$element.outerWidth(false); + var left = offset.left, top = offset.top; + + this.$widget.removeClass('timepicker-orient-top timepicker-orient-bottom timepicker-orient-right timepicker-orient-left'); + + if (this.orientation.x !== 'auto') { + this.picker.addClass('datepicker-orient-' + this.orientation.x); + if (this.orientation.x === 'right') { + left -= widgetWidth - width; + } + } else{ + // auto x orientation is best-placement: if it crosses a window edge, fudge it sideways + // Default to left + this.$widget.addClass('timepicker-orient-left'); + if (offset.left < 0) { + left -= offset.left - visualPadding; + } else if (offset.left + widgetWidth > windowWidth) { + left = windowWidth - widgetWidth - visualPadding; + } + } + // auto y orientation is best-situation: top or bottom, no fudging, decision based on which shows more of the widget + var yorient = this.orientation.y, topOverflow, bottomOverflow; + if (yorient === 'auto') { + topOverflow = -scrollTop + offset.top - widgetHeight; + bottomOverflow = scrollTop + windowHeight - (offset.top + height + widgetHeight); + if (Math.max(topOverflow, bottomOverflow) === bottomOverflow) { + yorient = 'top'; + } else { + yorient = 'bottom'; + } + } + this.$widget.addClass('timepicker-orient-' + yorient); + if (yorient === 'top'){ + top += height; + } else{ + top -= widgetHeight + parseInt(this.$widget.css('padding-top'), 10); + } + + this.$widget.css({ + top : top, + left : left, + zIndex : zIndex + }); + }, + + remove: function() { + $('document').off('.timepicker'); + if (this.$widget) { + this.$widget.remove(); + } + delete this.$element.data().timepicker; + }, + + setDefaultTime: function(defaultTime) { + if (!this.$element.val()) { + if (defaultTime === 'current') { + var dTime = new Date(), + hours = dTime.getHours(), + minutes = dTime.getMinutes(), + seconds = dTime.getSeconds(), + meridian = 'AM'; + + if (seconds !== 0) { + seconds = Math.ceil(dTime.getSeconds() / this.secondStep) * this.secondStep; + if (seconds === 60) { + minutes += 1; + seconds = 0; + } + } + + if (minutes !== 0) { + minutes = Math.ceil(dTime.getMinutes() / this.minuteStep) * this.minuteStep; + if (minutes === 60) { + hours += 1; + minutes = 0; + } + } + + if (this.showMeridian) { + if (hours === 0) { + hours = 12; + } else if (hours >= 12) { + if (hours > 12) { + hours = hours - 12; + } + meridian = 'PM'; + } else { + meridian = 'AM'; + } + } + + this.hour = hours; + this.minute = minutes; + this.second = seconds; + this.meridian = meridian; + + this.update(); + + } else if (defaultTime === false) { + this.hour = 0; + this.minute = 0; + this.second = 0; + this.meridian = 'AM'; + } else { + this.setTime(defaultTime); + } + } else { + this.updateFromElementVal(); + } + }, + + setTime: function(time, ignoreWidget) { + if (!time) { + this.clear(); + return; + } + + var timeArray, + hour, + minute, + second, + meridian; + + if (typeof time === 'object' && time.getMonth){ + // this is a date object + hour = time.getHours(); + minute = time.getMinutes(); + second = time.getSeconds(); + + if (this.showMeridian){ + meridian = 'AM'; + if (hour > 12){ + meridian = 'PM'; + hour = hour % 12; + } + + if (hour === 12){ + meridian = 'PM'; + } + } + } else { + if (time.match(/p/i) !== null) { + meridian = 'PM'; + } else { + meridian = 'AM'; + } + + time = time.replace(/[^0-9\:]/g, ''); + + timeArray = time.split(':'); + + hour = timeArray[0] ? timeArray[0].toString() : timeArray.toString(); + minute = timeArray[1] ? timeArray[1].toString() : ''; + second = timeArray[2] ? timeArray[2].toString() : ''; + + // idiot proofing + if (hour.length > 4) { + second = hour.substr(4, 2); + } + if (hour.length > 2) { + minute = hour.substr(2, 2); + hour = hour.substr(0, 2); + } + if (minute.length > 2) { + second = minute.substr(2, 2); + minute = minute.substr(0, 2); + } + if (second.length > 2) { + second = second.substr(2, 2); + } + + hour = parseInt(hour, 10); + minute = parseInt(minute, 10); + second = parseInt(second, 10); + + if (isNaN(hour)) { + hour = 0; + } + if (isNaN(minute)) { + minute = 0; + } + if (isNaN(second)) { + second = 0; + } + + if (this.showMeridian) { + if (hour < 1) { + hour = 1; + } else if (hour > 12) { + hour = 12; + } + } else { + if (hour >= 24) { + hour = 23; + } else if (hour < 0) { + hour = 0; + } + if (hour < 13 && meridian === 'PM') { + hour = hour + 12; + } + } + + if (minute < 0) { + minute = 0; + } else if (minute >= 60) { + minute = 59; + } + + if (this.showSeconds) { + if (isNaN(second)) { + second = 0; + } else if (second < 0) { + second = 0; + } else if (second >= 60) { + second = 59; + } + } + } + + this.hour = hour; + this.minute = minute; + this.second = second; + this.meridian = meridian; + + this.update(ignoreWidget); + }, + + showWidget: function() { + if (this.isOpen) { + return; + } + + if (this.$element.is(':disabled')) { + return; + } + + // show/hide approach taken by datepicker + this.$widget.appendTo(this.appendWidgetTo); + var self = this; + $(document).on('mousedown.timepicker, touchend.timepicker', function (e) { + // This condition was inspired by bootstrap-datepicker. + // The element the timepicker is invoked on is the input but it has a sibling for addon/button. + if (!(self.$element.parent().find(e.target).length || + self.$widget.is(e.target) || + self.$widget.find(e.target).length)) { + self.hideWidget(); + } + }); + + this.$element.trigger({ + 'type': 'show.timepicker', + 'time': { + 'value': this.getTime(), + 'hours': this.hour, + 'minutes': this.minute, + 'seconds': this.second, + 'meridian': this.meridian + } + }); + + this.place(); + if (this.disableFocus) { + this.$element.blur(); + } + + // widget shouldn't be empty on open + if (this.hour === '') { + if (this.defaultTime) { + this.setDefaultTime(this.defaultTime); + } else { + this.setTime('0:0:0'); + } + } + + if (this.template === 'modal' && this.$widget.modal) { + this.$widget.modal('show').on('hidden', $.proxy(this.hideWidget, this)); + } else { + if (this.isOpen === false) { + this.$widget.addClass('open'); + } + } + + this.isOpen = true; + }, + + toggleMeridian: function() { + this.meridian = this.meridian === 'AM' ? 'PM' : 'AM'; + }, + + update: function(ignoreWidget) { + this.updateElement(); + if (!ignoreWidget) { + this.updateWidget(); + } + + this.$element.trigger({ + 'type': 'changeTime.timepicker', + 'time': { + 'value': this.getTime(), + 'hours': this.hour, + 'minutes': this.minute, + 'seconds': this.second, + 'meridian': this.meridian + } + }); + }, + + updateElement: function() { + this.$element.val(this.getTime()).change(); + }, + + updateFromElementVal: function() { + this.setTime(this.$element.val()); + }, + + updateWidget: function() { + if (this.$widget === false) { + return; + } + + var hour = this.hour, + minute = this.minute.toString().length === 1 ? '0' + this.minute : this.minute, + second = this.second.toString().length === 1 ? '0' + this.second : this.second; + + if (this.showInputs) { + this.$widget.find('input.bootstrap-timepicker-hour').val(hour); + this.$widget.find('input.bootstrap-timepicker-minute').val(minute); + + if (this.showSeconds) { + this.$widget.find('input.bootstrap-timepicker-second').val(second); + } + if (this.showMeridian) { + this.$widget.find('input.bootstrap-timepicker-meridian').val(this.meridian); + } + } else { + this.$widget.find('span.bootstrap-timepicker-hour').text(hour); + this.$widget.find('span.bootstrap-timepicker-minute').text(minute); + + if (this.showSeconds) { + this.$widget.find('span.bootstrap-timepicker-second').text(second); + } + if (this.showMeridian) { + this.$widget.find('span.bootstrap-timepicker-meridian').text(this.meridian); + } + } + }, + + updateFromWidgetInputs: function() { + if (this.$widget === false) { + return; + } + + var t = this.$widget.find('input.bootstrap-timepicker-hour').val() + ':' + + this.$widget.find('input.bootstrap-timepicker-minute').val() + + (this.showSeconds ? ':' + this.$widget.find('input.bootstrap-timepicker-second').val() : '') + + (this.showMeridian ? this.$widget.find('input.bootstrap-timepicker-meridian').val() : '') + ; + + this.setTime(t, true); + }, + + widgetClick: function(e) { + e.stopPropagation(); + e.preventDefault(); + + var $input = $(e.target), + action = $input.closest('a').data('action'); + + if (action) { + this[action](); + } + this.update(); + + if ($input.is('input')) { + $input.get(0).setSelectionRange(0,2); + } + }, + + widgetKeydown: function(e) { + var $input = $(e.target), + name = $input.attr('class').replace('bootstrap-timepicker-', ''); + + switch (e.keyCode) { + case 9: //tab + if ((this.showMeridian && name === 'meridian') || (this.showSeconds && name === 'second') || (!this.showMeridian && !this.showSeconds && name === 'minute')) { + return this.hideWidget(); + } + break; + case 27: // escape + this.hideWidget(); + break; + case 38: // up arrow + e.preventDefault(); + switch (name) { + case 'hour': + this.incrementHour(); + break; + case 'minute': + this.incrementMinute(); + break; + case 'second': + this.incrementSecond(); + break; + case 'meridian': + this.toggleMeridian(); + break; + } + this.setTime(this.getTime()); + $input.get(0).setSelectionRange(0,2); + break; + case 40: // down arrow + e.preventDefault(); + switch (name) { + case 'hour': + this.decrementHour(); + break; + case 'minute': + this.decrementMinute(); + break; + case 'second': + this.decrementSecond(); + break; + case 'meridian': + this.toggleMeridian(); + break; + } + this.setTime(this.getTime()); + $input.get(0).setSelectionRange(0,2); + break; + } + }, + + widgetKeyup: function(e) { + if ((e.keyCode === 65) || (e.keyCode === 77) || (e.keyCode === 80) || (e.keyCode === 46) || (e.keyCode === 8) || (e.keyCode >= 46 && e.keyCode <= 57)) { + this.updateFromWidgetInputs(); + } + } + }; + + //TIMEPICKER PLUGIN DEFINITION + $.fn.timepicker = function(option) { + var args = Array.apply(null, arguments); + args.shift(); + return this.each(function() { + var $this = $(this), + data = $this.data('timepicker'), + options = typeof option === 'object' && option; + + if (!data) { + $this.data('timepicker', (data = new Timepicker(this, $.extend({}, $.fn.timepicker.defaults, options, $(this).data())))); + } + + if (typeof option === 'string') { + data[option].apply(data, args); + } + }); + }; + + $.fn.timepicker.defaults = { + defaultTime: 'current', + disableFocus: false, + disableMousewheel: false, + isOpen: false, + minuteStep: 15, + modalBackdrop: false, + orientation: { x: 'auto', y: 'auto'}, + secondStep: 15, + showSeconds: false, + showInputs: true, + showMeridian: true, + template: 'dropdown', + appendWidgetTo: 'body', + showWidgetOnAddonClick: true + }; + + $.fn.timepicker.Constructor = Timepicker; + +})(jQuery, window, document); diff --git a/app/assets/javascripts/time_entries.js b/app/assets/javascripts/time_entries.js new file mode 100644 index 0000000..9f3e5ef --- /dev/null +++ b/app/assets/javascripts/time_entries.js @@ -0,0 +1,11 @@ +$(document).ready(function(){ + +var currentdate = new Date(); +$("#date_id").datepicker().on('changeDate', function(ev){ + $("#date_id").datepicker('hide'); +}); +$("#date_id").datepicker('setValue', currentdate); + +$("#start_time_id").timepicker(); +$("#end_time_id").timepicker(); +}); diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 73a3169..6b66844 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -9,5 +9,7 @@ * compiled file, but it's generally better to create a new file per style scope. * *= require_self + *= require datepicker + *= require bootstrap-timepicker *= require bootstrap_and_overrides */ diff --git a/app/assets/stylesheets/bootstrap-timepicker.css b/app/assets/stylesheets/bootstrap-timepicker.css new file mode 100644 index 0000000..fa34752 --- /dev/null +++ b/app/assets/stylesheets/bootstrap-timepicker.css @@ -0,0 +1,146 @@ +/*! + * Timepicker Component for Twitter Bootstrap + * + * Copyright 2013 Joris de Wit + * + * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +.bootstrap-timepicker { + position: relative; +} +.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu { + left: auto; + right: 0; +} +.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before { + left: auto; + right: 12px; +} +.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after { + left: auto; + right: 13px; +} +.bootstrap-timepicker .add-on { + cursor: pointer; +} +.bootstrap-timepicker .add-on i { + display: inline-block; + width: 16px; + height: 16px; +} +.bootstrap-timepicker-widget.dropdown-menu { + padding: 4px; +} +.bootstrap-timepicker-widget.dropdown-menu.open { + display: inline-block; +} +.bootstrap-timepicker-widget.dropdown-menu:before { + border-bottom: 7px solid rgba(0, 0, 0, 0.2); + border-left: 7px solid transparent; + border-right: 7px solid transparent; + content: ""; + display: inline-block; + position: absolute; +} +.bootstrap-timepicker-widget.dropdown-menu:after { + border-bottom: 6px solid #FFFFFF; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + content: ""; + display: inline-block; + position: absolute; +} +.bootstrap-timepicker-widget.timepicker-orient-left:before { + left: 6px; +} +.bootstrap-timepicker-widget.timepicker-orient-left:after { + left: 7px; +} +.bootstrap-timepicker-widget.timepicker-orient-right:before { + right: 6px; +} +.bootstrap-timepicker-widget.timepicker-orient-right:after { + right: 7px; +} +.bootstrap-timepicker-widget.timepicker-orient-top:before { + top: -7px; +} +.bootstrap-timepicker-widget.timepicker-orient-top:after { + top: -6px; +} +.bootstrap-timepicker-widget.timepicker-orient-bottom:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999; +} +.bootstrap-timepicker-widget.timepicker-orient-bottom:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.bootstrap-timepicker-widget a.btn, +.bootstrap-timepicker-widget input { + border-radius: 4px; +} +.bootstrap-timepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-timepicker-widget table td { + text-align: center; + height: 30px; + margin: 0; + padding: 2px; +} +.bootstrap-timepicker-widget table td:not(.separator) { + min-width: 30px; +} +.bootstrap-timepicker-widget table td span { + width: 100%; +} +.bootstrap-timepicker-widget table td a { + border: 1px transparent solid; + width: 100%; + display: inline-block; + margin: 0; + padding: 8px 0; + outline: 0; + color: #333; +} +.bootstrap-timepicker-widget table td a:hover { + text-decoration: none; + background-color: #eee; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border-color: #ddd; +} +.bootstrap-timepicker-widget table td a i { + margin-top: 2px; + font-size: 18px; +} +.bootstrap-timepicker-widget table td input { + width: 25px; + margin: 0; + text-align: center; +} +.bootstrap-timepicker-widget .modal-content { + padding: 4px; +} +@media (min-width: 767px) { + .bootstrap-timepicker-widget.modal { + width: 200px; + margin-left: -100px; + } +} +@media (max-width: 767px) { + .bootstrap-timepicker { + width: 100%; + } + .bootstrap-timepicker .dropdown-menu { + width: 100%; + } +} diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 5f8534e..d838852 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -36,3 +36,7 @@ body { [data-toggle="buttons"] > .btn > input[type="checkbox"] { display: none; } + +.inline-block { + display: inline-block; +} diff --git a/app/assets/stylesheets/datepicker.css b/app/assets/stylesheets/datepicker.css new file mode 100755 index 0000000..b7065b7 --- /dev/null +++ b/app/assets/stylesheets/datepicker.css @@ -0,0 +1,182 @@ +/*! + * Datepicker for Bootstrap + * + * Copyright 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ +.datepicker { + top: 0; + left: 0; + padding: 4px; + margin-top: 1px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + /*.dow { + border-top: 1px solid #ddd !important; + }*/ + +} +.datepicker:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; + top: -7px; + left: 6px; +} +.datepicker:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + position: absolute; + top: -6px; + left: 7px; +} +.datepicker > div { + display: none; +} +.datepicker table { + width: 100%; + margin: 0; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker td.day:hover { + background: #eeeeee; + cursor: pointer; +} +.datepicker td.day.disabled { + color: #eeeeee; +} +.datepicker td.old, +.datepicker td.new { + color: #999999; +} +.datepicker td.active, +.datepicker td.active:hover { + color: #ffffff; + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #0044cc; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker td.active:hover, +.datepicker td.active:hover:hover, +.datepicker td.active:focus, +.datepicker td.active:hover:focus, +.datepicker td.active:active, +.datepicker td.active:hover:active, +.datepicker td.active.active, +.datepicker td.active:hover.active, +.datepicker td.active.disabled, +.datepicker td.active:hover.disabled, +.datepicker td.active[disabled], +.datepicker td.active:hover[disabled] { + color: #ffffff; + background-color: #0044cc; + *background-color: #003bb3; +} +.datepicker td.active:active, +.datepicker td.active:hover:active, +.datepicker td.active.active, +.datepicker td.active:hover.active { + background-color: #003399 \9; +} +.datepicker td span { + display: block; + width: 47px; + height: 54px; + line-height: 54px; + float: left; + margin: 2px; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker td span:hover { + background: #eeeeee; +} +.datepicker td span.active { + color: #ffffff; + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #0044cc; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker td span.active:hover, +.datepicker td span.active:focus, +.datepicker td span.active:active, +.datepicker td span.active.active, +.datepicker td span.active.disabled, +.datepicker td span.active[disabled] { + color: #ffffff; + background-color: #0044cc; + *background-color: #003bb3; +} +.datepicker td span.active:active, +.datepicker td span.active.active { + background-color: #003399 \9; +} +.datepicker td span.old { + color: #999999; +} +.datepicker th.switch { + width: 145px; +} +.datepicker th.next, +.datepicker th.prev { + font-size: 21px; +} +.datepicker thead tr:first-child th { + cursor: pointer; +} +.datepicker thead tr:first-child th:hover { + background: #eeeeee; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + display: block; + cursor: pointer; + width: 16px; + height: 16px; +} \ No newline at end of file diff --git a/app/controllers/time_entries_controller.rb b/app/controllers/time_entries_controller.rb new file mode 100644 index 0000000..0b73db1 --- /dev/null +++ b/app/controllers/time_entries_controller.rb @@ -0,0 +1,10 @@ +class TimeEntriesController < AuthenticatedController + + def new + + end + + def index + + end +end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 656bd77..9cad67e 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -4,7 +4,7 @@ %meta{:charset => "utf-8"}/ %title= content_for?(:title) ? yield(:title) : "Velocipede" = csrf_meta_tags - = stylesheet_link_tag "bootstrap_and_overrides", :media => "all" + = stylesheet_link_tag "bootstrap_and_overrides", "datepicker", "bootstrap-timepicker", :media => "all" /[if lt IE 9] = javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js" :css diff --git a/app/views/site/index.html.haml b/app/views/site/index.html.haml index 1877017..0a23f4a 100644 --- a/app/views/site/index.html.haml +++ b/app/views/site/index.html.haml @@ -3,7 +3,7 @@ %p %p - %a{class: "btn btn-lg btn-block btn-primary"} Add Time Entry + %a{class: "btn btn-lg btn-block btn-primary", href: new_time_entry_path} Add Time Entry %p %a{class: "btn btn-lg btn-block btn-primary"} View Timesheet %p diff --git a/app/views/time_entries/new.haml b/app/views/time_entries/new.haml new file mode 100644 index 0000000..1774b27 --- /dev/null +++ b/app/views/time_entries/new.haml @@ -0,0 +1,41 @@ +%a{ class: "btn btn-default btn-lg", href: root_path} + %span{ class:"icon-home"} +%h2 Add Time Entry + +%p + .control-group + .controls + %input{id: "date_id", placeholder: "Date", type: "text", class: "datepicker input-small" } + .help-block + .control-group + .controls{ class: "bootstrap-timepicker"} + %label Start + %input{id: "start_time_id", placeholder: "Time ID", type: "text", class: "input-small" } + .help-block + .control-group + .controls + %label End + %input{id: "end_time_id", placeholder: "Time ID", type: "text", class: "input-small" } + .help-block + .control-group + .controls + .btn-group{ "data-toggle" => "buttons-radio"} + %label{ class: "btn btn-default"} + %input{ type: "radio", name: "action_id", value: 3} Volunteer + %label{ class: "btn btn-default"} + %input{ type: "radio", name: "action_id", value: 1} Personal + %label{ class: "btn btn-default"} + %input{ type: "radio", name: "action_id", value: 2} Staff + %input{ id: "bike_style_id", type: "hidden"} + .help-block + .control-group + .controls + %label + Worked on a bike? + %input{ type: "checkbox"} + .control-group + .controls + %textarea{id: "work_description", placeholder: "Work description", class: "input-lg" } + .control-group + .controls + %input{id: "add_bike_submit", value: "Add Time Entry", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_bike_path}"} diff --git a/config/routes.rb b/config/routes.rb index a9d9627..568210b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,8 @@ Velocipede::Application.routes.draw do get 'task_lists/:id/edit' => "task_lists#edit", as: "edit_task_list" + get 'time_entries/new' => "time_entries#new", as: "new_time_entry" + ########################### # API Routes scope 'api', :module => :api, defaults: {format: :json} do