diff --git a/bower.json b/bower.json index f54edd9..3937a7c 100644 --- a/bower.json +++ b/bower.json @@ -16,6 +16,7 @@ ], "dependencies": { "angular": "~1.3.15", - "angular-ui-router": "~0.2.13" + "angular-ui-router": "~0.2.13", + "angularjs-datepicker": "^2.1.6" } } diff --git a/public/app/app.module.js b/public/app/app.module.js index 05f7e18..355a43d 100644 --- a/public/app/app.module.js +++ b/public/app/app.module.js @@ -102,6 +102,7 @@ angular.module('KDRPoints', [ 'ui.router', + '720kb.datepicker' ]) .config(['$stateProvider', '$urlRouterProvider', KDRPoints]); })(); diff --git a/public/app/directives/dateInput.drctv.js b/public/app/directives/dateInput.drctv.js new file mode 100644 index 0000000..9f7d435 --- /dev/null +++ b/public/app/directives/dateInput.drctv.js @@ -0,0 +1,61 @@ +(function() { + var months = [ + { id: 1, name: 'January' }, { id: 2, name: 'February' }, + { id: 3, name: 'March' }, { id: 4, name: 'April' }, + { id: 5, name: 'May' }, { id: 6, name: 'June' }, + { id: 7, name: 'July' }, { id: 8, name: 'August' }, + { id: 9, name: 'September' }, { id: 10, name: 'October' }, + { id: 11, name: 'November' }, { id: 12, name: 'December' } + ]; + + var monthToDays = function(month, year) { + if(month === 2) { + return (year % 4) ? 28 : 29; + } else if(month < 8) { + return 30 + month % 2; + } else { + return 30 + (month+1) % 2; + } + }; + + var verifyDate = function(date) { + date.min = parseInt(date.min); + var validHour = date.hour <= 12 && date.hour >= 1; + var validMin = date.min <=59 && date.min >= 0; + var validDay = date.day < monthToDays(date.month, date.year); + var currentDate = new Date(); + return (currentDate >= revParseDate(date)) && validHour && validMin && validDay; + }; + + var revParseDate = function(date) { + return new Date(date.year, date.month-1, date.day, date.hour, date.min); + }; + + var dateInputDirective = function() { + return { + restrict: 'E', + replace: false, + scope: { + date: '=', + notify: '=', + changeFn: '&' + }, + templateUrl: 'app/directives/templates/dateInput.html', + link: function(scope) { + scope.months = months; + scope.changeFunction = function() { + scope.notify = {}; + if(!verifyDate(scope.date)) { + scope.notify.error = 'Invalid Date.'; + return; + } + if(scope.changeFn) scope.notify.error = scope.changeFn(); + }; + + } + } + }; + + angular.module('KDRPoints') + .directive('dateInput', dateInputDirective); +})(); diff --git a/public/app/directives/templates/dateInput.html b/public/app/directives/templates/dateInput.html new file mode 100644 index 0000000..3d3767d --- /dev/null +++ b/public/app/directives/templates/dateInput.html @@ -0,0 +1,14 @@ +
+ + +
+ + : + + +
+ diff --git a/public/app/services/service.service.js b/public/app/services/service.service.js index 8c2b780..7596ce5 100644 --- a/public/app/services/service.service.js +++ b/public/app/services/service.service.js @@ -22,7 +22,65 @@ }); }; + var toDateObj = function(date) { + var newDate = date.date + ' ' + date.hour + ':' + date.min + ' ' + date.cycle; + newDate = new Date(Date.parse(newDate)); + return newDate.toString() !== 'Invalid Date' ? newDate : false; + }; + + var submitInProgress = false; + return { + cleanHour: function(hour, type) { + var cleanHour = {}; + cleanHour.description = hour.description; + if(!cleanHour.description) { + return 'Description is required.'; + }; + if(type === 'Hour') { + cleanHour.startTime = toDateObj(hour.start); + cleanHour.endTime = toDateObj(hour.end); + if(!cleanHour.startTime || !cleanHour.endTime) { + return 'Invalid Date'; + } + } else { + cleanHour.amount = hour.amount; + if(cleanHour.amount < 0) { + return 'Amount must be greater than zero.'; + } + } + return cleanHour; + }, + calculateHourEquivalent: function(amount) { + var amountToMinutes = amount / (5/60); + if(amountToMinutes < 0) { + return 'Amount must be greater than zero.'; + } + duration = { + hours: Math.floor(amountToMinutes / 60), + minutes: amountToMinutes % 60 + }; + if(duration.minutes < 10) { + duration.minutes = '0' + duration.minutes; + } + return duration; + }, + calculateDuration: function(startDate, endDate) { + startDate = toDateObj(startDate); + endDate = toDateObj(endDate); + var diffMinutes = (endDate - startDate) / 60000; + if(diffMinutes < 0) { + return 'Start date must be before end date.'; + } + var duration = { + hours: Math.floor(diffMinutes / 60), + minutes: diffMinutes % 60 + }; + if(duration.minutes < 10) { + duration.minutes = '0' + duration.minutes; + } + return duration; + }, submit: function(hour) { return postHttp('/service/submit', hour); }, diff --git a/public/app/submitService/submitService.ctrl.js b/public/app/submitService/submitService.ctrl.js index f02ce9d..fcd0237 100644 --- a/public/app/submitService/submitService.ctrl.js +++ b/public/app/submitService/submitService.ctrl.js @@ -1,31 +1,91 @@ (function() { + var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', + 'August', 'September', 'October', 'November', 'December']; + var hours = ['1','2','3','4','5','6','7','8','9','10','11','12']; + var minutes = ['00','15','30','45']; + + var parseDate = function(date) { + var month = months[date.getMonth()]; + var day = date.getDate(); + var year = date.getFullYear(); + var min = date.getMinutes(); + min = minutes[Math.floor(min / 15)]; + var hour = date.getHours(); + return { + date: month + ' ' + day + ', ' + year, + hour: (hour % 12).toString(), + min: min, + cycle: hour / 12 < 1 ? 'AM' : 'PM' + } + }; + var submitServiceCtrl = function($scope, serviceService) { $scope.type = 'Hour'; - $scope.hour = {}; + $scope.notify = {}; + + $scope.hours = hours; + $scope.minutes = minutes; + + var submitInProgress = false; $scope.submit = function() { - var cleanHour = {}; - var hour = $scope.hour; - cleanHour.description = hour.description; - if($scope.type === 'Hour') { - cleanHour.startTime = hour.startTime; - cleanHour.endTime = hour.endTime; - } else { - cleanHour.amount = hour.amount; + if(submitInProgress) { + return; + } + submitInProgress = true; + var cleanHour = serviceService.cleanHour($scope.hour, $scope.type); + if(typeof cleanHour === 'string') { + submitInProgress = false; + $scope.notify.error = cleanHour; + return; } serviceService.submit(cleanHour) - .then(function() { - $scope.hour = {}; - $scope.success = true; + .then(function(success) { + submitInProgress = false; + if(success) { + resetData(); + } else { + $scope.notify.error = 'Submission failed.'; + $scope.notify.success = null; + } }); + + }; + + $scope.calculateDuration = function() { + var duration = serviceService.calculateDuration($scope.hour.start, + $scope.hour.end); + if(typeof duration === 'string') { + $scope.notify.error = duration; + return; + } + $scope.hour.duration = duration; }; - //remove success when new service hour is input - $scope.$watch('hour', function() { - $scope.success = false; - }, true); + $scope.calculateEquivalent = function() { + var duration = serviceService.calculateHourEquivalent($scope.hour.amount); + if(typeof duration === 'string') { + $scope.notify.error = duration; + return; + } + $scope.hour.equivalent = duration; + }; + + var resetData = function() { + $scope.hour = {}; + var endDate = new Date(); + var startDate = new Date(endDate.getTime() - 15 * 60000); + + $scope.hour.start = parseDate(startDate); + $scope.hour.end = parseDate(endDate); + $scope.hour.amount = 0; + + $scope.calculateDuration(); + $scope.calculateEquivalent(); + }; + resetData(); } angular.module('KDRPoints') diff --git a/public/app/submitService/submitService.html b/public/app/submitService/submitService.html index 48a2fec..ad59ea3 100644 --- a/public/app/submitService/submitService.html +++ b/public/app/submitService/submitService.html @@ -1,23 +1,53 @@
-
- Description: - - -
- Start Time:
- End Time:
-
+
+ Description: + -
- Amount: -
+
+ Start: + + +
+ + : + +
+ + End: + + +
+ + : + +
- +
+ Duration: {{hour.duration.hours}}:{{hour.duration.minutes}} +
+
-
Hour successfully submitted.
+
+ Amount: $
+ Hour Equivalent: {{hour.equivalent.hours}}:{{hour.equivalent.minutes}} +
+ + + +
{{type}} successfully submitted.
+
{{notify.error}}
+ +
diff --git a/public/css/angular-datepicker.css b/public/css/angular-datepicker.css new file mode 100644 index 0000000..8cf66c8 --- /dev/null +++ b/public/css/angular-datepicker.css @@ -0,0 +1,209 @@ +datepicker a, [datepicker] a, .datepicker a{ + color:inherit; + text-decoration:none; +} +datepicker a:hover, [datepicker] a:hover, .datepicker a:hover{ + text-decoration:none; +} +datepicker select, datepicker select:focus, datepicker select:hover, +.datepicker select, .datepicker select:focus, .datepicker select:hover, +[datepicker] select, [datepicker] select:focus, [datepicker] select:hover{ + width:100%; + overflow: hidden; + background:none; + color:#fff; + background-color: #138EFA; + border-radius:2px; + border: 0; + margin-top:5px; +} +datepicker, .datepicker, [datepicker], +._720kb-datepicker-calendar-header, +._720kb-datepicker-calendar-body, +._720kb-datepicker-calendar-days-header, +._720kb-datepicker-calendar-years-pagination-pages { + font-family: Helvetica Neue, Arial, sans-serif; + font-size: 13.5px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + margin: 0 auto; + float: left; + clear: right; + position: relative; +} +._720kb-datepicker-calendar { + background: white; + color: #333; + position: absolute; + z-index: 999; + min-width: 220px; + margin: 0 auto; + width: 101%; + -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset; + -moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset; + visibility: hidden; + overflow:hidden; + margin-left:-0.5%; + padding: 0 0 2% 0; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +._720kb-datepicker-calendar._720kb-datepicker-open,._720kb-datepicker-calendar._720kb-datepicker-forced-to-open { + width: 0px; + visibility: visible; +} +._720kb-datepicker-calendar-header { + text-align: center; + font-size: 15px; + line-height: 40px; +} +._720kb-datepicker-calendar-header:nth-child(odd) { + background: #138EFA; +} +._720kb-datepicker-calendar-header:nth-child(even) { + background: #7BC6FC; +} +._720kb-datepicker-calendar-header-left, +._720kb-datepicker-calendar-header-middle, +._720kb-datepicker-calendar-header-right { + width: 15%; + float: left; +} +._720kb-datepicker-calendar-header-middle { + width: 70%; +} + +._720kb-datepicker-calendar-header-closed-pagination::after { + content: " \25BE"; +} + +._720kb-datepicker-calendar-header-opened-pagination::after { + content: " \25BE"; + margin-left: 4px; + position: relative; + bottom: -3px; + display:inline-block; + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -o-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +._720kb-datepicker-calendar-body { + width: 96%; + margin: 2%; + text-align: center; +} +._720kb-datepicker-calendar-day { + cursor: pointer; + font-size: 12.5px; + width: 12.2%; + margin:5px 1%; + padding: 1.5% 0; + float: left; + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; +} +._720kb-datepicker-calendar-day:hover,._720kb-datepicker-calendar-day._720kb-datepicker-active { + background: rgba(0, 0, 0, 0.03); +} +._720kb-datepicker-calendar-header a, ._720kb-datepicker-calendar-header a:hover { + text-decoration:none; + padding:3% 9% 4% 9%; + font-size: 13.5px; + color:rgba(0, 0, 0, 0.55); + font-weight: bold; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +._720kb-datepicker-calendar-header a:hover { + color:rgba(0, 0, 0, 0.9); + background: rgba(255, 255, 255, 0.45); +} +._720kb-datepicker-calendar-month { + color:#fff; +} +._720kb-datepicker-calendar-month span { + font-size: 13px; + color:rgba(0, 0, 0, 0.4); +} +._720kb-datepicker-calendar-month a span i { + font-style: normal; + font-size:15px; +} +._720kb-datepicker-calendar-month a, ._720kb-datepicker-calendar-month a:hover { + padding: 3px; + margin-left:1%; +} +._720kb-datepicker-calendar-years-pagination{ + padding:2% 0 0 0; + float:left; + clear: right; + width: 100%; +} +._720kb-datepicker-calendar-years-pagination a, ._720kb-datepicker-calendar-years-pagination a:hover { + font-size:12px; + padding:0 7px; + font-weight: normal; + margin:3px 1% 0 1%; + line-height: 20px; + display: inline-block; +} +._720kb-datepicker-calendar-years-pagination a._720kb-datepicker-active { + color:rgba(0, 0, 0, 0.9); + font-weight: 500; + background: rgba(255, 255, 255, 0.45); +} +._720kb-datepicker-calendar-years-pagination-pages a,._720kb-datepicker-calendar-years-pagination-pages a:hover{ + padding:5px 10px; +} +._720kb-datepicker-calendar-days-header{ + max-width: 100%; + margin:0 auto; + padding:0 2% 0 2%; + background: rgba(19, 142, 250, 0.08); + border-bottom:1px solid rgba(0,0,0,0.02); +} +._720kb-datepicker-calendar-days-header div{ + width: 14.18%; + font-weight: 500; + font-size: 11.5px; + padding:10px 0; + float:left; + text-align: center; + color:rgba(0,0,0,0.7); +} +._720kb-datepicker-calendar-days +._720kb-datepicker-default-button{ + font-size: 18.5px; + position: relative; + bottom:-0.5px; +} +._720kb-datepicker-default-button{ + padding:0 4.5px; +} +._720kb-datepicker-calendar-header-middle._720kb-datepicker-mobile-item{ + width:95%; + float:none; + margin:0 auto; +} +._720kb-datepicker-item-hidden{ + visibility:hidden; +} +._720kb-datepicker-calendar-day._720kb-datepicker-disabled, +._720kb-datepicker-calendar-day._720kb-datepicker-disabled:hover, +._720kb-datepicker-calendar-years-pagination a._720kb-datepicker-disabled, +._720kb-datepicker-calendar-years-pagination a._720kb-datepicker-disabled:hover, +._720kb-datepicker-calendar-years-pagination a._720kb-datepicker-active._720kb-datepicker-disabled, +._720kb-datepicker-calendar-years-pagination a._720kb-datepicker-active._720kb-datepicker-disabled:hover{ + color:rgba(0,0,0,0.2); + background: rgba(25,2,0,0.02); + cursor: default; +} diff --git a/public/index.html b/public/index.html index 43b0da6..228def7 100644 --- a/public/index.html +++ b/public/index.html @@ -1,20 +1,24 @@ + + + +