Author SHA1 Message Date
Jason Denney ef205ba46f testing 2013-11-26 17:10:18 -05:00
87 changed files with 325 additions and 3709 deletions
+1
View File
@@ -1 +1,2 @@
--colour --colour
-rturnip
-1
View File
@@ -1 +0,0 @@
velocipede
-1
View File
@@ -1 +0,0 @@
ruby-1.9.3-p374
+55
View File
@@ -0,0 +1,55 @@
#!/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
+10 -11
View File
@@ -14,9 +14,8 @@ gem 'decent_exposure', '~> 1.0.1'
gem 'devise', '~> 2.0.4' gem 'devise', '~> 2.0.4'
gem 'haml-rails', '~> 0.3.4' gem 'haml-rails', '~> 0.3.4'
gem 'jquery-rails', '~> 2.0' gem 'jquery-rails', '~> 2.0'
gem 'pg', '~> 0.17.1' gem 'pg'
gem 'will_paginate', '~> 3.0.3' gem 'will_paginate', '~> 3.0.3'
gem 'jbuilder', '~> 2.0.3'
# Gems used only for assets and not required # Gems used only for assets and not required
# in production environments by default. # in production environments by default.
@@ -31,21 +30,21 @@ group :assets do
end end
group :development, :test do group :development, :test do
gem 'rspec-rails', '~> 2.14.0' gem 'rspec-rails', '~> 2.8.1'
gem 'factory_girl_rails', '~> 1.2' gem 'factory_girl_rails', '~> 1.2'
gem 'pry', '~> 0.9.8' gem 'pry', '~> 0.9.8'
gem 'faker', '~> 1.2.0' gem 'faker'
end end
group :test do group :test do
gem 'shoulda-matchers', '~> 1.0.0' gem 'shoulda-matchers', '~> 1.0.0'
gem 'capybara', '~> 2.2.1' gem 'capybara', '~> 1.1.2'
gem 'poltergeist', '~> 1.5.0' gem 'turnip', '~> 0.3.0'
gem 'database_cleaner', '~> 1.2.0' gem 'database_cleaner'
gem 'launchy', '~> 2.4.2' gem 'launchy'
gem 'spork', '~> 0.9.2' gem 'spork'
#guard dependency for Mac OS 10 #guard dependency for Mac OS 10
gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
gem 'guard-spork', '~> 1.5.1' gem 'guard-spork'
gem 'guard-rspec', '~> 4.2.6' gem 'guard-rspec'
end end
+82 -92
View File
@@ -36,43 +36,42 @@ GEM
activesupport (3.2.13) activesupport (3.2.13)
i18n (= 0.6.1) i18n (= 0.6.1)
multi_json (~> 1.0) multi_json (~> 1.0)
addressable (2.3.5) addressable (2.3.3)
arel (3.0.3) arel (3.0.2)
bcrypt-ruby (3.1.2) bcrypt-ruby (3.0.1)
bootstrap-will_paginate (0.0.10) bootstrap-will_paginate (0.0.9)
will_paginate will_paginate
builder (3.0.4) builder (3.0.4)
cancan (1.6.10) cancan (1.6.9)
capybara (2.2.1) capybara (1.1.4)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
xpath (~> 2.0) selenium-webdriver (~> 2.0)
celluloid (0.15.2) xpath (~> 0.1.4)
timers (~> 1.1.0) childprocess (0.3.9)
childprocess (0.4.0)
ffi (~> 1.0, >= 1.0.11) ffi (~> 1.0, >= 1.0.11)
cliver (0.3.2) coderay (1.0.9)
coderay (1.1.0)
coffee-rails (3.2.2) coffee-rails (3.2.2)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
railties (~> 3.2.0) railties (~> 3.2.0)
coffee-script (2.2.0) coffee-script (2.2.0)
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.7.0) coffee-script-source (1.6.2)
commonjs (0.2.7) commonjs (0.2.6)
database_cleaner (1.2.0) database_cleaner (0.9.1)
decent_exposure (1.0.2) decent_exposure (1.0.2)
devise (2.0.6) devise (2.0.5)
bcrypt-ruby (~> 3.0) bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3) orm_adapter (~> 0.0.3)
railties (~> 3.1) railties (~> 3.1)
warden (~> 1.1.1) warden (~> 1.1.1)
diff-lcs (1.2.5) diff-lcs (1.1.3)
erubis (2.7.0) erubis (2.7.0)
execjs (2.0.2) execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (2.6.4) factory_girl (2.6.4)
activesupport (>= 2.3.9) activesupport (>= 2.3.9)
factory_girl_rails (1.7.0) factory_girl_rails (1.7.0)
@@ -80,18 +79,19 @@ GEM
railties (>= 3.0.0) railties (>= 3.0.0)
faker (1.2.0) faker (1.2.0)
i18n (~> 0.5) i18n (~> 0.5)
ffi (1.9.3) ffi (1.6.0)
formatador (0.2.4) formatador (0.2.4)
guard (2.4.0) gherkin (2.11.6)
json (>= 1.7.6)
guard (1.7.0)
formatador (>= 0.2.4) formatador (>= 0.2.4)
listen (~> 2.1) listen (>= 0.6.0)
lumberjack (~> 1.0) lumberjack (>= 1.0.2)
pry (>= 0.9.12) pry (>= 0.9.10)
thor (>= 0.18.1) thor (>= 0.14.6)
guard-rspec (4.2.6) guard-rspec (1.2.1)
guard (~> 2.1) guard (>= 1.1)
rspec (>= 2.14, < 4.0) guard-spork (1.5.0)
guard-spork (1.5.1)
childprocess (>= 0.2.3) childprocess (>= 0.2.3)
guard (>= 1.1) guard (>= 1.1)
spork (>= 0.8.4) spork (>= 0.8.4)
@@ -101,17 +101,14 @@ GEM
activesupport (>= 3.1, < 4.1) activesupport (>= 3.1, < 4.1)
haml (~> 3.1) haml (~> 3.1)
railties (>= 3.1, < 4.1) railties (>= 3.1, < 4.1)
hike (1.2.3) hike (1.2.1)
i18n (0.6.1) i18n (0.6.1)
jbuilder (2.0.3)
activesupport (>= 3.0.0)
multi_json (>= 1.2.0)
journey (1.0.4) journey (1.0.4)
jquery-rails (2.3.0) jquery-rails (2.2.1)
railties (>= 3.0, < 5.0) railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json (1.8.1) json (1.7.7)
launchy (2.4.2) launchy (2.2.0)
addressable (~> 2.3) addressable (~> 2.3)
less (2.2.2) less (2.2.2)
commonjs (~> 0.2.6) commonjs (~> 0.2.6)
@@ -119,18 +116,15 @@ GEM
actionpack (>= 3.1) actionpack (>= 3.1)
less (~> 2.2.0) less (~> 2.2.0)
libv8 (3.3.10.4) libv8 (3.3.10.4)
listen (2.4.1) listen (0.7.3)
celluloid (>= 0.15.2) lumberjack (1.0.3)
rb-fsevent (>= 0.9.3) mail (2.5.3)
rb-inotify (>= 0.9) i18n (>= 0.4.0)
lumberjack (1.0.4)
mail (2.5.4)
mime-types (~> 1.16) mime-types (~> 1.16)
treetop (~> 1.4.8) treetop (~> 1.4.8)
method_source (0.8.2) method_source (0.8.1)
mime-types (1.25.1) mime-types (1.22)
mini_portile (0.5.2) multi_json (1.8.2)
multi_json (1.8.4)
netzke-basepack (0.8.4) netzke-basepack (0.8.4)
netzke-core (~> 0.8.2) netzke-core (~> 0.8.2)
netzke-cancan (0.8.2) netzke-cancan (0.8.2)
@@ -139,18 +133,12 @@ GEM
netzke-core (0.8.4) netzke-core (0.8.4)
execjs execjs
uglifier uglifier
nokogiri (1.6.1) nokogiri (1.5.9)
mini_portile (~> 0.5.0)
orm_adapter (0.0.7) orm_adapter (0.0.7)
pg (0.17.1) pg (0.15.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) polyglot (0.3.3)
pry (0.9.12.6) pry (0.9.12)
coderay (~> 1.0) coderay (~> 1.0.5)
method_source (~> 0.8) method_source (~> 0.8)
slop (~> 3.4) slop (~> 3.4)
rack (1.4.5) rack (1.4.5)
@@ -175,30 +163,31 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
rdoc (~> 3.4) rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0) thor (>= 0.14.6, < 2.0)
rake (10.1.1) rake (10.0.4)
rb-fsevent (0.9.4) rb-fsevent (0.9.3)
rb-inotify (0.9.3)
ffi (>= 0.5.0)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
rspec (2.14.1) rspec (2.8.0)
rspec-core (~> 2.14.0) rspec-core (~> 2.8.0)
rspec-expectations (~> 2.14.0) rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.14.0) rspec-mocks (~> 2.8.0)
rspec-core (2.14.7) rspec-core (2.8.0)
rspec-expectations (2.14.5) rspec-expectations (2.8.0)
diff-lcs (>= 1.1.3, < 2.0) diff-lcs (~> 1.1.2)
rspec-mocks (2.14.5) rspec-mocks (2.8.0)
rspec-rails (2.14.1) rspec-rails (2.8.1)
actionpack (>= 3.0) actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 2.14.0) rspec (~> 2.8.0)
rspec-expectations (~> 2.14.0) rubyzip (0.9.9)
rspec-mocks (~> 2.14.0) selenium-webdriver (2.31.0)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip
websocket (~> 1.0.4)
shoulda-matchers (1.0.0) shoulda-matchers (1.0.0)
slop (3.4.7) slop (3.4.4)
spork (0.9.2) spork (0.9.2)
sprockets (2.2.2) sprockets (2.2.2)
hike (~> 1.2) hike (~> 1.2)
@@ -208,25 +197,27 @@ GEM
therubyracer (0.10.2) therubyracer (0.10.2)
libv8 (~> 3.3.10) libv8 (~> 3.3.10)
thor (0.18.1) thor (0.18.1)
tilt (1.4.1) tilt (1.3.6)
timers (1.1.0) treetop (1.4.12)
treetop (1.4.15)
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
turnip (0.3.1)
gherkin (>= 2.5)
rspec (~> 2.0)
twitter-bootstrap-rails (2.0.9) twitter-bootstrap-rails (2.0.9)
actionpack (>= 3.1) actionpack (>= 3.1)
less-rails (~> 2.2.2) less-rails (~> 2.2.2)
railties (>= 3.1) railties (>= 3.1)
therubyracer (~> 0.10.1) therubyracer (~> 0.10.1)
tzinfo (0.3.38) tzinfo (0.3.38)
uglifier (2.4.0) uglifier (2.1.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) multi_json (~> 1.0, >= 1.0.2)
warden (1.1.1) warden (1.1.1)
rack (>= 1.0) rack (>= 1.0)
websocket-driver (0.3.2) websocket (1.0.7)
will_paginate (3.0.5) will_paginate (3.0.4)
xpath (2.0.0) xpath (0.1.4)
nokogiri (~> 1.3) nokogiri (~> 1.3)
PLATFORMS PLATFORMS
@@ -236,30 +227,29 @@ DEPENDENCIES
acts_as_loggable! acts_as_loggable!
bootstrap-will_paginate (~> 0.0.6) bootstrap-will_paginate (~> 0.0.6)
cancan cancan
capybara (~> 2.2.1) capybara (~> 1.1.2)
coffee-rails (~> 3.2.1) coffee-rails (~> 3.2.1)
database_cleaner (~> 1.2.0) database_cleaner
decent_exposure (~> 1.0.1) decent_exposure (~> 1.0.1)
devise (~> 2.0.4) devise (~> 2.0.4)
factory_girl_rails (~> 1.2) factory_girl_rails (~> 1.2)
faker (~> 1.2.0) faker
guard-rspec (~> 4.2.6) guard-rspec
guard-spork (~> 1.5.1) guard-spork
haml-rails (~> 0.3.4) haml-rails (~> 0.3.4)
jbuilder (~> 2.0.3)
jquery-rails (~> 2.0) jquery-rails (~> 2.0)
launchy (~> 2.4.2) launchy
netzke-basepack (~> 0.8.0) netzke-basepack (~> 0.8.0)
netzke-cancan netzke-cancan
netzke-core (~> 0.8.0) netzke-core (~> 0.8.0)
pg (~> 0.17.1) pg
poltergeist (~> 1.5.0)
pry (~> 0.9.8) pry (~> 0.9.8)
rails (= 3.2.13) rails (= 3.2.13)
rb-fsevent rb-fsevent
rspec-rails (~> 2.14.0) rspec-rails (~> 2.8.1)
shoulda-matchers (~> 1.0.0) shoulda-matchers (~> 1.0.0)
spork (~> 0.9.2) spork
turnip (~> 0.3.0)
twitter-bootstrap-rails (~> 2.0.3) twitter-bootstrap-rails (~> 2.0.3)
uglifier (>= 1.0.3) uglifier (>= 1.0.3)
will_paginate (~> 3.0.3) will_paginate (~> 3.0.3)
+5 -28
View File
@@ -1,24 +1,3 @@
# About
A web application for bicycle collectives to track bicycles, bicycle work history, volunteer hours, volunteer work history, and volunteers currently in the shop.
See an overview video of what it looks like and how it works here: https://www.youtube.com/watch?v=0-JjM6d9nK4.
## Instructions/Guides
### Adding a User
To add a user, a user must sign themselves up from the initial login screen. An admin can later log in to grant that user admin privileges afterward, if desired.
### Adding a Bike
How to add a bike to the database: https://www.youtube.com/watch?v=1IchWUdMQ90.
## Views
The app has two different views. One for "core" volunteers which I consider admins, and another for "customer" volunteers which I consider users. Because of the Netzke/ExtJS framework I used, the current UI is non-intuitive and I feel like is only adequate for admins who can take the time to learn the system. I'm in the process of designing a simpler mobile friendly UI with the essential functionality that's intended for users.
## Deployment
Currently, at the Velocipede collective we have it running on a computer in the shop on the local network. The reason for this, is so that it's only accessible from within the shop. I'm weighing out different methods for ensuring that users can only log in from within the shop (and not from home), such as a shop password that only admins can see and would make visible from within the physical bike shop. I also don't have to worry about security issues while the app is in development. In the future, I'd like to see the app hosted much like http://freehub.bikekitchen.org/.
## Ideas
At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donations of old, unused smart phones, connected via local wifi, that can be used as stations to log a volunteer's work and hours. They can be secured by gluing cables into the earphone jacks to deter theft if necessary. This might be another way that the collective can promote reuse of materials.
# Developer Setup # Developer Setup
@@ -27,8 +6,6 @@ At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donati
1. `gem install bundler` 1. `gem install bundler`
1. `bundle` 1. `bundle`
1. Install Postgres (Mac OSX instructions below) 1. Install Postgres (Mac OSX instructions below)
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
1. `rake db:create db:migrate` 1. `rake db:create db:migrate`
1. `rake db:seed` 1. `rake db:seed`
1. Download extJS 4.1 (A version of 4.1 is hosted here: http://my.jasondenney.com/extjs-4.1.1.zip) Latest versions at http://www.sencha.com/products/extjs. Unzip and place where ever you like. 1. Download extJS 4.1 (A version of 4.1 is hosted here: http://my.jasondenney.com/extjs-4.1.1.zip) Latest versions at http://www.sencha.com/products/extjs. Unzip and place where ever you like.
@@ -43,11 +20,8 @@ At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donati
1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start` 1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start`
1. Create your PG user `createuser -d -P velocipede` 1. Create your PG user `createuser -d -P velocipede`
1. Create your database `createdb -U velocipede --owner=velocipede velocipede` 1. Create your database `createdb -U velocipede --owner=velocipede velocipede`
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
# Testing 1. Update config with your database (velocipede), user (velocipede), and password.
1. Install phantomjs `brew install phantomjs`
1. Run tests with `rspec`
# Optional # Optional
Add icons Add icons
@@ -59,3 +33,6 @@ At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donati
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT) Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
<a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a> <a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a>
Testing
+1 -5
View File
@@ -12,8 +12,4 @@
// //
//= require jquery //= require jquery
//= require jquery_ujs //= require jquery_ujs
//= require twitter/bootstrap/bootstrap-button //= require custom_netzke_helpers
//= require twitter/bootstrap/bootstrap-modal
//= require utils
//= require bootstrap-datepicker
//= require bootstrap-timepicker
-31
View File
@@ -1,31 +0,0 @@
$('.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
View File
@@ -1,474 +0,0 @@
/* =========================================================
* 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">&lsaquo;</th>'+
'<th colspan="5" class="switch"></th>'+
'<th class="next">&rsaquo;</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 );
File diff suppressed because it is too large Load Diff
@@ -22,7 +22,6 @@ Ext.define('Ext.ux.form.field.DateTime', {
dateSubmitFormat: 'Y-m-d', dateSubmitFormat: 'Y-m-d',
timeFormat: 'H:i:s', timeFormat: 'H:i:s',
timeSubmitFormat: 'H:i:s', timeSubmitFormat: 'H:i:s',
submitFormat: 'c',
dateConfig:{}, dateConfig:{},
timeConfig:{}, timeConfig:{},
@@ -185,10 +184,8 @@ Ext.define('Ext.ux.form.field.DateTime', {
var me = this var me = this
,format = me.getFormat() ,format = me.getFormat()
,value = me.getValue(); ,value = me.getValue();
console.log(value);
console.log(format); return value ? Ext.Date.format(value, format) : null;
console.log("formatted: " + Ext.Date.format(value, this.submitFormat));
return value ? Ext.Date.format(value, this.submitFormat) : null;
}, },
setValue: function(value){ setValue: function(value){
+4 -5
View File
@@ -1,7 +1,7 @@
$(document).ready(function(){ $(document).ready(function(){
$("#checkin_menu").show(); $("#checkin_menu").show();
$("#checkin").click( function(e){ $("#checkin").click( function(e){
var username = $("#user_username").val(); var username = $("#user_email").val();
var password = $("#user_password").val(); var password = $("#user_password").val();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
@@ -13,7 +13,7 @@ $(document).ready(function(){
complete: function() { }, complete: function() { },
success: function(data) { success: function(data) {
alert("Checked IN!"); alert("Checked IN!");
$("#user_username").val(''); $("#user_email").val('');
$("#user_password").val(''); $("#user_password").val('');
}, },
error: function(data,textStatus) { error: function(data,textStatus) {
@@ -22,7 +22,7 @@ $(document).ready(function(){
}) })
}); });
$("#checkout").click( function(e){ $("#checkout").click( function(e){
var username = $("#user_username").val(); var username = $("#user_email").val();
var password = $("#user_password").val(); var password = $("#user_password").val();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
@@ -34,8 +34,7 @@ $(document).ready(function(){
complete: function() { }, complete: function() { },
success: function(data) { success: function(data) {
alert("Checked OUT!"); alert("Checked OUT!");
$("#user_email").val('');
$("#user_username").val('');
$("#user_password").val(''); $("#user_password").val('');
}, },
error: function(data,textStatus) { error: function(data,textStatus) {
-9
View File
@@ -1,9 +0,0 @@
$("#index_logout").click(function(){
$.ajax({
type: "DELETE",
url: $("#index_logout").data("url"),
complete: function(){
window.location.href="/";
}
});
});
-32
View File
@@ -1,32 +0,0 @@
$(".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);
}
});
});
-76
View File
@@ -1,76 +0,0 @@
$(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";
}
json_data = { time_entries: [{
start_date: start_date.toISOString(),
end_date: end_date.toISOString(),
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
View File
@@ -1,8 +0,0 @@
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);
});
}
}
+1 -3
View File
@@ -9,7 +9,5 @@
* compiled file, but it's generally better to create a new file per style scope. * compiled file, but it's generally better to create a new file per style scope.
* *
*= require_self *= require_self
*= require datepicker *= require_tree .
*= require bootstrap-timepicker
*= require bootstrap_and_overrides
*/ */
-146
View File
@@ -1,146 +0,0 @@
/*!
* 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%;
}
}
-30
View File
@@ -1,26 +1,10 @@
@import "twitter/bootstrap/bootstrap"; @import "twitter/bootstrap/bootstrap";
body {
padding-top: 60px;
padding-left: 20px;
}
@import "twitter/bootstrap/responsive"; @import "twitter/bootstrap/responsive";
// Set the correct sprite paths // Set the correct sprite paths
@iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png'); @iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
@iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.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 // Your custom LESS stylesheets goes here
// //
// Since bootstrap was imported above you have access to its mixins which // Since bootstrap was imported above you have access to its mixins which
@@ -31,17 +15,3 @@ body {
// //
// Example: // Example:
// @linkColor: #ff0000; // @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
View File
@@ -1,182 +0,0 @@
/*!
* 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;
}
+7 -6
View File
@@ -2,6 +2,8 @@ class Tasks < Netzke::Basepack::Grid
def configure(c) def configure(c)
super super
#disable by default, will be enabled once bike is clicked
c.disabled = true
task_list_id = nil task_list_id = nil
if session[:selected_bike_id] if session[:selected_bike_id]
task_list_id = Bike.find_by_id(session[:selected_bike_id]).task_list.id task_list_id = Bike.find_by_id(session[:selected_bike_id]).task_list.id
@@ -29,14 +31,13 @@ class Tasks < Netzke::Basepack::Grid
end end
def default_fields_for_forms def default_fields_for_forms
bike = Bike.find_by_id(session[:selected_bike_id]) fields = []
bike = "Select a Bike First!" if bike.nil? fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
[ fields.concat( [
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Bike Selected", :value => "#{bike.to_s}"},
:done, :done,
:task, :task,
:notes, :notes
] ])
end end
#override with nil to remove actions #override with nil to remove actions
@@ -33,7 +33,7 @@
Ext.Msg.alert("Success", "New Password: "+data.password); Ext.Msg.alert("Success", "New Password: "+data.password);
}, },
error: function(data,textStatus) { error: function(data,textStatus) {
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["errors"][0]); Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]);
} }
}); });
} }
+2 -2
View File
@@ -6,12 +6,12 @@ class Api::V1::BaseController < ActionController::Base
private private
def authenticate_user def authenticate_user
if params[:username] if params[:username]
user = User.find_for_database_authentication( :username => params[:username] ) user = User.find_for_database_authentication( :email => params[:username] )
@current_user = user if user && user.valid_password?( params[:password] ) @current_user = user if user && user.valid_password?( params[:password] )
if @current_user.nil? if @current_user.nil?
msg = "Username/Password/Token invalid" msg = "Username/Password/Token invalid"
render :json => {:error => msg }, :status => 401 and return render :json => {:error => msg }, :status => 403 and return
end end
else else
authenticate_user! authenticate_user!
@@ -1,32 +0,0 @@
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
@@ -1,24 +0,0 @@
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
@@ -1,64 +0,0 @@
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
@@ -1,33 +0,0 @@
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
+4 -11
View File
@@ -1,27 +1,21 @@
require 'securerandom' require 'securerandom'
class Api::V1::UsersController < Api::V1::BaseController 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 def password_reset
if can? :manage, User if can? :manage, User
user = User.find_by_id(params[:user_id]) user = User.find_by_id(params[:user_id])
render :json => { "errors" => [NOT_FOUND]}, :status => 404 and return if user.nil? render :json => { "error" => "User not found"}, :status => 404 and return if user.nil?
render :json => { "errors" => [NOT_ALLOWED]}, :status => 403 and return if user.id == current_user.id render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_user.id
new_pass = SecureRandom.hex[0,PASS_LENGTH] new_pass = SecureRandom.hex[0,8]
user.password = new_pass user.password = new_pass
user.save user.save
render :json => { "password" => new_pass}, :status => 200 and return render :json => { "password" => new_pass}, :status => 200 and return
else else
render :json => { "errors" => [CANNOT_MANAGE]}, :status => 403 and return render :json => { "error" => "You do not have the permission"}, :status => 403 and return
end end
end end
=begin Is this here by accident? Commenting out for now (1/30/14)
def checkout def checkout
#must use @current_user since user may not have signed in #must use @current_user since user may not have signed in
if !@current_user.checked_in? if !@current_user.checked_in?
@@ -31,5 +25,4 @@ class Api::V1::UsersController < Api::V1::BaseController
render :nothing => true, :status => 204 and return render :nothing => true, :status => 204 and return
end end
end end
=end
end end
-16
View File
@@ -1,16 +0,0 @@
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
View File
@@ -1,7 +0,0 @@
class PanelController < ApplicationController
def index
render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke"
end
end
+2 -1
View File
@@ -1,6 +1,7 @@
class SiteController < ApplicationController class SiteController < ApplicationController
def index def index
@bike = current_user.bike render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application"
end end
end end
-5
View File
@@ -1,5 +0,0 @@
class TaskListsController < AuthenticatedController
def edit
@task_list = TaskList.find_by_id(params[:id])
end
end
@@ -1,16 +0,0 @@
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
+7 -7
View File
@@ -16,13 +16,13 @@ class Bike < ActiveRecord::Base
validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true } validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
validates :serial_number, :length => { :minimum => 3 } validates :serial_number, :length => { :minimum => 3 }
validates :model, :length => { :maximum => 50 } validates :model, :length => { :maximum => 50 }
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" } validates :bike_brand_id, :presence => true
#validates :color, :presence => true validates :color, :presence => true
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" } validates :bike_style_id, :presence => true
validates :seat_tube_height, :presence => true, :numericality => true validates :seat_tube_height, :presence => true
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" } validates :bike_wheel_size_id, :presence => true
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" } validates :bike_condition_id, :presence => true
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" } validates :bike_purpose_id, :presence => true
self.per_page = 15 self.per_page = 15
+1 -1
View File
@@ -1,5 +1,5 @@
class Task < ActiveRecord::Base class Task < ActiveRecord::Base
attr_accessible :task, :notes, :done, :task_list_id attr_accessible :task, :notes, :done
belongs_to :task_list belongs_to :task_list
+1 -1
View File
@@ -4,7 +4,7 @@ class TaskList < ActiveRecord::Base
attr_accessible :item_id, :item_type, :name attr_accessible :item_id, :item_type, :name
belongs_to :item, :polymorphic => true belongs_to :item, :polymorphic => true
has_many :tasks, order: "id ASC" has_many :tasks
after_save :create_default_bike_tasks after_save :create_default_bike_tasks
-27
View File
@@ -1,27 +0,0 @@
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
+19 -27
View File
@@ -47,8 +47,7 @@ class User < ActiveRecord::Base
### TODO methods below probably belong somewhere else ### TODO methods below probably belong somewhere else
def completed_build_bikes def completed_build_bikes
#default BUILDBIKE/CLASS ID is 5 purpose_id = BikePurpose.find_by_purpose("BUILDBIKE").id
purpose_id = 5
Bike.find_by_sql(" Bike.find_by_sql("
SELECT * SELECT *
FROM bikes FROM bikes
@@ -65,16 +64,15 @@ class User < ActiveRecord::Base
end end
def total_credits_spent def total_credits_spent
log_action_id = 1 #TIME log_action = ::ActsAsLoggable::TransactionAction.find_by_action("TIME")
transaction_logs. transaction_logs.
where( "log_action_id = ? AND log_action_type = ?", where( "log_action_id = ? AND log_action_type = ?",
log_action_id, ::ActsAsLoggable::TransactionAction.to_s). log_action.id, log_action.class.to_s).
sum{ |r| r.description.to_i }.round(2) sum{ |r| r.description.to_i }.round(2)
end end
def total_earned_credits def total_earned_credits
volunteer_id = 1 log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
staff_id = 3
# Find the first credit conversion which has a created_at date before the # 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 # log's created_at date and join it to the log's row so we can calculate
@@ -83,7 +81,7 @@ class User < ActiveRecord::Base
# #
# The DISTINCT ON, and ORDER BY are important to getting the # The DISTINCT ON, and ORDER BY are important to getting the
# single conversion rate that applies to the respective log. # 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, SELECT DISTINCT ON (logs.created_at) start_date, end_date,
conversion.conversion, conversion.created_at conversion.conversion, conversion.created_at
FROM logs FROM logs
@@ -91,26 +89,23 @@ class User < ActiveRecord::Base
SELECT conversion, created_at SELECT conversion, created_at
FROM credit_conversions FROM credit_conversions
) AS conversion ON logs.created_at > conversion.created_at ) AS conversion ON logs.created_at > conversion.created_at
WHERE logs.loggable_id = :id WHERE logs.loggable_id = #{self.id}
AND logs.loggable_type = 'User' AND logs.loggable_type = 'User'
AND (log_action_id IN (:credit_actions) AND log_action_type = :log_action_type) 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", 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) sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2)
end end
def total_hours def total_hours
log_action_id = 4 #CHECKIN log_action = ::ActsAsLoggable::UserAction.find_by_action("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) 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)
end end
def current_month_hours def current_month_hours
log_action_id = 4 #CHECKIN log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month #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) 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, ::ActsAsLoggable::UserAction.to_s) logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s)
.where( :start_date => current_month_range) .where( :start_date => current_month_range)
.where( :end_date => current_month_range) .where( :end_date => current_month_range)
.sum { |l| (l.end_date - l.start_date)/3600 } .sum { |l| (l.end_date - l.start_date)/3600 }
@@ -118,31 +113,28 @@ class User < ActiveRecord::Base
end end
def checked_in? def checked_in?
#default CHECKIN log action is id, yea yea should be a constant log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
log_action_id = 4 checked = logs.where( log_action_id: log_action.id).
checked = logs.where( log_action_id: log_action_id).
where("start_date >= ?", Time.zone.now.beginning_of_day). where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date") where("start_date = end_date")
!checked.empty? !checked.empty?
end end
def checkin def checkin
#default CHECKIN log action is id, yea yea should be a constant log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
log_action_id = 4
time = Time.now time = Time.now
logs.create( logger_id: self.id, logs.create( logger_id: self.id,
logger_type: self.class.to_s, logger_type: self.class.to_s,
start_date: time, start_date: time,
end_date: time, end_date: time,
log_action_id: log_action_id, log_action_id: log_action.id,
log_action_type: ::ActsAsLoggable::UserAction.to_s) log_action_type: log_action.class.to_s)
save save
end end
def checkout def checkout
#default CHECKIN log action is id, yea yea should be a constant log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
log_action_id = 4 checked = logs.where( log_action_id: log_action.id).
checked = logs.where( log_action_id: log_action_id).
where("start_date >= ?", Time.zone.now.beginning_of_day). where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date").first where("start_date = end_date").first
checked.end_date = Time.now checked.end_date = Time.now
@@ -1,4 +0,0 @@
json.bikes [@bike] do |bike|
json.array! bike
json.set! :href, api_bike_path(bike)
end
@@ -1,3 +0,0 @@
json.bikes [@bike] do |bike|
json.array! bike
end
@@ -1,15 +0,0 @@
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
@@ -1,3 +0,0 @@
json.tasks [@tasks.map{|x| x[:record]}] do |task|
json.array! task
end
@@ -1,4 +0,0 @@
json.time_entries [@time_entry] do |time_entry|
json.array! time_entry
#json.set! :href, api_time_entry_path(time_entry)
end
-62
View File
@@ -1,62 +0,0 @@
%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
View File
@@ -1,27 +0,0 @@
- 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
+5 -12
View File
@@ -1,28 +1,21 @@
<%- if controller_name != 'sessions' %> <%- if controller_name != 'sessions' %>
<p> <%= link_to "Sign in", new_session_path(resource_name) %><br />
<%= link_to "Sign in", new_session_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<% end -%> <% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %> <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<p> <%= link_to "Sign up", new_registration_path(resource_name) %><br />
<%= link_to "Sign up", new_registration_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<% end -%> <% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<p> <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<% end -%> <% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<p> <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<% end -%> <% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%- 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 -%> <% end -%>
<%- if devise_mapping.omniauthable? %> <%- if devise_mapping.omniauthable? %>
+4 -8
View File
@@ -1,16 +1,12 @@
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
<h2>Forgot your password?</h2> <h2>Forgot your password?</h2>
<br>
<br>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %> <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %> <%= devise_error_messages! %>
<div class="form-group"> <div><%= f.label :email %><br />
<%= f.email_field :email, placeholder: "Email", class: "form-control input-lg" %> <%= f.email_field :email %></div>
</div>
<div><%= f.submit "Reset Password", class:"btn btn-lg btn-primary"%></div> <div><%= f.submit "Send me reset password instructions" %></div>
<% end %> <% end %>
<%= render "links" %> <%= render "links" %>
@@ -1,14 +1,25 @@
%p %fieldset
%fieldset .control-group
.form-group = f.label :addrStreet1, :class => "control-label"
= f.text_field :addrStreet1, placeholder: "Street Address Line 1", :class => "form-control input-lg" .controls
.form-group = f.text_field :addrStreet1, :class => "control-label"
= f.text_field :addrStreet2, placeholder: "Street Address Line 2",:class => "form-control input-lg" .control-group
.form-group = f.label :addrStreet2, :class => "control-label"
= f.text_field :addrCity, placeholder: "City", :class => "form-control input-lg" .controls
.form-group = f.text_field :addrStreet2, :class => "control-label"
= f.text_field :addrState, placeholder: "State Abbreviation", :class => "form-control input-lg" .control-group
.form-group = f.label :addrCity, :class => "control-label"
= f.text_field :addrZip, placeholder: "Zip Code", :class => "form-control input-lg" .controls
.form-group = f.text_field :addrCity, :class => "control-label"
= f.text_field :phone, placeholder: "Phone", :class => "form-control input-lg" .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"
+28 -15
View File
@@ -1,23 +1,36 @@
= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" = stylesheet_link_tag "bootstrap_and_overrides", :media => "all"
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :class => "form-horizontal"}) do |f|
= devise_error_messages! = devise_error_messages!
.controls .controls
%h2 Sign up %h2 Sign up
.form-group .control-group
= f.text_field :username, placeholder: "Username", :class => "form-control input-lg" = f.label :username, :class => "control-label"
.form-group .controls
= f.text_field :first_name, placeholder: "First Name", :class => "form-control input-lg" = f.text_field :username
.form-group .control-group
= f.text_field :last_name, placeholder: "Last Name", :class => "form-control input-lg" = f.label :first_name, :class => "control-label"
.form-group .controls
= f.email_field :email, placeholder: "E-mail", :class => "form-control input-lg" = 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
- profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles - profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles
= f.fields_for :user_profiles, profile_builder do |builder| = f.fields_for :user_profiles, profile_builder do |builder|
= render 'user_profile_fields', f: builder = render 'user_profile_fields', f: builder
.form-group .control-group
= f.password_field :password, placeholder: "Password", :class => "form-control input-lg" = f.label :password, :class => "control-label"
.form-group .controls
= f.password_field :password_confirmation, placeholder: "Password Confirmation", :class => "form-control input-lg" = f.password_field :password
.form-group .control-group
= f.submit "Sign up", class:"btn btn-lg btn-primary" = f.label :password_confirmation, :class => "control-label"
.controls
= f.password_field :password_confirmation
.control-group
.controls
= f.submit "Sign up"
= render "links" = render "links"
+15 -25
View File
@@ -5,44 +5,34 @@
<br> <br>
<br> <br>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<div class="form-group"> <div><%= f.label :username%><br />
<%= f.text_field :username, placeholder: "Username", class: "form-control input-lg"%> <%= f.text_field :username%></div>
</div>
<div class="form-group"> <div><%= f.label :password %><br />
<%= f.password_field :password, placeholder: "Password", class: "form-control input-lg" %> <%= f.password_field :password %></div>
</div>
<div class="form-group">
<% if devise_mapping.rememberable? -%> <% if devise_mapping.rememberable? -%>
<label> <div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
<%= f.check_box :remember_me %> Remember Me
</label>
<% end -%> <% end -%>
</div>
<div class="form-group"> <div><%= f.submit "Sign in" %></div>
<p>
<%= f.submit "Sign in", class:"btn btn-lg btn-primary" %> <div id="checkin_menu"style="display:none;">
</p> <input id="checkin" name="checkin" type="button" value="CHECK IN">
<p> Or quickly... </p> <input id="checkout" name="checkout" type="button" value="CHECK OUT">
<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> </div>
<% end %> <% end %>
<%= render "links" %> <%= render "links" %>
<% if Rails.env.development? %> <% if Rails.env.development? %>
<p>
<% User.all.each do |user| %> <% User.all.each do |user| %>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= f.hidden_field :username, :value => user.username%> <%= f.hidden_field :username, :value => user.username%></div>
<%= f.hidden_field :password, :value => 'password' %> <%= f.hidden_field :password, :value => 'password' %></div>
<%= f.submit "Sign in as #{user.username}", class:"btn btn-info" %> <div><%= f.submit "Sign in as #{user.username}" %></div>
<% end %> <% end %>
<% end %> <% end %>
</p>
<% end %> <% end %>
+4 -5
View File
@@ -2,15 +2,14 @@
%html{:lang => "en"} %html{:lang => "en"}
%head %head
%meta{:charset => "utf-8"}/ %meta{:charset => "utf-8"}/
%meta{ name: "viewport", content: "width=device-width", "initial-scale" => "1.0"}
%title= content_for?(:title) ? yield(:title) : "Velocipede" %title= content_for?(:title) ? yield(:title) : "Velocipede"
= load_netzke
= csrf_meta_tags = csrf_meta_tags
= stylesheet_link_tag "bootstrap_and_overrides", "datepicker", "bootstrap-timepicker", :media => "all"
/[if lt IE 9] /[if lt IE 9]
= javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js" = javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js"
:css :css
body { body {
padding-top: 5px; padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
} }
.x-boundlist-item { .x-boundlist-item {
white-space: nowrap; white-space: nowrap;
@@ -25,11 +24,11 @@
- if flash[:alert] - if flash[:alert]
%p{:class => 'alert'}= flash[:alert] %p{:class => 'alert'}= flash[:alert]
.row .row
.span12 .span13
= yield = yield
%footer %footer
%p &copy; BikeShed #{Time.now.year} %p &copy; Velocipede 2013
= javascript_include_tag "application" = javascript_include_tag "application"
= javascript_include_tag params[:controller] = javascript_include_tag params[:controller]
-11
View File
@@ -1,11 +0,0 @@
!!! 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
View File
@@ -1,21 +0,0 @@
= 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
View File
@@ -1,16 +0,0 @@
%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
View File
@@ -1,44 +0,0 @@
%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
View File
@@ -1,42 +0,0 @@
%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" => "#{"."}"}
+1 -1
View File
@@ -10,7 +10,7 @@ development:
# Do not set this db to the same as development or production. # Do not set this db to the same as development or production.
test: test:
adapter: postgresql adapter: postgresql
database: velocipede_test database: velocipede
username: velocipede username: velocipede
password: password:
host: 127.0.0.1 host: 127.0.0.1
+3 -4
View File
@@ -222,12 +222,11 @@ Devise.setup do |config|
# end # end
end end
unless Rails.env.test? #Check in the user if they sign in. (Devise uses Warden)
#Check in the user if they sign in. (Devise uses Warden) Warden::Manager.after_set_user do |user,auth,opts|
Warden::Manager.after_set_user do |user,auth,opts|
# this essentially gets called after every netzke request, but alas, # this essentially gets called after every netzke request, but alas,
# only using the after_authenticaion callback doesn't get fired after # only using the after_authenticaion callback doesn't get fired after
# user creation. # user creation.
user.checkin unless user.checked_in? user.checkin unless user.checked_in?
end
end end
+1 -22
View File
@@ -4,34 +4,13 @@ Velocipede::Application.routes.draw do
netzke netzke
root :to => 'site#index' 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 # API Routes
scope 'api', :module => :api, defaults: {format: :json} do scope 'api', :module => :api do
scope 'v1', :module => :v1 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 '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
end end
@@ -1,15 +0,0 @@
class AddTimezonesToLogs < ActiveRecord::Migration
def up
change_table :logs do |t|
t.change :start_date, :timestamptz
t.change :end_date, :timestamptz
end
end
def down
change_table :logs do |t|
t.change :start_date, :timestamp
t.change :end_date, :timestamp
end
end
end
+3 -3
View File
@@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20140504154355) do ActiveRecord::Schema.define(:version => 20131019170248) do
create_table "bike_actions", :force => true do |t| create_table "bike_actions", :force => true do |t|
t.string "action", :limit => 128, :null => false t.string "action", :limit => 128, :null => false
@@ -47,8 +47,8 @@ ActiveRecord::Schema.define(:version => 20140504154355) do
end end
create_table "bike_wheel_sizes", :force => true do |t| create_table "bike_wheel_sizes", :force => true do |t|
t.integer "twmm" t.string "twmm"
t.integer "rdmm" t.string "rdmm"
t.string "twin" t.string "twin"
t.string "rdin" t.string "rdin"
t.string "twfr" t.string "twfr"
-3
View File
@@ -7,6 +7,3 @@ hybrid:
road: road:
id: 3 id: 3
style: ROAD style: ROAD
other:
id: 4
style: OTHER
+1 -1
View File
@@ -41,7 +41,7 @@ if Rails.env.development?
#create fake bikes #create fake bikes
if Bike.all.empty? if Bike.all.empty?
42.times do |n| 42.times do |n|
FactoryGirl.create(:seed_bike) FactoryGirl.create(:bike)
end end
end end
elsif Rails.env.production? elsif Rails.env.production?
@@ -1,134 +0,0 @@
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
@@ -1,79 +0,0 @@
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
@@ -1,52 +0,0 @@
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
View File
@@ -1,100 +0,0 @@
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
@@ -1,117 +0,0 @@
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
@@ -1,72 +0,0 @@
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
View File
@@ -1,5 +0,0 @@
FactoryGirl.define do
factory :bike_brand do
brand {Faker::Commerce.product_name}
end
end
-5
View File
@@ -1,5 +0,0 @@
FactoryGirl.define do
factory :bike_condition do
condition "POOR"
end
end
-5
View File
@@ -1,5 +0,0 @@
FactoryGirl.define do
factory :bike_purpose do
purpose "SHOP"
end
end
-5
View File
@@ -1,5 +0,0 @@
FactoryGirl.define do
factory :bike_style do
style {Faker::Commerce.product_name}
end
end
-12
View File
@@ -1,12 +0,0 @@
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
-17
View File
@@ -2,23 +2,6 @@
FactoryGirl.define do FactoryGirl.define do
factory :bike 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(:shop_id) {|n| n}
sequence :serial_number do |n| sequence :serial_number do |n|
"#{Faker::Code.isbn}-#{n}" "#{Faker::Code.isbn}-#{n}"
-7
View File
@@ -1,7 +0,0 @@
FactoryGirl.define do
factory :task_list do
item_id { FactoryGirl.create(:bike).id }
item_type "Bike"
name { Faker::Lorem.words.join(" ")}
end
end
-7
View File
@@ -1,7 +0,0 @@
FactoryGirl.define do
factory :task do
task_list { FactoryGirl.create(:task_list) }
done false
task { Faker::Lorem.words(7).join(" ")}
end
end
-10
View File
@@ -1,10 +0,0 @@
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
-4
View File
@@ -36,9 +36,5 @@ FactoryGirl.define do
end end
end end
factory :user_with_bike do
bike { FactoryGirl.create(:bike) }
end
end end
end end
-40
View File
@@ -1,40 +0,0 @@
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
-34
View File
@@ -1,34 +0,0 @@
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
View File
@@ -1,22 +0,0 @@
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
View File
@@ -1,48 +0,0 @@
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,11 +1,10 @@
require 'spec_helper' require 'spec_helper'
describe "New User Registrations" do describe "New User Registrations" do
it 'should have a link to sign up on the homepage' do it 'should have a link to sign up on the homepage' do
visit root_path visit root_path
page.should have_link 'Sign up' page.should have_link 'Register'
click_link 'Sign up' click_link 'Register'
current_path.should == new_user_registration_path current_path.should == new_user_registration_path
end end
@@ -14,29 +13,30 @@ describe "New User Registrations" do
visit new_user_registration_path visit new_user_registration_path
end end
it 'should have the additional user fields on the registration page' do it 'should have the additional user fields on the registration page' do
page.should have_field 'user_first_name' page.should have_field 'First name'
page.should have_field 'user_last_name' page.should have_field 'Last name'
page.should have_field 'user_email' page.should have_field 'Nickname'
page.should have_field 'user_password' page.should have_field 'Email'
page.should have_field 'user_password_confirmation' page.should have_field 'Password'
page.should have_field 'Password confirmation'
page.should have_button 'Sign up' page.should have_button 'Sign up'
end end
context 'required non-devise fields' do context 'required non-devise fields' do
before do before do
fill_in 'user_email', :with => 'FF@example.com' fill_in 'Email', :with => 'FF@example.com'
fill_in 'user_password', :with => 'password' fill_in 'Password', :with => 'password'
fill_in 'user_password_confirmation', :with => 'password' fill_in 'Password confirmation', :with => 'password'
end end
it 'should require first name' do it 'should require first name' do
fill_in 'user_last_name', :with => 'Footer' fill_in 'Last name', :with => 'Footer'
click_button 'Sign up' click_button 'Sign up'
page.should have_content "First name can't be blank" page.should have_content "First name can't be blank"
end end
it 'should require last name' do it 'should require last name' do
fill_in 'user_first_name', :with => 'Frank' fill_in 'First name', :with => 'Frank'
click_button 'Sign up' click_button 'Sign up'
page.should have_content "Last name can't be blank" page.should have_content "Last name can't be blank"
end end
+3 -5
View File
@@ -1,7 +1,6 @@
require 'rubygems' require 'rubygems'
require 'spork' require 'spork'
require 'rails' require 'rails'
require 'capybara/poltergeist'
#uncomment the following line to use spork with the debugger #uncomment the following line to use spork with the debugger
#require 'spork/ext/ruby-debug' #require 'spork/ext/ruby-debug'
@@ -20,13 +19,12 @@ Spork.prefork do
require 'rspec/rails' require 'rspec/rails'
require 'rspec/autorun' require 'rspec/autorun'
require 'capybara/rspec' require 'capybara/rspec'
require 'turnip/capybara'
# Requires supporting ruby files with custom matchers and macros, etc, # Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories. # in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
Capybara.javascript_driver = :poltergeist
RSpec.configure do |config| RSpec.configure do |config|
# ## Mock Framework # ## Mock Framework
# #
@@ -68,19 +66,19 @@ Spork.prefork do
config.infer_base_class_for_anonymous_controllers = false config.infer_base_class_for_anonymous_controllers = false
config.include FactoryGirlStepHelpers config.include FactoryGirlStepHelpers
config.include FactoryGirl::Syntax::Methods config.include FactoryGirl::Syntax::Methods
config.include ApiTestHelpers
config.include Devise::TestHelpers, type: :controller
#allows :focus tag to only run specific tests #allows :focus tag to only run specific tests
config.treat_symbols_as_metadata_keys_with_true_values = true config.treat_symbols_as_metadata_keys_with_true_values = true
config.filter_run :focus => true config.filter_run :focus => true
config.run_all_when_everything_filtered = true config.run_all_when_everything_filtered = true
end end
end end
Spork.each_run do Spork.each_run do
# This code will be run each time you run your specs. # This code will be run each time you run your specs.
#Reload Factories #Reload Factories
#FYI make sure that Factories use strings instead of class constants #FYI make sure that Factories use strings instead of class constants
FactoryGirl.reload FactoryGirl.reload
-5
View File
@@ -1,5 +0,0 @@
module ApiTestHelpers
def api_submit_json(user, json_hash)
json_hash.merge({username: user.username, password: user.password})
end
end