mirror of
https://github.com/fspc/BikeShed-1.git
synced 2026-09-17 00:41:38 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
523135ed9f | ||
|
|
5e95ce542c | ||
|
|
a8e0c87d37 | ||
|
|
1b2c0fa378 | ||
|
|
785e096aea | ||
|
|
5c4007a1be | ||
|
|
b9b6ed5ca0 | ||
|
|
0605d0f0d9 | ||
|
|
071815b9fd | ||
|
|
ca3026e7c5 | ||
|
|
5e0d678ea1 | ||
|
|
d94c436ba5 | ||
|
|
24facd4dee | ||
|
|
894423e66f | ||
|
|
4ce4bd6597 | ||
|
|
2e21e327ca | ||
|
|
57d72c04a7 | ||
|
|
4224f0de40 | ||
|
|
3d71add637 | ||
|
|
7dd556d718 | ||
|
|
338905878e | ||
|
|
7897795e8b | ||
|
|
00511c5b64 | ||
|
|
1191014471 | ||
|
|
c0a9d8c1e5 | ||
|
|
82f38fe35c | ||
|
|
2321197973 | ||
|
|
069a11f480 | ||
|
|
b30203b750 | ||
|
|
3e56f2eb19 | ||
|
|
2d65ebb45f | ||
|
|
80b2b7ff60 | ||
|
|
98b136e4f4 | ||
|
|
9527725e07 | ||
|
|
8f637348cf | ||
|
|
062ed9c2d1 | ||
|
|
124ce5901d | ||
|
|
1f9322ff3d | ||
|
|
b1ddcfb867 | ||
|
|
64e907b6f7 | ||
|
|
e2f442889e | ||
|
|
3b904f95f2 | ||
|
|
656f0f12e4 | ||
|
|
30d818ac87 | ||
|
|
dc0a567a27 | ||
|
|
523046b042 | ||
|
|
9231c91e6c | ||
|
|
14869cf8b6 | ||
|
|
b88bf545d2 | ||
|
|
20964eb268 | ||
|
|
b90e101d69 | ||
|
|
7b11da9583 | ||
|
|
64d4186b57 | ||
|
|
c6a3ed9991 | ||
|
|
d0c05a0244 | ||
|
|
55974feb4c | ||
|
|
e867366525 | ||
|
|
fb02646092 | ||
|
|
75832a3910 | ||
|
|
2d09aba26a | ||
|
|
e694dfbddd | ||
|
|
e19b80faec | ||
|
|
38a716b132 | ||
|
|
8110baf255 | ||
|
|
9fa0017d36 | ||
|
|
e0041662d3 | ||
|
|
c575fface9 | ||
|
|
83a4e4e9c1 | ||
|
|
f17eba810e | ||
|
|
4b5133fd02 | ||
|
|
9edb90203c | ||
|
|
8e112ca936 | ||
|
|
8c5ff986a2 | ||
|
|
f10b2adad6 | ||
|
|
bd6872413e | ||
|
|
a8bdb52536 | ||
|
|
0f890e12e9 | ||
|
|
61472e0fb6 | ||
|
|
ecb5083f1c | ||
|
|
a84b0e0fe7 | ||
|
|
2b863b8d8a | ||
|
|
a35bd2f22b | ||
|
|
c9d2da3e50 | ||
|
|
df3c29a899 | ||
|
|
a11f5a1b91 | ||
|
|
2a6e10ee9d | ||
|
|
ed1f2e6be4 | ||
|
|
852a00881e | ||
|
|
8cc72c9f48 | ||
|
|
90b3043d67 | ||
|
|
2ad9fab243 | ||
|
|
43db139dc7 | ||
|
|
2b72094f00 | ||
|
|
1aa3b16636 | ||
|
|
af05d1a0c6 | ||
|
|
076a59ab45 |
@@ -0,0 +1 @@
|
|||||||
|
velocipede
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ruby-1.9.3-p374
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
|
||||||
# development environment upon cd'ing into the directory
|
|
||||||
|
|
||||||
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
|
||||||
environment_id="ruby-1.9.3-p374@velocipede"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Uncomment following line if you want options to be set only for given project.
|
|
||||||
#
|
|
||||||
# PROJECT_JRUBY_OPTS=( --1.9 )
|
|
||||||
|
|
||||||
#
|
|
||||||
# First we attempt to load the desired environment directly from the environment
|
|
||||||
# file. This is very fast and efficient compared to running through the entire
|
|
||||||
# CLI and selector. If you want feedback on which environment was used then
|
|
||||||
# insert the word 'use' after --create as this triggers verbose mode.
|
|
||||||
#
|
|
||||||
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
|
||||||
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
|
||||||
then
|
|
||||||
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
|
||||||
|
|
||||||
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
|
||||||
then
|
|
||||||
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# If the environment file has not yet been created, use the RVM CLI to select.
|
|
||||||
if ! rvm --create use "$environment_id"
|
|
||||||
then
|
|
||||||
echo "Failed to create RVM environment '${environment_id}'."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
|
||||||
# it be automatically loaded. Uncomment the following and adjust the filename if
|
|
||||||
# necessary.
|
|
||||||
#
|
|
||||||
# filename=".gems"
|
|
||||||
# if [[ -s "$filename" ]]
|
|
||||||
# then
|
|
||||||
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
|
||||||
# fi
|
|
||||||
|
|
||||||
# If you use bundler, this might be useful to you:
|
|
||||||
# if command -v bundle && [[ -s Gemfile ]]
|
|
||||||
# then
|
|
||||||
# bundle install
|
|
||||||
# fi
|
|
||||||
|
|
||||||
|
|
||||||
@@ -14,8 +14,9 @@ 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'
|
gem 'pg', '~> 0.17.1'
|
||||||
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.
|
||||||
@@ -30,21 +31,21 @@ group :assets do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'rspec-rails', '~> 2.8.1'
|
gem 'rspec-rails', '~> 2.14.0'
|
||||||
gem 'factory_girl_rails', '~> 1.2'
|
gem 'factory_girl_rails', '~> 1.2'
|
||||||
gem 'pry', '~> 0.9.8'
|
gem 'pry', '~> 0.9.8'
|
||||||
gem 'faker'
|
gem 'faker', '~> 1.2.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'shoulda-matchers', '~> 1.0.0'
|
gem 'shoulda-matchers', '~> 1.0.0'
|
||||||
gem 'capybara', '~> 1.1.2'
|
gem 'capybara', '~> 2.2.1'
|
||||||
gem 'turnip', '~> 0.3.0'
|
gem 'poltergeist', '~> 1.5.0'
|
||||||
gem 'database_cleaner'
|
gem 'database_cleaner', '~> 1.2.0'
|
||||||
gem 'launchy'
|
gem 'launchy', '~> 2.4.2'
|
||||||
gem 'spork'
|
gem 'spork', '~> 0.9.2'
|
||||||
#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'
|
gem 'guard-spork', '~> 1.5.1'
|
||||||
gem 'guard-rspec'
|
gem 'guard-rspec', '~> 4.2.6'
|
||||||
end
|
end
|
||||||
|
|||||||
+92
-82
@@ -36,42 +36,43 @@ 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.3)
|
addressable (2.3.5)
|
||||||
arel (3.0.2)
|
arel (3.0.3)
|
||||||
bcrypt-ruby (3.0.1)
|
bcrypt-ruby (3.1.2)
|
||||||
bootstrap-will_paginate (0.0.9)
|
bootstrap-will_paginate (0.0.10)
|
||||||
will_paginate
|
will_paginate
|
||||||
builder (3.0.4)
|
builder (3.0.4)
|
||||||
cancan (1.6.9)
|
cancan (1.6.10)
|
||||||
capybara (1.1.4)
|
capybara (2.2.1)
|
||||||
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)
|
||||||
selenium-webdriver (~> 2.0)
|
xpath (~> 2.0)
|
||||||
xpath (~> 0.1.4)
|
celluloid (0.15.2)
|
||||||
childprocess (0.3.9)
|
timers (~> 1.1.0)
|
||||||
|
childprocess (0.4.0)
|
||||||
ffi (~> 1.0, >= 1.0.11)
|
ffi (~> 1.0, >= 1.0.11)
|
||||||
coderay (1.0.9)
|
cliver (0.3.2)
|
||||||
|
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.6.2)
|
coffee-script-source (1.7.0)
|
||||||
commonjs (0.2.6)
|
commonjs (0.2.7)
|
||||||
database_cleaner (0.9.1)
|
database_cleaner (1.2.0)
|
||||||
decent_exposure (1.0.2)
|
decent_exposure (1.0.2)
|
||||||
devise (2.0.5)
|
devise (2.0.6)
|
||||||
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.1.3)
|
diff-lcs (1.2.5)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
execjs (1.4.0)
|
execjs (2.0.2)
|
||||||
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)
|
||||||
@@ -79,19 +80,18 @@ 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.6.0)
|
ffi (1.9.3)
|
||||||
formatador (0.2.4)
|
formatador (0.2.4)
|
||||||
gherkin (2.11.6)
|
guard (2.4.0)
|
||||||
json (>= 1.7.6)
|
|
||||||
guard (1.7.0)
|
|
||||||
formatador (>= 0.2.4)
|
formatador (>= 0.2.4)
|
||||||
listen (>= 0.6.0)
|
listen (~> 2.1)
|
||||||
lumberjack (>= 1.0.2)
|
lumberjack (~> 1.0)
|
||||||
pry (>= 0.9.10)
|
pry (>= 0.9.12)
|
||||||
thor (>= 0.14.6)
|
thor (>= 0.18.1)
|
||||||
guard-rspec (1.2.1)
|
guard-rspec (4.2.6)
|
||||||
guard (>= 1.1)
|
guard (~> 2.1)
|
||||||
guard-spork (1.5.0)
|
rspec (>= 2.14, < 4.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,14 +101,17 @@ 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.1)
|
hike (1.2.3)
|
||||||
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.2.1)
|
jquery-rails (2.3.0)
|
||||||
railties (>= 3.0, < 5.0)
|
railties (>= 3.0, < 5.0)
|
||||||
thor (>= 0.14, < 2.0)
|
thor (>= 0.14, < 2.0)
|
||||||
json (1.7.7)
|
json (1.8.1)
|
||||||
launchy (2.2.0)
|
launchy (2.4.2)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
less (2.2.2)
|
less (2.2.2)
|
||||||
commonjs (~> 0.2.6)
|
commonjs (~> 0.2.6)
|
||||||
@@ -116,15 +119,18 @@ 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 (0.7.3)
|
listen (2.4.1)
|
||||||
lumberjack (1.0.3)
|
celluloid (>= 0.15.2)
|
||||||
mail (2.5.3)
|
rb-fsevent (>= 0.9.3)
|
||||||
i18n (>= 0.4.0)
|
rb-inotify (>= 0.9)
|
||||||
|
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.1)
|
method_source (0.8.2)
|
||||||
mime-types (1.22)
|
mime-types (1.25.1)
|
||||||
multi_json (1.8.2)
|
mini_portile (0.5.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)
|
||||||
@@ -133,12 +139,18 @@ GEM
|
|||||||
netzke-core (0.8.4)
|
netzke-core (0.8.4)
|
||||||
execjs
|
execjs
|
||||||
uglifier
|
uglifier
|
||||||
nokogiri (1.5.9)
|
nokogiri (1.6.1)
|
||||||
|
mini_portile (~> 0.5.0)
|
||||||
orm_adapter (0.0.7)
|
orm_adapter (0.0.7)
|
||||||
pg (0.15.1)
|
pg (0.17.1)
|
||||||
|
poltergeist (1.5.0)
|
||||||
|
capybara (~> 2.1)
|
||||||
|
cliver (~> 0.3.1)
|
||||||
|
multi_json (~> 1.0)
|
||||||
|
websocket-driver (>= 0.2.0)
|
||||||
polyglot (0.3.3)
|
polyglot (0.3.3)
|
||||||
pry (0.9.12)
|
pry (0.9.12.6)
|
||||||
coderay (~> 1.0.5)
|
coderay (~> 1.0)
|
||||||
method_source (~> 0.8)
|
method_source (~> 0.8)
|
||||||
slop (~> 3.4)
|
slop (~> 3.4)
|
||||||
rack (1.4.5)
|
rack (1.4.5)
|
||||||
@@ -163,31 +175,30 @@ 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.0.4)
|
rake (10.1.1)
|
||||||
rb-fsevent (0.9.3)
|
rb-fsevent (0.9.4)
|
||||||
|
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.8.0)
|
rspec (2.14.1)
|
||||||
rspec-core (~> 2.8.0)
|
rspec-core (~> 2.14.0)
|
||||||
rspec-expectations (~> 2.8.0)
|
rspec-expectations (~> 2.14.0)
|
||||||
rspec-mocks (~> 2.8.0)
|
rspec-mocks (~> 2.14.0)
|
||||||
rspec-core (2.8.0)
|
rspec-core (2.14.7)
|
||||||
rspec-expectations (2.8.0)
|
rspec-expectations (2.14.5)
|
||||||
diff-lcs (~> 1.1.2)
|
diff-lcs (>= 1.1.3, < 2.0)
|
||||||
rspec-mocks (2.8.0)
|
rspec-mocks (2.14.5)
|
||||||
rspec-rails (2.8.1)
|
rspec-rails (2.14.1)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
|
activemodel (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
railties (>= 3.0)
|
railties (>= 3.0)
|
||||||
rspec (~> 2.8.0)
|
rspec-core (~> 2.14.0)
|
||||||
rubyzip (0.9.9)
|
rspec-expectations (~> 2.14.0)
|
||||||
selenium-webdriver (2.31.0)
|
rspec-mocks (~> 2.14.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.4)
|
slop (3.4.7)
|
||||||
spork (0.9.2)
|
spork (0.9.2)
|
||||||
sprockets (2.2.2)
|
sprockets (2.2.2)
|
||||||
hike (~> 1.2)
|
hike (~> 1.2)
|
||||||
@@ -197,27 +208,25 @@ 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.3.6)
|
tilt (1.4.1)
|
||||||
treetop (1.4.12)
|
timers (1.1.0)
|
||||||
|
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.1.2)
|
uglifier (2.4.0)
|
||||||
execjs (>= 0.3.0)
|
execjs (>= 0.3.0)
|
||||||
multi_json (~> 1.0, >= 1.0.2)
|
json (>= 1.8.0)
|
||||||
warden (1.1.1)
|
warden (1.1.1)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
websocket (1.0.7)
|
websocket-driver (0.3.2)
|
||||||
will_paginate (3.0.4)
|
will_paginate (3.0.5)
|
||||||
xpath (0.1.4)
|
xpath (2.0.0)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.3)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
@@ -227,29 +236,30 @@ DEPENDENCIES
|
|||||||
acts_as_loggable!
|
acts_as_loggable!
|
||||||
bootstrap-will_paginate (~> 0.0.6)
|
bootstrap-will_paginate (~> 0.0.6)
|
||||||
cancan
|
cancan
|
||||||
capybara (~> 1.1.2)
|
capybara (~> 2.2.1)
|
||||||
coffee-rails (~> 3.2.1)
|
coffee-rails (~> 3.2.1)
|
||||||
database_cleaner
|
database_cleaner (~> 1.2.0)
|
||||||
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
|
faker (~> 1.2.0)
|
||||||
guard-rspec
|
guard-rspec (~> 4.2.6)
|
||||||
guard-spork
|
guard-spork (~> 1.5.1)
|
||||||
haml-rails (~> 0.3.4)
|
haml-rails (~> 0.3.4)
|
||||||
|
jbuilder (~> 2.0.3)
|
||||||
jquery-rails (~> 2.0)
|
jquery-rails (~> 2.0)
|
||||||
launchy
|
launchy (~> 2.4.2)
|
||||||
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
|
pg (~> 0.17.1)
|
||||||
|
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.8.1)
|
rspec-rails (~> 2.14.0)
|
||||||
shoulda-matchers (~> 1.0.0)
|
shoulda-matchers (~> 1.0.0)
|
||||||
spork
|
spork (~> 0.9.2)
|
||||||
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)
|
||||||
|
|||||||
@@ -1,3 +1,25 @@
|
|||||||
|
# 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. You can see those changes on this [branch]( https://github.com/spacemunkay/BikeShed/tree/mobile-ui)
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
1. use rvm
|
1. use rvm
|
||||||
@@ -5,6 +27,8 @@
|
|||||||
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.
|
||||||
@@ -19,8 +43,12 @@
|
|||||||
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`
|
1. Create your test database `createdb -U velocipede --owner=velocipede velocipede_test`
|
||||||
1. Update config with your database (velocipede), user (velocipede), and password.
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
1. Install phantomjs `brew install phantomjs`
|
||||||
|
1. Run tests with `rspec`
|
||||||
|
|
||||||
# Optional
|
# Optional
|
||||||
Add icons
|
Add icons
|
||||||
|
|||||||
@@ -12,4 +12,9 @@
|
|||||||
//
|
//
|
||||||
//= require jquery
|
//= require jquery
|
||||||
//= require jquery_ujs
|
//= require jquery_ujs
|
||||||
//= require custom_netzke_helpers
|
//= require twitter/bootstrap/bootstrap-button
|
||||||
|
//= require twitter/bootstrap/bootstrap-modal
|
||||||
|
//= require utils
|
||||||
|
//= require bootstrap-datepicker
|
||||||
|
//= require bootstrap-timepicker
|
||||||
|
//= require jquery-date-format
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
$('.btn').button();
|
||||||
|
|
||||||
|
$("#add_bike_submit").click(function(){
|
||||||
|
|
||||||
|
json_data = { bikes: [{
|
||||||
|
serial_number: $("#serial_number").val(),
|
||||||
|
bike_brand_id: parseInt($("#bike_brand_id").val()),
|
||||||
|
shop_id: parseInt($("#shop_id").val()),
|
||||||
|
model: $("#model").val(),
|
||||||
|
bike_style_id: parseInt($('input[name=bike_style]:checked').val()),
|
||||||
|
seat_tube_height: parseFloat($("#seat_tube_height").val()),
|
||||||
|
bike_condition_id: parseInt($('input[name=bike_condition]:checked').val()),
|
||||||
|
bike_purpose_id: 1,
|
||||||
|
bike_wheel_size_id: parseInt($("#bike_wheel_size_id").val()),
|
||||||
|
}]};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: $("#add_bike_submit").data("url"),
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify(json_data),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
window.location = data.bikes[0].id + "?add_bike=1";
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
+474
@@ -0,0 +1,474 @@
|
|||||||
|
/* =========================================================
|
||||||
|
* bootstrap-datepicker.js
|
||||||
|
* http://www.eyecon.ro/bootstrap-datepicker
|
||||||
|
* =========================================================
|
||||||
|
* Copyright 2012 Stefan Petre
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* ========================================================= */
|
||||||
|
|
||||||
|
!function( $ ) {
|
||||||
|
|
||||||
|
// Picker object
|
||||||
|
|
||||||
|
var Datepicker = function(element, options){
|
||||||
|
this.element = $(element);
|
||||||
|
this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
|
||||||
|
this.picker = $(DPGlobal.template)
|
||||||
|
.appendTo('body')
|
||||||
|
.on({
|
||||||
|
click: $.proxy(this.click, this)//,
|
||||||
|
//mousedown: $.proxy(this.mousedown, this)
|
||||||
|
});
|
||||||
|
this.isInput = this.element.is('input');
|
||||||
|
this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
|
||||||
|
|
||||||
|
if (this.isInput) {
|
||||||
|
this.element.on({
|
||||||
|
focus: $.proxy(this.show, this),
|
||||||
|
//blur: $.proxy(this.hide, this),
|
||||||
|
keyup: $.proxy(this.update, this)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (this.component){
|
||||||
|
this.component.on('click', $.proxy(this.show, this));
|
||||||
|
} else {
|
||||||
|
this.element.on('click', $.proxy(this.show, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
|
||||||
|
if (typeof this.minViewMode === 'string') {
|
||||||
|
switch (this.minViewMode) {
|
||||||
|
case 'months':
|
||||||
|
this.minViewMode = 1;
|
||||||
|
break;
|
||||||
|
case 'years':
|
||||||
|
this.minViewMode = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.minViewMode = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
|
||||||
|
if (typeof this.viewMode === 'string') {
|
||||||
|
switch (this.viewMode) {
|
||||||
|
case 'months':
|
||||||
|
this.viewMode = 1;
|
||||||
|
break;
|
||||||
|
case 'years':
|
||||||
|
this.viewMode = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.viewMode = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.startViewMode = this.viewMode;
|
||||||
|
this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
|
||||||
|
this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
|
||||||
|
this.onRender = options.onRender;
|
||||||
|
this.fillDow();
|
||||||
|
this.fillMonths();
|
||||||
|
this.update();
|
||||||
|
this.showMode();
|
||||||
|
};
|
||||||
|
|
||||||
|
Datepicker.prototype = {
|
||||||
|
constructor: Datepicker,
|
||||||
|
|
||||||
|
show: function(e) {
|
||||||
|
this.picker.show();
|
||||||
|
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
|
||||||
|
this.place();
|
||||||
|
$(window).on('resize', $.proxy(this.place, this));
|
||||||
|
if (e ) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (!this.isInput) {
|
||||||
|
}
|
||||||
|
var that = this;
|
||||||
|
$(document).on('mousedown', function(ev){
|
||||||
|
if ($(ev.target).closest('.datepicker').length == 0) {
|
||||||
|
that.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'show',
|
||||||
|
date: this.date
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function(){
|
||||||
|
this.picker.hide();
|
||||||
|
$(window).off('resize', this.place);
|
||||||
|
this.viewMode = this.startViewMode;
|
||||||
|
this.showMode();
|
||||||
|
if (!this.isInput) {
|
||||||
|
$(document).off('mousedown', this.hide);
|
||||||
|
}
|
||||||
|
//this.set();
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'hide',
|
||||||
|
date: this.date
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function() {
|
||||||
|
var formated = DPGlobal.formatDate(this.date, this.format);
|
||||||
|
if (!this.isInput) {
|
||||||
|
if (this.component){
|
||||||
|
this.element.find('input').prop('value', formated);
|
||||||
|
}
|
||||||
|
this.element.data('date', formated);
|
||||||
|
} else {
|
||||||
|
this.element.prop('value', formated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setValue: function(newDate) {
|
||||||
|
if (typeof newDate === 'string') {
|
||||||
|
this.date = DPGlobal.parseDate(newDate, this.format);
|
||||||
|
} else {
|
||||||
|
this.date = new Date(newDate);
|
||||||
|
}
|
||||||
|
this.set();
|
||||||
|
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
|
||||||
|
this.fill();
|
||||||
|
},
|
||||||
|
|
||||||
|
place: function(){
|
||||||
|
var offset = this.component ? this.component.offset() : this.element.offset();
|
||||||
|
this.picker.css({
|
||||||
|
top: offset.top + this.height,
|
||||||
|
left: offset.left
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function(newDate){
|
||||||
|
this.date = DPGlobal.parseDate(
|
||||||
|
typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
|
||||||
|
this.format
|
||||||
|
);
|
||||||
|
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
|
||||||
|
this.fill();
|
||||||
|
},
|
||||||
|
|
||||||
|
fillDow: function(){
|
||||||
|
var dowCnt = this.weekStart;
|
||||||
|
var html = '<tr>';
|
||||||
|
while (dowCnt < this.weekStart + 7) {
|
||||||
|
html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
|
||||||
|
}
|
||||||
|
html += '</tr>';
|
||||||
|
this.picker.find('.datepicker-days thead').append(html);
|
||||||
|
},
|
||||||
|
|
||||||
|
fillMonths: function(){
|
||||||
|
var html = '';
|
||||||
|
var i = 0
|
||||||
|
while (i < 12) {
|
||||||
|
html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
|
||||||
|
}
|
||||||
|
this.picker.find('.datepicker-months td').append(html);
|
||||||
|
},
|
||||||
|
|
||||||
|
fill: function() {
|
||||||
|
var d = new Date(this.viewDate),
|
||||||
|
year = d.getFullYear(),
|
||||||
|
month = d.getMonth(),
|
||||||
|
currentDate = this.date.valueOf();
|
||||||
|
this.picker.find('.datepicker-days th:eq(1)')
|
||||||
|
.text(DPGlobal.dates.months[month]+' '+year);
|
||||||
|
var prevMonth = new Date(year, month-1, 28,0,0,0,0),
|
||||||
|
day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
|
||||||
|
prevMonth.setDate(day);
|
||||||
|
prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
|
||||||
|
var nextMonth = new Date(prevMonth);
|
||||||
|
nextMonth.setDate(nextMonth.getDate() + 42);
|
||||||
|
nextMonth = nextMonth.valueOf();
|
||||||
|
var html = [];
|
||||||
|
var clsName,
|
||||||
|
prevY,
|
||||||
|
prevM;
|
||||||
|
while(prevMonth.valueOf() < nextMonth) {
|
||||||
|
if (prevMonth.getDay() === this.weekStart) {
|
||||||
|
html.push('<tr>');
|
||||||
|
}
|
||||||
|
clsName = this.onRender(prevMonth);
|
||||||
|
prevY = prevMonth.getFullYear();
|
||||||
|
prevM = prevMonth.getMonth();
|
||||||
|
if ((prevM < month && prevY === year) || prevY < year) {
|
||||||
|
clsName += ' old';
|
||||||
|
} else if ((prevM > month && prevY === year) || prevY > year) {
|
||||||
|
clsName += ' new';
|
||||||
|
}
|
||||||
|
if (prevMonth.valueOf() === currentDate) {
|
||||||
|
clsName += ' active';
|
||||||
|
}
|
||||||
|
html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
|
||||||
|
if (prevMonth.getDay() === this.weekEnd) {
|
||||||
|
html.push('</tr>');
|
||||||
|
}
|
||||||
|
prevMonth.setDate(prevMonth.getDate()+1);
|
||||||
|
}
|
||||||
|
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
|
||||||
|
var currentYear = this.date.getFullYear();
|
||||||
|
|
||||||
|
var months = this.picker.find('.datepicker-months')
|
||||||
|
.find('th:eq(1)')
|
||||||
|
.text(year)
|
||||||
|
.end()
|
||||||
|
.find('span').removeClass('active');
|
||||||
|
if (currentYear === year) {
|
||||||
|
months.eq(this.date.getMonth()).addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
html = '';
|
||||||
|
year = parseInt(year/10, 10) * 10;
|
||||||
|
var yearCont = this.picker.find('.datepicker-years')
|
||||||
|
.find('th:eq(1)')
|
||||||
|
.text(year + '-' + (year + 9))
|
||||||
|
.end()
|
||||||
|
.find('td');
|
||||||
|
year -= 1;
|
||||||
|
for (var i = -1; i < 11; i++) {
|
||||||
|
html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
|
||||||
|
year += 1;
|
||||||
|
}
|
||||||
|
yearCont.html(html);
|
||||||
|
},
|
||||||
|
|
||||||
|
click: function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
var target = $(e.target).closest('span, td, th');
|
||||||
|
if (target.length === 1) {
|
||||||
|
switch(target[0].nodeName.toLowerCase()) {
|
||||||
|
case 'th':
|
||||||
|
switch(target[0].className) {
|
||||||
|
case 'switch':
|
||||||
|
this.showMode(1);
|
||||||
|
break;
|
||||||
|
case 'prev':
|
||||||
|
case 'next':
|
||||||
|
this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
|
||||||
|
this.viewDate,
|
||||||
|
this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
|
||||||
|
DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
|
||||||
|
);
|
||||||
|
this.fill();
|
||||||
|
this.set();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'span':
|
||||||
|
if (target.is('.month')) {
|
||||||
|
var month = target.parent().find('span').index(target);
|
||||||
|
this.viewDate.setMonth(month);
|
||||||
|
} else {
|
||||||
|
var year = parseInt(target.text(), 10)||0;
|
||||||
|
this.viewDate.setFullYear(year);
|
||||||
|
}
|
||||||
|
if (this.viewMode !== 0) {
|
||||||
|
this.date = new Date(this.viewDate);
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'changeDate',
|
||||||
|
date: this.date,
|
||||||
|
viewMode: DPGlobal.modes[this.viewMode].clsName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.showMode(-1);
|
||||||
|
this.fill();
|
||||||
|
this.set();
|
||||||
|
break;
|
||||||
|
case 'td':
|
||||||
|
if (target.is('.day') && !target.is('.disabled')){
|
||||||
|
var day = parseInt(target.text(), 10)||1;
|
||||||
|
var month = this.viewDate.getMonth();
|
||||||
|
if (target.is('.old')) {
|
||||||
|
month -= 1;
|
||||||
|
} else if (target.is('.new')) {
|
||||||
|
month += 1;
|
||||||
|
}
|
||||||
|
var year = this.viewDate.getFullYear();
|
||||||
|
this.date = new Date(year, month, day,0,0,0,0);
|
||||||
|
this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
|
||||||
|
this.fill();
|
||||||
|
this.set();
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'changeDate',
|
||||||
|
date: this.date,
|
||||||
|
viewMode: DPGlobal.modes[this.viewMode].clsName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mousedown: function(e){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
showMode: function(dir) {
|
||||||
|
if (dir) {
|
||||||
|
this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
|
||||||
|
}
|
||||||
|
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.datepicker = function ( option, val ) {
|
||||||
|
return this.each(function () {
|
||||||
|
var $this = $(this),
|
||||||
|
data = $this.data('datepicker'),
|
||||||
|
options = typeof option === 'object' && option;
|
||||||
|
if (!data) {
|
||||||
|
$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
|
||||||
|
}
|
||||||
|
if (typeof option === 'string') data[option](val);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.datepicker.defaults = {
|
||||||
|
onRender: function(date) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$.fn.datepicker.Constructor = Datepicker;
|
||||||
|
|
||||||
|
var DPGlobal = {
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
clsName: 'days',
|
||||||
|
navFnc: 'Month',
|
||||||
|
navStep: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clsName: 'months',
|
||||||
|
navFnc: 'FullYear',
|
||||||
|
navStep: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clsName: 'years',
|
||||||
|
navFnc: 'FullYear',
|
||||||
|
navStep: 10
|
||||||
|
}],
|
||||||
|
dates:{
|
||||||
|
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
||||||
|
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||||
|
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
|
||||||
|
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
||||||
|
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||||
|
},
|
||||||
|
isLeapYear: function (year) {
|
||||||
|
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
|
||||||
|
},
|
||||||
|
getDaysInMonth: function (year, month) {
|
||||||
|
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
|
||||||
|
},
|
||||||
|
parseFormat: function(format){
|
||||||
|
var separator = format.match(/[.\/\-\s].*?/),
|
||||||
|
parts = format.split(/\W+/);
|
||||||
|
if (!separator || !parts || parts.length === 0){
|
||||||
|
throw new Error("Invalid date format.");
|
||||||
|
}
|
||||||
|
return {separator: separator, parts: parts};
|
||||||
|
},
|
||||||
|
parseDate: function(date, format) {
|
||||||
|
var parts = date.split(format.separator),
|
||||||
|
date = new Date(),
|
||||||
|
val;
|
||||||
|
date.setHours(0);
|
||||||
|
date.setMinutes(0);
|
||||||
|
date.setSeconds(0);
|
||||||
|
date.setMilliseconds(0);
|
||||||
|
if (parts.length === format.parts.length) {
|
||||||
|
var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
|
||||||
|
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
||||||
|
val = parseInt(parts[i], 10)||1;
|
||||||
|
switch(format.parts[i]) {
|
||||||
|
case 'dd':
|
||||||
|
case 'd':
|
||||||
|
day = val;
|
||||||
|
date.setDate(val);
|
||||||
|
break;
|
||||||
|
case 'mm':
|
||||||
|
case 'm':
|
||||||
|
month = val - 1;
|
||||||
|
date.setMonth(val - 1);
|
||||||
|
break;
|
||||||
|
case 'yy':
|
||||||
|
year = 2000 + val;
|
||||||
|
date.setFullYear(2000 + val);
|
||||||
|
break;
|
||||||
|
case 'yyyy':
|
||||||
|
year = val;
|
||||||
|
date.setFullYear(val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
date = new Date(year, month, day, 0 ,0 ,0);
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
},
|
||||||
|
formatDate: function(date, format){
|
||||||
|
var val = {
|
||||||
|
d: date.getDate(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
yy: date.getFullYear().toString().substring(2),
|
||||||
|
yyyy: date.getFullYear()
|
||||||
|
};
|
||||||
|
val.dd = (val.d < 10 ? '0' : '') + val.d;
|
||||||
|
val.mm = (val.m < 10 ? '0' : '') + val.m;
|
||||||
|
var date = [];
|
||||||
|
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
||||||
|
date.push(val[format.parts[i]]);
|
||||||
|
}
|
||||||
|
return date.join(format.separator);
|
||||||
|
},
|
||||||
|
headTemplate: '<thead>'+
|
||||||
|
'<tr>'+
|
||||||
|
'<th class="prev">‹</th>'+
|
||||||
|
'<th colspan="5" class="switch"></th>'+
|
||||||
|
'<th class="next">›</th>'+
|
||||||
|
'</tr>'+
|
||||||
|
'</thead>',
|
||||||
|
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
|
||||||
|
};
|
||||||
|
DPGlobal.template = '<div class="datepicker dropdown-menu">'+
|
||||||
|
'<div class="datepicker-days">'+
|
||||||
|
'<table class=" table-condensed">'+
|
||||||
|
DPGlobal.headTemplate+
|
||||||
|
'<tbody></tbody>'+
|
||||||
|
'</table>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="datepicker-months">'+
|
||||||
|
'<table class="table-condensed">'+
|
||||||
|
DPGlobal.headTemplate+
|
||||||
|
DPGlobal.contTemplate+
|
||||||
|
'</table>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="datepicker-years">'+
|
||||||
|
'<table class="table-condensed">'+
|
||||||
|
DPGlobal.headTemplate+
|
||||||
|
DPGlobal.contTemplate+
|
||||||
|
'</table>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
}( window.jQuery );
|
||||||
+1097
File diff suppressed because it is too large
Load Diff
@@ -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_email").val();
|
var username = $("#user_username").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_email").val('');
|
$("#user_username").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_email").val();
|
var username = $("#user_username").val();
|
||||||
var password = $("#user_password").val();
|
var password = $("#user_password").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
@@ -34,7 +34,8 @@ $(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) {
|
||||||
|
|||||||
+445
@@ -0,0 +1,445 @@
|
|||||||
|
var DateFormat = {};
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
var daysInWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
var shortMonthsInYear = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
var longMonthsInYear = ['January', 'February', 'March', 'April', 'May', 'June',
|
||||||
|
'July', 'August', 'September', 'October', 'November', 'December'];
|
||||||
|
var shortMonthsToNumber = { 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
|
||||||
|
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12' };
|
||||||
|
|
||||||
|
var YYYYMMDD_MATCHER = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;
|
||||||
|
|
||||||
|
$.format = (function() {
|
||||||
|
function numberToLongDay(value) {
|
||||||
|
// 0 to Sunday
|
||||||
|
// 1 to Monday
|
||||||
|
return daysInWeek[parseInt(value, 10)] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberToShortMonth(value) {
|
||||||
|
// 1 to Jan
|
||||||
|
// 2 to Feb
|
||||||
|
var monthArrayIndex = parseInt(value, 10) - 1;
|
||||||
|
return shortMonthsInYear[monthArrayIndex] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberToLongMonth(value) {
|
||||||
|
// 1 to January
|
||||||
|
// 2 to February
|
||||||
|
var monthArrayIndex = parseInt(value, 10) - 1;
|
||||||
|
return longMonthsInYear[monthArrayIndex] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortMonthToNumber(value) {
|
||||||
|
// Jan to 01
|
||||||
|
// Feb to 02
|
||||||
|
return shortMonthsToNumber[value] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTime(value) {
|
||||||
|
// 10:54:50.546
|
||||||
|
// => hour: 10, minute: 54, second: 50, millis: 546
|
||||||
|
// 10:54:50
|
||||||
|
// => hour: 10, minute: 54, second: 50, millis: ''
|
||||||
|
var time = value,
|
||||||
|
values,
|
||||||
|
subValues,
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
second,
|
||||||
|
millis = '',
|
||||||
|
delimited,
|
||||||
|
timeArray;
|
||||||
|
|
||||||
|
if(time.indexOf('.') !== -1) {
|
||||||
|
delimited = time.split('.');
|
||||||
|
// split time and milliseconds
|
||||||
|
time = delimited[0];
|
||||||
|
millis = delimited[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
timeArray = time.split(':');
|
||||||
|
|
||||||
|
if(timeArray.length === 3) {
|
||||||
|
hour = timeArray[0];
|
||||||
|
minute = timeArray[1];
|
||||||
|
// '20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 20
|
||||||
|
// '20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 20
|
||||||
|
second = timeArray[2].replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// '01:10:20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 01:10:20
|
||||||
|
// '01:10:20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 01:10:20
|
||||||
|
time = time.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
return {
|
||||||
|
time: time,
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
second: second,
|
||||||
|
millis: millis
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { time : '', hour : '', minute : '', second : '', millis : '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function padding(value, length) {
|
||||||
|
var paddingCount = length - String(value).length;
|
||||||
|
for(var i = 0; i < paddingCount; i++) {
|
||||||
|
value = '0' + value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
parseDate: function(value) {
|
||||||
|
var parsedDate = {
|
||||||
|
date: null,
|
||||||
|
year: null,
|
||||||
|
month: null,
|
||||||
|
dayOfMonth: null,
|
||||||
|
dayOfWeek: null,
|
||||||
|
time: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if(typeof value == 'number') {
|
||||||
|
return this.parseDate(new Date(value));
|
||||||
|
} else if(typeof value.getFullYear == 'function') {
|
||||||
|
parsedDate.year = String(value.getFullYear());
|
||||||
|
// d = new Date(1900, 1, 1) // 1 for Feb instead of Jan.
|
||||||
|
// => Thu Feb 01 1900 00:00:00
|
||||||
|
parsedDate.month = String(value.getMonth() + 1);
|
||||||
|
parsedDate.dayOfMonth = String(value.getDate());
|
||||||
|
parsedDate.time = parseTime(value.toTimeString());
|
||||||
|
} else if(value.search(YYYYMMDD_MATCHER) != -1) {
|
||||||
|
/* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */
|
||||||
|
values = value.split(/[T\+-]/);
|
||||||
|
parsedDate.year = values[0];
|
||||||
|
parsedDate.month = values[1];
|
||||||
|
parsedDate.dayOfMonth = values[2];
|
||||||
|
parsedDate.time = parseTime(values[3].split('.')[0]);
|
||||||
|
} else {
|
||||||
|
values = value.split(' ');
|
||||||
|
switch (values.length) {
|
||||||
|
case 6:
|
||||||
|
/* Wed Jan 13 10:43:41 CET 2010 */
|
||||||
|
parsedDate.year = values[5];
|
||||||
|
parsedDate.month = shortMonthToNumber(values[1]);
|
||||||
|
parsedDate.dayOfMonth = values[2];
|
||||||
|
parsedDate.time = parseTime(values[3]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
/* 2009-12-18 10:54:50.546 */
|
||||||
|
subValues = values[0].split('-');
|
||||||
|
parsedDate.year = subValues[0];
|
||||||
|
parsedDate.month = subValues[1];
|
||||||
|
parsedDate.dayOfMonth = subValues[2];
|
||||||
|
parsedDate.time = parseTime(values[1]);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
/* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */
|
||||||
|
case 9:
|
||||||
|
/* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */
|
||||||
|
case 10:
|
||||||
|
/* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */
|
||||||
|
parsedDate.year = values[3];
|
||||||
|
parsedDate.month = shortMonthToNumber(values[1]);
|
||||||
|
parsedDate.dayOfMonth = values[2];
|
||||||
|
parsedDate.time = parseTime(values[4]);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */
|
||||||
|
subValues = values[0].split('');
|
||||||
|
parsedDate.year = subValues[0] + subValues[1] + subValues[2] + subValues[3];
|
||||||
|
parsedDate.month = subValues[5] + subValues[6];
|
||||||
|
parsedDate.dayOfMonth = subValues[8] + subValues[9];
|
||||||
|
parsedDate.time = parseTime(subValues[13] + subValues[14] + subValues[15] + subValues[16] + subValues[17] + subValues[18] + subValues[19] + subValues[20]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsedDate.date = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth);
|
||||||
|
parsedDate.dayOfWeek = String(parsedDate.date.getDay());
|
||||||
|
|
||||||
|
return parsedDate;
|
||||||
|
},
|
||||||
|
|
||||||
|
date : function(value, format) {
|
||||||
|
try {
|
||||||
|
var parsedDate = this.parseDate(value);
|
||||||
|
|
||||||
|
if(parsedDate === null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var date = parsedDate.date,
|
||||||
|
year = parsedDate.year,
|
||||||
|
month = parsedDate.month,
|
||||||
|
dayOfMonth = parsedDate.dayOfMonth,
|
||||||
|
dayOfWeek = parsedDate.dayOfWeek,
|
||||||
|
time = parsedDate.time;
|
||||||
|
|
||||||
|
var pattern = '',
|
||||||
|
retValue = '',
|
||||||
|
unparsedRest = '',
|
||||||
|
inQuote = false;
|
||||||
|
|
||||||
|
/* Issue 1 - variable scope issue in format.date (Thanks jakemonO) */
|
||||||
|
for(var i = 0; i < format.length; i++) {
|
||||||
|
var currentPattern = format.charAt(i);
|
||||||
|
// Look-Ahead Right (LALR)
|
||||||
|
var nextRight = format.charAt(i + 1);
|
||||||
|
|
||||||
|
if (inQuote) {
|
||||||
|
if (currentPattern == "'") {
|
||||||
|
retValue += (pattern === '') ? "'" : pattern;
|
||||||
|
pattern = '';
|
||||||
|
inQuote = false;
|
||||||
|
} else {
|
||||||
|
pattern += currentPattern;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pattern += currentPattern;
|
||||||
|
unparsedRest = '';
|
||||||
|
switch (pattern) {
|
||||||
|
case 'ddd':
|
||||||
|
retValue += numberToLongDay(dayOfWeek);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'dd':
|
||||||
|
if(nextRight === 'd') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += padding(dayOfMonth, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
if(nextRight === 'd') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += parseInt(dayOfMonth, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
if(dayOfMonth == 1 || dayOfMonth == 21 || dayOfMonth == 31) {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'st';
|
||||||
|
} else if(dayOfMonth == 2 || dayOfMonth == 22) {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'nd';
|
||||||
|
} else if(dayOfMonth == 3 || dayOfMonth == 23) {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'rd';
|
||||||
|
} else {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'th';
|
||||||
|
}
|
||||||
|
retValue += dayOfMonth;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'MMMM':
|
||||||
|
retValue += numberToLongMonth(month);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'MMM':
|
||||||
|
if(nextRight === 'M') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += numberToShortMonth(month);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'MM':
|
||||||
|
if(nextRight === 'M') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += padding(month, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
if(nextRight === 'M') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += parseInt(month, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'y':
|
||||||
|
case 'yyy':
|
||||||
|
if(nextRight === 'y') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += pattern;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'yy':
|
||||||
|
if(nextRight === 'y') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += String(year).slice(-2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'yyyy':
|
||||||
|
retValue += year;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'HH':
|
||||||
|
retValue += padding(time.hour, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
if(nextRight === 'H') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += parseInt(time.hour, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'hh':
|
||||||
|
/* time.hour is '00' as string == is used instead of === */
|
||||||
|
hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
|
||||||
|
: time.hour - 12);
|
||||||
|
retValue += padding(hour, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
if(nextRight === 'h') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
|
||||||
|
: time.hour - 12);
|
||||||
|
retValue += parseInt(hour, 10);
|
||||||
|
// Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21
|
||||||
|
// retValue = parseInt(retValue, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'mm':
|
||||||
|
retValue += padding(time.minute, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
if(nextRight === 'm') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += time.minute;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'ss':
|
||||||
|
/* ensure only seconds are added to the return string */
|
||||||
|
retValue += padding(time.second.substring(0, 2), 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if(nextRight === 's') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += time.second;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
case 'SS':
|
||||||
|
if(nextRight === 'S') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += pattern;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'SSS':
|
||||||
|
retValue += time.millis.substring(0, 3);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
retValue += time.hour >= 12 ? 'PM' : 'AM';
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
retValue += time.hour >= 12 ? 'p.m.' : 'a.m.';
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case "'":
|
||||||
|
pattern = '';
|
||||||
|
inQuote = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
retValue += currentPattern;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retValue += unparsedRest;
|
||||||
|
return retValue;
|
||||||
|
} catch (e) {
|
||||||
|
if(console && console.log) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* JavaScript Pretty Date
|
||||||
|
* Copyright (c) 2011 John Resig (ejohn.org)
|
||||||
|
* Licensed under the MIT and GPL licenses.
|
||||||
|
*
|
||||||
|
* Takes an ISO time and returns a string representing how long ago the date
|
||||||
|
* represents
|
||||||
|
*
|
||||||
|
* ('2008-01-28T20:24:17Z') // => '2 hours ago'
|
||||||
|
* ('2008-01-27T22:24:17Z') // => 'Yesterday'
|
||||||
|
* ('2008-01-26T22:24:17Z') // => '2 days ago'
|
||||||
|
* ('2008-01-14T22:24:17Z') // => '2 weeks ago'
|
||||||
|
* ('2007-12-15T22:24:17Z') // => 'more than 5 weeks ago'
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
prettyDate : function(time) {
|
||||||
|
var date;
|
||||||
|
var diff;
|
||||||
|
var day_diff;
|
||||||
|
|
||||||
|
if(typeof time === 'string' || typeof time === 'number') {
|
||||||
|
date = new Date(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof time === 'object') {
|
||||||
|
date = new Date(time.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
diff = (((new Date()).getTime() - date.getTime()) / 1000);
|
||||||
|
|
||||||
|
day_diff = Math.floor(diff / 86400);
|
||||||
|
|
||||||
|
if(isNaN(day_diff) || day_diff < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(diff < 60) {
|
||||||
|
return 'just now';
|
||||||
|
} else if(diff < 120) {
|
||||||
|
return '1 minute ago';
|
||||||
|
} else if(diff < 3600) {
|
||||||
|
return Math.floor(diff / 60) + ' minutes ago';
|
||||||
|
} else if(diff < 7200) {
|
||||||
|
return '1 hour ago';
|
||||||
|
} else if(diff < 86400) {
|
||||||
|
return Math.floor(diff / 3600) + ' hours ago';
|
||||||
|
} else if(day_diff === 1) {
|
||||||
|
return 'Yesterday';
|
||||||
|
} else if(day_diff < 7) {
|
||||||
|
return day_diff + ' days ago';
|
||||||
|
} else if(day_diff < 31) {
|
||||||
|
return Math.ceil(day_diff / 7) + ' weeks ago';
|
||||||
|
} else if(day_diff >= 31) {
|
||||||
|
return 'more than 5 weeks ago';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toBrowserTimeZone : function(value, format) {
|
||||||
|
return this.date(new Date(value), format || 'MM/dd/yyyy HH:mm:ss');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
}(DateFormat));
|
||||||
|
;// require dateFormat.js
|
||||||
|
// please check `dist/jquery.dateFormat.js` for a complete version
|
||||||
|
(function($) {
|
||||||
|
$.format = DateFormat.format;
|
||||||
|
}(jQuery));
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
$("#index_logout").click(function(){
|
||||||
|
$.ajax({
|
||||||
|
type: "DELETE",
|
||||||
|
url: $("#index_logout").data("url"),
|
||||||
|
complete: function(){
|
||||||
|
window.location.href="/";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
$(".task_list_task").click(function(){
|
||||||
|
$("#update_tasks_submit").removeClass("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#update_tasks_submit").click(function(){
|
||||||
|
|
||||||
|
tasks = [];
|
||||||
|
$(".task_list_task").each(function(){
|
||||||
|
tasks.push({
|
||||||
|
id: parseInt($(this).data("id")),
|
||||||
|
done: $(this).is(":checked")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
json_data = { tasks: tasks };
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: $("#update_tasks_submit").data("url"),
|
||||||
|
type: "PUT",
|
||||||
|
data: JSON.stringify(json_data),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
//should re-render via JS, but for now reload
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
alert("An error occured updating tasks");
|
||||||
|
//displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
$(document).ready(function(){
|
||||||
|
|
||||||
|
var currentdate = new Date();
|
||||||
|
$("#date_id").datepicker().on('changeDate', function(ev){
|
||||||
|
$("#date_id").datepicker('hide');
|
||||||
|
});
|
||||||
|
$("#date_id").datepicker('setValue', currentdate);
|
||||||
|
|
||||||
|
$("#start_time_id").timepicker();
|
||||||
|
$("#end_time_id").timepicker();
|
||||||
|
|
||||||
|
$("#add_time_entry_submit").click(function(){
|
||||||
|
date = $("#date_id").val();
|
||||||
|
start_date = new Date(date + " " + $("#start_time_id").val());
|
||||||
|
end_date = new Date(date + " " + $("#end_time_id").val());
|
||||||
|
|
||||||
|
forward = $("#add_time_entry_submit").data("forward");
|
||||||
|
|
||||||
|
// If a bike is selected, forward to the bike
|
||||||
|
// checklist.
|
||||||
|
bike_id = parseInt($("#bike_id").val());
|
||||||
|
if( bike_id > 0 ){
|
||||||
|
forward = "/task_lists/" + bike_id + "/edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: Ideally, we'd submit the dates as ISO, but I can't figure out
|
||||||
|
// how to get Netzke to render UTC dates correctly (it calls to_json
|
||||||
|
// somewhere and drops off the timezone). For the time being, save dates
|
||||||
|
// in locale like Netzke.
|
||||||
|
json_data = { time_entries: [{
|
||||||
|
start_date: $.format.date(start_date, "dd-MM-yyyy hh:mm a"),
|
||||||
|
end_date: $.format.date(end_date, "dd-MM-yyyy hh:mm a"),
|
||||||
|
log_action_id: parseInt($('input[name=action_id]:checked').val()),
|
||||||
|
bike_id: bike_id,
|
||||||
|
description: $("#description_id").val(),
|
||||||
|
}]};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: $("#add_time_entry_submit").data("url"),
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify(json_data),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
window.location = forward;
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".work_entry-delete-btn").click(function(){
|
||||||
|
row = $(this).closest("tr");
|
||||||
|
entry_id = row.data("id");
|
||||||
|
start_date = row.data("start_date");
|
||||||
|
duration = row.data("duration");
|
||||||
|
description = row.data("description");
|
||||||
|
$("#work_entry_start_date").html(start_date);
|
||||||
|
$("#work_entry_duration").html(duration);
|
||||||
|
$("#work_entry_description").html(description);
|
||||||
|
$("#confirmation_delete").data("entry_id", entry_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#confirmation_delete").click(function(){
|
||||||
|
entry_id = $(this).data("entry_id");
|
||||||
|
url = $("#confirmation_delete").data("url") + entry_id;
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: "delete",
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
function displayFormErrors(data){
|
||||||
|
if( data.errors != undefined ){
|
||||||
|
$.each( data.errors, function( field, errorMsg) {
|
||||||
|
$("#"+field).parents(".control-group").addClass("error");
|
||||||
|
$("#"+field).siblings(".help-block").html(errorMsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,5 +9,7 @@
|
|||||||
* compiled file, but it's generally better to create a new file per style scope.
|
* compiled file, but it's generally better to create a new file per style scope.
|
||||||
*
|
*
|
||||||
*= require_self
|
*= require_self
|
||||||
*= require_tree .
|
*= require datepicker
|
||||||
|
*= require bootstrap-timepicker
|
||||||
|
*= require bootstrap_and_overrides
|
||||||
*/
|
*/
|
||||||
|
|||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
/*!
|
||||||
|
* Timepicker Component for Twitter Bootstrap
|
||||||
|
*
|
||||||
|
* Copyright 2013 Joris de Wit
|
||||||
|
*
|
||||||
|
* Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
.bootstrap-timepicker {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before {
|
||||||
|
left: auto;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after {
|
||||||
|
left: auto;
|
||||||
|
right: 13px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker .add-on {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker .add-on i {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu.open {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu:before {
|
||||||
|
border-bottom: 7px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-left: 7px solid transparent;
|
||||||
|
border-right: 7px solid transparent;
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu:after {
|
||||||
|
border-bottom: 6px solid #FFFFFF;
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-left:before {
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-left:after {
|
||||||
|
left: 7px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-right:before {
|
||||||
|
right: 6px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-right:after {
|
||||||
|
right: 7px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-top:before {
|
||||||
|
top: -7px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-top:after {
|
||||||
|
top: -6px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-bottom:before {
|
||||||
|
bottom: -7px;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-top: 7px solid #999;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-bottom:after {
|
||||||
|
bottom: -6px;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-top: 6px solid #ffffff;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget a.btn,
|
||||||
|
.bootstrap-timepicker-widget input {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td {
|
||||||
|
text-align: center;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td:not(.separator) {
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td span {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td a {
|
||||||
|
border: 1px transparent solid;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px 0;
|
||||||
|
outline: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #eee;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td a i {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td input {
|
||||||
|
width: 25px;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget .modal-content {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
@media (min-width: 767px) {
|
||||||
|
.bootstrap-timepicker-widget.modal {
|
||||||
|
width: 200px;
|
||||||
|
margin-left: -100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.bootstrap-timepicker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker .dropdown-menu {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,26 @@
|
|||||||
@import "twitter/bootstrap/bootstrap";
|
@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
|
||||||
@@ -15,3 +31,17 @@
|
|||||||
//
|
//
|
||||||
// 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;
|
||||||
|
}
|
||||||
|
|||||||
Executable
+182
@@ -0,0 +1,182 @@
|
|||||||
|
/*!
|
||||||
|
* Datepicker for Bootstrap
|
||||||
|
*
|
||||||
|
* Copyright 2012 Stefan Petre
|
||||||
|
* Licensed under the Apache License v2.0
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.datepicker {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 4px;
|
||||||
|
margin-top: 1px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
/*.dow {
|
||||||
|
border-top: 1px solid #ddd !important;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
}
|
||||||
|
.datepicker:before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
border-left: 7px solid transparent;
|
||||||
|
border-right: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid #ccc;
|
||||||
|
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||||
|
position: absolute;
|
||||||
|
top: -7px;
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
.datepicker:after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
border-bottom: 6px solid #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 7px;
|
||||||
|
}
|
||||||
|
.datepicker > div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.datepicker table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.datepicker td,
|
||||||
|
.datepicker th {
|
||||||
|
text-align: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.datepicker td.day:hover {
|
||||||
|
background: #eeeeee;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.datepicker td.day.disabled {
|
||||||
|
color: #eeeeee;
|
||||||
|
}
|
||||||
|
.datepicker td.old,
|
||||||
|
.datepicker td.new {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
.datepicker td.active,
|
||||||
|
.datepicker td.active:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #006dcc;
|
||||||
|
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||||
|
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
|
||||||
|
border-color: #0044cc #0044cc #002a80;
|
||||||
|
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||||
|
*background-color: #0044cc;
|
||||||
|
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
|
||||||
|
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.datepicker td.active:hover,
|
||||||
|
.datepicker td.active:hover:hover,
|
||||||
|
.datepicker td.active:focus,
|
||||||
|
.datepicker td.active:hover:focus,
|
||||||
|
.datepicker td.active:active,
|
||||||
|
.datepicker td.active:hover:active,
|
||||||
|
.datepicker td.active.active,
|
||||||
|
.datepicker td.active:hover.active,
|
||||||
|
.datepicker td.active.disabled,
|
||||||
|
.datepicker td.active:hover.disabled,
|
||||||
|
.datepicker td.active[disabled],
|
||||||
|
.datepicker td.active:hover[disabled] {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #0044cc;
|
||||||
|
*background-color: #003bb3;
|
||||||
|
}
|
||||||
|
.datepicker td.active:active,
|
||||||
|
.datepicker td.active:hover:active,
|
||||||
|
.datepicker td.active.active,
|
||||||
|
.datepicker td.active:hover.active {
|
||||||
|
background-color: #003399 \9;
|
||||||
|
}
|
||||||
|
.datepicker td span {
|
||||||
|
display: block;
|
||||||
|
width: 47px;
|
||||||
|
height: 54px;
|
||||||
|
line-height: 54px;
|
||||||
|
float: left;
|
||||||
|
margin: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.datepicker td span:hover {
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
.datepicker td span.active {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #006dcc;
|
||||||
|
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||||
|
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
|
||||||
|
border-color: #0044cc #0044cc #002a80;
|
||||||
|
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||||
|
*background-color: #0044cc;
|
||||||
|
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
|
||||||
|
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.datepicker td span.active:hover,
|
||||||
|
.datepicker td span.active:focus,
|
||||||
|
.datepicker td span.active:active,
|
||||||
|
.datepicker td span.active.active,
|
||||||
|
.datepicker td span.active.disabled,
|
||||||
|
.datepicker td span.active[disabled] {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #0044cc;
|
||||||
|
*background-color: #003bb3;
|
||||||
|
}
|
||||||
|
.datepicker td span.active:active,
|
||||||
|
.datepicker td span.active.active {
|
||||||
|
background-color: #003399 \9;
|
||||||
|
}
|
||||||
|
.datepicker td span.old {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
.datepicker th.switch {
|
||||||
|
width: 145px;
|
||||||
|
}
|
||||||
|
.datepicker th.next,
|
||||||
|
.datepicker th.prev {
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
.datepicker thead tr:first-child th {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.datepicker thead tr:first-child th:hover {
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
.input-append.date .add-on i,
|
||||||
|
.input-prepend.date .add-on i {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
@@ -19,6 +19,6 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onChangeAccountInfo: function(){
|
onChangeAccountInfo: function(){
|
||||||
window.location.href="users/edit";
|
window.location.href="/users/edit";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ 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
|
||||||
@@ -31,13 +29,14 @@ class Tasks < Netzke::Basepack::Grid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def default_fields_for_forms
|
def default_fields_for_forms
|
||||||
fields = []
|
bike = Bike.find_by_id(session[:selected_bike_id])
|
||||||
fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
|
bike = "Select a Bike First!" if bike.nil?
|
||||||
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)["error"]);
|
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["errors"][0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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( :email => params[:username] )
|
user = User.find_for_database_authentication( :username => 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 => 403 and return
|
render :json => {:error => msg }, :status => 401 and return
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
authenticate_user!
|
authenticate_user!
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
class Api::V1::BikesController < Api::V1::BaseController
|
||||||
|
CANNOT_MANAGE = "You do not have permission to manage bikes."
|
||||||
|
EXPECTED_BIKE = "Expected bike in submitted data"
|
||||||
|
NOT_FOUND = "The bike could not be found."
|
||||||
|
|
||||||
|
before_filter :check_bike_permission, except: :show
|
||||||
|
|
||||||
|
def create
|
||||||
|
if params[:bikes] && bike = params[:bikes].first
|
||||||
|
@bike = Bike.new(bike)
|
||||||
|
if !@bike.save
|
||||||
|
render json: { errors: @bike.errors }, status: 422 and return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
render json: { errors: [EXPECTED_BIKE]}, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@bike = Bike.find_by_id(params[:id])
|
||||||
|
if @bike.nil?
|
||||||
|
render json: { errors: [NOT_FOUND] }, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def check_bike_permission
|
||||||
|
if cannot? :manage, Bike
|
||||||
|
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
class Api::V1::TaskListsController < Api::V1::BaseController
|
||||||
|
CANNOT_MANAGE = "You do not have permission to manage this task list."
|
||||||
|
NOT_FOUND = "The task list could not be found."
|
||||||
|
|
||||||
|
before_filter :get_task_list
|
||||||
|
before_filter :check_task_list_permission, except: :show
|
||||||
|
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def get_task_list
|
||||||
|
@task_list = TaskList.find_by_id(params[:id])
|
||||||
|
if @task_list.nil?
|
||||||
|
render json: { errors: [NOT_FOUND] }, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_task_list_permission
|
||||||
|
if cannot? :manage, Bike and @task_list.item != current_user.bike
|
||||||
|
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
class Api::V1::TasksController < Api::V1::BaseController
|
||||||
|
EXPECTED_TASKS = "Expected a list of tasks in submitted data."
|
||||||
|
CANNOT_MANAGE = "You do not have permission to manage this task."
|
||||||
|
NOT_FOUND = "The task could not be found."
|
||||||
|
|
||||||
|
before_filter :validate_params
|
||||||
|
before_filter :get_tasks
|
||||||
|
before_filter :check_task_permission, except: :show
|
||||||
|
|
||||||
|
def update
|
||||||
|
errors = []
|
||||||
|
@tasks.each do |task_hash|
|
||||||
|
task = task_hash[:record]
|
||||||
|
attrs = task_hash[:new_attributes]
|
||||||
|
task.update_attributes(attrs)
|
||||||
|
if !task.errors.empty?
|
||||||
|
errors << { id: task.id, errors: task.errors }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !errors.empty?
|
||||||
|
render json: { errors: errors }, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def validate_params
|
||||||
|
if params[:tasks].nil? and not params[:tasks].kind_of?(Array)
|
||||||
|
render json: { errors: [EXPECTED_TASKS]}, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_tasks
|
||||||
|
@tasks = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
params[:tasks].each do |task|
|
||||||
|
t = Task.find_by_id(task[:id])
|
||||||
|
if t.nil?
|
||||||
|
errors << { id: task[:id], error: NOT_FOUND }
|
||||||
|
else
|
||||||
|
@tasks << { record: t, new_attributes: task }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !errors.empty?
|
||||||
|
render json: { errors: errors }, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_task_permission
|
||||||
|
errors = []
|
||||||
|
@tasks.each do |task_hash|
|
||||||
|
task = task_hash[:record]
|
||||||
|
if task.task_list.item != current_user.bike
|
||||||
|
errors << { id: task[:id], error: CANNOT_MANAGE }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if cannot? :manage, Bike and !errors.empty?
|
||||||
|
render json: { errors: errors}, status: 403 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
class Api::V1::TimeEntriesController < Api::V1::BaseController
|
||||||
|
EXPECTED_TIME_ENTRY = "Expected time entry in submitted data"
|
||||||
|
NOT_FOUND = "Time entry not found"
|
||||||
|
|
||||||
|
def create
|
||||||
|
if params[:time_entries] && time_entry = params[:time_entries].first
|
||||||
|
time_entry.merge!({ loggable_id: current_user.id,
|
||||||
|
logger_id: current_user.id })
|
||||||
|
bike_id = time_entry[:bike_id].to_i
|
||||||
|
|
||||||
|
@time_entry = TimeEntry.new(time_entry.except(:bike_id))
|
||||||
|
|
||||||
|
if bike_id > 0
|
||||||
|
@time_entry.copy_to_bike_history(bike_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
if !@time_entry.save
|
||||||
|
render json: { errors: @time_entry.errors }, status: 422 and return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
render json: { errors: [EXPECTED_TIME_ENTRY]}, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete
|
||||||
|
if time_entry = TimeEntry.find_by_id(params[:id])
|
||||||
|
time_entry.delete
|
||||||
|
render nothing: true, status: 204 and return
|
||||||
|
else
|
||||||
|
render json: { errors: [NOT_FOUND]}, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,21 +1,27 @@
|
|||||||
require 'securerandom'
|
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 => { "error" => "User not found"}, :status => 404 and return if user.nil?
|
render :json => { "errors" => [NOT_FOUND]}, :status => 404 and return if user.nil?
|
||||||
render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_user.id
|
render :json => { "errors" => [NOT_ALLOWED]}, :status => 403 and return if user.id == current_user.id
|
||||||
|
|
||||||
new_pass = SecureRandom.hex[0,8]
|
new_pass = SecureRandom.hex[0,PASS_LENGTH]
|
||||||
user.password = new_pass
|
user.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 => { "error" => "You do not have the permission"}, :status => 403 and return
|
render :json => { "errors" => [CANNOT_MANAGE]}, :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?
|
||||||
@@ -25,4 +31,5 @@ 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
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class BikesController < AuthenticatedController
|
||||||
|
|
||||||
|
def new
|
||||||
|
@brands = BikeBrand.all.map{ |b| [b.brand, b.id] }
|
||||||
|
@brands.unshift( ["Select a brand", -1] )
|
||||||
|
@wheel_sizes = BikeWheelSize.all.map{ |w| [w.display_string, w.id] }
|
||||||
|
@wheel_sizes.unshift( ["Select a wheel size", -1] )
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@bike = Bike.find_by_id(params[:id])
|
||||||
|
@task_list_id = @bike.task_list.id
|
||||||
|
@show_add_bike = true if params[:add_bike]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
class PanelController < ApplicationController
|
||||||
|
|
||||||
|
def index
|
||||||
|
render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
class SiteController < ApplicationController
|
class SiteController < ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application"
|
@bike = current_user.bike
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class TaskListsController < AuthenticatedController
|
||||||
|
def edit
|
||||||
|
@task_list = TaskList.find_by_id(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class TimeEntriesController < AuthenticatedController
|
||||||
|
|
||||||
|
def new
|
||||||
|
@bikes = Bike.all.map{ |b| [b.to_s , b.id] }
|
||||||
|
if bike = current_user.bike
|
||||||
|
@bikes.unshift( [bike.to_s, bike.id] )
|
||||||
|
end
|
||||||
|
@bikes.unshift( ["Non-bike work", -1] )
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@user_time_entries = TimeEntry.where(loggable_id: current_user.id)
|
||||||
|
@credits_available = current_user.total_credits
|
||||||
|
@hours_worked = current_user.total_hours
|
||||||
|
end
|
||||||
|
end
|
||||||
+7
-7
@@ -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
|
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" }
|
||||||
validates :color, :presence => true
|
#validates :color, :presence => true
|
||||||
validates :bike_style_id, :presence => true
|
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" }
|
||||||
validates :seat_tube_height, :presence => true
|
validates :seat_tube_height, :presence => true, :numericality => true
|
||||||
validates :bike_wheel_size_id, :presence => true
|
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" }
|
||||||
validates :bike_condition_id, :presence => true
|
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" }
|
||||||
validates :bike_purpose_id, :presence => true
|
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" }
|
||||||
|
|
||||||
self.per_page = 15
|
self.per_page = 15
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
class Task < ActiveRecord::Base
|
class Task < ActiveRecord::Base
|
||||||
attr_accessible :task, :notes, :done
|
attr_accessible :task, :notes, :done, :task_list_id
|
||||||
|
|
||||||
belongs_to :task_list
|
belongs_to :task_list
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
has_many :tasks, order: "id ASC"
|
||||||
|
|
||||||
after_save :create_default_bike_tasks
|
after_save :create_default_bike_tasks
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
class TimeEntry < ActsAsLoggable::Log
|
||||||
|
default_scope where( loggable_type: "User",
|
||||||
|
logger_type: "User",
|
||||||
|
log_action_type: "ActsAsLoggable::UserAction").where("log_action_id != 4").order("start_date DESC")
|
||||||
|
|
||||||
|
def copy_to_bike_history(bike_id)
|
||||||
|
self.assign_attributes({
|
||||||
|
copy_log: true,
|
||||||
|
copy_type: 'Bike',
|
||||||
|
copy_id: bike_id,
|
||||||
|
copy_action_type: 'ActsAsLoggable::BikeAction',
|
||||||
|
copy_action_id: 4
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def duration
|
||||||
|
end_date - start_date
|
||||||
|
end
|
||||||
|
|
||||||
|
def duration_in_hours
|
||||||
|
(duration / 1.hour).round(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
log_action.try(:action)
|
||||||
|
end
|
||||||
|
end
|
||||||
+27
-19
@@ -47,7 +47,8 @@ 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
|
||||||
purpose_id = BikePurpose.find_by_purpose("BUILDBIKE").id
|
#default BUILDBIKE/CLASS ID is 5
|
||||||
|
purpose_id = 5
|
||||||
Bike.find_by_sql("
|
Bike.find_by_sql("
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM bikes
|
FROM bikes
|
||||||
@@ -64,15 +65,16 @@ class User < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def total_credits_spent
|
def total_credits_spent
|
||||||
log_action = ::ActsAsLoggable::TransactionAction.find_by_action("TIME")
|
log_action_id = 1 #TIME
|
||||||
transaction_logs.
|
transaction_logs.
|
||||||
where( "log_action_id = ? AND log_action_type = ?",
|
where( "log_action_id = ? AND log_action_type = ?",
|
||||||
log_action.id, log_action.class.to_s).
|
log_action_id, ::ActsAsLoggable::TransactionAction.to_s).
|
||||||
sum{ |r| r.description.to_i }.round(2)
|
sum{ |r| r.description.to_i }.round(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_earned_credits
|
def total_earned_credits
|
||||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
volunteer_id = 1
|
||||||
|
staff_id = 3
|
||||||
|
|
||||||
# Find the first credit conversion which has a created_at date before the
|
# 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
|
||||||
@@ -81,7 +83,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
|
||||||
@@ -89,23 +91,26 @@ 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 = #{self.id}
|
WHERE logs.loggable_id = :id
|
||||||
AND logs.loggable_type = 'User'
|
AND logs.loggable_type = 'User'
|
||||||
AND (log_action_id != #{log_action.id} AND log_action_type = '#{log_action.class.to_s}')
|
AND (log_action_id IN (:credit_actions) AND log_action_type = :log_action_type)
|
||||||
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 = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
log_action_id = 4 #CHECKIN
|
||||||
logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
|
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_month_hours
|
def current_month_hours
|
||||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
log_action_id = 4 #CHECKIN
|
||||||
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month
|
#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, log_action.class.to_s)
|
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s)
|
||||||
.where( :start_date => current_month_range)
|
.where( :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 }
|
||||||
@@ -113,28 +118,31 @@ class User < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def checked_in?
|
def checked_in?
|
||||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
#default CHECKIN log action is id, yea yea should be a constant
|
||||||
checked = logs.where( log_action_id: log_action.id).
|
log_action_id = 4
|
||||||
|
checked = logs.where( log_action_id: log_action_id).
|
||||||
where("start_date >= ?", Time.zone.now.beginning_of_day).
|
where("start_date >= ?", 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
|
||||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
#default CHECKIN log action is id, yea yea should be a constant
|
||||||
|
log_action_id = 4
|
||||||
time = Time.now
|
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: log_action.class.to_s)
|
log_action_type: ::ActsAsLoggable::UserAction.to_s)
|
||||||
save
|
save
|
||||||
end
|
end
|
||||||
|
|
||||||
def checkout
|
def checkout
|
||||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
#default CHECKIN log action is id, yea yea should be a constant
|
||||||
checked = logs.where( log_action_id: log_action.id).
|
log_action_id = 4
|
||||||
|
checked = logs.where( log_action_id: log_action_id).
|
||||||
where("start_date >= ?", Time.zone.now.beginning_of_day).
|
where("start_date >= ?", 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
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
json.bikes [@bike] do |bike|
|
||||||
|
json.array! bike
|
||||||
|
json.set! :href, api_bike_path(bike)
|
||||||
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
json.bikes [@bike] do |bike|
|
||||||
|
json.array! bike
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
json.task_lists [@task_list] do |tl|
|
||||||
|
json.array! tl
|
||||||
|
json.links do
|
||||||
|
json.bike do
|
||||||
|
json.href api_bike_path(tl.item)
|
||||||
|
json.id tl.item_id
|
||||||
|
end
|
||||||
|
json.tasks tl.tasks do |task|
|
||||||
|
json.id task.id
|
||||||
|
json.done task.done
|
||||||
|
json.notes task.notes
|
||||||
|
json.task task.task
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
json.tasks [@tasks.map{|x| x[:record]}] do |task|
|
||||||
|
json.array! task
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
json.time_entries [@time_entry] do |time_entry|
|
||||||
|
json.array! time_entry
|
||||||
|
#json.set! :href, api_time_entry_path(time_entry)
|
||||||
|
end
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
%a{ class: "btn btn-default btn-lg", href: root_path}
|
||||||
|
%span{ class:"icon-home"}
|
||||||
|
%h2 Add Bike
|
||||||
|
|
||||||
|
%p
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "shop_id", placeholder: "Shop ID", type: "number", min:0, class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
= select_tag(:bike_brand_id, options_for_select(@brands))
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "model", placeholder: "Model", type: "text", class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "serial_number", placeholder: "Serial Number", type: "text", class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
.btn-group{ "data-toggle" => "buttons-radio"}
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 3} RD
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 1} MTN
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 2} HYB
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 4} OTHER
|
||||||
|
%input{ id: "bike_style_id", type: "hidden"}
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
= select_tag(:bike_wheel_size_id, options_for_select(@wheel_sizes), id: :bike_wheel_size_id)
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
.btn-group{ "data-toggle" => "buttons-radio"}
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 2} Poor
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 3} Fair
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 4} Good
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 5} Excellent
|
||||||
|
%input{ id: "bike_condition_id", type: "hidden"}
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "seat_tube_height", placeholder: "Seat Tube (cm)", type: "number", min: 0, max: 100, class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
-# Commenting this out until description is added to Bike
|
||||||
|
%p
|
||||||
|
%input{id: "bike_description", placeholder: "Short description", type: "text", class: "input-lg" }
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "add_bike_submit", value: "Add Bike", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_bike_path}"}
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
- if @show_add_bike
|
||||||
|
%p
|
||||||
|
%a{class: "btn btn-lg btn-block btn-info", href: new_bike_path} Add Another Bike?
|
||||||
|
|
||||||
|
%a{ class: "btn btn-default btn-lg", href: root_path}
|
||||||
|
%span{ class:"icon-home"}
|
||||||
|
%h2 #{@bike.shop_id}: #{@bike.bike_brand}
|
||||||
|
%h2 #{@bike.model}
|
||||||
|
|
||||||
|
%dl.dl-horizontal
|
||||||
|
%dt Type
|
||||||
|
%dd #{@bike.bike_style}
|
||||||
|
%dt Wheel Size
|
||||||
|
%dd #{@bike.bike_wheel_size.display_string}
|
||||||
|
%dt Condition
|
||||||
|
%dd #{@bike.bike_condition}
|
||||||
|
%dt Seat Tube (cm)
|
||||||
|
%dd #{@bike.seat_tube_height}
|
||||||
|
%dt Purpose
|
||||||
|
%dd #{@bike.bike_purpose}
|
||||||
|
- if !@bike.value.nil?
|
||||||
|
%dt Value
|
||||||
|
%dd #{@bike.value}
|
||||||
|
- if !@bike.color.nil?
|
||||||
|
%dt Color
|
||||||
|
%dd{ style: "width: 50px; background-color: ##{@bike.color}; border: black; border-width: 1px; border-style: solid;"} #{@bike.color}
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary", href: edit_task_list_path(@task_list_id) } View Checklist
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
<%- if controller_name != 'sessions' %>
|
<%- if controller_name != 'sessions' %>
|
||||||
<%= link_to "Sign in", new_session_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Sign in", new_session_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
||||||
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Sign up", new_registration_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
|
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
|
||||||
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
||||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% 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? %>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<h2>Forgot your password?</h2>
|
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
|
||||||
|
|
||||||
|
<h2>Forgot your password?</h2>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
|
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
|
||||||
<%= devise_error_messages! %>
|
<%= devise_error_messages! %>
|
||||||
|
|
||||||
<div><%= f.label :email %><br />
|
<div class="form-group">
|
||||||
<%= f.email_field :email %></div>
|
<%= f.email_field :email, placeholder: "Email", class: "form-control input-lg" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div><%= f.submit "Send me reset password instructions" %></div>
|
<div><%= f.submit "Reset Password", class:"btn btn-lg btn-primary"%></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render "links" %>
|
<%= render "links" %>
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
%fieldset
|
%p
|
||||||
.control-group
|
%fieldset
|
||||||
= f.label :addrStreet1, :class => "control-label"
|
.form-group
|
||||||
.controls
|
= f.text_field :addrStreet1, placeholder: "Street Address Line 1", :class => "form-control input-lg"
|
||||||
= f.text_field :addrStreet1, :class => "control-label"
|
.form-group
|
||||||
.control-group
|
= f.text_field :addrStreet2, placeholder: "Street Address Line 2",:class => "form-control input-lg"
|
||||||
= f.label :addrStreet2, :class => "control-label"
|
.form-group
|
||||||
.controls
|
= f.text_field :addrCity, placeholder: "City", :class => "form-control input-lg"
|
||||||
= f.text_field :addrStreet2, :class => "control-label"
|
.form-group
|
||||||
.control-group
|
= f.text_field :addrState, placeholder: "State Abbreviation", :class => "form-control input-lg"
|
||||||
= f.label :addrCity, :class => "control-label"
|
.form-group
|
||||||
.controls
|
= f.text_field :addrZip, placeholder: "Zip Code", :class => "form-control input-lg"
|
||||||
= f.text_field :addrCity, :class => "control-label"
|
.form-group
|
||||||
.control-group
|
= f.text_field :phone, placeholder: "Phone", :class => "form-control input-lg"
|
||||||
= 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"
|
|
||||||
|
|||||||
@@ -1,36 +1,23 @@
|
|||||||
= 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), :html => { :class => "form-horizontal"}) do |f|
|
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
|
||||||
= devise_error_messages!
|
= devise_error_messages!
|
||||||
.controls
|
.controls
|
||||||
%h2 Sign up
|
%h2 Sign up
|
||||||
.control-group
|
.form-group
|
||||||
= f.label :username, :class => "control-label"
|
= f.text_field :username, placeholder: "Username", :class => "form-control input-lg"
|
||||||
.controls
|
.form-group
|
||||||
= f.text_field :username
|
= f.text_field :first_name, placeholder: "First Name", :class => "form-control input-lg"
|
||||||
.control-group
|
.form-group
|
||||||
= f.label :first_name, :class => "control-label"
|
= f.text_field :last_name, placeholder: "Last Name", :class => "form-control input-lg"
|
||||||
.controls
|
.form-group
|
||||||
= f.text_field :first_name
|
= f.email_field :email, placeholder: "E-mail", :class => "form-control input-lg"
|
||||||
.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
|
||||||
.control-group
|
.form-group
|
||||||
= f.label :password, :class => "control-label"
|
= f.password_field :password, placeholder: "Password", :class => "form-control input-lg"
|
||||||
.controls
|
.form-group
|
||||||
= f.password_field :password
|
= f.password_field :password_confirmation, placeholder: "Password Confirmation", :class => "form-control input-lg"
|
||||||
.control-group
|
.form-group
|
||||||
= f.label :password_confirmation, :class => "control-label"
|
= f.submit "Sign up", class:"btn btn-lg btn-primary"
|
||||||
.controls
|
|
||||||
= f.password_field :password_confirmation
|
|
||||||
.control-group
|
|
||||||
.controls
|
|
||||||
= f.submit "Sign up"
|
|
||||||
= render "links"
|
= render "links"
|
||||||
|
|||||||
@@ -5,34 +5,44 @@
|
|||||||
<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><%= f.label :username%><br />
|
<div class="form-group">
|
||||||
<%= f.text_field :username%></div>
|
<%= f.text_field :username, placeholder: "Username", class: "form-control input-lg"%>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div><%= f.label :password %><br />
|
<div class="form-group">
|
||||||
<%= f.password_field :password %></div>
|
<%= f.password_field :password, placeholder: "Password", class: "form-control input-lg" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
<% if devise_mapping.rememberable? -%>
|
<% if devise_mapping.rememberable? -%>
|
||||||
<div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
|
<label>
|
||||||
|
<%= f.check_box :remember_me %> Remember Me
|
||||||
|
</label>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div><%= f.submit "Sign in" %></div>
|
<div class="form-group">
|
||||||
|
<p>
|
||||||
<div id="checkin_menu"style="display:none;">
|
<%= f.submit "Sign in", class:"btn btn-lg btn-primary" %>
|
||||||
<input id="checkin" name="checkin" type="button" value="CHECK IN">
|
</p>
|
||||||
<input id="checkout" name="checkout" type="button" value="CHECK OUT">
|
<p> Or quickly... </p>
|
||||||
|
<div class="btn-group">
|
||||||
|
<input id="checkin" name="checkin" type="button" value="CHECK IN" class="btn btn-lg btn-success">
|
||||||
|
<input id="checkout" name="checkout" type="button" value="CHECK OUT" class="btn btn-lg btn-danger">
|
||||||
|
</div>
|
||||||
</div>
|
</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%></div>
|
<%= f.hidden_field :username, :value => user.username%>
|
||||||
<%= f.hidden_field :password, :value => 'password' %></div>
|
<%= f.hidden_field :password, :value => 'password' %>
|
||||||
<div><%= f.submit "Sign in as #{user.username}" %></div>
|
<%= f.submit "Sign in as #{user.username}", class:"btn btn-info" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
%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: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
.x-boundlist-item {
|
.x-boundlist-item {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -24,11 +25,11 @@
|
|||||||
- if flash[:alert]
|
- if flash[:alert]
|
||||||
%p{:class => 'alert'}= flash[:alert]
|
%p{:class => 'alert'}= flash[:alert]
|
||||||
.row
|
.row
|
||||||
.span13
|
.span12
|
||||||
= yield
|
= yield
|
||||||
|
|
||||||
%footer
|
%footer
|
||||||
%p © Velocipede 2013
|
%p © BikeShed #{Time.now.year}
|
||||||
|
|
||||||
= javascript_include_tag "application"
|
= javascript_include_tag "application"
|
||||||
= javascript_include_tag params[:controller]
|
= javascript_include_tag params[:controller]
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
!!! 5
|
||||||
|
%html{:lang => "en"}
|
||||||
|
%head
|
||||||
|
%meta{:charset => "utf-8"}/
|
||||||
|
%title= content_for?(:title) ? yield(:title) : "Velocipede"
|
||||||
|
= load_netzke
|
||||||
|
= csrf_meta_tags
|
||||||
|
%body
|
||||||
|
= yield
|
||||||
|
= javascript_include_tag "application"
|
||||||
|
= javascript_include_tag params[:controller]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
= stylesheet_link_tag "bootstrap_and_overrides", :media => "all"
|
||||||
|
%h2 Dashboard
|
||||||
|
|
||||||
|
%p
|
||||||
|
%p
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary", href: new_time_entry_path} Add Time Entry
|
||||||
|
%p
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary", href: time_entries_path} View Timesheet
|
||||||
|
%p
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary", href: new_bike_path} Add Bike
|
||||||
|
|
||||||
|
- if !@bike.nil?
|
||||||
|
%p
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary", href: bike_path(@bike)} View Your Bike
|
||||||
|
|
||||||
|
|
||||||
|
%p
|
||||||
|
%p
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary hidden-xs", href: admin_index_path} Admin View
|
||||||
|
%p
|
||||||
|
%input{id: "index_logout", value: "Logout", type: "button", class: "btn btn-lg btn-block btn-danger", "data-url" => destroy_user_session_path }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
%a{ class: "btn btn-default btn-lg", href: root_path}
|
||||||
|
%span{ class:"icon-home"}
|
||||||
|
%h2 Task List
|
||||||
|
%h3
|
||||||
|
%a{ href: bike_path(@task_list.item)}#{@task_list.item.shop_id} #{@task_list.item.bike_brand}
|
||||||
|
%h4 #{@task_list.item.model}
|
||||||
|
|
||||||
|
%p
|
||||||
|
.control-group
|
||||||
|
- @task_list.tasks.each do |task|
|
||||||
|
.controls
|
||||||
|
%input{class: "task_list_task", type: "checkbox", "data-id" => task.id, checked: task.done}
|
||||||
|
#{task.task}
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "update_tasks_submit", value: "Save Changes", type: "button", class: "btn btn-lg btn-block btn-primary disabled", "data-url" => "#{api_update_task_path}"}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
%a{ class: "btn btn-default btn-lg", href: root_path}
|
||||||
|
%span{ class:"icon-home"}
|
||||||
|
%h2 Your Timesheet
|
||||||
|
|
||||||
|
%dl.dl-horizontal
|
||||||
|
%dd
|
||||||
|
%span.badge #{@hours_worked}
|
||||||
|
Total Hours Worked
|
||||||
|
%dd
|
||||||
|
%span.badge #{@credits_available}
|
||||||
|
Total Credits Available
|
||||||
|
|
||||||
|
%table.table
|
||||||
|
%tbody
|
||||||
|
- @user_time_entries.each do |entry|
|
||||||
|
%tr{ "data-id" => entry.id, "data-description" => entry.description,
|
||||||
|
"data-duration" => entry.duration_in_hours,
|
||||||
|
"data-start_date" => entry.start_date.to_date.to_formatted_s(:rfc822)}
|
||||||
|
%td #{entry.start_date.to_date.to_formatted_s(:rfc822)}
|
||||||
|
%td #{entry.duration_in_hours}
|
||||||
|
%td #{entry.type}
|
||||||
|
%td #{truncate(entry.description)}
|
||||||
|
%td
|
||||||
|
%button{ class: "btn icon-remove btn-danger work_entry-delete-btn", role: "button", "data-toggle" => "modal", "data-target" => "#confirmation" }
|
||||||
|
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary", href: new_time_entry_path } Add Time Entry
|
||||||
|
|
||||||
|
.modal{ id: "confirmation", class: "hide", role: "dialog", "aria-labelledby" => "confirmation_title", "aria-hidden" => "true", tabindex: -1 }
|
||||||
|
.modal-header
|
||||||
|
%button{ class: "close", "data-dismiss" => "modal", "aria-hidden" => "true"}x
|
||||||
|
%h4{ id: "confirmation_title" } Are you sure you want to delete?
|
||||||
|
.modal-body
|
||||||
|
%div{ id: "work_entry_start_date" }
|
||||||
|
Start Date
|
||||||
|
%div{ id: "work_entry_duration" }
|
||||||
|
Duration
|
||||||
|
%div{ id: "work_entry_description" }
|
||||||
|
Description
|
||||||
|
.modal-footer
|
||||||
|
%button{ class: "btn", "data-dismiss" => "modal", "aria-hidden" => "true"} Close
|
||||||
|
%button{ id: "confirmation_delete", class: "btn btn-primary", "data-url" => "api/v1/time_entries/" } Delete
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
%a{ class: "btn btn-default btn-lg", href: root_path}
|
||||||
|
%span{ class:"icon-home"}
|
||||||
|
%h2 Add Time Entry
|
||||||
|
|
||||||
|
%p
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "date_id", placeholder: "Date", type: "text", class: "datepicker input-small" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls{ class: "bootstrap-timepicker"}
|
||||||
|
%label Start
|
||||||
|
%input{id: "start_time_id", placeholder: "Time ID", type: "text", class: "input-small" }
|
||||||
|
.hidden{ id: "start_date" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%label End
|
||||||
|
%input{id: "end_time_id", placeholder: "Time ID", type: "text", class: "input-small" }
|
||||||
|
.hidden{ id: "end_date" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
.btn-group{ "data-toggle" => "buttons-radio"}
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "action_id", value: 1} Volunteer
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "action_id", value: 2} Personal
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "action_id", value: 3} Staff
|
||||||
|
.hidden{ id: "log_action_id" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%label
|
||||||
|
= select_tag(:bike_id, options_for_select(@bikes))
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%textarea{id: "description_id", placeholder: "Work description", class: "input-lg" }
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "add_time_entry_submit", value: "Add Time Entry", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_time_entry_path}", "data-forward" => "#{"."}"}
|
||||||
@@ -10,7 +10,7 @@ development:
|
|||||||
# Do not set this db to the same as development or production.
|
# Do not set this db to the same as development or production.
|
||||||
test:
|
test:
|
||||||
adapter: postgresql
|
adapter: postgresql
|
||||||
database: velocipede
|
database: velocipede_test
|
||||||
username: velocipede
|
username: velocipede
|
||||||
password:
|
password:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
|
|||||||
@@ -222,11 +222,12 @@ Devise.setup do |config|
|
|||||||
# end
|
# end
|
||||||
end
|
end
|
||||||
|
|
||||||
#Check in the user if they sign in. (Devise uses Warden)
|
unless Rails.env.test?
|
||||||
Warden::Manager.after_set_user do |user,auth,opts|
|
#Check in the user if they sign in. (Devise uses Warden)
|
||||||
# this essentially gets called after every netzke request, but alas,
|
Warden::Manager.after_set_user do |user,auth,opts|
|
||||||
# only using the after_authenticaion callback doesn't get fired after
|
# this essentially gets called after every netzke request, but alas,
|
||||||
# user creation.
|
# only using the after_authenticaion callback doesn't get fired after
|
||||||
user.checkin unless user.checked_in?
|
# user creation.
|
||||||
|
user.checkin unless user.checked_in?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+24
-3
@@ -4,13 +4,34 @@ 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 do
|
scope 'api', :module => :api, defaults: {format: :json} 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
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ class AddModelToBike < ActiveRecord::Migration
|
|||||||
add_column :bikes, :model, :string
|
add_column :bikes, :model, :string
|
||||||
change_column :bikes, :bike_model_id, :integer, :null => true
|
change_column :bikes, :bike_model_id, :integer, :null => true
|
||||||
Bike.all.each do |bike|
|
Bike.all.each do |bike|
|
||||||
bike.model = BikeModel.find_by_id(bike.bike_model_id).model
|
bike.update_attribute(:model, BikeModel.find_by_id(bike['bike_model_id']).model )
|
||||||
bike.save
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
class CreateBikeWheelSizes < ActiveRecord::Migration
|
class CreateBikeWheelSizes < ActiveRecord::Migration
|
||||||
def up
|
def up
|
||||||
create_table(:bike_wheel_sizes) do |t|
|
create_table(:bike_wheel_sizes) do |t|
|
||||||
t.string :twmm
|
t.integer :twmm
|
||||||
t.string :rdmm
|
t.integer :rdmm
|
||||||
t.string :twin
|
t.string :twin
|
||||||
t.string :rdin
|
t.string :rdin
|
||||||
t.string :twfr
|
t.string :twfr
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ class AlterBikeWheelToId < ActiveRecord::Migration
|
|||||||
else
|
else
|
||||||
wheel_size_id = wheel_size.id
|
wheel_size_id = wheel_size.id
|
||||||
end
|
end
|
||||||
bike.bike_wheel_size_id = wheel_size_id
|
bike.update_attribute(:bike_wheel_size_id, wheel_size_id)
|
||||||
bike.save
|
|
||||||
end
|
end
|
||||||
|
|
||||||
remove_column :bikes, :wheel_size
|
remove_column :bikes, :wheel_size
|
||||||
@@ -24,8 +23,7 @@ class AlterBikeWheelToId < ActiveRecord::Migration
|
|||||||
|
|
||||||
Bike.find_each do |bike|
|
Bike.find_each do |bike|
|
||||||
wheel_size = BikeWheelSize.find_by_id(bike['bike_wheel_size_id'])
|
wheel_size = BikeWheelSize.find_by_id(bike['bike_wheel_size_id'])
|
||||||
bike.wheel_size = wheel_size.rdin.to_i
|
bike.update_attribute(:wheel_size, wheel_size["rdin"].to_i)
|
||||||
bike.save
|
|
||||||
end
|
end
|
||||||
|
|
||||||
remove_column :bikes, :bike_wheel_size_id
|
remove_column :bikes, :bike_wheel_size_id
|
||||||
|
|||||||
+8
-8
@@ -47,14 +47,14 @@ ActiveRecord::Schema.define(:version => 20131019170248) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
create_table "bike_wheel_sizes", :force => true do |t|
|
create_table "bike_wheel_sizes", :force => true do |t|
|
||||||
t.string "twmm"
|
t.integer "twmm"
|
||||||
t.string "rdmm"
|
t.integer "rdmm"
|
||||||
t.string "twin"
|
t.string "twin"
|
||||||
t.string "rdin"
|
t.string "rdin"
|
||||||
t.string "twfr"
|
t.string "twfr"
|
||||||
t.string "rdfr"
|
t.string "rdfr"
|
||||||
t.string "description"
|
t.string "description"
|
||||||
t.string "tire_common_score"
|
t.string "tire_common_score"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "bikes", :force => true do |t|
|
create_table "bikes", :force => true do |t|
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ hybrid:
|
|||||||
road:
|
road:
|
||||||
id: 3
|
id: 3
|
||||||
style: ROAD
|
style: ROAD
|
||||||
|
other:
|
||||||
|
id: 4
|
||||||
|
style: OTHER
|
||||||
|
|||||||
+1
-1
@@ -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(:bike)
|
FactoryGirl.create(:seed_bike)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elsif Rails.env.production?
|
elsif Rails.env.production?
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Api::V1::BikesController do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
describe "#create" do
|
||||||
|
context "as a user" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user)
|
||||||
|
sign_in @user
|
||||||
|
end
|
||||||
|
it "returns 403" do
|
||||||
|
post :create
|
||||||
|
expect(@response.code.to_i).to eql 403
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
post :create
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::BikesController::CANNOT_MANAGE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "as a bike admin" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:bike_admin)
|
||||||
|
sign_in @user
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with no bike in json data" do
|
||||||
|
it "returns error status" do
|
||||||
|
post :create
|
||||||
|
expect(@response.code.to_i).to eql 422
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
post :create
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::BikesController::EXPECTED_BIKE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with valid bike in json data" do
|
||||||
|
before(:each) do
|
||||||
|
bike_data = { bikes: [{
|
||||||
|
serial_number: "XKCD",
|
||||||
|
bike_brand_id: 1,
|
||||||
|
shop_id: 1,
|
||||||
|
model: "Bike Model",
|
||||||
|
bike_style_id: 1,
|
||||||
|
seat_tube_height: 52,
|
||||||
|
bike_condition_id: 1,
|
||||||
|
bike_purpose_id: 1,
|
||||||
|
bike_wheel_size_id: 1,
|
||||||
|
}]}
|
||||||
|
#this is necessary because render_views does not work with sign_in devise helper
|
||||||
|
@submit_json = api_submit_json(@user, bike_data)
|
||||||
|
#not sure why format: :json not working
|
||||||
|
request.accept = 'application/json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
post :create, @submit_json
|
||||||
|
expect(@response.code.to_i).to eql 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the created bike json" do
|
||||||
|
post :create, @submit_json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json).to have_key("bikes")
|
||||||
|
expect(json.to_s).to include(@submit_json[:bikes].first[:serial_number])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with invalid bike in json data" do
|
||||||
|
before(:each) do
|
||||||
|
@submit_json = { bikes: [{
|
||||||
|
serial_number: "XKCD",
|
||||||
|
}]}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 422" do
|
||||||
|
post :create, @submit_json
|
||||||
|
expect(@response.code.to_i).to eql 422
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the fields with errors" do
|
||||||
|
post :create, @submit_json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json).to have_key("errors")
|
||||||
|
expect(json.to_s).to include("can't be blank")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#show" do
|
||||||
|
context "as a user" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user)
|
||||||
|
sign_in @user
|
||||||
|
end
|
||||||
|
|
||||||
|
context "no bike exists" do
|
||||||
|
it "returns 404" do
|
||||||
|
get :show, id: 999
|
||||||
|
expect(@response.code.to_i).to eql 404
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
get :show, id: 999
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::BikesController::NOT_FOUND
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "a bike exists" do
|
||||||
|
let!(:bike){ FactoryGirl.create(:bike) }
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
get :show, id: bike.id, format: :json
|
||||||
|
expect(@response.code.to_i).to eql 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the bike json" do
|
||||||
|
get :show, id: bike.id, format: :json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json).to have_key("bikes")
|
||||||
|
expect(json.to_s).to include(bike.serial_number)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Api::V1::LogsController do
|
||||||
|
|
||||||
|
describe "#checkin" do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user)
|
||||||
|
sign_in @user
|
||||||
|
controller.stub(:current_user).and_return(@user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "user is not checked in" do
|
||||||
|
before(:each) do
|
||||||
|
@user.stub(:checked_in?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 204" do
|
||||||
|
@user.stub(:checkin)
|
||||||
|
post :checkin
|
||||||
|
expect(@response.code.to_i).to eql 204
|
||||||
|
end
|
||||||
|
|
||||||
|
it "checks in a user" do
|
||||||
|
expect(@user).to receive(:checkin)
|
||||||
|
post :checkin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "user is already checked in" do
|
||||||
|
before(:each) do
|
||||||
|
@user.stub(:checked_in?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 404 if the user is already checked in" do
|
||||||
|
post :checkin
|
||||||
|
expect(@response.code.to_i).to eql 404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#checkout" do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user)
|
||||||
|
sign_in @user
|
||||||
|
controller.stub(:current_user).and_return(@user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "user is not checked in" do
|
||||||
|
before(:each) do
|
||||||
|
@user.stub(:checked_in?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 404" do
|
||||||
|
@user.stub(:checkin)
|
||||||
|
post :checkout
|
||||||
|
expect(@response.code.to_i).to eql 404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "user is already checked in" do
|
||||||
|
before(:each) do
|
||||||
|
@user.stub(:checked_in?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 204" do
|
||||||
|
@user.stub(:checkout)
|
||||||
|
post :checkout
|
||||||
|
expect(@response.code.to_i).to eql 204
|
||||||
|
end
|
||||||
|
|
||||||
|
it "checks out a user" do
|
||||||
|
expect(@user).to receive(:checkout)
|
||||||
|
post :checkout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Api::V1::TaskListsController do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
describe "#show" do
|
||||||
|
context "as a user" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user)
|
||||||
|
sign_in @user
|
||||||
|
end
|
||||||
|
|
||||||
|
context "task list exists" do
|
||||||
|
let!(:task_list){ FactoryGirl.create(:task_list) }
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
get :show, id: task_list.id, format: :json
|
||||||
|
expect(@response.code.to_i).to eql 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns valid task list json" do
|
||||||
|
get :show, id: task_list.id, format: :json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json.to_s).to include(task_list.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns task list with tasks json" do
|
||||||
|
get :show, id: task_list.id, format: :json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(task_list.tasks.count).to be > 0
|
||||||
|
task_list.tasks.each do |task|
|
||||||
|
expect(json.to_s).to include(task.task)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "task list does not exist" do
|
||||||
|
it "returns 404" do
|
||||||
|
get :show, id: 999, format: :json
|
||||||
|
expect(@response.code.to_i).to eql 404
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
get :show, id: 999, format: :json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).
|
||||||
|
to eql Api::V1::TaskListsController::NOT_FOUND
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Api::V1::TasksController do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
describe "#update" do
|
||||||
|
context "as a user with a bike" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user_with_bike)
|
||||||
|
sign_in @user
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with no tasks in json data" do
|
||||||
|
it "returns 400" do
|
||||||
|
put :update
|
||||||
|
expect(@response.code.to_i).to eql 422
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
put :update
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::TasksController::EXPECTED_TASKS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non existing task in data" do
|
||||||
|
before(:each) do
|
||||||
|
@submit_json = { tasks: [
|
||||||
|
{ id: 9999, done: false },
|
||||||
|
]}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 404" do
|
||||||
|
put :update, @submit_json
|
||||||
|
expect(@response.code.to_i).to eql 404
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
put :update, @submit_json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].to_s).to include(Api::V1::TasksController::NOT_FOUND)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with valid tasks json data" do
|
||||||
|
before(:each) do
|
||||||
|
@task = FactoryGirl.create(:task, done: false, task_list_id: @user.bike.task_list.id)
|
||||||
|
task_data = { tasks: [
|
||||||
|
{ id: @task.id, done: true},
|
||||||
|
]}
|
||||||
|
#this is necessary because render_views does not work with sign_in devise helper
|
||||||
|
@submit_json = api_submit_json(@user, task_data)
|
||||||
|
#not sure why format: :json not working
|
||||||
|
request.accept = 'application/json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
put :update, @submit_json
|
||||||
|
expect(@response.code.to_i).to eql 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the updated task json" do
|
||||||
|
put :update, @submit_json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json).to have_key("tasks")
|
||||||
|
expect(json.to_s).to include(Task.find_by_id(@task.id).done.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates the task" do
|
||||||
|
expect{put :update, @submit_json}.
|
||||||
|
to change{ Task.find_by_id(@task.id).done }.
|
||||||
|
from(false).to(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "as a user without a bike" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user, bike_id: nil)
|
||||||
|
sign_in @user
|
||||||
|
|
||||||
|
task = FactoryGirl.create(:task)
|
||||||
|
@submit_json = { tasks: [
|
||||||
|
{ id: task.id, done: false },
|
||||||
|
]}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return 403" do
|
||||||
|
put :update, @submit_json
|
||||||
|
expect(@response.code.to_i).to eql 403
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
put :update, @submit_json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].to_s).to include(Api::V1::TasksController::CANNOT_MANAGE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Api::V1::TimeEntriesController do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
describe "#create" do
|
||||||
|
|
||||||
|
context "as a user" do
|
||||||
|
let!(:user){ FactoryGirl.create(:user) }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
sign_in user
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with no time entry in json data" do
|
||||||
|
it "returns error status" do
|
||||||
|
post :create
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(@response.code.to_i).to eql 422
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
post :create
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::TimeEntriesController::EXPECTED_TIME_ENTRY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with valid time entry in json data" do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
time_data = { time_entries: [{
|
||||||
|
start_date: Time.zone.now,
|
||||||
|
end_date: Time.zone.now + 60,
|
||||||
|
log_action_id: 1,
|
||||||
|
bike_id: -1,
|
||||||
|
description: "My description"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
#this is necessary because render_views does not work with sign_in devise helper
|
||||||
|
@submit_json = api_submit_json(user, time_data)
|
||||||
|
#not sure why format: :json not working
|
||||||
|
request.accept = 'application/json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
post :create, @submit_json
|
||||||
|
expect(@response.code.to_i).to eql 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the created time entry json" do
|
||||||
|
post :create, @submit_json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json).to have_key("time_entries")
|
||||||
|
expect(json.to_s).to include(@submit_json[:time_entries].first[:description])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with invalid time entry in json data" do
|
||||||
|
before(:each) do
|
||||||
|
@submit_json = { time_entries: [{
|
||||||
|
description: "My description",
|
||||||
|
}]}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 422" do
|
||||||
|
post :create, @submit_json
|
||||||
|
expect(@response.code.to_i).to eql 422
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the fields with errors" do
|
||||||
|
post :create, @submit_json
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json).to have_key("errors")
|
||||||
|
expect(json.to_s).to include("can't be blank")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#delete" do
|
||||||
|
context "as a user" do
|
||||||
|
let!(:user){ FactoryGirl.create(:user) }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
sign_in user
|
||||||
|
end
|
||||||
|
|
||||||
|
context "entry does not exist" do
|
||||||
|
it "returns 404" do
|
||||||
|
delete :delete, id: 9000
|
||||||
|
expect(@response.code.to_i).to eql 404
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns not found" do
|
||||||
|
delete :delete, id: 9000
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json).to have_key("errors")
|
||||||
|
expect(json.to_s).to include("not found")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "entry exists" do
|
||||||
|
let!(:entry){ FactoryGirl.create(:time_entry) }
|
||||||
|
|
||||||
|
it "deletes the time entry" do
|
||||||
|
expect{delete :delete, id: entry.id}.to change{TimeEntry.count}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 204" do
|
||||||
|
delete :delete, id: entry.id
|
||||||
|
expect(@response.code.to_i).to eql 204
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Api::V1::UsersController do
|
||||||
|
|
||||||
|
describe "#password_reset" do
|
||||||
|
|
||||||
|
context "as a user" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:user)
|
||||||
|
sign_in @user
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 403" do
|
||||||
|
post :password_reset
|
||||||
|
expect(@response.code.to_i).to eql 403
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
post :password_reset
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::UsersController::CANNOT_MANAGE
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context "as an admin" do
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:admin)
|
||||||
|
sign_in @user
|
||||||
|
end
|
||||||
|
|
||||||
|
it "forbids a user to reset their own password" do
|
||||||
|
post :password_reset, user_id: @user.id
|
||||||
|
expect(@response.code.to_i).to eql 403
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::UsersController::NOT_ALLOWED
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with no user in json data" do
|
||||||
|
it "returns 404" do
|
||||||
|
post :password_reset
|
||||||
|
expect(@response.code.to_i).to eql 404
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error message" do
|
||||||
|
post :password_reset
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["errors"].first).to eql Api::V1::UsersController::NOT_FOUND
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "another user exists" do
|
||||||
|
before(:each) do
|
||||||
|
@user2 = FactoryGirl.create(:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
post :password_reset, user_id: @user2.id
|
||||||
|
expect(@response.code.to_i).to eql 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns that users new password" do
|
||||||
|
post :password_reset, user_id: @user2.id
|
||||||
|
json = JSON.parse(@response.body)
|
||||||
|
expect(json["password"].length).to eql Api::V1::UsersController::PASS_LENGTH
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :bike_brand do
|
||||||
|
brand {Faker::Commerce.product_name}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :bike_condition do
|
||||||
|
condition "POOR"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :bike_purpose do
|
||||||
|
purpose "SHOP"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :bike_style do
|
||||||
|
style {Faker::Commerce.product_name}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :bike_wheel_size do
|
||||||
|
twmm 40
|
||||||
|
rdmm 635
|
||||||
|
twin "1 1/2 [1 3/8]"
|
||||||
|
rdin 28
|
||||||
|
twfr "38B [35B]"
|
||||||
|
rdfr 700
|
||||||
|
description "a big wheel"
|
||||||
|
tire_common_score 1
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,6 +2,23 @@
|
|||||||
|
|
||||||
FactoryGirl.define do
|
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}"
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :task_list do
|
||||||
|
item_id { FactoryGirl.create(:bike).id }
|
||||||
|
item_type "Bike"
|
||||||
|
name { Faker::Lorem.words.join(" ")}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :task do
|
||||||
|
task_list { FactoryGirl.create(:task_list) }
|
||||||
|
done false
|
||||||
|
task { Faker::Lorem.words(7).join(" ")}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :time_entry do
|
||||||
|
loggable_type User.to_s
|
||||||
|
logger_type User.to_s
|
||||||
|
start_date Time.now
|
||||||
|
end_date Time.now
|
||||||
|
log_action_id 1
|
||||||
|
log_action_type ActsAsLoggable::UserAction.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -36,5 +36,9 @@ FactoryGirl.define do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory :user_with_bike do
|
||||||
|
bike { FactoryGirl.create(:bike) }
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
feature "Bikes" do
|
||||||
|
before(:each) do
|
||||||
|
# create brand and wheel size
|
||||||
|
@bike_brand = BikeBrand.create( brand: "Huffy")
|
||||||
|
@bike_wheel_size = BikeWheelSize.create( description: "MyWheelSize")
|
||||||
|
|
||||||
|
@user = FactoryGirl.create(:bike_admin)
|
||||||
|
visit new_user_session_path
|
||||||
|
fill_in "user_username", with: @user.username
|
||||||
|
fill_in "user_password", with: @user.password
|
||||||
|
click_button "Sign in"
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "User creates a new bike", js: true do
|
||||||
|
visit new_bike_path
|
||||||
|
fill_in "shop_id", with: 1
|
||||||
|
fill_in "model", with: "Huffle Puffle"
|
||||||
|
fill_in "serial_number", with: "XKCD"
|
||||||
|
find('label.btn.btn-default', text: 'RD').click
|
||||||
|
find('label.btn.btn-default', text: 'Poor').click
|
||||||
|
fill_in "seat_tube_height", with: 52
|
||||||
|
select @bike_brand.brand, from: "bike_brand_id"
|
||||||
|
select @bike_wheel_size.description, from: "bike_wheel_size_id"
|
||||||
|
click_button "Add Bike"
|
||||||
|
expect(page).to have_text(@bike_brand.brand)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "User submits a bike with errors", js: true do
|
||||||
|
visit new_bike_path
|
||||||
|
click_button "Add Bike"
|
||||||
|
expect(page).to have_text(:all, "is not a number")
|
||||||
|
expect(page).to have_text(:all, "is not a valid brand")
|
||||||
|
expect(page).to have_text(:all, "is too short")
|
||||||
|
expect(page).to have_text(:all, "is not a valid style")
|
||||||
|
expect(page).to have_text(:all, "is not a valid wheel size")
|
||||||
|
expect(page).to have_text(:all, "is not a valid condition")
|
||||||
|
end
|
||||||
|
end
|
||||||
+13
-13
@@ -1,10 +1,11 @@
|
|||||||
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 'Register'
|
page.should have_link 'Sign up'
|
||||||
click_link 'Register'
|
click_link 'Sign up'
|
||||||
current_path.should == new_user_registration_path
|
current_path.should == new_user_registration_path
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -13,30 +14,29 @@ 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 'First name'
|
page.should have_field 'user_first_name'
|
||||||
page.should have_field 'Last name'
|
page.should have_field 'user_last_name'
|
||||||
page.should have_field 'Nickname'
|
page.should have_field 'user_email'
|
||||||
page.should have_field 'Email'
|
page.should have_field 'user_password'
|
||||||
page.should have_field 'Password'
|
page.should have_field 'user_password_confirmation'
|
||||||
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 'Email', :with => 'FF@example.com'
|
fill_in 'user_email', :with => 'FF@example.com'
|
||||||
fill_in 'Password', :with => 'password'
|
fill_in 'user_password', :with => 'password'
|
||||||
fill_in 'Password confirmation', :with => 'password'
|
fill_in 'user_password_confirmation', :with => 'password'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should require first name' do
|
it 'should require first name' do
|
||||||
fill_in 'Last name', :with => 'Footer'
|
fill_in 'user_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 'First name', :with => 'Frank'
|
fill_in 'user_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
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe "Index Page" do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@user = FactoryGirl.create(:bike_admin)
|
||||||
|
visit root_path
|
||||||
|
fill_in "user_username", with: @user.username
|
||||||
|
fill_in "user_password", with: @user.password
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have a link to check in' do
|
||||||
|
page.should have_button 'CHECK IN'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have a link to check out' do
|
||||||
|
page.should have_button 'CHECK OUT'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'clicking check in should check in a user', js: true do
|
||||||
|
pending
|
||||||
|
expect{click_button 'CHECK IN'}.
|
||||||
|
to change{@user.checked_in?}.
|
||||||
|
from(false).to(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'clicking check out should check out a user', js: true do
|
||||||
|
pending
|
||||||
|
@user.checkin
|
||||||
|
expect{click_button 'CHECK OUT'}.
|
||||||
|
to change{@user.checked_in?}.
|
||||||
|
from(true).to(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
feature "TimeEntries" do
|
||||||
|
let!(:user){ FactoryGirl.create(:user) }
|
||||||
|
let!(:entry){ FactoryGirl.create(:time_entry, loggable_id: user.id) }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
visit new_user_session_path
|
||||||
|
fill_in "user_username", with: user.username
|
||||||
|
fill_in "user_password", with: user.password
|
||||||
|
click_button "Sign in"
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "User deletes a time entry", js: true do
|
||||||
|
visit time_entries_path
|
||||||
|
save_screenshot("/tmp/testingpoop.png")
|
||||||
|
find('button.work_entry-delete-btn').trigger('click')
|
||||||
|
click_button "Delete"
|
||||||
|
expect(page).to have_text("Your Timesheet")
|
||||||
|
expect(TimeEntry.count).to eql 0
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
feature "TimeEntries" do
|
||||||
|
let!(:user){ FactoryGirl.create(:user) }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
visit new_user_session_path
|
||||||
|
fill_in "user_username", with: user.username
|
||||||
|
fill_in "user_password", with: user.password
|
||||||
|
click_button "Sign in"
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "User creates a new time entry", js: true do
|
||||||
|
visit new_time_entry_path
|
||||||
|
fill_in "date_id", with: "04/27/2014"
|
||||||
|
fill_in "start_time_id", with: "3:00 PM"
|
||||||
|
fill_in "end_time_id", with: "3:15 PM"
|
||||||
|
find('label.btn.btn-default', text: 'Personal').trigger('click')
|
||||||
|
fill_in "description_id", with: "My Description"
|
||||||
|
click_button "Add Time Entry"
|
||||||
|
expect(page).to have_text("My Description")
|
||||||
|
expect(TimeEntry.count).to be > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
context "A bike exists" do
|
||||||
|
let!(:bike){ FactoryGirl.create(:bike) }
|
||||||
|
|
||||||
|
scenario "User creates a new bike work time entry", js: true do
|
||||||
|
visit new_time_entry_path
|
||||||
|
fill_in "date_id", with: "04/27/2014"
|
||||||
|
fill_in "start_time_id", with: "3:00 PM"
|
||||||
|
fill_in "end_time_id", with: "3:15 PM"
|
||||||
|
find('label.btn.btn-default', text: 'Personal').trigger('click')
|
||||||
|
select bike.to_s, from: "bike_id"
|
||||||
|
fill_in "description_id", with: "My Description"
|
||||||
|
click_button "Add Time Entry"
|
||||||
|
expect(page).to have_text("Task List")
|
||||||
|
expect(TimeEntry.count).to be > 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "User submits a time entry with errors", js: true do
|
||||||
|
visit new_time_entry_path
|
||||||
|
click_button "Add Time Entry"
|
||||||
|
expect(page).to have_text(:all, "can't be blank")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
+5
-3
@@ -1,6 +1,7 @@
|
|||||||
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'
|
||||||
|
|
||||||
@@ -19,12 +20,13 @@ 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
|
||||||
#
|
#
|
||||||
@@ -66,19 +68,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
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module ApiTestHelpers
|
||||||
|
def api_submit_json(user, json_hash)
|
||||||
|
json_hash.merge({username: user.username, password: user.password})
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user