mirror of
				https://github.com/fspc/BikeShed-1.git
				synced 2025-10-31 00:45:35 -04:00 
			
		
		
		
	
						commit
						a8e0c87d37
					
				
							
								
								
									
										1
									
								
								.ruby-gemset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.ruby-gemset
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| velocipede | ||||
							
								
								
									
										1
									
								
								.ruby-version
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.ruby-version
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| ruby-1.9.3-p374 | ||||
							
								
								
									
										55
									
								
								.rvmrc
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								.rvmrc
									
									
									
									
									
								
							| @ -1,55 +0,0 @@ | ||||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| # This is an RVM Project .rvmrc file, used to automatically load the ruby | ||||
| # development environment upon cd'ing into the directory | ||||
| 
 | ||||
| # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional. | ||||
| environment_id="ruby-1.9.3-p374@velocipede" | ||||
| 
 | ||||
| # | ||||
| # Uncomment following line if you want options to be set only for given project. | ||||
| # | ||||
| # PROJECT_JRUBY_OPTS=( --1.9 ) | ||||
| 
 | ||||
| # | ||||
| # First we attempt to load the desired environment directly from the environment | ||||
| # file. This is very fast and efficient compared to running through the entire | ||||
| # CLI and selector. If you want feedback on which environment was used then | ||||
| # insert the word 'use' after --create as this triggers verbose mode. | ||||
| # | ||||
| if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \ | ||||
|   && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] | ||||
| then | ||||
|   \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" | ||||
| 
 | ||||
|   if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] | ||||
|   then | ||||
|     . "${rvm_path:-$HOME/.rvm}/hooks/after_use" | ||||
|   fi | ||||
| else | ||||
|   # If the environment file has not yet been created, use the RVM CLI to select. | ||||
|   if ! rvm --create use  "$environment_id" | ||||
|   then | ||||
|     echo "Failed to create RVM environment '${environment_id}'." | ||||
|     return 1 | ||||
|   fi | ||||
| fi | ||||
| 
 | ||||
| # | ||||
| # If you use an RVM gemset file to install a list of gems (*.gems), you can have | ||||
| # it be automatically loaded. Uncomment the following and adjust the filename if | ||||
| # necessary. | ||||
| # | ||||
| # filename=".gems" | ||||
| # if [[ -s "$filename" ]] | ||||
| # then | ||||
| #   rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d' | ||||
| # fi | ||||
| 
 | ||||
| # If you use bundler, this might be useful to you: | ||||
| # if command -v bundle && [[ -s Gemfile ]] | ||||
| # then | ||||
| #   bundle install | ||||
| # fi | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										21
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Gemfile
									
									
									
									
									
								
							| @ -14,8 +14,9 @@ gem 'decent_exposure', '~> 1.0.1' | ||||
| gem 'devise', '~> 2.0.4' | ||||
| gem 'haml-rails', '~> 0.3.4' | ||||
| gem 'jquery-rails', '~> 2.0' | ||||
| gem 'pg' | ||||
| gem 'pg', '~> 0.17.1' | ||||
| gem 'will_paginate', '~> 3.0.3' | ||||
| gem 'jbuilder', '~> 2.0.3' | ||||
| 
 | ||||
| # Gems used only for assets and not required | ||||
| # in production environments by default. | ||||
| @ -30,21 +31,21 @@ group :assets do | ||||
| end | ||||
| 
 | ||||
| group :development, :test do | ||||
|   gem 'rspec-rails', '~> 2.8.1' | ||||
|   gem 'rspec-rails', '~> 2.14.0' | ||||
|   gem 'factory_girl_rails', '~> 1.2' | ||||
|   gem 'pry', '~> 0.9.8' | ||||
|   gem 'faker' | ||||
|   gem 'faker', '~> 1.2.0' | ||||
| end | ||||
| 
 | ||||
| group :test do | ||||
|   gem 'shoulda-matchers', '~> 1.0.0' | ||||
|   gem 'capybara', '~> 1.1.2' | ||||
|   gem 'turnip', '~> 0.3.0' | ||||
|   gem 'database_cleaner' | ||||
|   gem 'launchy' | ||||
|   gem 'spork' | ||||
|   gem 'capybara', '~> 2.2.1' | ||||
|   gem 'poltergeist', '~> 1.5.0' | ||||
|   gem 'database_cleaner', '~> 1.2.0' | ||||
|   gem 'launchy', '~> 2.4.2' | ||||
|   gem 'spork', '~> 0.9.2' | ||||
|   #guard dependency for Mac OS 10 | ||||
|   gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i | ||||
|   gem 'guard-spork' | ||||
|   gem 'guard-rspec' | ||||
|   gem 'guard-spork', '~> 1.5.1' | ||||
|   gem 'guard-rspec', '~> 4.2.6' | ||||
| end | ||||
|  | ||||
							
								
								
									
										174
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										174
									
								
								Gemfile.lock
									
									
									
									
									
								
							| @ -36,42 +36,43 @@ GEM | ||||
|     activesupport (3.2.13) | ||||
|       i18n (= 0.6.1) | ||||
|       multi_json (~> 1.0) | ||||
|     addressable (2.3.3) | ||||
|     arel (3.0.2) | ||||
|     bcrypt-ruby (3.0.1) | ||||
|     bootstrap-will_paginate (0.0.9) | ||||
|     addressable (2.3.5) | ||||
|     arel (3.0.3) | ||||
|     bcrypt-ruby (3.1.2) | ||||
|     bootstrap-will_paginate (0.0.10) | ||||
|       will_paginate | ||||
|     builder (3.0.4) | ||||
|     cancan (1.6.9) | ||||
|     capybara (1.1.4) | ||||
|     cancan (1.6.10) | ||||
|     capybara (2.2.1) | ||||
|       mime-types (>= 1.16) | ||||
|       nokogiri (>= 1.3.3) | ||||
|       rack (>= 1.0.0) | ||||
|       rack-test (>= 0.5.4) | ||||
|       selenium-webdriver (~> 2.0) | ||||
|       xpath (~> 0.1.4) | ||||
|     childprocess (0.3.9) | ||||
|       xpath (~> 2.0) | ||||
|     celluloid (0.15.2) | ||||
|       timers (~> 1.1.0) | ||||
|     childprocess (0.4.0) | ||||
|       ffi (~> 1.0, >= 1.0.11) | ||||
|     coderay (1.0.9) | ||||
|     cliver (0.3.2) | ||||
|     coderay (1.1.0) | ||||
|     coffee-rails (3.2.2) | ||||
|       coffee-script (>= 2.2.0) | ||||
|       railties (~> 3.2.0) | ||||
|     coffee-script (2.2.0) | ||||
|       coffee-script-source | ||||
|       execjs | ||||
|     coffee-script-source (1.6.2) | ||||
|     commonjs (0.2.6) | ||||
|     database_cleaner (0.9.1) | ||||
|     coffee-script-source (1.7.0) | ||||
|     commonjs (0.2.7) | ||||
|     database_cleaner (1.2.0) | ||||
|     decent_exposure (1.0.2) | ||||
|     devise (2.0.5) | ||||
|     devise (2.0.6) | ||||
|       bcrypt-ruby (~> 3.0) | ||||
|       orm_adapter (~> 0.0.3) | ||||
|       railties (~> 3.1) | ||||
|       warden (~> 1.1.1) | ||||
|     diff-lcs (1.1.3) | ||||
|     diff-lcs (1.2.5) | ||||
|     erubis (2.7.0) | ||||
|     execjs (1.4.0) | ||||
|       multi_json (~> 1.0) | ||||
|     execjs (2.0.2) | ||||
|     factory_girl (2.6.4) | ||||
|       activesupport (>= 2.3.9) | ||||
|     factory_girl_rails (1.7.0) | ||||
| @ -79,19 +80,18 @@ GEM | ||||
|       railties (>= 3.0.0) | ||||
|     faker (1.2.0) | ||||
|       i18n (~> 0.5) | ||||
|     ffi (1.6.0) | ||||
|     ffi (1.9.3) | ||||
|     formatador (0.2.4) | ||||
|     gherkin (2.11.6) | ||||
|       json (>= 1.7.6) | ||||
|     guard (1.7.0) | ||||
|     guard (2.4.0) | ||||
|       formatador (>= 0.2.4) | ||||
|       listen (>= 0.6.0) | ||||
|       lumberjack (>= 1.0.2) | ||||
|       pry (>= 0.9.10) | ||||
|       thor (>= 0.14.6) | ||||
|     guard-rspec (1.2.1) | ||||
|       guard (>= 1.1) | ||||
|     guard-spork (1.5.0) | ||||
|       listen (~> 2.1) | ||||
|       lumberjack (~> 1.0) | ||||
|       pry (>= 0.9.12) | ||||
|       thor (>= 0.18.1) | ||||
|     guard-rspec (4.2.6) | ||||
|       guard (~> 2.1) | ||||
|       rspec (>= 2.14, < 4.0) | ||||
|     guard-spork (1.5.1) | ||||
|       childprocess (>= 0.2.3) | ||||
|       guard (>= 1.1) | ||||
|       spork (>= 0.8.4) | ||||
| @ -101,14 +101,17 @@ GEM | ||||
|       activesupport (>= 3.1, < 4.1) | ||||
|       haml (~> 3.1) | ||||
|       railties (>= 3.1, < 4.1) | ||||
|     hike (1.2.1) | ||||
|     hike (1.2.3) | ||||
|     i18n (0.6.1) | ||||
|     jbuilder (2.0.3) | ||||
|       activesupport (>= 3.0.0) | ||||
|       multi_json (>= 1.2.0) | ||||
|     journey (1.0.4) | ||||
|     jquery-rails (2.2.1) | ||||
|     jquery-rails (2.3.0) | ||||
|       railties (>= 3.0, < 5.0) | ||||
|       thor (>= 0.14, < 2.0) | ||||
|     json (1.7.7) | ||||
|     launchy (2.2.0) | ||||
|     json (1.8.1) | ||||
|     launchy (2.4.2) | ||||
|       addressable (~> 2.3) | ||||
|     less (2.2.2) | ||||
|       commonjs (~> 0.2.6) | ||||
| @ -116,15 +119,18 @@ GEM | ||||
|       actionpack (>= 3.1) | ||||
|       less (~> 2.2.0) | ||||
|     libv8 (3.3.10.4) | ||||
|     listen (0.7.3) | ||||
|     lumberjack (1.0.3) | ||||
|     mail (2.5.3) | ||||
|       i18n (>= 0.4.0) | ||||
|     listen (2.4.1) | ||||
|       celluloid (>= 0.15.2) | ||||
|       rb-fsevent (>= 0.9.3) | ||||
|       rb-inotify (>= 0.9) | ||||
|     lumberjack (1.0.4) | ||||
|     mail (2.5.4) | ||||
|       mime-types (~> 1.16) | ||||
|       treetop (~> 1.4.8) | ||||
|     method_source (0.8.1) | ||||
|     mime-types (1.22) | ||||
|     multi_json (1.8.2) | ||||
|     method_source (0.8.2) | ||||
|     mime-types (1.25.1) | ||||
|     mini_portile (0.5.2) | ||||
|     multi_json (1.8.4) | ||||
|     netzke-basepack (0.8.4) | ||||
|       netzke-core (~> 0.8.2) | ||||
|     netzke-cancan (0.8.2) | ||||
| @ -133,12 +139,18 @@ GEM | ||||
|     netzke-core (0.8.4) | ||||
|       execjs | ||||
|       uglifier | ||||
|     nokogiri (1.5.9) | ||||
|     nokogiri (1.6.1) | ||||
|       mini_portile (~> 0.5.0) | ||||
|     orm_adapter (0.0.7) | ||||
|     pg (0.15.1) | ||||
|     pg (0.17.1) | ||||
|     poltergeist (1.5.0) | ||||
|       capybara (~> 2.1) | ||||
|       cliver (~> 0.3.1) | ||||
|       multi_json (~> 1.0) | ||||
|       websocket-driver (>= 0.2.0) | ||||
|     polyglot (0.3.3) | ||||
|     pry (0.9.12) | ||||
|       coderay (~> 1.0.5) | ||||
|     pry (0.9.12.6) | ||||
|       coderay (~> 1.0) | ||||
|       method_source (~> 0.8) | ||||
|       slop (~> 3.4) | ||||
|     rack (1.4.5) | ||||
| @ -163,31 +175,30 @@ GEM | ||||
|       rake (>= 0.8.7) | ||||
|       rdoc (~> 3.4) | ||||
|       thor (>= 0.14.6, < 2.0) | ||||
|     rake (10.0.4) | ||||
|     rb-fsevent (0.9.3) | ||||
|     rake (10.1.1) | ||||
|     rb-fsevent (0.9.4) | ||||
|     rb-inotify (0.9.3) | ||||
|       ffi (>= 0.5.0) | ||||
|     rdoc (3.12.2) | ||||
|       json (~> 1.4) | ||||
|     rspec (2.8.0) | ||||
|       rspec-core (~> 2.8.0) | ||||
|       rspec-expectations (~> 2.8.0) | ||||
|       rspec-mocks (~> 2.8.0) | ||||
|     rspec-core (2.8.0) | ||||
|     rspec-expectations (2.8.0) | ||||
|       diff-lcs (~> 1.1.2) | ||||
|     rspec-mocks (2.8.0) | ||||
|     rspec-rails (2.8.1) | ||||
|     rspec (2.14.1) | ||||
|       rspec-core (~> 2.14.0) | ||||
|       rspec-expectations (~> 2.14.0) | ||||
|       rspec-mocks (~> 2.14.0) | ||||
|     rspec-core (2.14.7) | ||||
|     rspec-expectations (2.14.5) | ||||
|       diff-lcs (>= 1.1.3, < 2.0) | ||||
|     rspec-mocks (2.14.5) | ||||
|     rspec-rails (2.14.1) | ||||
|       actionpack (>= 3.0) | ||||
|       activemodel (>= 3.0) | ||||
|       activesupport (>= 3.0) | ||||
|       railties (>= 3.0) | ||||
|       rspec (~> 2.8.0) | ||||
|     rubyzip (0.9.9) | ||||
|     selenium-webdriver (2.31.0) | ||||
|       childprocess (>= 0.2.5) | ||||
|       multi_json (~> 1.0) | ||||
|       rubyzip | ||||
|       websocket (~> 1.0.4) | ||||
|       rspec-core (~> 2.14.0) | ||||
|       rspec-expectations (~> 2.14.0) | ||||
|       rspec-mocks (~> 2.14.0) | ||||
|     shoulda-matchers (1.0.0) | ||||
|     slop (3.4.4) | ||||
|     slop (3.4.7) | ||||
|     spork (0.9.2) | ||||
|     sprockets (2.2.2) | ||||
|       hike (~> 1.2) | ||||
| @ -197,27 +208,25 @@ GEM | ||||
|     therubyracer (0.10.2) | ||||
|       libv8 (~> 3.3.10) | ||||
|     thor (0.18.1) | ||||
|     tilt (1.3.6) | ||||
|     treetop (1.4.12) | ||||
|     tilt (1.4.1) | ||||
|     timers (1.1.0) | ||||
|     treetop (1.4.15) | ||||
|       polyglot | ||||
|       polyglot (>= 0.3.1) | ||||
|     turnip (0.3.1) | ||||
|       gherkin (>= 2.5) | ||||
|       rspec (~> 2.0) | ||||
|     twitter-bootstrap-rails (2.0.9) | ||||
|       actionpack (>= 3.1) | ||||
|       less-rails (~> 2.2.2) | ||||
|       railties (>= 3.1) | ||||
|       therubyracer (~> 0.10.1) | ||||
|     tzinfo (0.3.38) | ||||
|     uglifier (2.1.2) | ||||
|     uglifier (2.4.0) | ||||
|       execjs (>= 0.3.0) | ||||
|       multi_json (~> 1.0, >= 1.0.2) | ||||
|       json (>= 1.8.0) | ||||
|     warden (1.1.1) | ||||
|       rack (>= 1.0) | ||||
|     websocket (1.0.7) | ||||
|     will_paginate (3.0.4) | ||||
|     xpath (0.1.4) | ||||
|     websocket-driver (0.3.2) | ||||
|     will_paginate (3.0.5) | ||||
|     xpath (2.0.0) | ||||
|       nokogiri (~> 1.3) | ||||
| 
 | ||||
| PLATFORMS | ||||
| @ -227,29 +236,30 @@ DEPENDENCIES | ||||
|   acts_as_loggable! | ||||
|   bootstrap-will_paginate (~> 0.0.6) | ||||
|   cancan | ||||
|   capybara (~> 1.1.2) | ||||
|   capybara (~> 2.2.1) | ||||
|   coffee-rails (~> 3.2.1) | ||||
|   database_cleaner | ||||
|   database_cleaner (~> 1.2.0) | ||||
|   decent_exposure (~> 1.0.1) | ||||
|   devise (~> 2.0.4) | ||||
|   factory_girl_rails (~> 1.2) | ||||
|   faker | ||||
|   guard-rspec | ||||
|   guard-spork | ||||
|   faker (~> 1.2.0) | ||||
|   guard-rspec (~> 4.2.6) | ||||
|   guard-spork (~> 1.5.1) | ||||
|   haml-rails (~> 0.3.4) | ||||
|   jbuilder (~> 2.0.3) | ||||
|   jquery-rails (~> 2.0) | ||||
|   launchy | ||||
|   launchy (~> 2.4.2) | ||||
|   netzke-basepack (~> 0.8.0) | ||||
|   netzke-cancan | ||||
|   netzke-core (~> 0.8.0) | ||||
|   pg | ||||
|   pg (~> 0.17.1) | ||||
|   poltergeist (~> 1.5.0) | ||||
|   pry (~> 0.9.8) | ||||
|   rails (= 3.2.13) | ||||
|   rb-fsevent | ||||
|   rspec-rails (~> 2.8.1) | ||||
|   rspec-rails (~> 2.14.0) | ||||
|   shoulda-matchers (~> 1.0.0) | ||||
|   spork | ||||
|   turnip (~> 0.3.0) | ||||
|   spork (~> 0.9.2) | ||||
|   twitter-bootstrap-rails (~> 2.0.3) | ||||
|   uglifier (>= 1.0.3) | ||||
|   will_paginate (~> 3.0.3) | ||||
|  | ||||
| @ -45,6 +45,11 @@ At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donati | ||||
| 1. Create your database `createdb -U velocipede --owner=velocipede velocipede` | ||||
| 1. Create your test database `createdb -U velocipede --owner=velocipede velocipede_test` | ||||
| 
 | ||||
| # Testing | ||||
| 
 | ||||
| 1. Install phantomjs `brew install phantomjs` | ||||
| 1. Run tests with `rspec` | ||||
| 
 | ||||
| # Optional | ||||
|  Add icons | ||||
| 
 | ||||
|  | ||||
| @ -12,4 +12,9 @@ | ||||
| //
 | ||||
| //= require jquery
 | ||||
| //= require jquery_ujs
 | ||||
| //= require custom_netzke_helpers
 | ||||
| //= require twitter/bootstrap/bootstrap-button
 | ||||
| //= require twitter/bootstrap/bootstrap-modal
 | ||||
| //= require utils
 | ||||
| //= require bootstrap-datepicker
 | ||||
| //= require bootstrap-timepicker
 | ||||
| //= require jquery-date-format
 | ||||
|  | ||||
							
								
								
									
										31
									
								
								app/assets/javascripts/bikes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/assets/javascripts/bikes.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| $('.btn').button(); | ||||
| 
 | ||||
| $("#add_bike_submit").click(function(){ | ||||
| 
 | ||||
|   json_data = { bikes: [{ | ||||
|       serial_number:      $("#serial_number").val(), | ||||
|       bike_brand_id:      parseInt($("#bike_brand_id").val()), | ||||
|       shop_id:            parseInt($("#shop_id").val()), | ||||
|       model:              $("#model").val(), | ||||
|       bike_style_id:      parseInt($('input[name=bike_style]:checked').val()), | ||||
|       seat_tube_height:   parseFloat($("#seat_tube_height").val()), | ||||
|       bike_condition_id:  parseInt($('input[name=bike_condition]:checked').val()), | ||||
|       bike_purpose_id:    1, | ||||
|       bike_wheel_size_id: parseInt($("#bike_wheel_size_id").val()), | ||||
|     }]}; | ||||
| 
 | ||||
|   $.ajax({ | ||||
|     url: $("#add_bike_submit").data("url"), | ||||
|     type: "POST", | ||||
|     data: JSON.stringify(json_data), | ||||
|     contentType: 'application/json', | ||||
|     dataType: "json", | ||||
|     success: function(data, status, xhr){ | ||||
|       window.location = data.bikes[0].id + "?add_bike=1"; | ||||
|     }, | ||||
|     error: function(data, status ){ | ||||
|       displayFormErrors(data.responseJSON); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
| }); | ||||
							
								
								
									
										474
									
								
								app/assets/javascripts/bootstrap-datepicker.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										474
									
								
								app/assets/javascripts/bootstrap-datepicker.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @ -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 = '<tr>'; | ||||
| 			while (dowCnt < this.weekStart + 7) { | ||||
| 				html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>'; | ||||
| 			} | ||||
| 			html += '</tr>'; | ||||
| 			this.picker.find('.datepicker-days thead').append(html); | ||||
| 		}, | ||||
| 		 | ||||
| 		fillMonths: function(){ | ||||
| 			var html = ''; | ||||
| 			var i = 0 | ||||
| 			while (i < 12) { | ||||
| 				html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>'; | ||||
| 			} | ||||
| 			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('<tr>'); | ||||
| 				} | ||||
| 				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('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>'); | ||||
| 				if (prevMonth.getDay() === this.weekEnd) { | ||||
| 					html.push('</tr>'); | ||||
| 				} | ||||
| 				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 += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>'; | ||||
| 				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: '<thead>'+ | ||||
| 							'<tr>'+ | ||||
| 								'<th class="prev">‹</th>'+ | ||||
| 								'<th colspan="5" class="switch"></th>'+ | ||||
| 								'<th class="next">›</th>'+ | ||||
| 							'</tr>'+ | ||||
| 						'</thead>', | ||||
| 		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>' | ||||
| 	}; | ||||
| 	DPGlobal.template = '<div class="datepicker dropdown-menu">'+ | ||||
| 							'<div class="datepicker-days">'+ | ||||
| 								'<table class=" table-condensed">'+ | ||||
| 									DPGlobal.headTemplate+ | ||||
| 									'<tbody></tbody>'+ | ||||
| 								'</table>'+ | ||||
| 							'</div>'+ | ||||
| 							'<div class="datepicker-months">'+ | ||||
| 								'<table class="table-condensed">'+ | ||||
| 									DPGlobal.headTemplate+ | ||||
| 									DPGlobal.contTemplate+ | ||||
| 								'</table>'+ | ||||
| 							'</div>'+ | ||||
| 							'<div class="datepicker-years">'+ | ||||
| 								'<table class="table-condensed">'+ | ||||
| 									DPGlobal.headTemplate+ | ||||
| 									DPGlobal.contTemplate+ | ||||
| 								'</table>'+ | ||||
| 							'</div>'+ | ||||
| 						'</div>'; | ||||
| 
 | ||||
| }( window.jQuery ); | ||||
							
								
								
									
										1097
									
								
								app/assets/javascripts/bootstrap-timepicker.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1097
									
								
								app/assets/javascripts/bootstrap-timepicker.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,7 +1,7 @@ | ||||
| $(document).ready(function(){ | ||||
|   $("#checkin_menu").show(); | ||||
|   $("#checkin").click( function(e){ | ||||
|     var username = $("#user_email").val(); | ||||
|     var username = $("#user_username").val(); | ||||
|     var password = $("#user_password").val(); | ||||
|     $.ajax({ | ||||
|       type: 'POST', | ||||
| @ -13,7 +13,7 @@ $(document).ready(function(){ | ||||
|       complete: function() { }, | ||||
|       success: function(data) { | ||||
|         alert("Checked IN!"); | ||||
|         $("#user_email").val(''); | ||||
|         $("#user_username").val(''); | ||||
|         $("#user_password").val(''); | ||||
|       }, | ||||
|       error: function(data,textStatus) { | ||||
| @ -22,7 +22,7 @@ $(document).ready(function(){ | ||||
|     }) | ||||
|   }); | ||||
|   $("#checkout").click( function(e){ | ||||
|     var username = $("#user_email").val(); | ||||
|     var username = $("#user_username").val(); | ||||
|     var password = $("#user_password").val(); | ||||
|     $.ajax({ | ||||
|       type: 'POST', | ||||
| @ -34,7 +34,8 @@ $(document).ready(function(){ | ||||
|       complete: function() { }, | ||||
|       success: function(data) { | ||||
|         alert("Checked OUT!"); | ||||
|         $("#user_email").val(''); | ||||
| 
 | ||||
|         $("#user_username").val(''); | ||||
|         $("#user_password").val(''); | ||||
|       }, | ||||
|       error: function(data,textStatus) { | ||||
|  | ||||
							
								
								
									
										445
									
								
								app/assets/javascripts/jquery-date-format.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										445
									
								
								app/assets/javascripts/jquery-date-format.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,445 @@ | ||||
| var DateFormat = {}; | ||||
| 
 | ||||
| (function($) { | ||||
|   var daysInWeek          = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; | ||||
|   var shortMonthsInYear   = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', | ||||
|                               'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; | ||||
|   var longMonthsInYear    = ['January', 'February', 'March', 'April', 'May', 'June', | ||||
|                               'July', 'August', 'September', 'October', 'November', 'December']; | ||||
|   var shortMonthsToNumber = { 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', | ||||
|                               'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12' }; | ||||
| 
 | ||||
|   var YYYYMMDD_MATCHER = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/; | ||||
| 
 | ||||
|   $.format = (function() { | ||||
|     function numberToLongDay(value) { | ||||
|       // 0 to Sunday
 | ||||
|       // 1 to Monday
 | ||||
|       return daysInWeek[parseInt(value, 10)] || value; | ||||
|     } | ||||
| 
 | ||||
|     function numberToShortMonth(value) { | ||||
|       // 1 to Jan
 | ||||
|       // 2 to Feb
 | ||||
|       var monthArrayIndex = parseInt(value, 10) - 1; | ||||
|       return shortMonthsInYear[monthArrayIndex] || value; | ||||
|     } | ||||
| 
 | ||||
|     function numberToLongMonth(value) { | ||||
|       // 1 to January
 | ||||
|       // 2 to February
 | ||||
|       var monthArrayIndex = parseInt(value, 10) - 1; | ||||
|       return longMonthsInYear[monthArrayIndex] || value; | ||||
|     } | ||||
| 
 | ||||
|     function shortMonthToNumber(value) { | ||||
|       // Jan to 01
 | ||||
|       // Feb to 02
 | ||||
|       return shortMonthsToNumber[value] || value; | ||||
|     } | ||||
| 
 | ||||
|     function parseTime(value) { | ||||
|       // 10:54:50.546
 | ||||
|       // => hour: 10, minute: 54, second: 50, millis: 546
 | ||||
|       // 10:54:50
 | ||||
|       // => hour: 10, minute: 54, second: 50, millis: ''
 | ||||
|       var time = value, | ||||
|           values, | ||||
|           subValues, | ||||
|           hour, | ||||
|           minute, | ||||
|           second, | ||||
|           millis = '', | ||||
|           delimited, | ||||
|           timeArray; | ||||
| 
 | ||||
|       if(time.indexOf('.') !== -1) { | ||||
|         delimited = time.split('.'); | ||||
|         // split time and milliseconds
 | ||||
|         time   = delimited[0]; | ||||
|         millis = delimited[1]; | ||||
|       } | ||||
| 
 | ||||
|       timeArray = time.split(':'); | ||||
| 
 | ||||
|       if(timeArray.length === 3) { | ||||
|         hour   = timeArray[0]; | ||||
|         minute = timeArray[1]; | ||||
|         // '20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
 | ||||
|         // => 20
 | ||||
|         // '20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
 | ||||
|         // => 20
 | ||||
|         second = timeArray[2].replace(/\s.+/, '').replace(/[a-z]/gi, ''); | ||||
|         // '01:10:20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
 | ||||
|         // => 01:10:20
 | ||||
|         // '01:10:20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
 | ||||
|         // => 01:10:20
 | ||||
|         time = time.replace(/\s.+/, '').replace(/[a-z]/gi, ''); | ||||
|         return { | ||||
|           time:    time, | ||||
|           hour:    hour, | ||||
|           minute:  minute, | ||||
|           second:  second, | ||||
|           millis:  millis | ||||
|         }; | ||||
|       } | ||||
| 
 | ||||
|       return { time : '', hour : '', minute : '', second : '', millis : '' }; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     function padding(value, length) { | ||||
|       var paddingCount = length - String(value).length; | ||||
|       for(var i = 0; i < paddingCount; i++) { | ||||
|         value = '0' + value; | ||||
|       } | ||||
|       return value; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
| 
 | ||||
|       parseDate: function(value) { | ||||
|         var parsedDate = { | ||||
|           date:       null, | ||||
|           year:       null, | ||||
|           month:      null, | ||||
|           dayOfMonth: null, | ||||
|           dayOfWeek:  null, | ||||
|           time:       null | ||||
|         }; | ||||
| 
 | ||||
|         if(typeof value == 'number') { | ||||
|           return this.parseDate(new Date(value)); | ||||
|         } else if(typeof value.getFullYear == 'function') { | ||||
|           parsedDate.year       = String(value.getFullYear()); | ||||
|           // d = new Date(1900, 1, 1) // 1 for Feb instead of Jan.
 | ||||
|           // => Thu Feb 01 1900 00:00:00
 | ||||
|           parsedDate.month      = String(value.getMonth() + 1); | ||||
|           parsedDate.dayOfMonth = String(value.getDate()); | ||||
|           parsedDate.time       = parseTime(value.toTimeString()); | ||||
|         } else if(value.search(YYYYMMDD_MATCHER) != -1) { | ||||
|           /* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */ | ||||
|           values = value.split(/[T\+-]/); | ||||
|           parsedDate.year       = values[0]; | ||||
|           parsedDate.month      = values[1]; | ||||
|           parsedDate.dayOfMonth = values[2]; | ||||
|           parsedDate.time       = parseTime(values[3].split('.')[0]); | ||||
|         } else { | ||||
|           values = value.split(' '); | ||||
|           switch (values.length) { | ||||
|             case 6: | ||||
|               /* Wed Jan 13 10:43:41 CET 2010 */ | ||||
|               parsedDate.year       = values[5]; | ||||
|               parsedDate.month      = shortMonthToNumber(values[1]); | ||||
|               parsedDate.dayOfMonth = values[2]; | ||||
|               parsedDate.time       = parseTime(values[3]); | ||||
|               break; | ||||
|             case 2: | ||||
|               /* 2009-12-18 10:54:50.546 */ | ||||
|               subValues = values[0].split('-'); | ||||
|               parsedDate.year       = subValues[0]; | ||||
|               parsedDate.month      = subValues[1]; | ||||
|               parsedDate.dayOfMonth = subValues[2]; | ||||
|               parsedDate.time       = parseTime(values[1]); | ||||
|               break; | ||||
|             case 7: | ||||
|               /* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */ | ||||
|             case 9: | ||||
|               /* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */ | ||||
|             case 10: | ||||
|               /* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */ | ||||
|               parsedDate.year       = values[3]; | ||||
|               parsedDate.month      = shortMonthToNumber(values[1]); | ||||
|               parsedDate.dayOfMonth = values[2]; | ||||
|               parsedDate.time       = parseTime(values[4]); | ||||
|               break; | ||||
|             case 1: | ||||
|               /* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */ | ||||
|               subValues = values[0].split(''); | ||||
|               parsedDate.year       = subValues[0] + subValues[1] + subValues[2] + subValues[3]; | ||||
|               parsedDate.month      = subValues[5] + subValues[6]; | ||||
|               parsedDate.dayOfMonth = subValues[8] + subValues[9]; | ||||
|               parsedDate.time       = parseTime(subValues[13] + subValues[14] + subValues[15] + subValues[16] + subValues[17] + subValues[18] + subValues[19] + subValues[20]); | ||||
|               break; | ||||
|             default: | ||||
|               return null; | ||||
|           } | ||||
|         } | ||||
|         parsedDate.date       = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth); | ||||
|         parsedDate.dayOfWeek  = String(parsedDate.date.getDay()); | ||||
| 
 | ||||
|         return parsedDate; | ||||
|       }, | ||||
| 
 | ||||
|       date : function(value, format) { | ||||
|         try { | ||||
|           var parsedDate = this.parseDate(value); | ||||
| 
 | ||||
|           if(parsedDate === null) { | ||||
|             return value; | ||||
|           } | ||||
| 
 | ||||
|           var date       = parsedDate.date, | ||||
|               year       = parsedDate.year, | ||||
|               month      = parsedDate.month, | ||||
|               dayOfMonth = parsedDate.dayOfMonth, | ||||
|               dayOfWeek  = parsedDate.dayOfWeek, | ||||
|               time       = parsedDate.time; | ||||
| 
 | ||||
|           var pattern      = '', | ||||
|               retValue     = '', | ||||
|               unparsedRest = '', | ||||
|               inQuote      = false; | ||||
| 
 | ||||
|           /* Issue 1 - variable scope issue in format.date (Thanks jakemonO) */ | ||||
|           for(var i = 0; i < format.length; i++) { | ||||
|             var currentPattern = format.charAt(i); | ||||
|             // Look-Ahead Right (LALR)
 | ||||
|             var nextRight      = format.charAt(i + 1); | ||||
| 
 | ||||
|             if (inQuote) { | ||||
|               if (currentPattern == "'") { | ||||
|                 retValue += (pattern === '') ? "'" : pattern; | ||||
|                 pattern = ''; | ||||
|                 inQuote = false; | ||||
|               } else { | ||||
|                 pattern += currentPattern; | ||||
|               } | ||||
|               continue; | ||||
|             } | ||||
|             pattern += currentPattern; | ||||
|             unparsedRest = ''; | ||||
|             switch (pattern) { | ||||
|               case 'ddd': | ||||
|                 retValue += numberToLongDay(dayOfWeek); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'dd': | ||||
|                 if(nextRight === 'd') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += padding(dayOfMonth, 2); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'd': | ||||
|                 if(nextRight === 'd') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += parseInt(dayOfMonth, 10); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'D': | ||||
|                 if(dayOfMonth == 1 || dayOfMonth == 21 || dayOfMonth == 31) { | ||||
|                   dayOfMonth = parseInt(dayOfMonth, 10) + 'st'; | ||||
|                 } else if(dayOfMonth == 2 || dayOfMonth == 22) { | ||||
|                   dayOfMonth = parseInt(dayOfMonth, 10) + 'nd'; | ||||
|                 } else if(dayOfMonth == 3 || dayOfMonth == 23) { | ||||
|                   dayOfMonth = parseInt(dayOfMonth, 10) + 'rd'; | ||||
|                 } else { | ||||
|                   dayOfMonth = parseInt(dayOfMonth, 10) + 'th'; | ||||
|                 } | ||||
|                 retValue += dayOfMonth; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'MMMM': | ||||
|                 retValue += numberToLongMonth(month); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'MMM': | ||||
|                 if(nextRight === 'M') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += numberToShortMonth(month); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'MM': | ||||
|                 if(nextRight === 'M') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += padding(month, 2); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'M': | ||||
|                 if(nextRight === 'M') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += parseInt(month, 10); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'y': | ||||
|               case 'yyy': | ||||
|                 if(nextRight === 'y') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += pattern; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'yy': | ||||
|                 if(nextRight === 'y') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += String(year).slice(-2); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'yyyy': | ||||
|                 retValue += year; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'HH': | ||||
|                 retValue += padding(time.hour, 2); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'H': | ||||
|                 if(nextRight === 'H') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += parseInt(time.hour, 10); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'hh': | ||||
|                 /* time.hour is '00' as string == is used instead of === */ | ||||
|                 hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour | ||||
|                     : time.hour - 12); | ||||
|                 retValue += padding(hour, 2); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'h': | ||||
|                 if(nextRight === 'h') { | ||||
|                   break; | ||||
|                 } | ||||
|                 hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour | ||||
|                     : time.hour - 12); | ||||
|                 retValue += parseInt(hour, 10); | ||||
|                 // Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21
 | ||||
|                 // retValue = parseInt(retValue, 10);
 | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'mm': | ||||
|                 retValue += padding(time.minute, 2); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'm': | ||||
|                 if(nextRight === 'm') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += time.minute; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'ss': | ||||
|                 /* ensure only seconds are added to the return string */ | ||||
|                 retValue += padding(time.second.substring(0, 2), 2); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 's': | ||||
|                 if(nextRight === 's') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += time.second; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'S': | ||||
|               case 'SS': | ||||
|                 if(nextRight === 'S') { | ||||
|                   break; | ||||
|                 } | ||||
|                 retValue += pattern; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'SSS': | ||||
|                 retValue += time.millis.substring(0, 3); | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'a': | ||||
|                 retValue += time.hour >= 12 ? 'PM' : 'AM'; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case 'p': | ||||
|                 retValue += time.hour >= 12 ? 'p.m.' : 'a.m.'; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|               case "'": | ||||
|                 pattern = ''; | ||||
|                 inQuote = true; | ||||
|                 break; | ||||
|               default: | ||||
|                 retValue += currentPattern; | ||||
|                 pattern = ''; | ||||
|                 break; | ||||
|             } | ||||
|           } | ||||
|           retValue += unparsedRest; | ||||
|           return retValue; | ||||
|         } catch (e) { | ||||
|           if(console && console.log) { | ||||
|             console.log(e); | ||||
|           } | ||||
|           return value; | ||||
|         } | ||||
|       }, | ||||
|       /* | ||||
|        * JavaScript Pretty Date | ||||
|        * Copyright (c) 2011 John Resig (ejohn.org) | ||||
|        * Licensed under the MIT and GPL licenses. | ||||
|        * | ||||
|        * Takes an ISO time and returns a string representing how long ago the date | ||||
|        * represents | ||||
|        * | ||||
|        * ('2008-01-28T20:24:17Z') // => '2 hours ago'
 | ||||
|        * ('2008-01-27T22:24:17Z') // => 'Yesterday'
 | ||||
|        * ('2008-01-26T22:24:17Z') // => '2 days ago'
 | ||||
|        * ('2008-01-14T22:24:17Z') // => '2 weeks ago'
 | ||||
|        * ('2007-12-15T22:24:17Z') // => 'more than 5 weeks ago'
 | ||||
|        * | ||||
|        */ | ||||
|       prettyDate : function(time) { | ||||
|         var date; | ||||
|         var diff; | ||||
|         var day_diff; | ||||
| 
 | ||||
|         if(typeof time === 'string' || typeof time === 'number') { | ||||
|           date = new Date(time); | ||||
|         } | ||||
| 
 | ||||
|         if(typeof time === 'object') { | ||||
|           date = new Date(time.toString()); | ||||
|         } | ||||
| 
 | ||||
|         diff = (((new Date()).getTime() - date.getTime()) / 1000); | ||||
| 
 | ||||
|         day_diff = Math.floor(diff / 86400); | ||||
| 
 | ||||
|         if(isNaN(day_diff) || day_diff < 0) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         if(diff < 60) { | ||||
|           return 'just now'; | ||||
|         } else if(diff < 120) { | ||||
|           return '1 minute ago'; | ||||
|         } else if(diff < 3600) { | ||||
|           return Math.floor(diff / 60) + ' minutes ago'; | ||||
|         } else if(diff < 7200) { | ||||
|           return '1 hour ago'; | ||||
|         } else if(diff < 86400) { | ||||
|           return Math.floor(diff / 3600) + ' hours ago'; | ||||
|         } else if(day_diff === 1) { | ||||
|           return 'Yesterday'; | ||||
|         } else if(day_diff < 7) { | ||||
|           return day_diff + ' days ago'; | ||||
|         } else if(day_diff < 31) { | ||||
|           return Math.ceil(day_diff / 7) + ' weeks ago'; | ||||
|         } else if(day_diff >= 31) { | ||||
|           return 'more than 5 weeks ago'; | ||||
|         } | ||||
|       }, | ||||
|       toBrowserTimeZone : function(value, format) { | ||||
|         return this.date(new Date(value), format || 'MM/dd/yyyy HH:mm:ss'); | ||||
|       } | ||||
|     }; | ||||
|   }()); | ||||
| }(DateFormat)); | ||||
| ;// require dateFormat.js
 | ||||
| // please check `dist/jquery.dateFormat.js` for a complete version
 | ||||
| (function($) { | ||||
|   $.format = DateFormat.format; | ||||
| }(jQuery)); | ||||
| @ -0,0 +1,9 @@ | ||||
| $("#index_logout").click(function(){ | ||||
|   $.ajax({ | ||||
|     type: "DELETE", | ||||
|     url: $("#index_logout").data("url"), | ||||
|     complete: function(){ | ||||
|       window.location.href="/"; | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										32
									
								
								app/assets/javascripts/task_lists.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/assets/javascripts/task_lists.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| $(".task_list_task").click(function(){ | ||||
|   $("#update_tasks_submit").removeClass("disabled"); | ||||
| }); | ||||
| 
 | ||||
| $("#update_tasks_submit").click(function(){ | ||||
| 
 | ||||
|   tasks = []; | ||||
|   $(".task_list_task").each(function(){ | ||||
|     tasks.push({ | ||||
|       id: parseInt($(this).data("id")), | ||||
|       done: $(this).is(":checked") | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   json_data = { tasks: tasks }; | ||||
| 
 | ||||
|   $.ajax({ | ||||
|     url: $("#update_tasks_submit").data("url"), | ||||
|     type: "PUT", | ||||
|     data: JSON.stringify(json_data), | ||||
|     contentType: 'application/json', | ||||
|     dataType: "json", | ||||
|     success: function(data, status, xhr){ | ||||
|       //should re-render via JS, but for now reload
 | ||||
|       location.reload(); | ||||
|     }, | ||||
|     error: function(data, status ){ | ||||
|       alert("An error occured updating tasks"); | ||||
|       //displayFormErrors(data.responseJSON);
 | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										81
									
								
								app/assets/javascripts/time_entries.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								app/assets/javascripts/time_entries.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| $(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(); | ||||
| 
 | ||||
|   $("#add_time_entry_submit").click(function(){ | ||||
|     date = $("#date_id").val(); | ||||
|     start_date = new Date(date + " " + $("#start_time_id").val()); | ||||
|     end_date = new Date(date + " " + $("#end_time_id").val()); | ||||
| 
 | ||||
|     forward = $("#add_time_entry_submit").data("forward"); | ||||
| 
 | ||||
|     // If a bike is selected, forward to the bike
 | ||||
|     // checklist.
 | ||||
|     bike_id = parseInt($("#bike_id").val()); | ||||
|     if( bike_id > 0 ){ | ||||
|       forward = "/task_lists/" + bike_id + "/edit"; | ||||
|     } | ||||
| 
 | ||||
|     //FIXME: Ideally, we'd submit the dates as ISO, but I can't figure out
 | ||||
|     // how to get Netzke to render UTC dates correctly (it calls to_json
 | ||||
|     // somewhere and drops off the timezone).  For the time being, save dates
 | ||||
|     // in locale like Netzke.
 | ||||
|     json_data = { time_entries: [{ | ||||
|         start_date:    $.format.date(start_date, "dd-MM-yyyy hh:mm a"), | ||||
|         end_date:      $.format.date(end_date, "dd-MM-yyyy hh:mm a"), | ||||
|         log_action_id: parseInt($('input[name=action_id]:checked').val()), | ||||
|         bike_id:       bike_id, | ||||
|         description:   $("#description_id").val(), | ||||
|       }]}; | ||||
| 
 | ||||
|     $.ajax({ | ||||
|       url: $("#add_time_entry_submit").data("url"), | ||||
|       type: "POST", | ||||
|       data: JSON.stringify(json_data), | ||||
|       contentType: 'application/json', | ||||
|       dataType: "json", | ||||
|       success: function(data, status, xhr){ | ||||
|         window.location = forward; | ||||
|       }, | ||||
|       error: function(data, status ){ | ||||
|         displayFormErrors(data.responseJSON); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
|   $(".work_entry-delete-btn").click(function(){ | ||||
|     row = $(this).closest("tr"); | ||||
|     entry_id = row.data("id"); | ||||
|     start_date = row.data("start_date"); | ||||
|     duration = row.data("duration"); | ||||
|     description = row.data("description"); | ||||
|     $("#work_entry_start_date").html(start_date); | ||||
|     $("#work_entry_duration").html(duration); | ||||
|     $("#work_entry_description").html(description); | ||||
|     $("#confirmation_delete").data("entry_id", entry_id); | ||||
|   }); | ||||
| 
 | ||||
|   $("#confirmation_delete").click(function(){ | ||||
|     entry_id = $(this).data("entry_id"); | ||||
|     url = $("#confirmation_delete").data("url") + entry_id; | ||||
|     $.ajax({ | ||||
|       url: url, | ||||
|       type: "delete", | ||||
|       contentType: 'application/json', | ||||
|       success: function(data, status, xhr){ | ||||
|         location.reload(); | ||||
|       }, | ||||
|       error: function(data, status ){ | ||||
|         displayFormErrors(data.responseJSON); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										8
									
								
								app/assets/javascripts/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/assets/javascripts/utils.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| function displayFormErrors(data){ | ||||
|   if( data.errors != undefined ){ | ||||
|     $.each( data.errors, function( field, errorMsg) { | ||||
|       $("#"+field).parents(".control-group").addClass("error"); | ||||
|       $("#"+field).siblings(".help-block").html(errorMsg); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @ -9,5 +9,7 @@ | ||||
|  * compiled file, but it's generally better to create a new file per style scope. | ||||
|  * | ||||
|  *= require_self | ||||
|  *= require_tree . | ||||
|  *= require datepicker | ||||
|  *= require bootstrap-timepicker | ||||
|  *= require bootstrap_and_overrides | ||||
| */ | ||||
|  | ||||
							
								
								
									
										146
									
								
								app/assets/stylesheets/bootstrap-timepicker.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								app/assets/stylesheets/bootstrap-timepicker.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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%; | ||||
|   } | ||||
| } | ||||
| @ -1,10 +1,26 @@ | ||||
| @import "twitter/bootstrap/bootstrap"; | ||||
| body {  | ||||
| 	padding-top: 60px; | ||||
| 	padding-left: 20px; | ||||
| } | ||||
| 
 | ||||
| @import "twitter/bootstrap/responsive"; | ||||
| 
 | ||||
| // Set the correct sprite paths | ||||
| @iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png'); | ||||
| @iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png'); | ||||
| 
 | ||||
| // Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines) | ||||
| // Note: If you use asset_path() here, your compiled boostrap_and_overrides.css will not  | ||||
| //       have the proper paths. So for now we use the absolute path. | ||||
| @fontAwesomeEotPath: '/assets/fontawesome-webfont.eot'; | ||||
| @fontAwesomeWoffPath: '/assets/fontawesome-webfont.woff'; | ||||
| @fontAwesomeTtfPath: '/assets/fontawesome-webfont.ttf'; | ||||
| @fontAwesomeSvgPath: '/assets/fontawesome-webfont.svg'; | ||||
| 
 | ||||
| // Font Awesome | ||||
| @import "fontawesome"; | ||||
| 
 | ||||
| // Your custom LESS stylesheets goes here | ||||
| // | ||||
| // Since bootstrap was imported above you have access to its mixins which | ||||
| @ -15,3 +31,17 @@ | ||||
| // | ||||
| // Example: | ||||
| // @linkColor: #ff0000; | ||||
| 
 | ||||
| [data-toggle="buttons-radio"] > .btn > input[type="radio"], | ||||
| [data-toggle="buttons"] > .btn > input[type="checkbox"] { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .inline-block { | ||||
|   display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .control-group.error .btn-group > .btn { | ||||
|   color: #b94a48; | ||||
|   border-color: #b94a48; | ||||
| } | ||||
|  | ||||
							
								
								
									
										182
									
								
								app/assets/stylesheets/datepicker.css
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										182
									
								
								app/assets/stylesheets/datepicker.css
									
									
									
									
									
										Executable file
									
								
							| @ -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; | ||||
| } | ||||
| @ -2,8 +2,6 @@ class Tasks < Netzke::Basepack::Grid | ||||
|   def configure(c) | ||||
|     super | ||||
| 
 | ||||
|     #disable by default, will be enabled once bike is clicked | ||||
|     c.disabled = true | ||||
|     task_list_id = nil | ||||
|     if session[:selected_bike_id] | ||||
|       task_list_id = Bike.find_by_id(session[:selected_bike_id]).task_list.id | ||||
| @ -31,13 +29,14 @@ class Tasks < Netzke::Basepack::Grid | ||||
|   end | ||||
| 
 | ||||
|   def default_fields_for_forms | ||||
|     fields = [] | ||||
|     fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"} | ||||
|     fields.concat( [ | ||||
|     bike = Bike.find_by_id(session[:selected_bike_id]) | ||||
|     bike = "Select a Bike First!" if bike.nil? | ||||
|     [ | ||||
|         { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Bike Selected", :value => "#{bike.to_s}"}, | ||||
|         :done, | ||||
|         :task, | ||||
|         :notes | ||||
|       ]) | ||||
|         :notes, | ||||
|     ] | ||||
|   end | ||||
| 
 | ||||
|   #override with nil to remove actions | ||||
|  | ||||
| @ -33,7 +33,7 @@ | ||||
|               Ext.Msg.alert("Success", "New Password: "+data.password); | ||||
|             }, | ||||
|             error: function(data,textStatus) { | ||||
|               Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]); | ||||
|               Ext.Msg.alert( "Error", JSON.parse(data.responseText)["errors"][0]); | ||||
|             } | ||||
|           }); | ||||
|         } | ||||
|  | ||||
| @ -6,12 +6,12 @@ class Api::V1::BaseController < ActionController::Base | ||||
|   private | ||||
|     def authenticate_user | ||||
|       if params[:username] | ||||
|         user = User.find_for_database_authentication( :email => params[:username] ) | ||||
|         user = User.find_for_database_authentication( :username => params[:username] ) | ||||
|         @current_user = user if user && user.valid_password?( params[:password] ) | ||||
| 
 | ||||
|         if @current_user.nil? | ||||
|           msg = "Username/Password/Token invalid" | ||||
|           render :json => {:error => msg }, :status => 403 and return | ||||
|           render :json => {:error => msg }, :status => 401 and return | ||||
|         end | ||||
|       else | ||||
|         authenticate_user! | ||||
|  | ||||
							
								
								
									
										32
									
								
								app/controllers/api/v1/bikes_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/controllers/api/v1/bikes_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| class Api::V1::BikesController < Api::V1::BaseController | ||||
|   CANNOT_MANAGE = "You do not have permission to manage bikes." | ||||
|   EXPECTED_BIKE = "Expected bike in submitted data" | ||||
|   NOT_FOUND = "The bike could not be found." | ||||
| 
 | ||||
|   before_filter :check_bike_permission, except: :show | ||||
| 
 | ||||
|   def create | ||||
|     if params[:bikes] && bike = params[:bikes].first | ||||
|       @bike = Bike.new(bike) | ||||
|       if !@bike.save | ||||
|         render json: { errors: @bike.errors }, status: 422 and return | ||||
|       end | ||||
|     else | ||||
|         render json: { errors: [EXPECTED_BIKE]}, status: 422 and return | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def show | ||||
|     @bike = Bike.find_by_id(params[:id]) | ||||
|     if @bike.nil? | ||||
|       render json: { errors: [NOT_FOUND] }, status: 404 and return | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|     def check_bike_permission | ||||
|       if cannot? :manage, Bike | ||||
|         render json: { errors: [CANNOT_MANAGE]}, status: 403 and return | ||||
|       end | ||||
|     end | ||||
| end | ||||
							
								
								
									
										24
									
								
								app/controllers/api/v1/task_lists_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/controllers/api/v1/task_lists_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| class Api::V1::TaskListsController < Api::V1::BaseController | ||||
|   CANNOT_MANAGE = "You do not have permission to manage this task list." | ||||
|   NOT_FOUND = "The task list could not be found." | ||||
| 
 | ||||
|   before_filter :get_task_list | ||||
|   before_filter :check_task_list_permission, except: :show | ||||
| 
 | ||||
|   def show | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|     def get_task_list | ||||
|       @task_list = TaskList.find_by_id(params[:id]) | ||||
|       if @task_list.nil? | ||||
|         render json: { errors: [NOT_FOUND] }, status: 404 and return | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def check_task_list_permission | ||||
|       if cannot? :manage, Bike and @task_list.item != current_user.bike | ||||
|         render json: { errors: [CANNOT_MANAGE]}, status: 403 and return | ||||
|       end | ||||
|     end | ||||
| end | ||||
							
								
								
									
										64
									
								
								app/controllers/api/v1/tasks_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								app/controllers/api/v1/tasks_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| class Api::V1::TasksController < Api::V1::BaseController | ||||
|   EXPECTED_TASKS = "Expected a list of tasks in submitted data." | ||||
|   CANNOT_MANAGE = "You do not have permission to manage this task." | ||||
|   NOT_FOUND = "The task could not be found." | ||||
| 
 | ||||
|   before_filter :validate_params | ||||
|   before_filter :get_tasks | ||||
|   before_filter :check_task_permission, except: :show | ||||
| 
 | ||||
|   def update | ||||
|     errors = [] | ||||
|     @tasks.each do |task_hash| | ||||
|       task = task_hash[:record] | ||||
|       attrs = task_hash[:new_attributes] | ||||
|       task.update_attributes(attrs) | ||||
|       if !task.errors.empty? | ||||
|         errors << { id: task.id, errors: task.errors } | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     if !errors.empty? | ||||
|       render json: { errors: errors }, status: 422 and return | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|     def validate_params | ||||
|       if params[:tasks].nil? and not params[:tasks].kind_of?(Array) | ||||
|         render json: { errors: [EXPECTED_TASKS]}, status: 422 and return | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def get_tasks | ||||
|       @tasks = [] | ||||
|       errors = [] | ||||
| 
 | ||||
|       params[:tasks].each do |task| | ||||
|         t = Task.find_by_id(task[:id]) | ||||
|         if t.nil? | ||||
|           errors << { id: task[:id], error: NOT_FOUND } | ||||
|         else | ||||
|           @tasks << { record: t, new_attributes: task } | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       if !errors.empty? | ||||
|           render json: { errors: errors }, status: 404 and return | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def check_task_permission | ||||
|       errors = [] | ||||
|       @tasks.each do |task_hash| | ||||
|         task = task_hash[:record] | ||||
|         if task.task_list.item != current_user.bike | ||||
|           errors << { id: task[:id], error: CANNOT_MANAGE } | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       if cannot? :manage, Bike and !errors.empty? | ||||
|         render json: { errors: errors}, status: 403 and return | ||||
|       end | ||||
|     end | ||||
| end | ||||
							
								
								
									
										33
									
								
								app/controllers/api/v1/time_entries_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/controllers/api/v1/time_entries_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| class Api::V1::TimeEntriesController < Api::V1::BaseController | ||||
|   EXPECTED_TIME_ENTRY = "Expected time entry in submitted data" | ||||
|   NOT_FOUND = "Time entry not found" | ||||
| 
 | ||||
|   def create | ||||
|     if params[:time_entries] && time_entry = params[:time_entries].first | ||||
|       time_entry.merge!({ loggable_id: current_user.id, | ||||
|                           logger_id: current_user.id  }) | ||||
|       bike_id = time_entry[:bike_id].to_i | ||||
| 
 | ||||
|       @time_entry = TimeEntry.new(time_entry.except(:bike_id)) | ||||
| 
 | ||||
|       if bike_id > 0 | ||||
|         @time_entry.copy_to_bike_history(bike_id) | ||||
|       end | ||||
| 
 | ||||
|       if !@time_entry.save | ||||
|         render json: { errors: @time_entry.errors }, status: 422 and return | ||||
|       end | ||||
|     else | ||||
|       render json: { errors: [EXPECTED_TIME_ENTRY]}, status: 422 and return | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def delete | ||||
|     if time_entry = TimeEntry.find_by_id(params[:id]) | ||||
|       time_entry.delete | ||||
|       render nothing: true, status: 204 and return | ||||
|     else | ||||
|       render json: { errors: [NOT_FOUND]}, status: 404 and return | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -1,21 +1,27 @@ | ||||
| require 'securerandom' | ||||
| class Api::V1::UsersController < Api::V1::BaseController | ||||
|   CANNOT_MANAGE = "You do not have the permission to manager users" | ||||
|   NOT_FOUND = "User not found" | ||||
|   NOT_ALLOWED = "Not allowed to reset your own password in this fashion" | ||||
|   PASS_LENGTH = 8 | ||||
| 
 | ||||
|   def password_reset | ||||
|     if can? :manage, User | ||||
|       user = User.find_by_id(params[:user_id]) | ||||
|       render :json => { "error" => "User not found"}, :status => 404 and return if user.nil? | ||||
|       render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_user.id | ||||
|       render :json => { "errors" => [NOT_FOUND]}, :status => 404 and return if user.nil? | ||||
|       render :json => { "errors" => [NOT_ALLOWED]}, :status => 403 and return if user.id == current_user.id | ||||
| 
 | ||||
|       new_pass = SecureRandom.hex[0,8] | ||||
|       new_pass = SecureRandom.hex[0,PASS_LENGTH] | ||||
|       user.password = new_pass | ||||
|       user.save | ||||
|       render :json => { "password" => new_pass}, :status => 200 and return | ||||
|     else | ||||
|       render :json => { "error" => "You do not have the permission"}, :status => 403 and return | ||||
|       render :json => { "errors" => [CANNOT_MANAGE]}, :status => 403 and return | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
| 
 | ||||
| =begin  Is this here by accident? Commenting out for now (1/30/14) | ||||
|   def checkout | ||||
|     #must use @current_user since user may not have signed in | ||||
|     if !@current_user.checked_in? | ||||
| @ -25,4 +31,5 @@ class Api::V1::UsersController < Api::V1::BaseController | ||||
|       render :nothing => true, :status => 204 and return | ||||
|     end | ||||
|   end | ||||
| =end | ||||
| end | ||||
|  | ||||
							
								
								
									
										16
									
								
								app/controllers/bikes_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/controllers/bikes_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| class BikesController < AuthenticatedController | ||||
| 
 | ||||
|   def new | ||||
|     @brands = BikeBrand.all.map{ |b| [b.brand, b.id] } | ||||
|     @brands.unshift( ["Select a brand", -1] ) | ||||
|     @wheel_sizes = BikeWheelSize.all.map{ |w| [w.display_string, w.id] } | ||||
|     @wheel_sizes.unshift( ["Select a wheel size", -1] ) | ||||
|   end | ||||
| 
 | ||||
|   def show | ||||
|     @bike = Bike.find_by_id(params[:id]) | ||||
|     @task_list_id = @bike.task_list.id | ||||
|     @show_add_bike = true if params[:add_bike] | ||||
|   end | ||||
| 
 | ||||
| end | ||||
							
								
								
									
										7
									
								
								app/controllers/panel_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/controllers/panel_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| class PanelController < ApplicationController | ||||
| 
 | ||||
|   def index | ||||
|     render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke" | ||||
|   end | ||||
| 
 | ||||
| end | ||||
| @ -1,7 +1,6 @@ | ||||
| class SiteController < ApplicationController | ||||
| 
 | ||||
|   def index | ||||
|     render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application" | ||||
|     @bike = current_user.bike | ||||
|   end | ||||
| 
 | ||||
| end | ||||
|  | ||||
							
								
								
									
										5
									
								
								app/controllers/task_lists_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/controllers/task_lists_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| class TaskListsController < AuthenticatedController | ||||
|   def edit | ||||
|     @task_list = TaskList.find_by_id(params[:id]) | ||||
|   end | ||||
| end | ||||
							
								
								
									
										16
									
								
								app/controllers/time_entries_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/controllers/time_entries_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| class TimeEntriesController < AuthenticatedController | ||||
| 
 | ||||
|   def new | ||||
|     @bikes = Bike.all.map{ |b| [b.to_s , b.id] } | ||||
|     if bike = current_user.bike | ||||
|       @bikes.unshift( [bike.to_s, bike.id] ) | ||||
|     end | ||||
|     @bikes.unshift( ["Non-bike work", -1] ) | ||||
|   end | ||||
| 
 | ||||
|   def index | ||||
|     @user_time_entries = TimeEntry.where(loggable_id: current_user.id) | ||||
|     @credits_available = current_user.total_credits | ||||
|     @hours_worked = current_user.total_hours | ||||
|   end | ||||
| end | ||||
| @ -16,13 +16,13 @@ class Bike < ActiveRecord::Base | ||||
|   validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true } | ||||
|   validates :serial_number, :length => { :minimum => 3 } | ||||
|   validates :model, :length => { :maximum => 50 } | ||||
|   validates :bike_brand_id, :presence => true | ||||
|   validates :color, :presence => true | ||||
|   validates :bike_style_id, :presence => true | ||||
|   validates :seat_tube_height, :presence => true | ||||
|   validates :bike_wheel_size_id, :presence => true | ||||
|   validates :bike_condition_id, :presence => true | ||||
|   validates :bike_purpose_id, :presence => true | ||||
|   validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" } | ||||
|   #validates :color, :presence => true | ||||
|   validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" } | ||||
|   validates :seat_tube_height, :presence => true, :numericality => true | ||||
|   validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" } | ||||
|   validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" } | ||||
|   validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" } | ||||
| 
 | ||||
|   self.per_page = 15 | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| class Task < ActiveRecord::Base | ||||
|   attr_accessible :task, :notes, :done | ||||
|   attr_accessible :task, :notes, :done, :task_list_id | ||||
| 
 | ||||
|   belongs_to :task_list | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ class TaskList < ActiveRecord::Base | ||||
|   attr_accessible :item_id, :item_type, :name | ||||
| 
 | ||||
|   belongs_to :item, :polymorphic => true | ||||
|   has_many :tasks | ||||
|   has_many :tasks, order: "id ASC" | ||||
| 
 | ||||
|   after_save :create_default_bike_tasks | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										27
									
								
								app/models/time_entry.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/models/time_entry.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| class TimeEntry < ActsAsLoggable::Log | ||||
|   default_scope where( loggable_type: "User", | ||||
|                        logger_type: "User", | ||||
|                        log_action_type: "ActsAsLoggable::UserAction").where("log_action_id != 4").order("start_date DESC") | ||||
| 
 | ||||
|   def copy_to_bike_history(bike_id) | ||||
|     self.assign_attributes({ | ||||
|           copy_log: true, | ||||
|           copy_type: 'Bike', | ||||
|           copy_id: bike_id, | ||||
|           copy_action_type: 'ActsAsLoggable::BikeAction', | ||||
|           copy_action_id: 4 | ||||
|         }) | ||||
|   end | ||||
| 
 | ||||
|   def duration | ||||
|     end_date - start_date | ||||
|   end | ||||
| 
 | ||||
|   def duration_in_hours | ||||
|     (duration / 1.hour).round(2) | ||||
|   end | ||||
| 
 | ||||
|   def type | ||||
|     log_action.try(:action) | ||||
|   end | ||||
| end | ||||
| @ -50,9 +50,9 @@ class User < ActiveRecord::Base | ||||
|     #default BUILDBIKE/CLASS ID is 5 | ||||
|     purpose_id = 5 | ||||
|     Bike.find_by_sql(" | ||||
|       SELECT *  | ||||
|       SELECT * | ||||
|       FROM bikes | ||||
|       INNER JOIN(  | ||||
|       INNER JOIN( | ||||
|         SELECT * | ||||
|         FROM transactions | ||||
|         WHERE customer_id = #{self.id} | ||||
| @ -65,15 +65,16 @@ class User < ActiveRecord::Base | ||||
|   end | ||||
| 
 | ||||
|   def total_credits_spent | ||||
|     log_action = ::ActsAsLoggable::TransactionAction.find_by_action("TIME") | ||||
|     log_action_id = 1 #TIME | ||||
|     transaction_logs. | ||||
|       where(  "log_action_id = ? AND log_action_type = ?", | ||||
|               log_action.id, log_action.class.to_s). | ||||
|               log_action_id, ::ActsAsLoggable::TransactionAction.to_s). | ||||
|       sum{ |r| r.description.to_i }.round(2) | ||||
|   end | ||||
| 
 | ||||
|   def total_earned_credits | ||||
|     log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN") | ||||
|     volunteer_id = 1 | ||||
|     staff_id = 3 | ||||
| 
 | ||||
|     # Find the first credit conversion which has a created_at date before the | ||||
|     # log's created_at date and join it to the log's row so we can calculate | ||||
| @ -82,7 +83,7 @@ class User < ActiveRecord::Base | ||||
|     # | ||||
|     # The DISTINCT ON, and ORDER BY are important to getting the | ||||
|     # single conversion rate that applies to the respective log. | ||||
|     ::ActsAsLoggable::Log.find_by_sql(" | ||||
|     ::ActsAsLoggable::Log.find_by_sql([" | ||||
|       SELECT DISTINCT ON (logs.created_at) start_date, end_date, | ||||
|         conversion.conversion, conversion.created_at | ||||
|       FROM logs | ||||
| @ -90,23 +91,26 @@ class User < ActiveRecord::Base | ||||
|         SELECT conversion, created_at | ||||
|         FROM credit_conversions | ||||
|       ) AS conversion ON logs.created_at > conversion.created_at | ||||
|       WHERE logs.loggable_id = #{self.id} | ||||
|       WHERE logs.loggable_id = :id | ||||
|       AND logs.loggable_type = 'User' | ||||
|       AND (log_action_id != #{log_action.id} AND log_action_type = '#{log_action.class.to_s}') | ||||
|       ORDER BY logs.created_at, conversion.created_at DESC"). | ||||
|       AND (log_action_id IN (:credit_actions) AND log_action_type = :log_action_type) | ||||
|       ORDER BY logs.created_at, conversion.created_at DESC", | ||||
|         {id: self.id, | ||||
|          credit_actions: [volunteer_id, staff_id], | ||||
|          log_action_type: ::ActsAsLoggable::UserAction.to_s}]). | ||||
|         sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2) | ||||
|   end | ||||
| 
 | ||||
|   def total_hours | ||||
|     log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN") | ||||
|     logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2) | ||||
|     log_action_id = 4 #CHECKIN | ||||
|     logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2) | ||||
|   end | ||||
| 
 | ||||
|   def current_month_hours | ||||
|     log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN") | ||||
|     log_action_id = 4 #CHECKIN | ||||
|     #TODO need to prevent users from saving logs across months, force to create a new log if crossing month | ||||
|     current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month) | ||||
|     logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s) | ||||
|     logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s) | ||||
|       .where( :start_date => current_month_range) | ||||
|       .where( :end_date => current_month_range) | ||||
|       .sum { |l| (l.end_date - l.start_date)/3600 } | ||||
| @ -114,28 +118,31 @@ class User < ActiveRecord::Base | ||||
|   end | ||||
| 
 | ||||
|   def checked_in? | ||||
|     log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN") | ||||
|     checked = logs.where( log_action_id: log_action.id). | ||||
|     #default CHECKIN log action is id, yea yea should be a constant | ||||
|     log_action_id = 4 | ||||
|     checked = logs.where( log_action_id: log_action_id). | ||||
|       where("start_date >= ?", Time.zone.now.beginning_of_day). | ||||
|       where("start_date = end_date") | ||||
|     !checked.empty? | ||||
|   end | ||||
| 
 | ||||
|   def checkin | ||||
|     log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN") | ||||
|     #default CHECKIN log action is id, yea yea should be a constant | ||||
|     log_action_id = 4 | ||||
|     time = Time.now | ||||
|     logs.create( logger_id: self.id, | ||||
|                  logger_type: self.class.to_s, | ||||
|                  start_date: time, | ||||
|                  end_date: time, | ||||
|                  log_action_id: log_action.id, | ||||
|                  log_action_type: log_action.class.to_s) | ||||
|                  log_action_id: log_action_id, | ||||
|                  log_action_type: ::ActsAsLoggable::UserAction.to_s) | ||||
|     save | ||||
|   end | ||||
| 
 | ||||
|   def checkout | ||||
|     log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN") | ||||
|     checked = logs.where( log_action_id: log_action.id). | ||||
|     #default CHECKIN log action is id, yea yea should be a constant | ||||
|     log_action_id = 4 | ||||
|     checked = logs.where( log_action_id: log_action_id). | ||||
|       where("start_date >= ?", Time.zone.now.beginning_of_day). | ||||
|       where("start_date = end_date").first | ||||
|     checked.end_date = Time.now | ||||
|  | ||||
							
								
								
									
										4
									
								
								app/views/api/v1/bikes/create.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/views/api/v1/bikes/create.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| json.bikes [@bike] do |bike| | ||||
|   json.array! bike | ||||
|   json.set! :href, api_bike_path(bike) | ||||
| end | ||||
							
								
								
									
										3
									
								
								app/views/api/v1/bikes/show.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/views/api/v1/bikes/show.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| json.bikes [@bike] do |bike| | ||||
|   json.array! bike | ||||
| end | ||||
							
								
								
									
										15
									
								
								app/views/api/v1/task_lists/show.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/views/api/v1/task_lists/show.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| json.task_lists [@task_list] do |tl| | ||||
|   json.array! tl | ||||
|   json.links do | ||||
|     json.bike do | ||||
|       json.href api_bike_path(tl.item) | ||||
|       json.id tl.item_id | ||||
|     end | ||||
|     json.tasks tl.tasks do |task| | ||||
|       json.id task.id | ||||
|       json.done task.done | ||||
|       json.notes task.notes | ||||
|       json.task task.task | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										3
									
								
								app/views/api/v1/tasks/update.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/views/api/v1/tasks/update.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| json.tasks [@tasks.map{|x| x[:record]}] do |task| | ||||
|   json.array! task | ||||
| end | ||||
							
								
								
									
										4
									
								
								app/views/api/v1/time_entries/create.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/views/api/v1/time_entries/create.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| json.time_entries [@time_entry] do |time_entry| | ||||
|   json.array! time_entry | ||||
|   #json.set! :href, api_time_entry_path(time_entry) | ||||
| end | ||||
							
								
								
									
										62
									
								
								app/views/bikes/new.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								app/views/bikes/new.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| %a{ class: "btn btn-default btn-lg", href: root_path} | ||||
|   %span{ class:"icon-home"} | ||||
| %h2 Add Bike | ||||
| 
 | ||||
| %p | ||||
|   .control-group | ||||
|     .controls | ||||
|       %input{id: "shop_id", placeholder: "Shop ID", type: "number", min:0, class: "input-lg" } | ||||
|       .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       = select_tag(:bike_brand_id, options_for_select(@brands)) | ||||
|       .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       %input{id: "model", placeholder: "Model", type: "text", class: "input-lg" } | ||||
|       .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       %input{id: "serial_number", placeholder: "Serial Number", type: "text", class: "input-lg" } | ||||
|       .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       .btn-group{ "data-toggle" => "buttons-radio"} | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_style", value: 3} RD | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_style", value: 1} MTN | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_style", value: 2} HYB | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_style", value: 4} OTHER | ||||
|     %input{ id: "bike_style_id", type: "hidden"} | ||||
|     .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       = select_tag(:bike_wheel_size_id, options_for_select(@wheel_sizes), id: :bike_wheel_size_id) | ||||
|       .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       .btn-group{ "data-toggle" => "buttons-radio"} | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_condition", value: 2} Poor | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_condition", value: 3} Fair | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_condition", value: 4} Good | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "bike_condition", value: 5} Excellent | ||||
|     %input{ id: "bike_condition_id", type: "hidden"} | ||||
|     .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       %input{id: "seat_tube_height", placeholder: "Seat Tube (cm)", type: "number", min: 0, max: 100, class: "input-lg" } | ||||
|       .help-block | ||||
|   -# Commenting this out until description is added to Bike | ||||
|     %p | ||||
|       %input{id: "bike_description", placeholder: "Short description", type: "text", class: "input-lg" } | ||||
|   .control-group | ||||
|     .controls | ||||
|       %input{id: "add_bike_submit", value: "Add Bike", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_bike_path}"} | ||||
| 
 | ||||
							
								
								
									
										27
									
								
								app/views/bikes/show.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/views/bikes/show.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| - if @show_add_bike | ||||
|   %p | ||||
|     %a{class: "btn btn-lg btn-block btn-info", href: new_bike_path} Add Another Bike? | ||||
| 
 | ||||
| %a{ class: "btn btn-default btn-lg", href: root_path} | ||||
|   %span{ class:"icon-home"} | ||||
| %h2 #{@bike.shop_id}: #{@bike.bike_brand} | ||||
| %h2 #{@bike.model} | ||||
| 
 | ||||
| %dl.dl-horizontal | ||||
|   %dt Type | ||||
|   %dd #{@bike.bike_style} | ||||
|   %dt Wheel Size | ||||
|   %dd #{@bike.bike_wheel_size.display_string} | ||||
|   %dt Condition | ||||
|   %dd #{@bike.bike_condition} | ||||
|   %dt Seat Tube (cm) | ||||
|   %dd #{@bike.seat_tube_height} | ||||
|   %dt Purpose | ||||
|   %dd #{@bike.bike_purpose} | ||||
|   - if !@bike.value.nil? | ||||
|     %dt Value | ||||
|     %dd #{@bike.value} | ||||
|   - if !@bike.color.nil? | ||||
|     %dt Color | ||||
|     %dd{ style: "width: 50px; background-color: ##{@bike.color}; border: black; border-width: 1px; border-style: solid;"} #{@bike.color} | ||||
| %a{class: "btn btn-lg btn-block btn-primary", href: edit_task_list_path(@task_list_id) } View Checklist | ||||
| @ -1,21 +1,28 @@ | ||||
| <%- if controller_name != 'sessions' %> | ||||
|   <%= link_to "Sign in", new_session_path(resource_name) %><br /> | ||||
|   <p> | ||||
|   <%= link_to "Sign in", new_session_path(resource_name), class: "btn btn-block btn-default" %><br /> | ||||
|   </p> | ||||
| <% end -%> | ||||
| 
 | ||||
| <%- if devise_mapping.registerable? && controller_name != 'registrations' %> | ||||
|   <%= link_to "Sign up", new_registration_path(resource_name) %><br /> | ||||
|   <p> | ||||
|   <%= link_to "Sign up", new_registration_path(resource_name), class: "btn btn-block btn-default" %><br /> | ||||
|   </p> | ||||
| <% end -%> | ||||
| 
 | ||||
| <%- if devise_mapping.recoverable? && controller_name != 'passwords' %> | ||||
|   <%= link_to "Forgot your password?", new_password_path(resource_name) %><br /> | ||||
|   <p> | ||||
|   <%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn btn-block btn-default" %><br /> | ||||
|   </p> | ||||
| <% end -%> | ||||
| 
 | ||||
| <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> | ||||
|   <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br /> | ||||
|   <p> | ||||
|   <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "btn btn-block btn-default" %><br /> | ||||
|   </p> | ||||
| <% end -%> | ||||
| 
 | ||||
| <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> | ||||
|   <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br /> | ||||
| <% end -%> | ||||
| 
 | ||||
| <%- if devise_mapping.omniauthable? %> | ||||
|  | ||||
| @ -1,12 +1,16 @@ | ||||
| <h2>Forgot your password?</h2> | ||||
| <%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %> | ||||
| 
 | ||||
| <h2>Forgot your password?</h2> | ||||
| <br> | ||||
| <br> | ||||
| <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %> | ||||
|   <%= devise_error_messages! %> | ||||
| 
 | ||||
|   <div><%= f.label :email %><br /> | ||||
|   <%= f.email_field :email %></div> | ||||
|   <div class="form-group"> | ||||
|     <%= f.email_field :email, placeholder: "Email", class: "form-control input-lg" %> | ||||
|   </div> | ||||
| 
 | ||||
|   <div><%= f.submit "Send me reset password instructions" %></div> | ||||
|   <div><%= f.submit "Reset Password", class:"btn btn-lg btn-primary"%></div> | ||||
| <% end %> | ||||
| 
 | ||||
| <%= render "links" %> | ||||
|  | ||||
| @ -1,25 +1,14 @@ | ||||
| %fieldset | ||||
|   .control-group | ||||
|     = f.label :addrStreet1, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :addrStreet1, :class => "control-label" | ||||
|   .control-group | ||||
|     = f.label :addrStreet2, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :addrStreet2, :class => "control-label" | ||||
|   .control-group | ||||
|     = f.label :addrCity, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :addrCity, :class => "control-label" | ||||
|   .control-group | ||||
|     = f.label :addrState, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :addrState, :class => "control-label" | ||||
|   .control-group | ||||
|     = f.label :addrZip, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :addrZip, :class => "control-label" | ||||
|   .control-group | ||||
|     = f.label :phone, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :phone, :class => "control-label" | ||||
| %p | ||||
|   %fieldset | ||||
|     .form-group | ||||
|       = f.text_field :addrStreet1, placeholder: "Street Address Line 1", :class => "form-control input-lg" | ||||
|     .form-group | ||||
|       = f.text_field :addrStreet2, placeholder: "Street Address Line 2",:class => "form-control input-lg" | ||||
|     .form-group | ||||
|       = f.text_field :addrCity, placeholder: "City", :class => "form-control input-lg" | ||||
|     .form-group | ||||
|       = f.text_field :addrState, placeholder: "State Abbreviation", :class => "form-control input-lg" | ||||
|     .form-group | ||||
|       = f.text_field :addrZip, placeholder: "Zip Code", :class => "form-control input-lg" | ||||
|     .form-group | ||||
|       = f.text_field :phone, placeholder: "Phone", :class => "form-control input-lg" | ||||
|  | ||||
| @ -1,36 +1,23 @@ | ||||
| = stylesheet_link_tag "bootstrap_and_overrides", :media => "all" | ||||
| = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :class => "form-horizontal"}) do |f| | ||||
| = form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| | ||||
|   = devise_error_messages! | ||||
|   .controls | ||||
|     %h2 Sign up | ||||
|   .control-group | ||||
|     = f.label :username, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :username | ||||
|   .control-group | ||||
|     = f.label :first_name, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :first_name | ||||
|   .control-group | ||||
|     = f.label :last_name, :class => "control-label" | ||||
|     .controls | ||||
|       = f.text_field :last_name | ||||
|   .control-group | ||||
|     = f.label :email, :class => "control-label" | ||||
|     .controls | ||||
|       = f.email_field :email | ||||
|   .form-group | ||||
|     = f.text_field :username, placeholder: "Username", :class => "form-control input-lg" | ||||
|   .form-group | ||||
|     = f.text_field :first_name, placeholder: "First Name", :class => "form-control input-lg" | ||||
|   .form-group | ||||
|     = f.text_field :last_name, placeholder: "Last Name", :class => "form-control input-lg" | ||||
|   .form-group | ||||
|     = f.email_field :email, placeholder: "E-mail", :class => "form-control input-lg" | ||||
|   - profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles | ||||
|   = f.fields_for :user_profiles, profile_builder do |builder| | ||||
|     = render 'user_profile_fields', f: builder | ||||
|   .control-group | ||||
|     = f.label :password, :class => "control-label" | ||||
|     .controls | ||||
|       = f.password_field :password | ||||
|   .control-group | ||||
|     = f.label :password_confirmation, :class => "control-label" | ||||
|     .controls | ||||
|       = f.password_field :password_confirmation | ||||
|   .control-group | ||||
|     .controls | ||||
|       = f.submit "Sign up" | ||||
|   .form-group | ||||
|     = f.password_field :password, placeholder: "Password", :class => "form-control input-lg" | ||||
|   .form-group | ||||
|     = f.password_field :password_confirmation, placeholder: "Password Confirmation", :class => "form-control input-lg" | ||||
|   .form-group | ||||
|     = f.submit "Sign up", class:"btn btn-lg btn-primary" | ||||
| = render "links" | ||||
|  | ||||
| @ -5,34 +5,44 @@ | ||||
| <br> | ||||
| <br> | ||||
| <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> | ||||
|   <div><%= f.label :username%><br /> | ||||
|     <%= f.text_field :username%></div> | ||||
|   <div class="form-group"> | ||||
|     <%= f.text_field :username, placeholder: "Username", class: "form-control input-lg"%> | ||||
|   </div> | ||||
| 
 | ||||
|   <div><%= f.label :password %><br /> | ||||
|     <%= f.password_field :password %></div> | ||||
|   <div class="form-group"> | ||||
|     <%= f.password_field :password, placeholder: "Password", class: "form-control input-lg" %> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="form-group"> | ||||
|   <% if devise_mapping.rememberable? -%> | ||||
|     <div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div> | ||||
|       <label> | ||||
|         <%= f.check_box :remember_me %> Remember Me | ||||
|       </label> | ||||
|   <% end -%> | ||||
|   </div> | ||||
| 
 | ||||
|   <div><%= f.submit "Sign in" %></div> | ||||
| 
 | ||||
|   <div id="checkin_menu"style="display:none;"> | ||||
|     <input id="checkin" name="checkin" type="button" value="CHECK IN"> | ||||
|     <input id="checkout" name="checkout" type="button" value="CHECK OUT"> | ||||
|   <div class="form-group"> | ||||
|     <p> | ||||
|     <%= f.submit "Sign in", class:"btn btn-lg btn-primary" %> | ||||
|     </p> | ||||
|     <p> Or quickly... </p> | ||||
|     <div class="btn-group"> | ||||
|       <input id="checkin" name="checkin" type="button" value="CHECK IN" class="btn btn-lg btn-success"> | ||||
|       <input id="checkout" name="checkout" type="button" value="CHECK OUT" class="btn btn-lg btn-danger"> | ||||
|     </div> | ||||
|   </div> | ||||
| <% end %> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <%= render "links" %> | ||||
| 
 | ||||
| <% if Rails.env.development? %> | ||||
|   <p> | ||||
|   <% User.all.each do |user| %> | ||||
|     <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> | ||||
|       <%= f.hidden_field :username, :value => user.username%></div> | ||||
|       <%= f.hidden_field :password, :value => 'password' %></div> | ||||
|       <div><%= f.submit "Sign in as #{user.username}" %></div> | ||||
|       <%= f.hidden_field :username, :value => user.username%> | ||||
|       <%= f.hidden_field :password, :value => 'password' %> | ||||
|       <%= f.submit "Sign in as #{user.username}", class:"btn btn-info" %> | ||||
|     <% end %> | ||||
|   <% end %> | ||||
|   </p> | ||||
| <% end %> | ||||
|  | ||||
| @ -2,14 +2,15 @@ | ||||
| %html{:lang => "en"} | ||||
|   %head | ||||
|     %meta{:charset => "utf-8"}/ | ||||
|     %meta{ name: "viewport", content: "width=device-width", "initial-scale" => "1.0"} | ||||
|     %title= content_for?(:title) ? yield(:title) : "Velocipede" | ||||
|     = load_netzke | ||||
|     = csrf_meta_tags | ||||
|     = 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 | ||||
|       body { | ||||
|         padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */ | ||||
|         padding-top: 5px; | ||||
|       } | ||||
|       .x-boundlist-item { | ||||
|         white-space: nowrap; | ||||
| @ -24,11 +25,11 @@ | ||||
|         - if flash[:alert] | ||||
|           %p{:class => 'alert'}= flash[:alert] | ||||
|         .row | ||||
|           .span13 | ||||
|           .span12 | ||||
|             = yield | ||||
| 
 | ||||
|       %footer | ||||
|         %p © Velocipede 2013 | ||||
|         %p © BikeShed #{Time.now.year} | ||||
| 
 | ||||
|     = javascript_include_tag "application" | ||||
|     = javascript_include_tag params[:controller]  | ||||
|  | ||||
							
								
								
									
										11
									
								
								app/views/layouts/netzke.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/views/layouts/netzke.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| !!! 5 | ||||
| %html{:lang => "en"} | ||||
|   %head | ||||
|     %meta{:charset => "utf-8"}/ | ||||
|     %title= content_for?(:title) ? yield(:title) : "Velocipede" | ||||
|     = load_netzke | ||||
|     = csrf_meta_tags | ||||
|   %body | ||||
|     = yield | ||||
|     = javascript_include_tag "application" | ||||
|     = javascript_include_tag params[:controller]  | ||||
							
								
								
									
										21
									
								
								app/views/site/index.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/views/site/index.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| = stylesheet_link_tag "bootstrap_and_overrides", :media => "all" | ||||
| %h2 Dashboard | ||||
| 
 | ||||
| %p | ||||
|   %p | ||||
|     %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", href: time_entries_path} View Timesheet | ||||
|   %p | ||||
|     %a{class: "btn btn-lg btn-block btn-primary", href: new_bike_path} Add Bike | ||||
| 
 | ||||
| - if !@bike.nil? | ||||
|   %p | ||||
|     %a{class: "btn btn-lg btn-block btn-primary", href: bike_path(@bike)} View Your Bike | ||||
| 
 | ||||
| 
 | ||||
| %p | ||||
|   %p | ||||
|     %a{class: "btn btn-lg btn-block btn-primary hidden-xs", href: admin_index_path} Admin View | ||||
|   %p | ||||
|     %input{id: "index_logout", value: "Logout", type: "button", class: "btn btn-lg btn-block btn-danger", "data-url" => destroy_user_session_path } | ||||
							
								
								
									
										16
									
								
								app/views/task_lists/edit.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/views/task_lists/edit.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| %a{ class: "btn btn-default btn-lg", href: root_path} | ||||
|   %span{ class:"icon-home"} | ||||
| %h2 Task List | ||||
| %h3 | ||||
|   %a{ href: bike_path(@task_list.item)}#{@task_list.item.shop_id} #{@task_list.item.bike_brand} | ||||
| %h4 #{@task_list.item.model} | ||||
| 
 | ||||
| %p | ||||
|   .control-group | ||||
|     - @task_list.tasks.each do |task| | ||||
|       .controls | ||||
|         %input{class: "task_list_task", type: "checkbox", "data-id" => task.id, checked: task.done} | ||||
|         #{task.task} | ||||
|   .control-group | ||||
|     .controls | ||||
|       %input{id: "update_tasks_submit", value: "Save Changes", type: "button", class: "btn btn-lg btn-block btn-primary disabled", "data-url" => "#{api_update_task_path}"} | ||||
							
								
								
									
										44
									
								
								app/views/time_entries/index.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/views/time_entries/index.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| %a{ class: "btn btn-default btn-lg", href: root_path} | ||||
|   %span{ class:"icon-home"} | ||||
| %h2 Your Timesheet | ||||
| 
 | ||||
| %dl.dl-horizontal | ||||
|   %dd | ||||
|     %span.badge #{@hours_worked} | ||||
|     Total Hours Worked | ||||
|   %dd | ||||
|     %span.badge #{@credits_available} | ||||
|     Total Credits Available | ||||
| 
 | ||||
| %table.table | ||||
|   %tbody | ||||
|     - @user_time_entries.each do |entry| | ||||
|       %tr{ "data-id" => entry.id, "data-description" => entry.description, | ||||
|           "data-duration" => entry.duration_in_hours, | ||||
|           "data-start_date" => entry.start_date.to_date.to_formatted_s(:rfc822)} | ||||
|         %td #{entry.start_date.to_date.to_formatted_s(:rfc822)} | ||||
|         %td #{entry.duration_in_hours} | ||||
|         %td #{entry.type} | ||||
|         %td #{truncate(entry.description)} | ||||
|         %td | ||||
|           %button{ class: "btn icon-remove btn-danger work_entry-delete-btn", role: "button", "data-toggle" => "modal", "data-target" => "#confirmation" } | ||||
| 
 | ||||
| %a{class: "btn btn-lg btn-block btn-primary", href: new_time_entry_path } Add Time Entry | ||||
| 
 | ||||
| .modal{ id: "confirmation", class: "hide", role: "dialog", "aria-labelledby" => "confirmation_title", "aria-hidden" => "true", tabindex: -1 } | ||||
|   .modal-header | ||||
|     %button{ class: "close", "data-dismiss" => "modal", "aria-hidden" => "true"}x | ||||
|     %h4{ id: "confirmation_title" } Are you sure you want to delete? | ||||
|   .modal-body | ||||
|     %div{ id: "work_entry_start_date" } | ||||
|       Start Date | ||||
|     %div{ id: "work_entry_duration" } | ||||
|       Duration | ||||
|     %div{ id: "work_entry_description" } | ||||
|       Description | ||||
|   .modal-footer | ||||
|     %button{ class: "btn", "data-dismiss" => "modal", "aria-hidden" => "true"} Close | ||||
|     %button{ id: "confirmation_delete", class: "btn btn-primary", "data-url" => "api/v1/time_entries/" } Delete | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										42
									
								
								app/views/time_entries/new.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/views/time_entries/new.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| %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" } | ||||
|       .hidden{ id: "start_date" } | ||||
|       .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       %label End | ||||
|       %input{id: "end_time_id", placeholder: "Time ID", type: "text", class: "input-small" } | ||||
|       .hidden{ id: "end_date" } | ||||
|       .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       .btn-group{ "data-toggle" => "buttons-radio"} | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "action_id", value: 1} Volunteer | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "action_id", value: 2} Personal | ||||
|         %label{ class: "btn btn-default"} | ||||
|           %input{ type: "radio", name: "action_id", value: 3} Staff | ||||
|     .hidden{ id: "log_action_id" } | ||||
|     .help-block | ||||
|   .control-group | ||||
|     .controls | ||||
|       %label | ||||
|         = select_tag(:bike_id, options_for_select(@bikes)) | ||||
|   .control-group | ||||
|     .controls | ||||
|       %textarea{id: "description_id", placeholder: "Work description", class: "input-lg" } | ||||
|   .control-group | ||||
|     .controls | ||||
|       %input{id: "add_time_entry_submit", value: "Add Time Entry", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_time_entry_path}", "data-forward" => "#{"."}"} | ||||
| @ -10,7 +10,7 @@ development: | ||||
| # Do not set this db to the same as development or production. | ||||
| test: | ||||
|   adapter: postgresql | ||||
|   database: velocipede | ||||
|   database: velocipede_test | ||||
|   username: velocipede | ||||
|   password: | ||||
|   host: 127.0.0.1 | ||||
|  | ||||
| @ -222,11 +222,12 @@ Devise.setup do |config| | ||||
|   # end | ||||
| end | ||||
| 
 | ||||
| #Check in the user if they sign in. (Devise uses Warden) | ||||
| Warden::Manager.after_set_user do |user,auth,opts| | ||||
|   # this essentially gets called after every netzke request, but alas, | ||||
|   # only using the after_authenticaion callback doesn't get fired after | ||||
|   # user creation. | ||||
|   user.checkin unless user.checked_in? | ||||
| unless Rails.env.test? | ||||
|   #Check in the user if they sign in. (Devise uses Warden) | ||||
|   Warden::Manager.after_set_user do |user,auth,opts| | ||||
|     # this essentially gets called after every netzke request, but alas, | ||||
|     # only using the after_authenticaion callback doesn't get fired after | ||||
|     # user creation. | ||||
|     user.checkin unless user.checked_in? | ||||
|   end | ||||
| end | ||||
| 
 | ||||
|  | ||||
| @ -4,13 +4,34 @@ Velocipede::Application.routes.draw do | ||||
|   netzke | ||||
| 
 | ||||
|   root :to => 'site#index' | ||||
|   get 'admin/index', to: 'panel#index', as: "admin_index" | ||||
| 
 | ||||
|   get 'bikes/new', to: 'bikes#new', as: "new_bike" | ||||
|   get 'bikes/:id', to: 'bikes#show', as: "bike" | ||||
| 
 | ||||
|   get  'task_lists/:id/edit' => "task_lists#edit", as: "edit_task_list" | ||||
| 
 | ||||
|   get  'time_entries'     => "time_entries#index", as: "time_entries" | ||||
|   get  'time_entries/new' => "time_entries#new",   as: "new_time_entry" | ||||
| 
 | ||||
|   ########################### | ||||
|   # API Routes | ||||
|   scope 'api', :module => :api do | ||||
|   scope 'api', :module => :api, defaults: {format: :json} do | ||||
|     scope 'v1', :module => :v1 do | ||||
|       post 'checkin' => "logs#checkin", :as => "api_checkin" | ||||
|       post 'checkin'  => "logs#checkin",  :as => "api_checkin" | ||||
|       post 'checkout' => "logs#checkout", :as => "api_checkout" | ||||
|       post 'reset' => "users#password_reset", :as => "api_password_reset" | ||||
| 
 | ||||
|       post 'reset'    => "users#password_reset", :as => "api_password_reset" | ||||
| 
 | ||||
|       get  'bikes/:id'    => "bikes#show",   as: "api_bike" | ||||
|       post 'bikes/create' => "bikes#create", as: "api_create_bike" | ||||
| 
 | ||||
|       get  'task_lists/:id' => "task_lists#show",   as: "api_task_list" | ||||
| 
 | ||||
|       put  'tasks/update' => "tasks#update", as: "api_update_task" | ||||
| 
 | ||||
|       post 'time_entries/create' => "time_entries#create", as: "api_create_time_entry" | ||||
|       delete 'time_entries/:id' => "time_entries#delete", as: "api_delete_time_entry" | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  | ||||
| @ -7,3 +7,6 @@ hybrid: | ||||
| road: | ||||
|   id: 3 | ||||
|   style: ROAD | ||||
| other: | ||||
|   id: 4 | ||||
|   style: OTHER | ||||
|  | ||||
| @ -41,7 +41,7 @@ if Rails.env.development? | ||||
|   #create fake bikes | ||||
|   if Bike.all.empty? | ||||
|     42.times do |n| | ||||
|       FactoryGirl.create(:bike) | ||||
|       FactoryGirl.create(:seed_bike) | ||||
|     end | ||||
|   end | ||||
| elsif Rails.env.production? | ||||
|  | ||||
							
								
								
									
										134
									
								
								spec/controllers/api/bikes_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								spec/controllers/api/bikes_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Api::V1::BikesController do | ||||
|   render_views | ||||
| 
 | ||||
|   describe "#create" do | ||||
|     context "as a user" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:user) | ||||
|         sign_in @user | ||||
|       end | ||||
|       it "returns 403" do | ||||
|         post :create | ||||
|         expect(@response.code.to_i).to eql 403 | ||||
|       end | ||||
| 
 | ||||
|       it "returns an error message" do | ||||
|         post :create | ||||
|         json = JSON.parse(@response.body) | ||||
|         expect(json["errors"].first).to eql Api::V1::BikesController::CANNOT_MANAGE | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context "as a bike admin" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:bike_admin) | ||||
|         sign_in @user | ||||
|       end | ||||
| 
 | ||||
|       context "with no bike in json data" do | ||||
|         it "returns error status" do | ||||
|           post :create | ||||
|           expect(@response.code.to_i).to eql 422 | ||||
|         end | ||||
| 
 | ||||
|         it "returns an error message" do | ||||
|           post :create | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["errors"].first).to eql Api::V1::BikesController::EXPECTED_BIKE | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "with valid bike in json data" do | ||||
|         before(:each) do | ||||
|           bike_data = { bikes: [{ | ||||
|             serial_number: "XKCD", | ||||
|             bike_brand_id: 1, | ||||
|             shop_id: 1, | ||||
|             model: "Bike Model", | ||||
|             bike_style_id: 1, | ||||
|             seat_tube_height: 52, | ||||
|             bike_condition_id: 1, | ||||
|             bike_purpose_id: 1, | ||||
|             bike_wheel_size_id: 1, | ||||
|           }]} | ||||
|           #this is necessary because render_views does not work with sign_in devise helper | ||||
|           @submit_json = api_submit_json(@user, bike_data) | ||||
|           #not sure why format: :json not working | ||||
|           request.accept = 'application/json' | ||||
|         end | ||||
| 
 | ||||
|         it "returns 200" do | ||||
|           post :create, @submit_json | ||||
|           expect(@response.code.to_i).to eql 200 | ||||
|         end | ||||
| 
 | ||||
|         it "returns the created bike json" do | ||||
|           post :create, @submit_json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json).to have_key("bikes") | ||||
|           expect(json.to_s).to include(@submit_json[:bikes].first[:serial_number]) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "with invalid bike in json data" do | ||||
|         before(:each) do | ||||
|           @submit_json = { bikes: [{ | ||||
|             serial_number: "XKCD", | ||||
|           }]} | ||||
|         end | ||||
| 
 | ||||
|         it "returns 422" do | ||||
|           post :create, @submit_json | ||||
|           expect(@response.code.to_i).to eql 422 | ||||
|         end | ||||
| 
 | ||||
|         it "returns the fields with errors" do | ||||
|           post :create, @submit_json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json).to have_key("errors") | ||||
|           expect(json.to_s).to include("can't be blank") | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe "#show" do | ||||
|     context "as a user" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:user) | ||||
|         sign_in @user | ||||
|       end | ||||
| 
 | ||||
|       context "no bike exists" do | ||||
|         it "returns 404" do | ||||
|           get :show, id: 999 | ||||
|           expect(@response.code.to_i).to eql 404 | ||||
|         end | ||||
| 
 | ||||
|         it "returns an error message" do | ||||
|           get :show, id: 999 | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["errors"].first).to eql Api::V1::BikesController::NOT_FOUND | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "a bike exists" do | ||||
|         let!(:bike){ FactoryGirl.create(:bike) } | ||||
| 
 | ||||
|         it "returns 200" do | ||||
|           get :show, id: bike.id, format: :json | ||||
|           expect(@response.code.to_i).to eql 200 | ||||
|         end | ||||
| 
 | ||||
|         it "returns the bike json" do | ||||
|           get :show, id: bike.id, format: :json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json).to have_key("bikes") | ||||
|           expect(json.to_s).to include(bike.serial_number) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										79
									
								
								spec/controllers/api/logs_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								spec/controllers/api/logs_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Api::V1::LogsController do | ||||
| 
 | ||||
|   describe "#checkin" do | ||||
| 
 | ||||
|     before(:each) do | ||||
|       @user = FactoryGirl.create(:user) | ||||
|       sign_in @user | ||||
|       controller.stub(:current_user).and_return(@user) | ||||
|     end | ||||
| 
 | ||||
|     context "user is not checked in" do | ||||
|       before(:each) do | ||||
|         @user.stub(:checked_in?).and_return(false) | ||||
|       end | ||||
| 
 | ||||
|       it "returns 204" do | ||||
|           @user.stub(:checkin) | ||||
|           post :checkin | ||||
|           expect(@response.code.to_i).to eql 204 | ||||
|       end | ||||
| 
 | ||||
|       it "checks in a user" do | ||||
|           expect(@user).to receive(:checkin) | ||||
|           post :checkin | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context "user is already checked in" do | ||||
|       before(:each) do | ||||
|         @user.stub(:checked_in?).and_return(true) | ||||
|       end | ||||
| 
 | ||||
|       it "returns 404 if the user is already checked in" do | ||||
|         post :checkin | ||||
|         expect(@response.code.to_i).to eql 404 | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe "#checkout" do | ||||
| 
 | ||||
|     before(:each) do | ||||
|       @user = FactoryGirl.create(:user) | ||||
|       sign_in @user | ||||
|       controller.stub(:current_user).and_return(@user) | ||||
|     end | ||||
| 
 | ||||
|     context "user is not checked in" do | ||||
|       before(:each) do | ||||
|         @user.stub(:checked_in?).and_return(false) | ||||
|       end | ||||
| 
 | ||||
|       it "returns 404" do | ||||
|           @user.stub(:checkin) | ||||
|           post :checkout | ||||
|           expect(@response.code.to_i).to eql 404 | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context "user is already checked in" do | ||||
|       before(:each) do | ||||
|         @user.stub(:checked_in?).and_return(true) | ||||
|       end | ||||
| 
 | ||||
|       it "returns 204" do | ||||
|           @user.stub(:checkout) | ||||
|           post :checkout | ||||
|           expect(@response.code.to_i).to eql 204 | ||||
|       end | ||||
| 
 | ||||
|       it "checks out a user" do | ||||
|           expect(@user).to receive(:checkout) | ||||
|           post :checkout | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										52
									
								
								spec/controllers/api/task_lists_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								spec/controllers/api/task_lists_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Api::V1::TaskListsController do | ||||
|   render_views | ||||
| 
 | ||||
|   describe "#show" do | ||||
|     context "as a user" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:user) | ||||
|         sign_in @user | ||||
|       end | ||||
| 
 | ||||
|       context "task list exists" do | ||||
|         let!(:task_list){ FactoryGirl.create(:task_list) } | ||||
| 
 | ||||
|         it "returns 200" do | ||||
|           get :show, id: task_list.id, format: :json | ||||
|           expect(@response.code.to_i).to eql 200 | ||||
|         end | ||||
| 
 | ||||
|         it "returns valid task list json" do | ||||
|           get :show, id: task_list.id, format: :json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json.to_s).to include(task_list.name) | ||||
|         end | ||||
| 
 | ||||
|         it "returns task list with tasks json" do | ||||
|           get :show, id: task_list.id, format: :json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(task_list.tasks.count).to be > 0 | ||||
|           task_list.tasks.each do |task| | ||||
|             expect(json.to_s).to include(task.task) | ||||
|           end | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "task list does not exist" do | ||||
|         it "returns 404" do | ||||
|           get :show, id: 999, format: :json | ||||
|           expect(@response.code.to_i).to eql 404 | ||||
|         end | ||||
| 
 | ||||
|         it "returns an error message" do | ||||
|           get :show, id: 999, format: :json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["errors"].first). | ||||
|             to eql Api::V1::TaskListsController::NOT_FOUND | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										100
									
								
								spec/controllers/api/tasks_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								spec/controllers/api/tasks_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Api::V1::TasksController do | ||||
|   render_views | ||||
| 
 | ||||
|   describe "#update" do | ||||
|     context "as a user with a bike" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:user_with_bike) | ||||
|         sign_in @user | ||||
|       end | ||||
| 
 | ||||
|       context "with no tasks in json data" do | ||||
|         it "returns 400" do | ||||
|           put :update | ||||
|           expect(@response.code.to_i).to eql 422 | ||||
|         end | ||||
| 
 | ||||
|         it "returns an error message" do | ||||
|           put :update | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["errors"].first).to eql Api::V1::TasksController::EXPECTED_TASKS | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "with non existing task in data" do | ||||
|         before(:each) do | ||||
|           @submit_json = { tasks: [ | ||||
|             { id: 9999, done: false }, | ||||
|           ]} | ||||
|         end | ||||
| 
 | ||||
|         it "returns 404" do | ||||
|           put :update, @submit_json | ||||
|           expect(@response.code.to_i).to eql 404 | ||||
|         end | ||||
| 
 | ||||
|         it "returns an error message" do | ||||
|           put :update, @submit_json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["errors"].to_s).to include(Api::V1::TasksController::NOT_FOUND) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "with valid tasks json data" do | ||||
|         before(:each) do | ||||
|           @task = FactoryGirl.create(:task, done: false, task_list_id: @user.bike.task_list.id) | ||||
|           task_data = { tasks: [ | ||||
|             { id: @task.id, done: true}, | ||||
|           ]} | ||||
|           #this is necessary because render_views does not work with sign_in devise helper | ||||
|           @submit_json = api_submit_json(@user, task_data) | ||||
|           #not sure why format: :json not working | ||||
|           request.accept = 'application/json' | ||||
|         end | ||||
| 
 | ||||
|         it "returns 200" do | ||||
|           put :update, @submit_json | ||||
|           expect(@response.code.to_i).to eql 200 | ||||
|         end | ||||
| 
 | ||||
|         it "returns the updated task json" do | ||||
|           put :update, @submit_json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json).to have_key("tasks") | ||||
|           expect(json.to_s).to include(Task.find_by_id(@task.id).done.to_s) | ||||
|         end | ||||
| 
 | ||||
|         it "updates the task" do | ||||
|           expect{put :update, @submit_json}. | ||||
|             to change{ Task.find_by_id(@task.id).done }. | ||||
|               from(false).to(true) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context "as a user without a bike" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:user, bike_id: nil) | ||||
|         sign_in @user | ||||
| 
 | ||||
|         task = FactoryGirl.create(:task) | ||||
|         @submit_json = { tasks: [ | ||||
|           { id: task.id, done: false }, | ||||
|         ]} | ||||
|       end | ||||
| 
 | ||||
|       it "should return 403" do | ||||
|         put :update, @submit_json | ||||
|         expect(@response.code.to_i).to eql 403 | ||||
|       end | ||||
| 
 | ||||
|       it "returns an error message" do | ||||
|         put :update, @submit_json | ||||
|         json = JSON.parse(@response.body) | ||||
|         expect(json["errors"].to_s).to include(Api::V1::TasksController::CANNOT_MANAGE) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										117
									
								
								spec/controllers/api/time_entries_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								spec/controllers/api/time_entries_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Api::V1::TimeEntriesController do | ||||
|   render_views | ||||
| 
 | ||||
|   describe "#create" do | ||||
| 
 | ||||
|     context "as a user" do | ||||
|       let!(:user){ FactoryGirl.create(:user) } | ||||
| 
 | ||||
|       before(:each) do | ||||
|         sign_in user | ||||
|       end | ||||
| 
 | ||||
|       context "with no time entry in json data" do | ||||
|         it "returns error status" do | ||||
|           post :create | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(@response.code.to_i).to eql 422 | ||||
|         end | ||||
| 
 | ||||
|         it "returns an error message" do | ||||
|           post :create | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["errors"].first).to eql Api::V1::TimeEntriesController::EXPECTED_TIME_ENTRY | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "with valid time entry in json data" do | ||||
| 
 | ||||
|         before(:each) do | ||||
|           time_data = { time_entries: [{ | ||||
|             start_date: Time.zone.now, | ||||
|             end_date: Time.zone.now + 60, | ||||
|             log_action_id: 1, | ||||
|             bike_id: -1, | ||||
|             description: "My description"}] | ||||
|           } | ||||
| 
 | ||||
|           #this is necessary because render_views does not work with sign_in devise helper | ||||
|           @submit_json = api_submit_json(user, time_data) | ||||
|           #not sure why format: :json not working | ||||
|           request.accept = 'application/json' | ||||
|         end | ||||
| 
 | ||||
|         it "returns 200" do | ||||
|           post :create, @submit_json | ||||
|           expect(@response.code.to_i).to eql 200 | ||||
|         end | ||||
| 
 | ||||
|         it "returns the created time entry json" do | ||||
|           post :create, @submit_json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json).to have_key("time_entries") | ||||
|           expect(json.to_s).to include(@submit_json[:time_entries].first[:description]) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "with invalid time entry in json data" do | ||||
|         before(:each) do | ||||
|           @submit_json = { time_entries: [{ | ||||
|             description: "My description", | ||||
|           }]} | ||||
|         end | ||||
| 
 | ||||
|         it "returns 422" do | ||||
|           post :create, @submit_json | ||||
|           expect(@response.code.to_i).to eql 422 | ||||
|         end | ||||
| 
 | ||||
|         it "returns the fields with errors" do | ||||
|           post :create, @submit_json | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json).to have_key("errors") | ||||
|           expect(json.to_s).to include("can't be blank") | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe "#delete" do | ||||
|     context "as a user" do | ||||
|       let!(:user){ FactoryGirl.create(:user) } | ||||
| 
 | ||||
|       before(:each) do | ||||
|         sign_in user | ||||
|       end | ||||
| 
 | ||||
|       context "entry does not exist" do | ||||
|         it "returns 404" do | ||||
|           delete :delete, id: 9000 | ||||
|           expect(@response.code.to_i).to eql 404 | ||||
|         end | ||||
| 
 | ||||
|         it "returns not found" do | ||||
|           delete :delete, id: 9000 | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json).to have_key("errors") | ||||
|           expect(json.to_s).to include("not found") | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "entry exists" do | ||||
|         let!(:entry){ FactoryGirl.create(:time_entry) } | ||||
| 
 | ||||
|         it "deletes the time entry" do | ||||
|           expect{delete :delete, id: entry.id}.to change{TimeEntry.count} | ||||
|         end | ||||
| 
 | ||||
|         it "returns 204" do | ||||
|           delete :delete, id: entry.id | ||||
|           expect(@response.code.to_i).to eql 204 | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										72
									
								
								spec/controllers/api/users_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								spec/controllers/api/users_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Api::V1::UsersController do | ||||
| 
 | ||||
|   describe "#password_reset" do | ||||
| 
 | ||||
|     context "as a user" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:user) | ||||
|         sign_in @user | ||||
|       end | ||||
| 
 | ||||
|       it "returns 403" do | ||||
|         post :password_reset | ||||
|         expect(@response.code.to_i).to eql 403 | ||||
|       end | ||||
| 
 | ||||
|       it "returns an error message" do | ||||
|         post :password_reset | ||||
|         json = JSON.parse(@response.body) | ||||
|         expect(json["errors"].first).to eql Api::V1::UsersController::CANNOT_MANAGE | ||||
|       end | ||||
| 
 | ||||
|     end | ||||
| 
 | ||||
|     context "as an admin" do | ||||
|       before(:each) do | ||||
|         @user = FactoryGirl.create(:admin) | ||||
|         sign_in @user | ||||
|       end | ||||
| 
 | ||||
|       it "forbids a user to reset their own password" do | ||||
|         post :password_reset, user_id: @user.id | ||||
|         expect(@response.code.to_i).to eql 403 | ||||
|         json = JSON.parse(@response.body) | ||||
|         expect(json["errors"].first).to eql Api::V1::UsersController::NOT_ALLOWED | ||||
|       end | ||||
| 
 | ||||
|       context "with no user in json data" do | ||||
|         it "returns 404" do | ||||
|           post :password_reset | ||||
|           expect(@response.code.to_i).to eql 404 | ||||
|         end | ||||
| 
 | ||||
|         it "returns an error message" do | ||||
|           post :password_reset | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["errors"].first).to eql Api::V1::UsersController::NOT_FOUND | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context "another user exists" do | ||||
|         before(:each) do | ||||
|           @user2 = FactoryGirl.create(:user) | ||||
|         end | ||||
| 
 | ||||
|         it "returns 200" do | ||||
|           post :password_reset, user_id: @user2.id | ||||
|           expect(@response.code.to_i).to eql 200 | ||||
|         end | ||||
| 
 | ||||
|         it "returns that users new password" do | ||||
|           post :password_reset, user_id: @user2.id | ||||
|           json = JSON.parse(@response.body) | ||||
|           expect(json["password"].length).to eql Api::V1::UsersController::PASS_LENGTH | ||||
|         end | ||||
| 
 | ||||
|       end | ||||
| 
 | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										5
									
								
								spec/factories/bike_brands.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/factories/bike_brands.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :bike_brand do | ||||
|     brand {Faker::Commerce.product_name} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										5
									
								
								spec/factories/bike_conditions.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/factories/bike_conditions.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :bike_condition do | ||||
|     condition "POOR" | ||||
|   end | ||||
| end | ||||
							
								
								
									
										5
									
								
								spec/factories/bike_purposes.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/factories/bike_purposes.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :bike_purpose do | ||||
|     purpose "SHOP" | ||||
|   end | ||||
| end | ||||
							
								
								
									
										5
									
								
								spec/factories/bike_styles.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/factories/bike_styles.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :bike_style do | ||||
|     style {Faker::Commerce.product_name} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										12
									
								
								spec/factories/bike_wheel_sizes.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								spec/factories/bike_wheel_sizes.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :bike_wheel_size do | ||||
|     twmm 40 | ||||
|     rdmm 635 | ||||
|     twin "1 1/2 [1 3/8]" | ||||
|     rdin 28 | ||||
|     twfr "38B [35B]" | ||||
|     rdfr 700 | ||||
|     description "a big wheel" | ||||
|     tire_common_score 1 | ||||
|   end | ||||
| end | ||||
| @ -2,6 +2,23 @@ | ||||
| 
 | ||||
| FactoryGirl.define do | ||||
|   factory :bike do | ||||
|     sequence(:shop_id) {|n| n} | ||||
|     sequence :serial_number do |n| | ||||
|       "#{Faker::Code.isbn}-#{n}" | ||||
|     end | ||||
|     bike_brand { FactoryGirl.create(:bike_brand) } | ||||
|     model { Faker::Commerce.product_name } | ||||
|     color "FFFFFF" | ||||
|     bike_style { FactoryGirl.create(:bike_style) } | ||||
|     seat_tube_height 42 | ||||
|     top_tube_length 42 | ||||
|     bike_wheel_size { FactoryGirl.create(:bike_wheel_size) } | ||||
|     value 200 | ||||
|     bike_condition { FactoryGirl.create(:bike_condition) } | ||||
|     bike_purpose { FactoryGirl.create(:bike_purpose) } | ||||
|   end | ||||
| 
 | ||||
|   factory :seed_bike do | ||||
|     sequence(:shop_id) {|n| n} | ||||
|     sequence :serial_number do |n| | ||||
|       "#{Faker::Code.isbn}-#{n}" | ||||
|  | ||||
							
								
								
									
										7
									
								
								spec/factories/task_lists.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								spec/factories/task_lists.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :task_list do | ||||
|     item_id { FactoryGirl.create(:bike).id } | ||||
|     item_type "Bike" | ||||
|     name { Faker::Lorem.words.join(" ")} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										7
									
								
								spec/factories/tasks.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								spec/factories/tasks.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :task do | ||||
|     task_list { FactoryGirl.create(:task_list) } | ||||
|     done false | ||||
|     task { Faker::Lorem.words(7).join(" ")} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										10
									
								
								spec/factories/time_entries.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								spec/factories/time_entries.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| FactoryGirl.define do | ||||
|   factory :time_entry do | ||||
|     loggable_type User.to_s | ||||
|     logger_type User.to_s | ||||
|     start_date Time.now | ||||
|     end_date Time.now | ||||
|     log_action_id 1 | ||||
|     log_action_type ActsAsLoggable::UserAction.to_s | ||||
|   end | ||||
| end | ||||
| @ -36,5 +36,9 @@ FactoryGirl.define do | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     factory :user_with_bike do | ||||
|       bike { FactoryGirl.create(:bike) } | ||||
|     end | ||||
| 
 | ||||
|   end | ||||
| end | ||||
|  | ||||
							
								
								
									
										40
									
								
								spec/features/bikes/new_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								spec/features/bikes/new_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| require "spec_helper" | ||||
| 
 | ||||
| feature "Bikes" do | ||||
|   before(:each) do | ||||
|     # create brand and wheel size | ||||
|     @bike_brand = BikeBrand.create( brand: "Huffy") | ||||
|     @bike_wheel_size = BikeWheelSize.create( description: "MyWheelSize") | ||||
| 
 | ||||
|     @user = FactoryGirl.create(:bike_admin) | ||||
|     visit new_user_session_path | ||||
|     fill_in "user_username", with: @user.username | ||||
|     fill_in "user_password", with: @user.password | ||||
|     click_button "Sign in" | ||||
|   end | ||||
| 
 | ||||
|   scenario "User creates a new bike", js: true do | ||||
|     visit new_bike_path | ||||
|     fill_in "shop_id", with: 1 | ||||
|     fill_in "model", with: "Huffle Puffle" | ||||
|     fill_in "serial_number", with: "XKCD" | ||||
|     find('label.btn.btn-default', text: 'RD').click | ||||
|     find('label.btn.btn-default', text: 'Poor').click | ||||
|     fill_in "seat_tube_height", with: 52 | ||||
|     select @bike_brand.brand, from: "bike_brand_id" | ||||
|     select @bike_wheel_size.description, from: "bike_wheel_size_id" | ||||
|     click_button "Add Bike" | ||||
|     expect(page).to have_text(@bike_brand.brand) | ||||
|   end | ||||
| 
 | ||||
|   scenario "User submits a bike with errors", js: true do | ||||
|     visit new_bike_path | ||||
|     click_button "Add Bike" | ||||
|     expect(page).to have_text(:all, "is not a number") | ||||
|     expect(page).to have_text(:all, "is not a valid brand") | ||||
|     expect(page).to have_text(:all, "is too short") | ||||
|     expect(page).to have_text(:all, "is not a valid style") | ||||
|     expect(page).to have_text(:all, "is not a valid wheel size") | ||||
|     expect(page).to have_text(:all, "is not a valid condition") | ||||
|   end | ||||
| end | ||||
| @ -1,10 +1,11 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe "New User Registrations" do | ||||
| 
 | ||||
|   it 'should have a link to sign up on the homepage' do | ||||
|     visit root_path | ||||
|     page.should have_link 'Register' | ||||
|     click_link 'Register' | ||||
|     page.should have_link 'Sign up' | ||||
|     click_link 'Sign up' | ||||
|     current_path.should == new_user_registration_path | ||||
|   end | ||||
| 
 | ||||
| @ -13,30 +14,29 @@ describe "New User Registrations" do | ||||
|       visit new_user_registration_path | ||||
|     end | ||||
|     it 'should have the additional user fields on the registration page' do | ||||
|       page.should have_field 'First name' | ||||
|       page.should have_field 'Last name' | ||||
|       page.should have_field 'Nickname' | ||||
|       page.should have_field 'Email' | ||||
|       page.should have_field 'Password' | ||||
|       page.should have_field 'Password confirmation' | ||||
|       page.should have_field 'user_first_name' | ||||
|       page.should have_field 'user_last_name' | ||||
|       page.should have_field 'user_email' | ||||
|       page.should have_field 'user_password' | ||||
|       page.should have_field 'user_password_confirmation' | ||||
|       page.should have_button 'Sign up' | ||||
|     end | ||||
| 
 | ||||
|     context 'required non-devise fields' do | ||||
|       before do | ||||
|         fill_in 'Email', :with => 'FF@example.com' | ||||
|         fill_in 'Password', :with => 'password' | ||||
|         fill_in 'Password confirmation', :with => 'password' | ||||
|         fill_in 'user_email', :with => 'FF@example.com' | ||||
|         fill_in 'user_password', :with => 'password' | ||||
|         fill_in 'user_password_confirmation', :with => 'password' | ||||
|       end | ||||
| 
 | ||||
|       it 'should require first name' do | ||||
|         fill_in 'Last name', :with => 'Footer' | ||||
|         fill_in 'user_last_name', :with => 'Footer' | ||||
|         click_button 'Sign up' | ||||
|         page.should have_content "First name can't be blank" | ||||
|       end | ||||
| 
 | ||||
|       it 'should require last name' do | ||||
|         fill_in 'First name', :with => 'Frank' | ||||
|         fill_in 'user_first_name', :with => 'Frank' | ||||
|         click_button 'Sign up' | ||||
|         page.should have_content "Last name can't be blank" | ||||
|       end | ||||
							
								
								
									
										34
									
								
								spec/features/site/index_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								spec/features/site/index_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe "Index Page" do | ||||
| 
 | ||||
|   before(:each) do | ||||
|     @user = FactoryGirl.create(:bike_admin) | ||||
|     visit root_path | ||||
|     fill_in "user_username", with: @user.username | ||||
|     fill_in "user_password", with: @user.password | ||||
|   end | ||||
| 
 | ||||
|   it 'should have a link to check in' do | ||||
|     page.should have_button 'CHECK IN' | ||||
|   end | ||||
| 
 | ||||
|   it 'should have a link to check out' do | ||||
|     page.should have_button 'CHECK OUT' | ||||
|   end | ||||
| 
 | ||||
|   it 'clicking check in should check in a user', js: true do | ||||
|     pending | ||||
|     expect{click_button 'CHECK IN'}. | ||||
|       to change{@user.checked_in?}. | ||||
|       from(false).to(true) | ||||
|   end | ||||
| 
 | ||||
|   it 'clicking check out should check out a user', js: true do | ||||
|     pending | ||||
|     @user.checkin | ||||
|     expect{click_button 'CHECK OUT'}. | ||||
|       to change{@user.checked_in?}. | ||||
|       from(true).to(false) | ||||
|   end | ||||
| end | ||||
							
								
								
									
										22
									
								
								spec/features/time_entries/index_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								spec/features/time_entries/index_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| require "spec_helper" | ||||
| 
 | ||||
| feature "TimeEntries" do | ||||
|   let!(:user){ FactoryGirl.create(:user) } | ||||
|   let!(:entry){ FactoryGirl.create(:time_entry, loggable_id: user.id) } | ||||
| 
 | ||||
|   before(:each) do | ||||
|     visit new_user_session_path | ||||
|     fill_in "user_username", with: user.username | ||||
|     fill_in "user_password", with: user.password | ||||
|     click_button "Sign in" | ||||
|   end | ||||
| 
 | ||||
|   scenario "User deletes a time entry", js: true do | ||||
|     visit time_entries_path | ||||
|     save_screenshot("/tmp/testingpoop.png") | ||||
|     find('button.work_entry-delete-btn').trigger('click') | ||||
|     click_button "Delete" | ||||
|     expect(page).to have_text("Your Timesheet") | ||||
|     expect(TimeEntry.count).to eql 0 | ||||
|   end | ||||
| end | ||||
							
								
								
									
										48
									
								
								spec/features/time_entries/new_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								spec/features/time_entries/new_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| require "spec_helper" | ||||
| 
 | ||||
| feature "TimeEntries" do | ||||
|   let!(:user){ FactoryGirl.create(:user) } | ||||
| 
 | ||||
|   before(:each) do | ||||
|     visit new_user_session_path | ||||
|     fill_in "user_username", with: user.username | ||||
|     fill_in "user_password", with: user.password | ||||
|     click_button "Sign in" | ||||
|   end | ||||
| 
 | ||||
|   scenario "User creates a new time entry", js: true do | ||||
|     visit new_time_entry_path | ||||
|     fill_in "date_id", with: "04/27/2014" | ||||
|     fill_in "start_time_id", with: "3:00 PM" | ||||
|     fill_in "end_time_id", with: "3:15 PM" | ||||
|     find('label.btn.btn-default', text: 'Personal').trigger('click') | ||||
|     fill_in "description_id", with: "My Description" | ||||
|     click_button "Add Time Entry" | ||||
|     expect(page).to have_text("My Description") | ||||
|     expect(TimeEntry.count).to be > 0 | ||||
|   end | ||||
| 
 | ||||
|   context "A bike exists" do | ||||
|     let!(:bike){ FactoryGirl.create(:bike) } | ||||
| 
 | ||||
|     scenario "User creates a new bike work time entry", js: true do | ||||
|       visit new_time_entry_path | ||||
|       fill_in "date_id", with: "04/27/2014" | ||||
|       fill_in "start_time_id", with: "3:00 PM" | ||||
|       fill_in "end_time_id", with: "3:15 PM" | ||||
|       find('label.btn.btn-default', text: 'Personal').trigger('click') | ||||
|       select bike.to_s, from: "bike_id" | ||||
|       fill_in "description_id", with: "My Description" | ||||
|       click_button "Add Time Entry" | ||||
|       expect(page).to have_text("Task List") | ||||
|       expect(TimeEntry.count).to be > 0 | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   scenario "User submits a time entry with errors", js: true do | ||||
|     visit new_time_entry_path | ||||
|     click_button "Add Time Entry" | ||||
|     expect(page).to have_text(:all, "can't be blank") | ||||
|   end | ||||
| 
 | ||||
| end | ||||
| @ -1,6 +1,7 @@ | ||||
| require 'rubygems' | ||||
| require 'spork' | ||||
| require 'rails' | ||||
| require 'capybara/poltergeist' | ||||
| #uncomment the following line to use spork with the debugger | ||||
| #require 'spork/ext/ruby-debug' | ||||
| 
 | ||||
| @ -19,12 +20,13 @@ Spork.prefork do | ||||
|   require 'rspec/rails' | ||||
|   require 'rspec/autorun' | ||||
|   require 'capybara/rspec' | ||||
|   require 'turnip/capybara' | ||||
| 
 | ||||
|   # Requires supporting ruby files with custom matchers and macros, etc, | ||||
|   # in spec/support/ and its subdirectories. | ||||
|   Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} | ||||
| 
 | ||||
|   Capybara.javascript_driver = :poltergeist | ||||
| 
 | ||||
|   RSpec.configure do |config| | ||||
|     # ## Mock Framework | ||||
|     # | ||||
| @ -66,19 +68,19 @@ Spork.prefork do | ||||
|     config.infer_base_class_for_anonymous_controllers = false | ||||
|     config.include FactoryGirlStepHelpers | ||||
|     config.include FactoryGirl::Syntax::Methods | ||||
|     config.include ApiTestHelpers | ||||
|     config.include Devise::TestHelpers, type: :controller | ||||
| 
 | ||||
|     #allows :focus tag to only run specific tests | ||||
|     config.treat_symbols_as_metadata_keys_with_true_values = true | ||||
|     config.filter_run :focus => true | ||||
|     config.run_all_when_everything_filtered = true | ||||
|      | ||||
|   end | ||||
| 
 | ||||
| end | ||||
| 
 | ||||
| Spork.each_run do | ||||
|   # This code will be run each time you run your specs. | ||||
| 
 | ||||
|   #Reload Factories | ||||
|   #FYI make sure that Factories use strings instead of class constants | ||||
|   FactoryGirl.reload | ||||
|  | ||||
							
								
								
									
										5
									
								
								spec/support/api_test_helpers.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/support/api_test_helpers.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| module ApiTestHelpers | ||||
|   def api_submit_json(user, json_hash) | ||||
|     json_hash.merge({username: user.username, password: user.password}) | ||||
|   end | ||||
| end | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user