diff --git a/app/controllers/clock.js b/app/controllers/clock.js new file mode 100644 index 0000000..c6a8f14 --- /dev/null +++ b/app/controllers/clock.js @@ -0,0 +1,42 @@ +var mongoose = require('mongoose'), + Clock = mongoose.model('Clock'); + +module.exports = function(piler) { + return { + index: function(req, res, next) { + host = String(req.headers['x-forwarded-host']); + host = host.split(':')[0]; + + if (host != 'clock.atlb.co') { + return next(); + } + + if (!req.user) { + req.session.redirectUrl = req.url + } + + var path = req.path.slice(1); + + res.render('clock.jade', { + css: piler.css.renderTags() + }); + }, + post: function(req, res) { + var clock = new Clock({ + tech: req.user, + action: req.body.action, + lat: req.body.lat, + long: req.body.long, + dt: new Date() + }); + + clock.save(function(err, result) { + if (err) { + return res.json(500, err); + } else { + res.json(result); + } + }); + } + } +} diff --git a/app/controllers/users.js b/app/controllers/users.js index 29ce87b..d45f572 100644 --- a/app/controllers/users.js +++ b/app/controllers/users.js @@ -1,7 +1,8 @@ var mongoose = require('mongoose'), async = require('async'), - User = mongoose.model('User'); + User = mongoose.model('User'), + Clock = mongoose.model('Clock'); var log = require('log4node'); @@ -163,6 +164,24 @@ module.exports = function(config, directory) { return res.json(user); }); }); + }, + + clocks: function(req, res) { + var id = req.param('user_id'); + + var criteria = { + tech: id + }; + + var query = Clock.find(criteria) + .sort('-dt') + .exec(function(err, results) { + if (err) { + res.json(500, err); + } else { + res.json(results); + } + }); } }; }; diff --git a/app/model/clock.js b/app/model/clock.js new file mode 100644 index 0000000..c7b237e --- /dev/null +++ b/app/model/clock.js @@ -0,0 +1,14 @@ +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + ObjectId = Schema.ObjectId; + +var clockSchema = new Schema({ + _id: String, + tech: { type: ObjectId, ref: 'User' }, + dt: Date, + action: String, + lat: Number, + long: Number +}, { versionKey: false }) + +var Clock = module.exports = mongoose.model('Clock', clockSchema); diff --git a/app/views/clock.jade b/app/views/clock.jade new file mode 100644 index 0000000..b2208ad --- /dev/null +++ b/app/views/clock.jade @@ -0,0 +1,40 @@ +doctype 5 +html(lang="en", ng-app="clock", ng-controller="clock.PageCtrl") + head + title Atlantic Biomedical + !{css} + link(rel='stylesheet', href='/css/clock.css') + meta(name='viewport', content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0') + body + script(type='text/javascript', src='//ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular.js') + script(type='text/javascript', src='//ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular-resource.js') + script(type='text/javascript', src='//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js') + script(type='text/javascript', src='/clock/app.js') + + error-panel + .navbar + .navbar-inner + a.brand(href='/', target='_self') Atlantic Biomedical + progress-panel + + .container-fluid + h1 Time Clock + + div.loading(ng-show='working') + i.loader + br + | Detecting your location + + div(ng-hide='working') + div.error(ng-show='error') + {{ error }} + + div.success(ng-show='success') + {{ success }} + + div(ng-hide='error || success') + .control-group + button.btn.btn-primary(ng-click='clockIn()') Clock In + button.btn.btn-primary(ng-click='clockOut()') Clock Out + div.mapOuter + gmap.mapInner(size='600x600', markers='markers', sensor='false', zoom='14') diff --git a/config/routes.js b/config/routes.js index 718bd62..01fae67 100644 --- a/config/routes.js +++ b/config/routes.js @@ -46,6 +46,7 @@ module.exports = function(app, auth, piler, calendar, directory, config) { app.get('/api/users/details', users.details); app.post('/api/users', users.create); app.post('/api/users/:user_id', users.update); + app.get('/api/users/:user_id/clocks', users.clocks); var account = require('../app/controllers/account'); app.get('/api/account', account.profile); @@ -56,6 +57,9 @@ module.exports = function(app, auth, piler, calendar, directory, config) { var tags = require('../app/controllers/tags')(piler); app.post('/api/tags', tags.post); + var clock = require('../app/controllers/clock')(piler); + app.post('/api/clock', clock.post); + var login = require('../app/controllers/login')(piler); app.get('/login', login.login); app.get('/login/error', login.error); @@ -63,6 +67,6 @@ module.exports = function(app, auth, piler, calendar, directory, config) { var home = require('../app/controllers/home')(piler); - app.get('/', tags.index, auth.requiresUiLogin, home.index); - app.get('*', tags.index, auth.requiresUiLogin, home.index); + app.get('/', tags.index, auth.requiresUiLogin, clock.index, home.index); + app.get('*', tags.index, auth.requiresUiLogin, clock.index, home.index); }; diff --git a/public/clock/app.js b/public/clock/app.js new file mode 100644 index 0000000..1567c25 --- /dev/null +++ b/public/clock/app.js @@ -0,0 +1,172 @@ +clock = {}; + + +angular.module('clock', ['ngResource']) + .factory("Clock", function($resource) { + return $resource('/api/clock'); + }) + .constant('geolocation_msgs', { + 'errors.location.unsupportedBrowser':'Browser does not support location services', + 'errors.location.permissionDenied':'You have rejected access to your location', + 'errors.location.positionUnavailable':'Unable to determine your location', + 'errors.location.timeout':'Service timeout has been reached' + }) + .factory('geolocation', ['$q','$rootScope','$window','geolocation_msgs',function ($q,$rootScope,$window,geolocation_msgs) { + return { + getLocation: function (opts) { + var deferred = $q.defer(); + if ($window.navigator && $window.navigator.geolocation) { + $window.navigator.geolocation.getCurrentPosition(function(position){ + $rootScope.$apply(function(){deferred.resolve(position);}); + }, function(error) { + switch (error.code) { + case 1: + $rootScope.$broadcast('error',geolocation_msgs['errors.location.permissionDenied']); + $rootScope.$apply(function() { + deferred.reject(geolocation_msgs['errors.location.permissionDenied']); + }); + break; + case 2: + $rootScope.$broadcast('error',geolocation_msgs['errors.location.positionUnavailable']); + $rootScope.$apply(function() { + deferred.reject(geolocation_msgs['errors.location.positionUnavailable']); + }); + break; + case 3: + $rootScope.$broadcast('error',geolocation_msgs['errors.location.timeout']); + $rootScope.$apply(function() { + deferred.reject(geolocation_msgs['errors.location.timeout']); + }); + break; + } + }, opts); + } + else + { + $rootScope.$broadcast('error',geolocation_msgs['errors.location.unsupportedBrowser']); + $rootScope.$apply(function(){deferred.reject(geolocation_msgs['errors.location.unsupportedBrowser']);}); + } + return deferred.promise; + } + }; + }]) + .directive('gmap', function($parse) { + return { + template: 'Google Map', + replace: true, + restrict: 'E', + controller: 'GMapController', + scope: true, + link: function postLink(scope, element, attrs, ctrl) { + var el = element[0]; + + var sizeBits = attrs.size.split('x'); + el.width = parseInt(sizeBits[0], 10); + el.height = parseInt(sizeBits[1], 10); + + scope.$watch(attrs.markers, function(value) { + el.src = ctrl.buildSourceString(attrs, value); + }); + } + } + }) + .controller('GMapController', function() { + var BASE_URL = '//maps.googleapis.com/maps/api/staticmap?'; + var STYLE_ATTRIBUTES = ['color', 'label', 'size']; + + function makeMarkerStrings(markers) { + return markers.map(function(marker) { + var str = Object.keys(marker).map(function(key) { + if (STYLE_ATTRIBUTES.indexOf(key) > -1) { + return key + ':' + marker[key] + '|'; + } + }).join(''); + + return str + marker.coords.join(','); + }); + } + + this.buildSourceString = function(attrs, markers) { + var markerStrings; + if (markers) { + if (!angular.isArray(markers)) { + markers = [markers]; + } + markerStrings = makeMarkerStrings(markers); + } + + var params = Object.keys(attrs).map(function(attr) { + if (attr === 'markers' && markerStrings) { + return Object.keys(markerStrings).map(function(key) { + return 'markers=' + encodeURIComponent(markerStrings[key]); + }).join('&'); + } + + if (attr[0] !== '$' && attr !== 'alt') { + return encodeURIComponent(attr) + '=' + encodeURIComponent(attrs[attr]); + } + }); + + return BASE_URL + params.reduce(function(a, b) { + if (!a) { + return b; + } + + if (b !== undefined) { + return a + '&' + b; + } + + return a; + }, ''); + }; + }); + +clock.PageCtrl = function($scope, $rootScope, geolocation, Clock) { + + function save(action) { + Clock.save({ + action: action, + lat: $scope.coords.latitude, + long: $scope.coords.longitude + }, function(success) { + $scope.success = 'Request was successful.'; + }, function(error) { + $scope.error = 'Unable to complete request, Try again later'; + }); + } + + $scope.working = true; + + + $scope.clockIn = function() { + save('in'); + }; + + $scope.clockOut = function() { + save('out'); + }; + + $rootScope.$on('error', function(event, msg) { + $scope.working = false; + + console.log("ERROR"); + console.log(msg); + + $scope.error = msg; + }); + + geolocation.getLocation().then(function(data) { + $scope.working = false; + + $scope.coords = data.coords; + + console.log('Got location Data'); + console.log(data); + $scope.markers = [{ + color: 'blue', + coords: [data.coords.latitude, data.coords.longitude] + }]; + }); +} + + diff --git a/public/css/clock.css b/public/css/clock.css new file mode 100644 index 0000000..19a52dd --- /dev/null +++ b/public/css/clock.css @@ -0,0 +1,44 @@ +html, body { +} + +.loading { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + text-align: center; + height:100px; + width:200px; +} + +.btn { + width: 100%; + margin-bottom: 5px; + display: block; +} + +.mapOuter { + width: 100%; + overflow: hidden; + position: absolute; + left: 0; +} + +.mapInner { + width: 100%; + height: auto; +} + +.error { + padding: 5px 10px; + color: white; + background: #ba6d6d; +} + +.success { + padding: 5px 10px; + color: white; + background: #6ebb72; +} diff --git a/public/js/app.js b/public/js/app.js index 6220e68..7064240 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -78,6 +78,10 @@ angular.module('biomed', ['biomed.filters', 'biomed.services', 'biomed.directive controller: biomed.UsersIndexCtrl, reloadOnSearch: false }) + .when('/admin/users/:id', { + templateUrl: '/partials/users/clock.html', + controller: biomed.UserClockCtrl + }) .otherwise({ redirectTo: '/schedule' }); diff --git a/public/js/controllers.js b/public/js/controllers.js index 186de9e..5341a50 100644 --- a/public/js/controllers.js +++ b/public/js/controllers.js @@ -172,6 +172,15 @@ biomed.UsersIndexCtrl = function($scope, $filter, $routeParams, $location, Users }; }; +biomed.UserClockCtrl = function($scope, $routeParams, Users) { + Users.index({userid: $routeParams.id}, function(result) { + console.log(result); + $scope.tech = result[0]; + }); + + $scope.clocks = Users.clocks($routeParams); +}; + biomed.ClientIndexCtrl = function($scope, $filter, $routeParams, Clients, LocationBinder) { $scope.loading = true; diff --git a/public/js/services.js b/public/js/services.js index dc1c511..fe95fde 100644 --- a/public/js/services.js +++ b/public/js/services.js @@ -32,6 +32,7 @@ angular.module('biomed.services', []) details: { method: 'GET', params: { cmd: 'details' }, isArray: true }, create: { method: 'POST', params: {} }, update: { method: 'POST', params: { id: 0 } }, + clocks: { method: 'GET', params: { id: 0, cmd: 'clocks' }, isArray: true } }); }) .factory("Schedule", function($resource) { diff --git a/public/partials/users/clock.html b/public/partials/users/clock.html new file mode 100644 index 0000000..5f97cbc --- /dev/null +++ b/public/partials/users/clock.html @@ -0,0 +1,31 @@ + +
+

{{tech.name.first}} {{tech.name.last}}

+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
ActionDateLocation
There is no information to display.
{{clock.action}}{{clock.dt | date:'medium'}}{{clock.lat}},{{clock.long}}View in Google Maps
+
+
diff --git a/public/partials/users/index.html b/public/partials/users/index.html index 4ed2a2b..bec6ae2 100644 --- a/public/partials/users/index.html +++ b/public/partials/users/index.html @@ -41,7 +41,7 @@ There is no information to display. - {{user.name.first}} {{user.name.last}} NEW + {{user.name.first}} {{user.name.last}} NEW {{user.email | email}}