1
0
mirror of https://github.com/fspc/BikeShed-1.git synced 2025-04-04 05:33:22 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Jason Denney
ef205ba46f testing 2013-11-26 17:10:18 -05:00
129 changed files with 879 additions and 4142 deletions

4
.gitignore vendored
View File

@ -6,7 +6,7 @@ tmp/
.powrc .powrc
.rbenv-version .rbenv-version
.DS_Store .DS_Store
public/extjs
public/assets public/assets
public/system public/images/icons
config/database.yml config/database.yml
.idea/

15
.powrc
View File

@ -1,15 +0,0 @@
if [ -f "$rvm_path/scripts/rvm" ]; then
source "$rvm_path/scripts/rvm"
if [ -f ".rvmrc" ]; then
source ".rvmrc"
fi
if [ -f ".ruby-version" ]; then
rvm use `cat .ruby-version`
fi
if [ -f ".ruby-gemset" ]; then
rvm gemset use --create `cat .ruby-gemset`
fi
fi

1
.rspec
View File

@ -1 +1,2 @@
--colour --colour
-rturnip

View File

@ -1 +0,0 @@
velocipede

View File

@ -1 +0,0 @@
ruby-2.1.1

55
.rvmrc Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env bash
# This is an RVM Project .rvmrc file, used to automatically load the ruby
# development environment upon cd'ing into the directory
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
environment_id="ruby-1.9.3-p374@velocipede"
#
# Uncomment following line if you want options to be set only for given project.
#
# PROJECT_JRUBY_OPTS=( --1.9 )
#
# First we attempt to load the desired environment directly from the environment
# file. This is very fast and efficient compared to running through the entire
# CLI and selector. If you want feedback on which environment was used then
# insert the word 'use' after --create as this triggers verbose mode.
#
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
then
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
then
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
fi
else
# If the environment file has not yet been created, use the RVM CLI to select.
if ! rvm --create use "$environment_id"
then
echo "Failed to create RVM environment '${environment_id}'."
return 1
fi
fi
#
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
# it be automatically loaded. Uncomment the following and adjust the filename if
# necessary.
#
# filename=".gems"
# if [[ -s "$filename" ]]
# then
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
# fi
# If you use bundler, this might be useful to you:
# if command -v bundle && [[ -s Gemfile ]]
# then
# bundle install
# fi

View File

@ -1,31 +0,0 @@
FROM ruby:2.1
# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y nodejs --no-install-recommends && \
apt-get install -y mysql-client postgresql-client sqlite3 --no-install-recommends && \
apt-get install -y zip unzip --no-install-recommends && \
rm -rf /var/lib/apt/lists/*
COPY Gemfile /usr/src/app/
COPY Gemfile.lock /usr/src/app/
RUN env NOKOGIRI_USE_SYSTEM_LIBRARIES=true bundle install
#COPY install_extjs.rb /usr/src/app/
#RUN /usr/src/app/install_extjs.rb
COPY extjs-4.1.1.zip /usr/lib/
RUN unzip -d /usr/lib /usr/lib/extjs-4.1.1.zip; \
mv /usr/lib/ext-4.1.1a /usr/lib/extjs
COPY install_famfamfam.rb /usr/src/app/
RUN /usr/src/app/install_famfamfam.rb
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]

39
Gemfile
View File

@ -1,7 +1,6 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rails', '3.2.13' gem 'rails', '3.2.13'
gem 'rake', '< 11.0'
gem 'netzke-cancan' gem 'netzke-cancan'
gem 'netzke-core', '~>0.8.0' gem 'netzke-core', '~>0.8.0'
@ -13,39 +12,39 @@ gem 'bootstrap-will_paginate', '~> 0.0.6'
gem 'cancan' gem 'cancan'
gem 'decent_exposure', '~> 1.0.1' gem 'decent_exposure', '~> 1.0.1'
gem 'devise', '~> 2.0.4' gem 'devise', '~> 2.0.4'
gem 'haml-rails' gem 'haml-rails', '~> 0.3.4'
gem 'jquery-rails', '~> 2.0' gem 'jquery-rails', '~> 2.0'
gem 'pg', '~> 0.17.1' gem 'pg'
gem 'will_paginate', '~> 3.0.3' gem 'will_paginate', '~> 3.0.3'
gem 'jbuilder', '~> 2.0.3'
gem 'paperclip', '~> 4.3'
# Assets # Gems used only for assets and not required
gem 'sass-rails', '~> 3.0' # in production environments by default.
group :assets do
gem 'coffee-rails', '~> 3.2.1' gem 'coffee-rails', '~> 3.2.1'
gem 'bootstrap-sass', '~> 3.1.1' gem "twitter-bootstrap-rails", "~> 2.0.3"
gem 'momentjs-rails', '>= 2.9.0' # See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'bootstrap3-datetimepicker-rails', '~> 4.17.43' # gem 'therubyracer'
gem 'uglifier', '>= 1.0.3' gem 'uglifier', '>= 1.0.3'
end
group :development, :test do group :development, :test do
gem 'rspec-rails', '~> 2.14.0' gem 'rspec-rails', '~> 2.8.1'
gem 'factory_girl_rails', '~> 1.2' gem 'factory_girl_rails', '~> 1.2'
gem 'pry', '~> 0.9.8' gem 'pry', '~> 0.9.8'
gem 'faker', '~> 1.2.0' gem 'faker'
gem 'colorize'
end end
group :test do group :test do
gem 'shoulda-matchers', '~> 1.0.0' gem 'shoulda-matchers', '~> 1.0.0'
gem 'capybara', '~> 2.2.1' gem 'capybara', '~> 1.1.2'
gem 'poltergeist', '~> 1.10.0' gem 'turnip', '~> 0.3.0'
gem 'database_cleaner', '~> 1.2.0' gem 'database_cleaner'
gem 'launchy', '~> 2.4.2' gem 'launchy'
gem 'spork', '~> 0.9.2' gem 'spork'
#guard dependency for Mac OS 10 #guard dependency for Mac OS 10
gem 'guard-spork', '~> 1.5.1' gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
gem 'guard-rspec', '~> 4.2.6' gem 'guard-spork'
gem 'guard-rspec'
end end

View File

@ -36,50 +36,42 @@ GEM
activesupport (3.2.13) activesupport (3.2.13)
i18n (= 0.6.1) i18n (= 0.6.1)
multi_json (~> 1.0) multi_json (~> 1.0)
addressable (2.4.0) addressable (2.3.3)
arel (3.0.3) arel (3.0.2)
bcrypt (3.1.10) bcrypt-ruby (3.0.1)
bcrypt-ruby (3.1.5) bootstrap-will_paginate (0.0.9)
bcrypt (>= 3.1.3)
bootstrap-sass (3.1.1.1)
sass (~> 3.2)
bootstrap-will_paginate (0.0.10)
will_paginate will_paginate
bootstrap3-datetimepicker-rails (4.17.43)
momentjs-rails (>= 2.8.1)
builder (3.0.4) builder (3.0.4)
cancan (1.6.10) cancan (1.6.9)
capybara (2.2.1) capybara (1.1.4)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
xpath (~> 2.0) selenium-webdriver (~> 2.0)
childprocess (0.5.9) xpath (~> 0.1.4)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11) ffi (~> 1.0, >= 1.0.11)
climate_control (0.1.0) coderay (1.0.9)
cliver (0.3.2)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
coderay (1.1.1)
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.4.1) coffee-script (2.2.0)
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.10.0) coffee-script-source (1.6.2)
colorize (0.8.1) commonjs (0.2.6)
database_cleaner (1.2.0) database_cleaner (0.9.1)
decent_exposure (1.0.2) decent_exposure (1.0.2)
devise (2.0.6) devise (2.0.5)
bcrypt-ruby (~> 3.0) bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3) orm_adapter (~> 0.0.3)
railties (~> 3.1) railties (~> 3.1)
warden (~> 1.1.1) warden (~> 1.1.1)
diff-lcs (1.2.5) diff-lcs (1.1.3)
erubis (2.7.0) erubis (2.7.0)
execjs (2.6.0) execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (2.6.4) factory_girl (2.6.4)
activesupport (>= 2.3.9) activesupport (>= 2.3.9)
factory_girl_rails (1.7.0) factory_girl_rails (1.7.0)
@ -87,58 +79,52 @@ GEM
railties (>= 3.0.0) railties (>= 3.0.0)
faker (1.2.0) faker (1.2.0)
i18n (~> 0.5) i18n (~> 0.5)
ffi (1.9.10) ffi (1.6.0)
formatador (0.2.5) formatador (0.2.4)
guard (2.13.0) gherkin (2.11.6)
json (>= 1.7.6)
guard (1.7.0)
formatador (>= 0.2.4) formatador (>= 0.2.4)
listen (>= 2.7, <= 4.0) listen (>= 0.6.0)
lumberjack (~> 1.0) lumberjack (>= 1.0.2)
nenv (~> 0.1) pry (>= 0.9.10)
notiffany (~> 0.0) thor (>= 0.14.6)
pry (>= 0.9.12) guard-rspec (1.2.1)
shellany (~> 0.0) guard (>= 1.1)
thor (>= 0.18.1) guard-spork (1.5.0)
guard-rspec (4.2.10)
guard (~> 2.1)
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)
haml (4.0.7) haml (3.1.8)
tilt haml-rails (0.3.5)
haml-rails (0.4)
actionpack (>= 3.1, < 4.1) actionpack (>= 3.1, < 4.1)
activesupport (>= 3.1, < 4.1) activesupport (>= 3.1, < 4.1)
haml (>= 3.1, < 4.1) haml (~> 3.1)
railties (>= 3.1, < 4.1) railties (>= 3.1, < 4.1)
hike (1.2.3) hike (1.2.1)
i18n (0.6.1) i18n (0.6.1)
jbuilder (2.0.8)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
journey (1.0.4) journey (1.0.4)
jquery-rails (2.3.0) jquery-rails (2.2.1)
railties (>= 3.0, < 5.0) railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json (1.8.3) json (1.7.7)
launchy (2.4.3) launchy (2.2.0)
addressable (~> 2.3) addressable (~> 2.3)
listen (3.0.6) less (2.2.2)
rb-fsevent (>= 0.9.3) commonjs (~> 0.2.6)
rb-inotify (>= 0.9.7) less-rails (2.2.6)
lumberjack (1.0.10) actionpack (>= 3.1)
mail (2.5.4) less (~> 2.2.0)
libv8 (3.3.10.4)
listen (0.7.3)
lumberjack (1.0.3)
mail (2.5.3)
i18n (>= 0.4.0)
mime-types (~> 1.16) mime-types (~> 1.16)
treetop (~> 1.4.8) treetop (~> 1.4.8)
method_source (0.8.2) method_source (0.8.1)
mime-types (1.25.1) mime-types (1.22)
mimemagic (0.3.0) multi_json (1.8.2)
mini_portile2 (2.0.0)
momentjs-rails (2.15.1)
railties (>= 3.1)
multi_json (1.12.1)
nenv (0.3.0)
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)
@ -147,34 +133,20 @@ GEM
netzke-core (0.8.4) netzke-core (0.8.4)
execjs execjs
uglifier uglifier
nokogiri (1.6.7.2) nokogiri (1.5.9)
mini_portile2 (~> 2.0.0.rc2)
notiffany (0.0.8)
nenv (~> 0.1)
shellany (~> 0.0)
orm_adapter (0.0.7) orm_adapter (0.0.7)
paperclip (4.3.7) pg (0.15.1)
activemodel (>= 3.2.0) polyglot (0.3.3)
activesupport (>= 3.2.0) pry (0.9.12)
cocaine (~> 0.5.5) coderay (~> 1.0.5)
mime-types
mimemagic (= 0.3.0)
pg (0.17.1)
poltergeist (1.10.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
websocket-driver (>= 0.2.0)
polyglot (0.3.5)
pry (0.9.12.6)
coderay (~> 1.0)
method_source (~> 0.8) method_source (~> 0.8)
slop (~> 3.4) slop (~> 3.4)
rack (1.4.7) rack (1.4.5)
rack-cache (1.6.1) rack-cache (1.2)
rack (>= 0.4) rack (>= 0.4)
rack-ssl (1.3.4) rack-ssl (1.3.3)
rack rack
rack-test (0.6.3) rack-test (0.6.2)
rack (>= 1.0) rack (>= 1.0)
rails (3.2.13) rails (3.2.13)
actionmailer (= 3.2.13) actionmailer (= 3.2.13)
@ -191,58 +163,61 @@ 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.5.0) rake (10.0.4)
rb-fsevent (0.9.7) rb-fsevent (0.9.3)
rb-inotify (0.9.7)
ffi (>= 0.5.0)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
rspec (2.14.1) rspec (2.8.0)
rspec-core (~> 2.14.0) rspec-core (~> 2.8.0)
rspec-expectations (~> 2.14.0) rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.14.0) rspec-mocks (~> 2.8.0)
rspec-core (2.14.8) rspec-core (2.8.0)
rspec-expectations (2.14.5) rspec-expectations (2.8.0)
diff-lcs (>= 1.1.3, < 2.0) diff-lcs (~> 1.1.2)
rspec-mocks (2.14.6) rspec-mocks (2.8.0)
rspec-rails (2.14.2) rspec-rails (2.8.1)
actionpack (>= 3.0) actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 2.14.0) rspec (~> 2.8.0)
rspec-expectations (~> 2.14.0) rubyzip (0.9.9)
rspec-mocks (~> 2.14.0) selenium-webdriver (2.31.0)
sass (3.4.22) childprocess (>= 0.2.5)
sass-rails (3.2.6) multi_json (~> 1.0)
railties (~> 3.2.0) rubyzip
sass (>= 3.1.10) websocket (~> 1.0.4)
tilt (~> 1.3)
shellany (0.0.1)
shoulda-matchers (1.0.0) shoulda-matchers (1.0.0)
slop (3.6.0) slop (3.4.4)
spork (0.9.2) spork (0.9.2)
sprockets (2.2.3) sprockets (2.2.2)
hike (~> 1.2) hike (~> 1.2)
multi_json (~> 1.0) multi_json (~> 1.0)
rack (~> 1.0) rack (~> 1.0)
tilt (~> 1.1, != 1.3.0) tilt (~> 1.1, != 1.3.0)
thor (0.19.4) therubyracer (0.10.2)
tilt (1.4.1) libv8 (~> 3.3.10)
treetop (1.4.15) thor (0.18.1)
tilt (1.3.6)
treetop (1.4.12)
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
tzinfo (0.3.46) turnip (0.3.1)
uglifier (2.7.2) gherkin (>= 2.5)
rspec (~> 2.0)
twitter-bootstrap-rails (2.0.9)
actionpack (>= 3.1)
less-rails (~> 2.2.2)
railties (>= 3.1)
therubyracer (~> 0.10.1)
tzinfo (0.3.38)
uglifier (2.1.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) multi_json (~> 1.0, >= 1.0.2)
warden (1.1.1) warden (1.1.1)
rack (>= 1.0) rack (>= 1.0)
websocket-driver (0.6.5) websocket (1.0.7)
websocket-extensions (>= 0.1.0) will_paginate (3.0.4)
websocket-extensions (0.1.2) xpath (0.1.4)
will_paginate (3.0.7)
xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
PLATFORMS PLATFORMS
@ -250,37 +225,31 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
acts_as_loggable! acts_as_loggable!
bootstrap-sass (~> 3.1.1)
bootstrap-will_paginate (~> 0.0.6) bootstrap-will_paginate (~> 0.0.6)
bootstrap3-datetimepicker-rails (~> 4.17.43)
cancan cancan
capybara (~> 2.2.1) capybara (~> 1.1.2)
coffee-rails (~> 3.2.1) coffee-rails (~> 3.2.1)
colorize 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 (~> 1.2.0) faker
guard-rspec (~> 4.2.6) guard-rspec
guard-spork (~> 1.5.1) guard-spork
haml-rails haml-rails (~> 0.3.4)
jbuilder (~> 2.0.3)
jquery-rails (~> 2.0) jquery-rails (~> 2.0)
launchy (~> 2.4.2) launchy
momentjs-rails (>= 2.9.0)
netzke-basepack (~> 0.8.0) netzke-basepack (~> 0.8.0)
netzke-cancan netzke-cancan
netzke-core (~> 0.8.0) netzke-core (~> 0.8.0)
paperclip (~> 4.3) pg
pg (~> 0.17.1)
poltergeist (~> 1.10.0)
pry (~> 0.9.8) pry (~> 0.9.8)
rails (= 3.2.13) rails (= 3.2.13)
rake (< 11.0) rb-fsevent
rspec-rails (~> 2.14.0) rspec-rails (~> 2.8.1)
sass-rails (~> 3.0)
shoulda-matchers (~> 1.0.0) shoulda-matchers (~> 1.0.0)
spork (~> 0.9.2) spork
turnip (~> 0.3.0)
twitter-bootstrap-rails (~> 2.0.3)
uglifier (>= 1.0.3) uglifier (>= 1.0.3)
will_paginate (~> 3.0.3) will_paginate (~> 3.0.3)

View File

@ -1,50 +1,38 @@
# 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 a live demo here: <http://bikeshed.wvcompletestreets.org/> Thanks @fspc for setting that up!
See an overview video of what the desktop view looks like and how it works here: https://www.youtube.com/watch?v=0-JjM6d9nK4.
## Overview/Instructions/Guides
See [guides](doc/guides.md)
# Developer Setup # Developer Setup
## Running with Docker (recommended) 1. use rvm
1. allow the .rvmrc file
1. `gem install bundler`
1. `bundle`
1. Install Postgres (Mac OSX instructions below)
1. `rake db:create db:migrate`
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. Link to your extJS folder path under `public/extjs`: (From app root) `ln -s /MY/PATH/extjs/ public/extjs`
1. `rails s`
1. These instructions haven't been tested, please provide corrections!
1. Install Docker Toolbox <https://www.docker.com/toolbox>
1. Make sure you have a machine running: `docker-machine start default && eval "$(docker-machine env default)"`
1. Execute `docker-compose build`
1. Execute `docker-compose run web rake db:setup`
1. Execute `docker-compose up`
1. If using Docker Toolbox, use `docker-machine ip default` to get the IP where the server is running.
1. Test the Rails server is running with by visiting `<INSERT IP>:8080` in your browser.
### Alternative Dockerfile # Postgres 9.2 Mac OSX Install
1. Install homebrew `ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"`
1. `brew install postgres`
1. First time db initialization `initdb /usr/local/var/postgres -E utf8`
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 database `createdb -U velocipede --owner=velocipede velocipede`
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
See <https://github.com/fspc/bikeshed> for an alternative docker setup. # Optional
Add icons
### Developer Workflow 1. Download icons from http://www.famfamfam.com/lab/icons/silk/
The project directory should already be mounted inside the container, so you should be able to make live changes. However, since the project is running in the 'web' container, you need to prepend commands with `docker-compose run web`. 1. Link to the icons under `public/images/icons`: (From app root) `ln -s /MY/PATH/famfamfam_silk_icons/icons public/images/icons`
You'll likely want to add the following aliases:
```
alias dm='docker-machine'
alias dc='docker-compose'
alias dcrw='docker-compose run web'
```
That way your commands can be shortened to:
```
dcrw rake routes
dcrw rails console
dcrw rspec
```
If there's a better way, I'm all ears. Alternatively you could ssh into the machine with `dcrw bash`.
# License # License
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT) Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
<a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a> <a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a>
Testing

View File

@ -12,9 +12,4 @@
// //
//= require jquery //= require jquery
//= require jquery_ujs //= require jquery_ujs
//= require bootstrap //= require custom_netzke_helpers
//= require utils
//= require moment
//= require bootstrap-datetimepicker
//= require jquery.form

View File

@ -1,11 +0,0 @@
$('.btn').button();
$("#add_bike_form").ajaxForm({
dataType: "json",
success: function(data){
window.location = data.bikes[0].id + "?add_bike=1";
},
error: function(data){
displayFormErrors(data.responseJSON);
}
})

View File

@ -1,6 +1,6 @@
//when signed out, or session expires forward to sign in page //when signed out, or session expires forward to sign in page
Ext.Ajax.on('requestexception', function(conn, response, options) { Ext.Ajax.on('requestexception', function(conn, response, options) {
if (response.status === 401 && response.statusText === "Unauthorized") { window.location = '/users/sign_in'; } if (response.status === 401) { window.location = '/users/sign_in'; }
}, this); }, this);
//Override default netzke time entry field //Override default netzke time entry field

View File

@ -2,8 +2,8 @@ $(document).ready(function(){
var MIN_LEN = 3; var MIN_LEN = 3;
var MAX_SUBMITS = 3; var MAX_SUBMITS = 3;
var submit_count = 0; var submit_count = 0;
$("input[name=commit]").click( function(e){
var checkContacts = function() { console.log("clicked");
submit_count += 1; submit_count += 1;
//IDs of contact info //IDs of contact info
var contact_info_ids = [ var contact_info_ids = [
@ -14,15 +14,15 @@ $(document).ready(function(){
"user_user_profiles_attributes_0_addrZip", "user_user_profiles_attributes_0_addrZip",
"user_user_profiles_attributes_0_phone" "user_user_profiles_attributes_0_phone"
]; ];
var contact_vals = ""; var contact_vals = "";
var index = 0;
//see if any contact info exists //see if any contact info exists
for( var index in contact_info_ids){ for( var index in contact_info_ids){
contact_vals += $("#"+contact_info_ids[index]).val(); contact_vals += $("#"+contact_info_ids[index]).val();
} }
if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){ if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){
if( submit_count == MAX_SUBMITS + 1 ){ if( submit_count > MAX_SUBMITS ){
alert("Fine."); alert("Fine.");
} }
return true; return true;
@ -47,34 +47,5 @@ $(document).ready(function(){
} }
return false; return false;
} }
};
var checkValid = function() {
var errors = {};
var hasErrors = false;
["username", "first_name", "last_name"].forEach(function(requiredField) {
if(!$("#user_" + requiredField).val().trim()) {
errors["user_" + requiredField] = ["can't be blank"];
hasErrors = true;
}
});
if($("#user_password").val().length < 6) {
errors["user_password"] = ["is too short (minimum is 6 characters)"];
hasErrors = true;
}
if($("#user_password").val() != $("#user_password_confirmation").val()) {
errors["user_password_confirmation"] = ["confirmation doesn't match password"];
hasErrors = true;
}
displayFormErrors({errors: errors}, "#new_user");
return !hasErrors;
};
$("input[name=commit]").click(function(e){
return checkContacts() && checkValid();
}); });
}); });

View File

@ -1,7 +1,7 @@
$(document).ready(function(){ $(document).ready(function(){
$("#checkin_menu").show(); $("#checkin_menu").show();
$("#checkin").click( function(e){ $("#checkin").click( function(e){
var username = $("#user_username").val(); var username = $("#user_email").val();
var password = $("#user_password").val(); var password = $("#user_password").val();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
@ -13,7 +13,7 @@ $(document).ready(function(){
complete: function() { }, complete: function() { },
success: function(data) { success: function(data) {
alert("Checked IN!"); alert("Checked IN!");
$("#user_username").val(''); $("#user_email").val('');
$("#user_password").val(''); $("#user_password").val('');
}, },
error: function(data,textStatus) { error: function(data,textStatus) {
@ -22,7 +22,7 @@ $(document).ready(function(){
}) })
}); });
$("#checkout").click( function(e){ $("#checkout").click( function(e){
var username = $("#user_username").val(); var username = $("#user_email").val();
var password = $("#user_password").val(); var password = $("#user_password").val();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
@ -34,8 +34,7 @@ $(document).ready(function(){
complete: function() { }, complete: function() { },
success: function(data) { success: function(data) {
alert("Checked OUT!"); alert("Checked OUT!");
$("#user_email").val('');
$("#user_username").val('');
$("#user_password").val(''); $("#user_password").val('');
}, },
error: function(data,textStatus) { error: function(data,textStatus) {

View File

@ -1,9 +0,0 @@
$("#index_logout").click(function(){
$.ajax({
type: "DELETE",
url: $("#index_logout").data("url"),
complete: function(){
window.location.href="/";
}
});
});

View File

@ -1,32 +0,0 @@
$(".task_list_task").click(function(){
$("#update_tasks_submit").removeClass("disabled");
});
$("#update_tasks_submit").click(function(){
var tasks = [];
$(".task_list_task").each(function(){
tasks.push({
id: parseInt($(this).data("id")),
done: $(this).is(":checked")
});
});
var 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);
}
});
});

View File

@ -1,83 +0,0 @@
$(document).ready(function () {
var $date_input = $("#date_id");
$date_input.datetimepicker({format: $date_input.data("format")});
var $start_time_input = $("#start_time_id");
var $end_time_input = $("#end_time_id");
[$start_time_input, $end_time_input].forEach(function($x) {
$x.datetimepicker({format: $x.data("format"), stepping: 15});
})
$("#add_time_entry_submit").click(function () {
var date = $date_input.val();
var start_date = new Date(date + " " + $start_time_input.val());
var end_date = new Date(date + " " + $end_time_input.val());
var forward = $("#add_time_entry_submit").data("forward");
// If a bike is selected, forward to the bike
// checklist.
var 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.
var json_data = {
time_entries: [{
start_date: moment(start_date).format("DD-MM-YYYY h:mm A"),
end_date: moment(end_date).format("DD-MM-YYYY h: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 () {
var row = $(this).closest("tr");
var entry_id = row.data("id");
var start_date = row.data("start_date");
var duration = row.data("duration");
var description = row.data("description");
$("#work_entry_start_date").text(start_date);
$("#work_entry_duration").text(duration);
$("#work_entry_description").text(description);
$("#confirmation_delete").data("entry_id", entry_id);
});
$("#confirmation_delete").click(function () {
var entry_id = $(this).data("entry_id");
var url = $("#confirmation_delete").data("url-template").replace(/__ID__/, entry_id);
$.ajax({
url: url,
type: "delete",
contentType: 'application/json',
success: function (data, status, xhr) {
location.reload();
},
error: function (data, status) {
displayFormErrors(data.responseJSON);
}
});
});
});

View File

@ -1,10 +0,0 @@
function displayFormErrors(data, form){
if(form){
$(form).find(".form-group.has-error").removeClass("has-error").find(".help-block").html("");
}
if(data.errors != undefined ){
$.each(data.errors, function(field, errorMsg) {
$("#"+field).closest(".form-group").addClass("has-error").find(".help-block").html(errorMsg.join(", "));
});
}
}

View File

@ -0,0 +1,13 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require_tree .
*/

View File

@ -1,3 +0,0 @@
@import "bootstrap";
@import "bootstrap-datetimepicker";
@import "frontend";

View File

@ -0,0 +1,17 @@
@import "twitter/bootstrap/bootstrap";
@import "twitter/bootstrap/responsive";
// Set the correct sprite paths
@iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
@iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png');
// Your custom LESS stylesheets goes here
//
// Since bootstrap was imported above you have access to its mixins which
// you may use and inherit here
//
// If you'd like to override bootstrap's own variables, you can do so here as well
// See http://twitter.github.com/bootstrap/less.html for their names and documentation
//
// Example:
// @linkColor: #ff0000;

View File

@ -1,69 +0,0 @@
body {
padding-top: $grid-gutter-width / 2;
}
.x-boundlist-item {
white-space: nowrap;
}
fieldset, .fieldset {
margin-top: $line-height-computed;
margin-bottom: $line-height-computed;
}
.timepickers {
.form-group {
vertical-align: top;
&.dash {
line-height: 2.4em;
}
}
.bootstrap-datetimepicker-widget.dropdown-menu {
width: auto;
}
.bootstrap-datetimepicker-widget .timepicker-hour,
.bootstrap-datetimepicker-widget .timepicker-minute,
.bootstrap-datetimepicker-widget .timepicker-second {
width: 40px;
}
.bootstrap-datetimepicker-widget table td {
height: 40px;
line-height: 40px;
width: 40px;
padding: 0 !important;
}
.bootstrap-datetimepicker-widget table td.separator {
width: 10px;
}
.bootstrap-datetimepicker-widget table td span {
width: 40px;
height: 40px;
line-height: 40px;
margin: 0;
}
.bootstrap-datetimepicker-widget a[data-action] {
padding: 0;
color: #000;
}
}
.bike-color {
display: inline-block;
padding-left: 6px;
padding-right: 6px;
}
.navbar-default {
.navbar-text {
width: 100%;
font-size: 150%;
margin-top: 10px;
margin-bottom: 10px;
}
}

View File

@ -19,6 +19,6 @@
}); });
}, },
onChangeAccountInfo: function(){ onChangeAccountInfo: function(){
window.location.href="/users/edit"; window.location.href="users/edit";
} }
} }

View File

@ -2,6 +2,8 @@ class Tasks < Netzke::Basepack::Grid
def configure(c) def configure(c)
super super
#disable by default, will be enabled once bike is clicked
c.disabled = true
task_list_id = nil task_list_id = nil
if session[:selected_bike_id] if session[:selected_bike_id]
task_list_id = Bike.find_by_id(session[:selected_bike_id]).task_list.id task_list_id = Bike.find_by_id(session[:selected_bike_id]).task_list.id
@ -29,14 +31,13 @@ class Tasks < Netzke::Basepack::Grid
end end
def default_fields_for_forms def default_fields_for_forms
bike = Bike.find_by_id(session[:selected_bike_id]) fields = []
bike = "Select a Bike First!" if bike.nil? fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
[ fields.concat( [
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Bike Selected", :value => "#{bike.to_s}"},
:done, :done,
:task, :task,
:notes, :notes
] ])
end end
#override with nil to remove actions #override with nil to remove actions

View File

@ -33,7 +33,7 @@
Ext.Msg.alert("Success", "New Password: "+data.password); Ext.Msg.alert("Success", "New Password: "+data.password);
}, },
error: function(data,textStatus) { error: function(data,textStatus) {
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["errors"][0]); Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]);
} }
}); });
} }

View File

@ -6,12 +6,12 @@ class Api::V1::BaseController < ActionController::Base
private private
def authenticate_user def authenticate_user
if params[:username] if params[:username]
user = User.find_for_database_authentication( :username => params[:username] ) user = User.find_for_database_authentication( :email => params[:username] )
@current_user = user if user && user.valid_password?( params[:password] ) @current_user = user if user && user.valid_password?( params[:password] )
if @current_user.nil? if @current_user.nil?
msg = "Username/Password/Token invalid" msg = "Username/Password/Token invalid"
render :json => {:error => msg }, :status => 401 and return render :json => {:error => msg }, :status => 403 and return
end end
else else
authenticate_user! authenticate_user!

View File

@ -1,32 +0,0 @@
class Api::V1::BikesController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have permission to manage bikes."
EXPECTED_BIKE = "Expected bike in submitted data"
NOT_FOUND = "The bike could not be found."
before_filter :check_bike_permission, except: :show
def create
if params[:bikes] && bike = params[:bikes].first
@bike = Bike.new(bike)
if !@bike.save
render json: { errors: @bike.errors }, status: 422 and return
end
else
render json: { errors: [EXPECTED_BIKE]}, status: 422 and return
end
end
def show
@bike = Bike.find_by_id(params[:id])
if @bike.nil?
render json: { errors: [NOT_FOUND] }, status: 404 and return
end
end
private
def check_bike_permission
if cannot? :manage, Bike
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
end
end
end

View File

@ -1,24 +0,0 @@
class Api::V1::TaskListsController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have permission to manage this task list."
NOT_FOUND = "The task list could not be found."
before_filter :get_task_list
before_filter :check_task_list_permission, except: :show
def show
end
private
def get_task_list
@task_list = TaskList.find_by_id(params[:id])
if @task_list.nil?
render json: { errors: [NOT_FOUND] }, status: 404 and return
end
end
def check_task_list_permission
if cannot? :manage, Bike and @task_list.item != current_user.bike
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
end
end
end

View File

@ -1,64 +0,0 @@
class Api::V1::TasksController < Api::V1::BaseController
EXPECTED_TASKS = "Expected a list of tasks in submitted data."
CANNOT_MANAGE = "You do not have permission to manage this task."
NOT_FOUND = "The task could not be found."
before_filter :validate_params
before_filter :get_tasks
before_filter :check_task_permission, except: :show
def update
errors = []
@tasks.each do |task_hash|
task = task_hash[:record]
attrs = task_hash[:new_attributes]
task.update_attributes(attrs)
if !task.errors.empty?
errors << { id: task.id, errors: task.errors }
end
end
if !errors.empty?
render json: { errors: errors }, status: 422 and return
end
end
private
def validate_params
if params[:tasks].nil? and not params[:tasks].kind_of?(Array)
render json: { errors: [EXPECTED_TASKS]}, status: 422 and return
end
end
def get_tasks
@tasks = []
errors = []
params[:tasks].each do |task|
t = Task.find_by_id(task[:id])
if t.nil?
errors << { id: task[:id], error: NOT_FOUND }
else
@tasks << { record: t, new_attributes: task }
end
end
if !errors.empty?
render json: { errors: errors }, status: 404 and return
end
end
def check_task_permission
errors = []
@tasks.each do |task_hash|
task = task_hash[:record]
if task.task_list.item != current_user.bike
errors << { id: task[:id], error: CANNOT_MANAGE }
end
end
if cannot? :manage, Bike and !errors.empty?
render json: { errors: errors}, status: 403 and return
end
end
end

View File

@ -1,33 +0,0 @@
class Api::V1::TimeEntriesController < Api::V1::BaseController
EXPECTED_TIME_ENTRY = "Expected time entry in submitted data"
NOT_FOUND = "Time entry not found"
def create
if params[:time_entries] && time_entry = params[:time_entries].first
time_entry.merge!({ loggable_id: current_user.id,
logger_id: current_user.id })
bike_id = time_entry[:bike_id].to_i
@time_entry = TimeEntry.new(time_entry.except(:bike_id))
if bike_id > 0
@time_entry.copy_to_bike_history(bike_id)
end
if !@time_entry.save
render json: { errors: @time_entry.errors }, status: 422 and return
end
else
render json: { errors: [EXPECTED_TIME_ENTRY]}, status: 422 and return
end
end
def delete
if time_entry = TimeEntry.find_by_id(params[:id])
time_entry.delete
render nothing: true, status: 204 and return
else
render json: { errors: [NOT_FOUND]}, status: 404 and return
end
end
end

View File

@ -1,27 +1,21 @@
require 'securerandom' require 'securerandom'
class Api::V1::UsersController < Api::V1::BaseController class Api::V1::UsersController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have the permission to manager users"
NOT_FOUND = "User not found"
NOT_ALLOWED = "Not allowed to reset your own password in this fashion"
PASS_LENGTH = 8
def password_reset def password_reset
if can? :manage, User if can? :manage, User
user = User.find_by_id(params[:user_id]) user = User.find_by_id(params[:user_id])
render :json => { "errors" => [NOT_FOUND]}, :status => 404 and return if user.nil? render :json => { "error" => "User not found"}, :status => 404 and return if user.nil?
render :json => { "errors" => [NOT_ALLOWED]}, :status => 403 and return if user.id == current_user.id render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_user.id
new_pass = SecureRandom.hex[0,PASS_LENGTH] new_pass = SecureRandom.hex[0,8]
user.password = new_pass user.password = new_pass
user.save user.save
render :json => { "password" => new_pass}, :status => 200 and return render :json => { "password" => new_pass}, :status => 200 and return
else else
render :json => { "errors" => [CANNOT_MANAGE]}, :status => 403 and return render :json => { "error" => "You do not have the permission"}, :status => 403 and return
end end
end end
=begin Is this here by accident? Commenting out for now (1/30/14)
def checkout def checkout
#must use @current_user since user may not have signed in #must use @current_user since user may not have signed in
if !@current_user.checked_in? if !@current_user.checked_in?
@ -31,5 +25,4 @@ class Api::V1::UsersController < Api::V1::BaseController
render :nothing => true, :status => 204 and return render :nothing => true, :status => 204 and return
end end
end end
=end
end end

View File

@ -1,17 +0,0 @@
class BikesController < AuthenticatedController
def new
@bike = Bike.new bike_purpose_id: 1
@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 = @bike.task_list
@show_add_bike = true if params[:add_bike]
end
end

View File

@ -1,7 +0,0 @@
class PanelController < ApplicationController
def index
render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke"
end
end

View File

@ -1,6 +1,7 @@
class SiteController < ApplicationController class SiteController < ApplicationController
def index def index
@bike = current_user.bike render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application"
end end
end end

View File

@ -1,5 +0,0 @@
class TaskListsController < AuthenticatedController
def edit
@task_list = TaskList.find_by_id(params[:id])
end
end

View File

@ -1,16 +0,0 @@
class TimeEntriesController < AuthenticatedController
def new
@bikes = Bike.all.map{ |b| [b.to_s , b.id] }
if bike = current_user.bike
@bikes.unshift( [bike.to_s, bike.id] )
end
@bikes.unshift( ["Non-bike work", -1] )
end
def index
@user_time_entries = TimeEntry.where(loggable_id: current_user.id)
@credits_available = current_user.total_credits
@hours_worked = current_user.total_hours
end
end

View File

@ -0,0 +1,34 @@
class UsersController < AuthenticatedController
expose(:user)
expose(:users) { User.order('id').paginate(:page => params[:page]) }
def index
end
def show
end
def new
end
def create
if user.save
redirect_to user
else
render :new
end
end
def update
if user.save
redirect_to user
else
render :edit
end
end
def destroy
user.destroy
redirect_to bikes_url
end
end

View File

@ -7,28 +7,4 @@ module ApplicationHelper
end end
link_to(name, '#', id: "add_#{association.to_s.singularize}" , class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")}) link_to(name, '#', id: "add_#{association.to_s.singularize}" , class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end end
def top_menu(right = nil)
content_tag :nav, class: 'navbar navbar-default' do
content_tag :div, class: 'container-fluid' do
content_tag :div, class: 'row' do
[
content_tag(:div, link_to_dashboard, class: 'col-xs-3'),
content_tag(:div, header_logo, class: 'col-xs-6'),
content_tag(:div, class: 'col-xs-3') { content_tag :div, right, class: 'pull-right' },
].join.html_safe
end
end
end
end
def link_to_dashboard
link_to root_path, class: 'navbar-brand', title: 'Back to dashboard' do
content_tag :i, '', class: "glyphicon glyphicon-home"
end
end
def header_logo
content_tag :div, 'Velocipede', class: 'navbar-text text-center'
end
end end

View File

@ -1,18 +1,21 @@
module DeviseHelper module DeviseHelper
# A simple way to show error messages for the current devise resource. If you need
# to customize this method, you can either overwrite it in your application helpers or
# copy the views to your application.
#
# This method is intended to stay simple and it is unlikely that we are going to change
# it to add more behavior or options.
def devise_error_messages! def devise_error_messages!
return '' if resource.errors.empty? return "" if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) } messages = resource.errors.full_messages.map { |msg| content_tag(:p, msg, :class => "alert") }.join
sentence = I18n.t("errors.messages.not_saved", sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count, :count => resource.errors.count,
:resource => resource.class.model_name.human.downcase) :resource => resource.class.model_name.human.downcase)
html = <<-HTML html = <<-HTML
<div class="alert alert-danger">#{sentence} <p>#{sentence}</p>
<ul> #{messages}
#{messages.join}
</ul>
</div>
HTML HTML
html.html_safe html.html_safe

View File

@ -23,7 +23,7 @@ class Ability
def user def user
can :read, :all can :read, :all
can :manage, @current_user.bike unless @current_user.bike.nil? can :manage, Bike, :id => @current_user.bike_id unless @current_user.bike.nil?
can :manage, ::ActsAsLoggable::Log, :loggable_type => "Bike", :loggable_id => @current_user.bike_id can :manage, ::ActsAsLoggable::Log, :loggable_type => "Bike", :loggable_id => @current_user.bike_id
can :manage, ::ActsAsLoggable::Log, :loggable_type => "User", :loggable_id => @current_user.id can :manage, ::ActsAsLoggable::Log, :loggable_type => "User", :loggable_id => @current_user.id
end end

View File

@ -1,34 +1,28 @@
class Bike < ActiveRecord::Base class Bike < ActiveRecord::Base
acts_as_loggable acts_as_loggable
attr_accessible :shop_id, :serial_number, :bike_brand_id, :bike_model_id, :model, :color, :bike_style_id, attr_accessible :shop_id, :serial_number, :bike_brand_id, :model, :color, :bike_style_id, :seat_tube_height,
:seat_tube_height, :top_tube_length, :bike_wheel_size_id, :value, :bike_condition_id, :bike_purpose_id, :photo :top_tube_length, :bike_wheel_size_id, :value, :bike_condition_id, :bike_purpose_id
has_many :transactions has_many :transactions
has_one :owner, :class_name => 'User' has_one :owner, :class_name => 'User'
has_one :task_list, :as => :item, :dependent => :destroy has_one :task_list, :as => :item, :dependent => :destroy
belongs_to :bike_brand belongs_to :bike_brand
belongs_to :bike_model
belongs_to :bike_style belongs_to :bike_style
belongs_to :bike_condition belongs_to :bike_condition
belongs_to :bike_purpose belongs_to :bike_purpose
belongs_to :bike_wheel_size belongs_to :bike_wheel_size
has_attached_file :photo, :styles => {:thumb => '100x100>'}
validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true } validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
validates :serial_number, :length => { :minimum => 3 } validates :serial_number, :length => { :minimum => 3 }
validates :model, :length => { :maximum => 50 } validates :model, :length => { :maximum => 50 }
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" } validates :bike_brand_id, :presence => true
#validates :color, :presence => true validates :color, :presence => true
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" } validates :bike_style_id, :presence => true
validates :seat_tube_height, :presence => true, :numericality => true validates :seat_tube_height, :presence => true
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" } validates :bike_wheel_size_id, :presence => true
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" } validates :bike_condition_id, :presence => true
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" } validates :bike_purpose_id, :presence => true
validates_attachment :photo, :content_type => {:content_type => %w{ image/jpeg image/gif image/png }},
:file_name => {:matches => [/png\Z/, /jpe?g\Z/, /gif\Z/]}
self.per_page = 15 self.per_page = 15

View File

@ -1,160 +0,0 @@
require 'csv'
# Imports data from CSV file into the bikes database.
class BikeCsvImporter
include BikeCsvImporter::Cache
include BikeCsvImporter::Cleaner
include BikeCsvImporter::BikeAttrs
include BikeCsvImporter::Logs
attr_reader :file
# Default constructor
#
# @param [String] file Path to the CSV file
def initialize(file)
@file = file
end
# Runs the import. Will print out progress to stdout
#
# @param [Boolean] dry_run If true, does not save data, only shows the progress of validation
def run(dry_run)
imported_count, skipped_count = 0, 0
puts "Performing a #{dry_run ? 'DRY RUN' : 'LIVE RUN'} of import"
fetch do |bike_hash|
bike = new_bike bike_hash
check_method = dry_run ? :valid? : :save
if bike.try check_method
puts "Imported #{bike.shop_id}: #{bike}".green
logs = new_logs_entries bike, bike_hash
logs.each do |log|
if log.send check_method
puts "\tLog entry created: #{log.inspect}".green
else
puts "\tLog entry creation failed: #{log.errors.full_messages.join '; '}".red
end
end
imported_count += 1
else
puts "Skipped #{bike.try(:shop_id) || bike_hash.values.first}: #{bike.try(:errors).try(:full_messages).try :join, '; '}".red
skipped_count += 1
end
end
puts "#{imported_count} bikes imported, #{skipped_count} bikes skipped, total of #{imported_count + skipped_count} rows in the CSV"
end
# Analyzes and prints out the input CSV file values
#
# @param [Array<Strong>] fields If passed, analyze only the given fields (names are down cased)
def analyze(fields = [])
puts "Analyzing CSV values frequency for #{fields.any? ? fields.join(', ') + ' field' : 'all fields'}"
fields = fields.map &:downcase
grouped = {}
fetch do |bike_hash|
bike_hash.each do |key, value|
next if fields.any? && !fields.include?(key)
grouped[key] ||= {}
grouped[key][value] ||= 0
grouped[key][value] += 1
end
end
grouped.each do |field, values|
puts "#{field}:"
values.each do |value, count|
puts "\t#{value.inspect}: #{count}"
end
puts "\tTotal of #{values.count} distinct values"
end
end
# Imports new brands from CSV file (field 'make'). Will print out progress to stdout
#
# @param [Boolean] dry_run If true, does not save data, only shows the progress of validation
def brands(dry_run)
created_count, skipped_count = 0, 0
puts "Performing a #{dry_run ? 'DRY RUN' : 'LIVE RUN'} of brands import"
fetch do |bike_hash|
make = clean_value bike_hash['make']
brand = bike_attr_bike_brand make, true
check_method = dry_run ? :valid? : :save
if brand.try :persisted?
puts "Skipped already existing brand #{brand.brand}"
skipped_count +=1
elsif brand.try check_method
puts "Created brand #{brand.brand}".green
created_count += 1
else
puts "Skipped #{brand.try(:brand) || make}: #{brand.try(:errors).try(:full_messages).try(:join, '; ') || 'object not created'}".red
skipped_count += 1
end
end
puts "#{created_count} brand created, #{skipped_count} brand skipped, total of #{created_count + skipped_count} rows in the CSV"
end
private
# Parses the CSV header & rows, yielding a block for each row (except the header)
# Header is down cased!
#
# @param [Proc] &block The block to yield to
def fetch
CSV.foreach(file).each_with_index do |row, i|
if i.zero?
parse_header row
else
yield parse_bike(row)
end
end
end
# Parses & stores the input header, down casing by the way
#
# @param [Array<String>] row
def parse_header(row)
@header = row.map(&:downcase)
end
# Parses the input row into a hash with keys from the header, @see #parse_header
#
# @param [Array<String>] row
#
# @return [Hash]
def parse_bike(row)
@header.zip(row).to_h
end
# Constructs a new Bike instance from the given hash from a CSV row
#
# @param [Hash] bike_hash
#
# @return [Bike]
def new_bike(bike_hash)
Bike.new bike_attrs(bike_hash)
end
# Constructs new Bike Log Entries instances from the given hash from a CSV row
#
# @param [Bike] bike The Bike instance to construct log entries for
# @param [Hash] bike_hash The input hash from a CSV row
#
# @return [Array<ActsAsLoggable::Log>]
def new_logs_entries(bike, bike_hash)
%i{ acquired comment gone }.map { |x| send :"log_entry_#{x}", bike, bike_hash }.compact
end
end

View File

@ -1,93 +0,0 @@
# Helper module to create various Bike instanct fields from a CSV row hash
class BikeCsvImporter
module BikeAttrs
def bike_attr_fields
{
shop_id: 'velocipede number',
bike_purpose_id: 'program',
#gone: 'gone',
value: 'price',
bike_brand_id: 'make',
bike_model_id: 'model',
model: 'model',
bike_style_id: nil,
bike_condition_id: nil,
seat_tube_height: nil,
bike_wheel_size_id: nil,
serial_number: nil,
}
end
def bike_attrs(bike_hash)
bike_attr_fields.each_with_object({}) do |(model_field, csv_field), memo|
memo[model_field] = send :"bike_attr_#{model_field}", clean_value(bike_hash[csv_field])
end
end
def bike_attr_shop_id(value)
value.to_i
end
def bike_attr_bike_purpose_id(value)
map = {
'SALE' => /shop|as(-|\s+)is|safety\s*check/,
'BUILDBIKE' => /build|bikes.*world/,
'STORAGE' => nil,
'PARTS' => /part|frame/,
'SCRAP' => /scrap|strip/,
}
default = 'UNDETERMINED'
test_value = value.try :downcase
value = map.find { |_, regexp| regexp.try :match, test_value }.try :first
cached_bike_purpose(value || default).id
end
def bike_attr_gone(value)
%w{ yes yeah y }.include? value.try :downcase
end
def bike_attr_value(value)
value.try(:gsub, /[$]/, '').try :to_i
end
def bike_attr_bike_brand(value, new_if_empty = false)
value = 'Unknown' if !value || value =~ /\Aunknown/i
cached_bike_brand value, new_if_empty
end
def bike_attr_bike_brand_id(value)
bike_attr_bike_brand(value, false).try :id
end
def bike_attr_bike_model_id(value)
return unless value
cached_bike_model(value).try :id
end
def bike_attr_model(value)
value if value && value !~ /unknown/i
end
def bike_attr_bike_style_id(_)
@bike_style_other_cache ||= BikeStyle.find_by_style('OTHER').id
end
def bike_attr_bike_condition_id(_)
@bike_condition_undertermined_cache ||= BikeCondition.find_by_condition('UNDETERMINED').id
end
def bike_attr_seat_tube_height(_)
0
end
def bike_attr_bike_wheel_size_id(_)
@bike_condition_wheel_size_undertermined_cache ||= BikeWheelSize.find_by_description('UNDETERMINED').id
end
def bike_attr_serial_number(_)
'UNDETERMINED'
end
end
end

View File

@ -1,35 +0,0 @@
# Helper module to create various cached instances for bike CSV imports
class BikeCsvImporter
module Cache
def cached_bike_purpose(purpose)
@bike_purpose_cache ||= {}
@bike_purpose_cache[purpose] ||= BikePurpose.find_by_purpose purpose
end
def cached_bike_brand(brand, new_if_empty = false)
@bike_brand_cache ||= {}
if @bike_brand_cache.has_key? brand
@bike_brand_cache[brand]
else
bike_brand = BikeBrand.where('lower(brand) = ?', brand.downcase).first
bike_brand ||= BikeBrand.new(brand: brand) if new_if_empty
@bike_brand_cache[brand] = bike_brand
end
end
def cached_bike_model(model)
@bike_model_cache ||= {}
if @bike_model_cache.has_key? model
@bike_model_cache[model]
else
@bike_model_cache[model] = BikeModel.where('lower(model) = ?', model.downcase).first
end
end
def cached_log_bike_action(action)
@log_bike_action_id_cache ||= {}
@log_bike_action_id_cache[action] ||= ActsAsLoggable::BikeAction.find_by_action(action)
end
end
end

View File

@ -1,16 +0,0 @@
# Helper module to clean the incoming data from CSV fields
class BikeCsvImporter
module Cleaner
def clean_value(value)
value_or_nil strip_value(value)
end
def strip_value(value)
value.try(:strip).try(:gsub, /\n|\r/, '')
end
def value_or_nil(value)
return value unless ['?', 'n/a', 'missing', 'unknown', ''].include? value.try(:downcase)
end
end
end

View File

@ -1,42 +0,0 @@
# Helper module to create ActsAsLoggable log entries for a Bike instance from a CSV row hash
class BikeCsvImporter
module Logs
def log_entry_gone(bike, bike_hash)
if clean_value(bike_hash['gone']).to_s =~ /y/i
log_entry bike, log_entry_date(clean_value(bike_hash['date out'])), 'COMPLETED', 'Gone'
end
end
def log_entry_acquired(bike, bike_hash)
if clean_value(bike_hash['date in'])
log_entry bike, log_entry_date(clean_value(bike_hash['date in'])), 'ACQUIRED'
end
end
def log_entry_comment(bike, bike_hash)
if clean_value(bike_hash['comment']).present?
log_entry bike, nil, 'NOTE', clean_value(bike_hash['comment'])
end
end
def log_entry_date(value)
return unless value
Date.strptime value, '%m/%d/%y' rescue nil
end
def log_entry(bike, date, type, description = nil)
date ||= DateTime.now
bike_action = cached_log_bike_action(type)
ActsAsLoggable::Log.new(
loggable_type: bike.class.to_s,
loggable_id: bike.id || bike.shop_id.to_i, # for dry run
log_action_type: bike_action.class.to_s,
log_action_id: bike_action.id,
start_date: date,
end_date: date,
description: description,
)
end
end
end

View File

@ -1,5 +1,5 @@
class Task < ActiveRecord::Base class Task < ActiveRecord::Base
attr_accessible :task, :notes, :done, :task_list_id attr_accessible :task, :notes, :done
belongs_to :task_list belongs_to :task_list

View File

@ -4,7 +4,7 @@ class TaskList < ActiveRecord::Base
attr_accessible :item_id, :item_type, :name attr_accessible :item_id, :item_type, :name
belongs_to :item, :polymorphic => true belongs_to :item, :polymorphic => true
has_many :tasks, order: "id ASC" has_many :tasks
after_save :create_default_bike_tasks after_save :create_default_bike_tasks

View File

@ -1,27 +0,0 @@
class TimeEntry < ActsAsLoggable::Log
default_scope where( loggable_type: "User",
logger_type: "User",
log_action_type: "ActsAsLoggable::UserAction").where("log_action_id != 4").order("start_date DESC")
def copy_to_bike_history(bike_id)
self.assign_attributes({
copy_log: true,
copy_type: 'Bike',
copy_id: bike_id,
copy_action_type: 'ActsAsLoggable::BikeAction',
copy_action_id: 4
})
end
def duration
end_date - start_date
end
def duration_in_hours
(duration / 1.hour).round(2)
end
def type
log_action.try(:action)
end
end

View File

@ -1,6 +1,4 @@
class User < ActiveRecord::Base class User < ActiveRecord::Base
MAX_AVATAR_SIZE_KB = 1024
acts_as_loggable acts_as_loggable
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
@ -10,7 +8,7 @@ class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model # Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :bike_id, :first_name, :last_name, :bike_id,
:user_profiles_attributes, :username, :avatar :user_profiles_attributes, :username
has_many :transactions, as: :customer has_many :transactions, as: :customer
has_many :transaction_logs, through: :transactions, source: :logs has_many :transaction_logs, through: :transactions, source: :logs
@ -22,19 +20,11 @@ class User < ActiveRecord::Base
belongs_to :bike belongs_to :bike
has_attached_file :avatar, :styles => {:thumb => '100x100>'}
default_scope order('username ASC') default_scope order('username ASC')
validates :username, :presence => true, uniqueness: true
validates :first_name, :presence => true validates :first_name, :presence => true
validates :last_name, :presence => true validates :last_name, :presence => true
validates_attachment :avatar, :content_type => {:content_type => %w{ image/jpeg image/gif image/png }},
:file_name => {:matches => [/png\Z/, /jpe?g\Z/, /gif\Z/]},
:size => {:in => 0..MAX_AVATAR_SIZE_KB.kilobytes}
def to_s def to_s
"#{first_name} #{last_name}" "#{first_name} #{last_name}"
end end
@ -54,16 +44,10 @@ class User < ActiveRecord::Base
roles.include?(role) roles.include?(role)
end end
# try keeping the email field in DB clear and consistent, without empty strings (NULLs instead)
def email=(other)
super(other.blank? ? nil : other)
end
### TODO methods below probably belong somewhere else ### TODO methods below probably belong somewhere else
def completed_build_bikes def completed_build_bikes
#default BUILDBIKE/CLASS ID is 5 purpose_id = BikePurpose.find_by_purpose("BUILDBIKE").id
purpose_id = 5
Bike.find_by_sql(" Bike.find_by_sql("
SELECT * SELECT *
FROM bikes FROM bikes
@ -80,16 +64,15 @@ class User < ActiveRecord::Base
end end
def total_credits_spent def total_credits_spent
log_action_id = 1 #TIME log_action = ::ActsAsLoggable::TransactionAction.find_by_action("TIME")
transaction_logs. transaction_logs.
where( "log_action_id = ? AND log_action_type = ?", where( "log_action_id = ? AND log_action_type = ?",
log_action_id, ::ActsAsLoggable::TransactionAction.to_s). log_action.id, log_action.class.to_s).
sum{ |r| r.description.to_i }.round(2) sum{ |r| r.description.to_i }.round(2)
end end
def total_earned_credits def total_earned_credits
volunteer_id = 1 log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
staff_id = 3
# Find the first credit conversion which has a created_at date before the # Find the first credit conversion which has a created_at date before the
# log's created_at date and join it to the log's row so we can calculate # log's created_at date and join it to the log's row so we can calculate
@ -98,7 +81,7 @@ class User < ActiveRecord::Base
# #
# The DISTINCT ON, and ORDER BY are important to getting the # The DISTINCT ON, and ORDER BY are important to getting the
# single conversion rate that applies to the respective log. # single conversion rate that applies to the respective log.
::ActsAsLoggable::Log.find_by_sql([" ::ActsAsLoggable::Log.find_by_sql("
SELECT DISTINCT ON (logs.created_at) start_date, end_date, SELECT DISTINCT ON (logs.created_at) start_date, end_date,
conversion.conversion, conversion.created_at conversion.conversion, conversion.created_at
FROM logs FROM logs
@ -106,26 +89,23 @@ class User < ActiveRecord::Base
SELECT conversion, created_at SELECT conversion, created_at
FROM credit_conversions FROM credit_conversions
) AS conversion ON logs.created_at > conversion.created_at ) AS conversion ON logs.created_at > conversion.created_at
WHERE logs.loggable_id = :id WHERE logs.loggable_id = #{self.id}
AND logs.loggable_type = 'User' AND logs.loggable_type = 'User'
AND (log_action_id IN (:credit_actions) AND log_action_type = :log_action_type) AND (log_action_id != #{log_action.id} AND log_action_type = '#{log_action.class.to_s}')
ORDER BY logs.created_at, conversion.created_at DESC", ORDER BY logs.created_at, conversion.created_at DESC").
{id: self.id,
credit_actions: [volunteer_id, staff_id],
log_action_type: ::ActsAsLoggable::UserAction.to_s}]).
sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2) sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2)
end end
def total_hours def total_hours
log_action_id = 4 #CHECKIN log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2) logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
end end
def current_month_hours def current_month_hours
log_action_id = 4 #CHECKIN log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month #TODO need to prevent users from saving logs across months, force to create a new log if crossing month
current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month) current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month)
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s) logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s)
.where( :start_date => current_month_range) .where( :start_date => current_month_range)
.where( :end_date => current_month_range) .where( :end_date => current_month_range)
.sum { |l| (l.end_date - l.start_date)/3600 } .sum { |l| (l.end_date - l.start_date)/3600 }
@ -133,31 +113,28 @@ class User < ActiveRecord::Base
end end
def checked_in? def checked_in?
#default CHECKIN log action is id, yea yea should be a constant log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
log_action_id = 4 checked = logs.where( log_action_id: log_action.id).
checked = logs.where( log_action_id: log_action_id).
where("start_date >= ?", Time.zone.now.beginning_of_day). where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date") where("start_date = end_date")
!checked.empty? !checked.empty?
end end
def checkin def checkin
#default CHECKIN log action is id, yea yea should be a constant log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
log_action_id = 4
time = Time.now time = Time.now
logs.create( logger_id: self.id, logs.create( logger_id: self.id,
logger_type: self.class.to_s, logger_type: self.class.to_s,
start_date: time, start_date: time,
end_date: time, end_date: time,
log_action_id: log_action_id, log_action_id: log_action.id,
log_action_type: ::ActsAsLoggable::UserAction.to_s) log_action_type: log_action.class.to_s)
save save
end end
def checkout def checkout
#default CHECKIN log action is id, yea yea should be a constant log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
log_action_id = 4 checked = logs.where( log_action_id: log_action.id).
checked = logs.where( log_action_id: log_action_id).
where("start_date >= ?", Time.zone.now.beginning_of_day). where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date").first where("start_date = end_date").first
checked.end_date = Time.now checked.end_date = Time.now

View File

@ -1,4 +0,0 @@
json.bikes [@bike] do |bike|
json.array! bike
json.set! :href, api_bike_path(bike)
end

View File

@ -1,3 +0,0 @@
json.bikes [@bike] do |bike|
json.array! bike
end

View File

@ -1,15 +0,0 @@
json.task_lists [@task_list] do |tl|
json.array! tl
json.links do
json.bike do
json.href api_bike_path(tl.item)
json.id tl.item_id
end
json.tasks tl.tasks do |task|
json.id task.id
json.done task.done
json.notes task.notes
json.task task.task
end
end
end

View File

@ -1,3 +0,0 @@
json.tasks [@tasks.map{|x| x[:record]}] do |task|
json.array! task
end

View File

@ -1,4 +0,0 @@
json.time_entries [@time_entry] do |time_entry|
json.array! time_entry
#json.set! :href, api_time_entry_path(time_entry)
end

View File

@ -1,80 +0,0 @@
= top_menu
%h1 Add Bike
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for @bike, as: 'bikes', index: '', url: api_create_bike_path, method: :post, enctype: 'multipart/form-data', html: {id: 'add_bike_form'} do |f|
= f.hidden_field 'bike_purpose_id'
%fieldset
.form-group
= f.number_field 'shop_id', placeholder: 'Shop ID', min: 0, class: 'form-control', id: 'shop_id'
.help-block
.form-group
= f.select 'bike_brand_id', options_for_select(@brands), {}, class: 'form-control', id: 'bike_brand_id'
.help-block
.form-group
= f.text_field 'model', placeholder: 'Model', class: 'form-control', id: 'model'
.help-block
.form-group
= f.text_field 'serial_number', placeholder: 'Serial Number', class: 'form-control', id: 'serial_number'
.help-block
.form-group
.btn-group(data-toggle="buttons")
%label.btn.btn-default
= f.radio_button 'bike_style_id', 3
RD
%label.btn.btn-default
= f.radio_button 'bike_style_id', 1
MTN
%label.btn.btn-default
= f.radio_button 'bike_style_id', 2
HYB
%label.btn.btn-default
= f.radio_button 'bike_style_id', 4
OTHER
= hidden_field_tag nil, nil, id: 'bike_style_id'
.help-block
.form-group
= f.select 'bike_wheel_size_id', options_for_select(@wheel_sizes), {}, class: 'form-control', id: 'bike_wheel_size_id'
.help-block
.form-group
.btn-group(data-toggle="buttons")
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 2
Poor
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 3
Fair
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 4
Good
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 5
Excellent
= hidden_field_tag nil, nil, id: 'bike_condition_id'
.help-block
.form-group
.input-group
= f.number_field 'seat_tube_height', placeholder: 'Seat Tube', min: 0, max: 100, class: 'form-control', id: 'seat_tube_height'
.input-group-addon cm
.help-block
.form-group
%label Bike photo (optional)
= f.file_field 'photo', id: 'photo'
.help-block
-# Commenting this out until description is added to Bike
/.form-group
%input{id: "bike_description", placeholder: "Short description", type: "text", class: "input-lg" }
.form-group
= button_tag 'Add Bike', class: 'btn btn-primary'

View File

@ -1,57 +0,0 @@
- btn = if @show_add_bike
- capture do
= link_to 'Add Another Bike?', new_bike_path, class: 'btn btn-default navbar-btn hidden-xs'
= link_to new_bike_path, class: 'navbar-brand visible-xs', title: 'Add Another Bike?' do
%i.glyphicon.glyphicon-plus
= top_menu btn
%h1 #{@bike.shop_id}: #{@bike.bike_brand}
%h4= @bike.model
.form-horizontal
- if @bike.photo?
.form-group
%label.col-sm-2.control-label Photo
.col-sm-10
= link_to @bike.photo.url, target: '_blank' do
%img{src: @bike.photo.url(:thumb), class: 'img-thumbnail'}
.form-group
%label.col-sm-2.control-label Type
.col-sm-10.form-control-static= @bike.bike_style
.form-group
%label.col-sm-2.control-label Wheel Size
.col-sm-10.form-control-static= @bike.bike_wheel_size.display_string
.form-group
%label.col-sm-2.control-label Condition
.col-sm-10.form-control-static= @bike.bike_condition
.form-group
%label.col-sm-2.control-label Seat Tube (cm)
.col-sm-10.form-control-static= @bike.seat_tube_height
.form-group
%label.col-sm-2.control-label Purpose
.col-sm-10.form-control-static= @bike.bike_purpose
- unless @bike.value.nil?
.form-group
%label.col-sm-2.control-label Value
.col-sm-10.form-control-static= @bike.value
- unless @bike.color.nil?
.form-group
%label.col-sm-2.control-label Color
.col-sm-10.form-control-static
.bike-color(style="background-color: ##{@bike.color}")= @bike.color
- if @task_list
- tasks = @task_list.tasks.to_a
.form-group
%label.col-sm-2.control-label Task list
.col-sm-10.form-control-static
= link_to edit_task_list_path(@task_list.id) do
#{tasks.select(&:done).count}/#{tasks.count}

View File

@ -0,0 +1,25 @@
<%- if controller_name != 'sessions' %>
<%= link_to "Sign in", new_session_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %><br />
<% end -%>
<% end -%>

View File

@ -1,20 +0,0 @@
- links = []
- if controller_name != 'sessions'
- links << ['Sign in', new_session_path(resource_name)]
- if devise_mapping.registerable? && controller_name != 'registrations'
- links << ['Sign up', new_registration_path(resource_name)]
- if devise_mapping.recoverable? && controller_name != 'passwords'
- links << ['Forgot your password?', new_password_path(resource_name)]
- if devise_mapping.confirmable? && controller_name != 'confirmations'
- links << ['Didn\'t receive confirmation instructions?', new_confirmation_path(resource_name)]
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
- links << ["Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider)]
- if links.any?
%p= links.map { |title, url| link_to title, url }.join(' &middot; ').html_safe

View File

@ -0,0 +1,12 @@
<h2>Resend confirmation instructions</h2>
<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.submit "Resend confirmation instructions" %></div>
<% end %>
<%= render "links" %>

View File

@ -0,0 +1,16 @@
<h2>Change your password</h2>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<%= f.hidden_field :reset_password_token %>
<div><%= f.label :password, "New password" %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.submit "Change my password" %></div>
<% end %>
<%= render "links" %>

View File

@ -1,19 +0,0 @@
%h1 Change your password
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for resource, as: resource_name, url: password_path(resource_name), html: {method: :put} do |f|
= devise_error_messages!
= f.hidden_field :reset_password_token
%fieldset
.form-group
= f.password_field :password, placeholder: 'New password', class: 'form-control'
.form-group
= f.password_field :password_confirmation, placeholder: 'Confirm new password', class: 'form-control'
.form-group
= f.submit 'Change my password', class: 'btn btn-primary'
= render 'links'

View File

@ -0,0 +1,12 @@
<h2>Forgot your password?</h2>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.submit "Send me reset password instructions" %></div>
<% end %>
<%= render "links" %>

View File

@ -1,15 +0,0 @@
%h1 Forgot your password?
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for resource, as: resource_name, url: password_path(resource_name), html: {method: :post} do |f|
= devise_error_messages!
%fieldset
.form-group
= f.email_field :email, placeholder: 'Email', class: 'form-control'
.form-group
= f.submit 'Reset Password', class: 'btn btn-primary'
= render 'links'

View File

@ -1,17 +1,25 @@
.form-group %fieldset
= f.text_field :addrStreet1, placeholder: 'Street Address Line 1', class: 'form-control' .control-group
= f.label :addrStreet1, :class => "control-label"
.form-group .controls
= f.text_field :addrStreet2, placeholder: 'Street Address Line 2',class: 'form-control' = f.text_field :addrStreet1, :class => "control-label"
.control-group
.form-group = f.label :addrStreet2, :class => "control-label"
= f.text_field :addrCity, placeholder: 'City', class: 'form-control' .controls
= f.text_field :addrStreet2, :class => "control-label"
.form-group .control-group
= f.text_field :addrState, placeholder: 'State Abbreviation', class: 'form-control' = f.label :addrCity, :class => "control-label"
.controls
.form-group = f.text_field :addrCity, :class => "control-label"
= f.text_field :addrZip, placeholder: 'Zip Code', class: 'form-control' .control-group
= f.label :addrState, :class => "control-label"
.form-group .controls
= f.text_field :phone, placeholder: 'Phone', class: 'form-control' = 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"

View File

@ -0,0 +1,27 @@
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, :autocomplete => "off" %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %></div>
<div><%= f.submit "Update" %></div>
<% end %>
<h3>Cancel my account</h3>
<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
<%= link_to "Back", :back %>

View File

@ -1,47 +1,36 @@
%h1 Sign up = 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|
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for resource, as: resource_name, url: registration_path(resource_name) do |f|
= devise_error_messages! = devise_error_messages!
.controls
%fieldset %h2 Sign up
.form-group .control-group
= f.text_field :username, placeholder: 'Username', class: 'form-control', required: true = f.label :username, :class => "control-label"
.help-block .controls
= f.text_field :username
.form-group .control-group
= f.text_field :first_name, placeholder: 'First Name', class: 'form-control', required: true = f.label :first_name, :class => "control-label"
.help-block .controls
= f.text_field :first_name
.form-group .control-group
= f.text_field :last_name, placeholder: 'Last Name', class: 'form-control', required: true = f.label :last_name, :class => "control-label"
.help-block .controls
= f.text_field :last_name
.form-group .control-group
= f.email_field :email, placeholder: 'E-mail', class: 'form-control' = f.label :email, :class => "control-label"
.controls
%fieldset = 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
%fieldset = f.label :password, :class => "control-label"
.form-group .controls
%label You may upload your photo to be used as an avatar = f.password_field :password
= f.file_field :avatar .control-group
.help-block #{User::MAX_AVATAR_SIZE_KB} Kb max = f.label :password_confirmation, :class => "control-label"
.controls
%fieldset = f.password_field :password_confirmation
.form-group .control-group
= f.password_field :password, placeholder: 'Password', class: 'form-control', required: true .controls
.help-block = f.submit "Sign up"
= render "links"
.form-group
= f.password_field :password_confirmation, placeholder: 'Password Confirmation', class: 'form-control', required: true
.help-block
.form-group
= f.submit 'Sign up', class: 'btn btn-primary'
= render 'links'

View File

@ -0,0 +1,38 @@
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
<h2>Velocipede</h2>
<a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a>
<br>
<br>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<div><%= f.label :username%><br />
<%= f.text_field :username%></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<% if devise_mapping.rememberable? -%>
<div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
<% end -%>
<div><%= f.submit "Sign in" %></div>
<div id="checkin_menu"style="display:none;">
<input id="checkin" name="checkin" type="button" value="CHECK IN">
<input id="checkout" name="checkout" type="button" value="CHECK OUT">
</div>
<% end %>
<%= render "links" %>
<% if Rails.env.development? %>
<% User.all.each do |user| %>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= f.hidden_field :username, :value => user.username%></div>
<%= f.hidden_field :password, :value => 'password' %></div>
<div><%= f.submit "Sign in as #{user.username}" %></div>
<% end %>
<% end %>
<% end %>

View File

@ -1,41 +0,0 @@
%h1 Velocipede
%p= link_to 'Made with &hearts; in Baltimore'.html_safe, 'http://madewithloveinbaltimore.org'
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for resource, as: resource_name, url: session_path(resource_name) do |f|
%fieldset
.form-group
= f.text_field :username, placeholder: 'Username', class: 'form-control'
.form-group
= f.password_field :password, placeholder: 'Password', class: 'form-control'
- if devise_mapping.rememberable?
.form-group
.checkbox
= f.label :remember_me do
= f.check_box :remember_me
Remember Me
.form-group
.pull-left
= f.submit 'Sign in', class: 'btn btn-primary'
.pull-right
%span.btn-group
= f.button 'Check in', id: 'checkin', name: 'checkin', type: 'button', class: 'btn btn-success'
= f.button 'Check out', id: 'checkout', name: 'checkout', type: 'button', class: 'btn btn-danger'
.clearfix
= render 'links'
- if Rails.env.development?
%hr
- User.all.each do |user|
= form_for resource, as: resource_name, url: session_path(resource_name) do |f|
= f.hidden_field :username, value: user.username
= f.hidden_field :password, value: 'password'
%p= f.submit "Sign in as #{user.username}", class: 'btn btn-sm btn-info'

View File

@ -0,0 +1,12 @@
<h2>Resend unlock instructions</h2>
<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.submit "Resend unlock instructions" %></div>
<% end %>
<%= render "links" %>

View File

@ -1,27 +1,34 @@
!!! 5 !!! 5
%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 'application', 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
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
}
.x-boundlist-item {
white-space: nowrap;
}
%body %body
.container .container
.content .content
- if flash[:notice] - if flash[:notice]
%div.alert.alert-info= flash[:notice] %p{:class => 'notice'}= flash[:notice]
- if flash[:alert] - if flash[:alert]
%div.alert.alert-danger= flash[:alert] %p{:class => 'alert'}= flash[:alert]
.row
.span13
= yield = yield
%footer %footer
%hr %p &copy; Velocipede 2013
%p.text-muted &copy; BikeShed #{Time.now.year}
= javascript_include_tag 'application' = javascript_include_tag "application"
= javascript_include_tag params[:controller] = javascript_include_tag params[:controller]

View File

@ -1,11 +0,0 @@
!!! 5
%html{:lang => "en"}
%head
%meta{:charset => "utf-8"}/
%title= content_for?(:title) ? yield(:title) : "Velocipede"
= load_netzke
= csrf_meta_tags
%body
= yield
= javascript_include_tag "application"
= javascript_include_tag params[:controller]

View File

@ -1,31 +0,0 @@
%h1 Dashboard
.row.fieldset
.col-xs-12.col-sm-3.col-lg-2
%p= link_to 'Add Time Entry', new_time_entry_path, class: 'btn btn-default btn-block'
.col-xs-12.col-sm-3.col-lg-2
%p= link_to 'View Timesheet', time_entries_path, class: 'btn btn-default btn-block'
- can_manage_bike = can? :manage, Bike
- has_bike = !@bike.nil?
- if can_manage_bike || has_bike
.row.fieldset
- if can_manage_bike
.col-xs-12.col-sm-3.col-lg-2
%p
= link_to 'Add Bike', new_bike_path, class: 'btn btn-default btn-block'
- if has_bike
.col-xs-12.col-sm-3.col-lg-2
%p
= link_to 'View Your Bike', bike_path(@bike), class: 'btn btn-default btn-block'
.row.fieldset
.col-xs-12.col-sm-3.col-lg-2
%p
= link_to 'Desktop View', admin_index_path, class: 'btn btn-default btn-block'
.row.fieldset
.col-xs-12.col-sm-3.col-lg-2
%p
= button_tag 'Logout', id: 'index_logout', class: 'btn btn-danger btn-block', data: {url: destroy_user_session_path}

View File

@ -1,24 +0,0 @@
= top_menu
%h1 Task List
%h3
= link_to bike_path(@task_list.item) do
#{@task_list.item.shop_id}: #{@task_list.item.bike_brand}
%h4= @task_list.item.model
- if @task_list.item.photo?
%p
%img{src: @task_list.item.photo.url(:thumb), class: 'img-thumbnail'}
%fieldset
.form-group
- @task_list.tasks.each do |task|
.checkbox
%label
= check_box_tag nil, nil, task.done, class: 'task_list_task', type: 'checkbox', data: {id: task.id}
= task.task
.form-group
= button_tag 'Save Changes', id: 'update_tasks_submit', class: 'btn btn-primary disabled', data: {url: api_update_task_path}

View File

@ -1,47 +0,0 @@
= top_menu
%h1 Your Timesheet
.row
.col-xs-12.col-sm-6.col-lg-3
%p
%span.badge= @hours_worked
Total Hours Worked
.col-xs-12.col-sm-6.col-lg-3
%p
%span.badge= @credits_available
Total Credits Available
%table.table
%tbody
- @user_time_entries.each do |entry|
-# data: {start_date: ..} gets transformed to data-start-date instead of data-start_date, we don't want this
%tr{data: {id: entry.id, description: entry.description, 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= link_to truncate(entry.description).presence || '<em>No title</em>'.html_safe, edit_task_list_path(entry)
%td
= link_to '#modal', class: 'text-danger work_entry-delete-btn', role: 'button', data: {toggle: 'modal', target: '#confirmation'} do
%i.glyphicon.glyphicon-remove
= link_to 'Add Time Entry', new_time_entry_path, class: 'btn btn-primary'
.modal#confirmation(role="dialog" aria-labelledby="confirmation_title" aria-hidden="true" tabindex="-1")
.modal-dialog
.modal-content
.modal-header
%button.close(data-dismiss="modal" aria-hidden="true") &times;
%h4#confirmation_title Are you sure you want to delete?
.modal-body
#work_entry_start_date Start Date
#work_entry_duration Duration
#work_entry_description Description
.modal-footer
%button.btn.btn-default(data-dismiss="modal" aria-hidden="true") Cancel
%button.btn.btn-danger#confirmation_delete{data: {url_template: api_delete_time_entry_path('__ID__')}} Delete

View File

@ -1,56 +0,0 @@
= top_menu
%h1 Add Time Entry
- time = Time.now
- date_format, time_format = '%m/%d/%Y', '%l:%M %p'
- mjs_date_format, mjs_time_format = 'MM/DD/YYYY', 'h:mm A'
.row
.col-xs-12.col-sm-6.col-lg-4
%fieldset
.form-group.form-inline
= text_field_tag nil, time.strftime(date_format), id: 'date_id', placeholder: 'Date', class: 'form-control',
size: 12, data: {format: mjs_date_format}
.help-block
.form-group.timepickers.form-inline
.form-group
%label.visible-xs Start time
= text_field_tag nil, time.strftime(time_format), id: 'start_time_id', placeholder: 'Start time',
class: 'form-control', size: 8, data: {format: mjs_time_format}
.hidden#start_date
.help-block
.form-group.dash.hidden-xs
&mdash;
.form-group
%label.visible-xs End time
= text_field_tag nil, time.strftime(time_format), id: 'end_time_id', placeholder: 'End time',
class: 'form-control', size: 8, data: {format: mjs_time_format}
.hidden#end_date
.help-block
.form-group
.btn-group(data-toggle="buttons")
%label.btn.btn-default
= radio_button_tag 'action_id', 1, false, autocomplete: 'off'
Volunteer
%label.btn.btn-default
= radio_button_tag 'action_id', 2, false, autocomplete: 'off'
Personal
%label.btn.btn-default
= radio_button_tag 'action_id', 3, false, autocomplete: 'off'
Staff
.hidden#log_action_id
.help-block
.form-group
= select_tag :bike_id, options_for_select(@bikes), class: 'form-control'
.form-group
= text_area_tag nil, nil, id: 'description_id', placeholder: 'Work description', class: 'form-control', rows: 5
.control-group
.controls
= button_tag 'Add Time Entry', id: 'add_time_entry_submit', class: 'btn btn-primary', data: {url: api_create_time_entry_path, forward: '.'}

View File

@ -0,0 +1,68 @@
= form_for @user, :html => { :class => 'form-horizontal' } do |f|
.control-group
= f.label :email, :class => 'control-label'
.controls
= f.text_field :email, :class => 'text_field'
.control-group
= f.label :encrypted_password, :class => 'control-label'
.controls
= f.text_field :encrypted_password, :class => 'text_field'
.control-group
= f.label :reset_password_token, :class => 'control-label'
.controls
= f.text_field :reset_password_token, :class => 'text_field'
.control-group
= f.label :reset_password_sent_at, :class => 'control-label'
.controls
= f.datetime_select :reset_password_sent_at, :class => 'datetime_select'
.control-group
= f.label :remember_created_at, :class => 'control-label'
.controls
= f.datetime_select :remember_created_at, :class => 'datetime_select'
.control-group
= f.label :sign_in_count, :class => 'control-label'
.controls
= f.number_field :sign_in_count, :class => 'number_field'
.control-group
= f.label :current_sign_in_at, :class => 'control-label'
.controls
= f.datetime_select :current_sign_in_at, :class => 'datetime_select'
.control-group
= f.label :last_sign_in_at, :class => 'control-label'
.controls
= f.datetime_select :last_sign_in_at, :class => 'datetime_select'
.control-group
= f.label :current_sign_in_ip, :class => 'control-label'
.controls
= f.text_field :current_sign_in_ip, :class => 'text_field'
.control-group
= f.label :last_sign_in_ip, :class => 'control-label'
.controls
= f.text_field :last_sign_in_ip, :class => 'text_field'
.control-group
= f.label :failed_attempts, :class => 'control-label'
.controls
= f.number_field :failed_attempts, :class => 'number_field'
.control-group
= f.label :unlock_token, :class => 'control-label'
.controls
= f.text_field :unlock_token, :class => 'text_field'
.control-group
= f.label :locked_at, :class => 'control-label'
.controls
= f.datetime_select :locked_at, :class => 'datetime_select'
.control-group
= f.label :first_name, :class => 'control-label'
.controls
= f.text_field :first_name, :class => 'text_field'
.control-group
= f.label :last_name, :class => 'control-label'
.controls
= f.text_field :last_name, :class => 'text_field'
.control-group
= f.label :nickname, :class => 'control-label'
.controls
= f.text_field :nickname, :class => 'text_field'
.form-actions
= f.submit nil, :class => 'btn btn-primary'
= link_to t('.cancel', :default => t("helpers.links.cancel")), users_path, :class => 'btn'

View File

@ -0,0 +1,4 @@
- model_class = user.class
.page-header
%h1=t '.title', :default => t('helpers.titles.edit', :model => model_class.model_name.human, :default => "Edit #{model_class.model_name.human}")
= render :partial => "form"

View File

@ -0,0 +1,28 @@
- model_class = User.new.class
.page-header
%h1=t '.title', :default => model_class.model_name.human.pluralize
%table.table.table-striped
%thead
%tr
%th= model_class.human_attribute_name(:id)
%th= model_class.human_attribute_name(:email)
%th= model_class.human_attribute_name(:first_name)
%th= model_class.human_attribute_name(:last_name)
%th= model_class.human_attribute_name(:nickname)
%th= model_class.human_attribute_name(:created_at)
%th=t '.actions', :default => t("helpers.actions")
%tbody
- users.each do |user|
%tr
%td= link_to user.id, user_path(user)
%td= link_to user.email, user_path(user)
%td= user.first_name
%td= user.last_name
%td= user.nickname
%td=l user.created_at
%td
= link_to t('.edit', :default => t("helpers.links.edit")), edit_user_path(user), :class => 'btn btn-mini'
= link_to t('.destroy', :default => t("helpers.links.destroy")), user_path(user), :method => :delete, :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')), :class => 'btn btn-mini btn-danger'
= will_paginate users
= link_to t('.new', :default => t("helpers.links.new")), new_user_path, :class => 'btn btn-primary'

View File

@ -0,0 +1,4 @@
- model_class = user.class
.page-header
%h1=t '.title', :default => t('helpers.titles.new', :model => model_class.model_name.human, :default => "New #{model_class.model_name.human}")
= render :partial => "form"

View File

@ -0,0 +1,73 @@
- model_class = user.class
.page-header
%h1=t '.title', :default => model_class.model_name.human
%p
%strong= model_class.human_attribute_name(:email) + ':'
%br
= user.email
%p
%strong= model_class.human_attribute_name(:encrypted_password) + ':'
%br
= user.encrypted_password
%p
%strong= model_class.human_attribute_name(:reset_password_token) + ':'
%br
= user.reset_password_token
%p
%strong= model_class.human_attribute_name(:reset_password_sent_at) + ':'
%br
= user.reset_password_sent_at
%p
%strong= model_class.human_attribute_name(:remember_created_at) + ':'
%br
= user.remember_created_at
%p
%strong= model_class.human_attribute_name(:sign_in_count) + ':'
%br
= user.sign_in_count
%p
%strong= model_class.human_attribute_name(:current_sign_in_at) + ':'
%br
= user.current_sign_in_at
%p
%strong= model_class.human_attribute_name(:last_sign_in_at) + ':'
%br
= user.last_sign_in_at
%p
%strong= model_class.human_attribute_name(:current_sign_in_ip) + ':'
%br
= user.current_sign_in_ip
%p
%strong= model_class.human_attribute_name(:last_sign_in_ip) + ':'
%br
= user.last_sign_in_ip
%p
%strong= model_class.human_attribute_name(:failed_attempts) + ':'
%br
= user.failed_attempts
%p
%strong= model_class.human_attribute_name(:unlock_token) + ':'
%br
= user.unlock_token
%p
%strong= model_class.human_attribute_name(:locked_at) + ':'
%br
= user.locked_at
%p
%strong= model_class.human_attribute_name(:first_name) + ':'
%br
= user.first_name
%p
%strong= model_class.human_attribute_name(:last_name) + ':'
%br
= user.last_name
%p
%strong= model_class.human_attribute_name(:nickname) + ':'
%br
= user.nickname
.form-actions
= link_to t('.back', :default => t("helpers.links.back")), users_path, :class => 'btn'
= link_to t('.edit', :default => t("helpers.links.edit")), edit_user_path(user), :class => 'btn'
= link_to t('.destroy', :default => t("helpers.links.destroy")), user_path(user), :method => "delete", :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')), :class => 'btn btn-danger'

View File

@ -62,8 +62,5 @@ module Velocipede
# Version of your assets, change this if you want to expire all your assets # Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0' config.assets.version = '1.0'
# Enabled for bootstrap-sass gem
config.assets.initialize_on_precompile
end end
end end

View File

@ -1,19 +1,23 @@
development: &default development:
adapter: postgresql adapter: postgresql
encoding: unicode database: velocipede
pool: 5 username: velocipede
database: <%= ENV['RDS_DB_NAME'] || 'postgres' %> password:
username: <%= ENV['RDS_USERNAME'] || 'postgres' %> host: 127.0.0.1
password: <%= ENV['RDS_PASSWORD'] %>
host: <%= ENV['RDS_HOSTNAME'] || 'db' %>
# Warning: The database defined as "test" will be erased and # Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake". # re-generated from your development database when you run "rake".
# 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:
<<: *default adapter: postgresql
database: velocipede_test database: velocipede
username: velocipede
password:
host: 127.0.0.1
production: production:
<<: *default adapter: postgresql
database: velocipede_production database: velocipede
username: velocipede
password:
host: 127.0.0.1

View File

@ -222,7 +222,6 @@ Devise.setup do |config|
# end # end
end end
unless Rails.env.test?
#Check in the user if they sign in. (Devise uses Warden) #Check in the user if they sign in. (Devise uses Warden)
Warden::Manager.after_set_user do |user,auth,opts| Warden::Manager.after_set_user do |user,auth,opts|
# this essentially gets called after every netzke request, but alas, # this essentially gets called after every netzke request, but alas,
@ -230,4 +229,4 @@ unless Rails.env.test?
# user creation. # user creation.
user.checkin unless user.checked_in? user.checkin unless user.checked_in?
end end
end

View File

@ -4,34 +4,13 @@ Velocipede::Application.routes.draw do
netzke netzke
root :to => 'site#index' root :to => 'site#index'
get 'admin/index', to: 'panel#index', as: "admin_index"
get 'bikes/new', to: 'bikes#new', as: "new_bike"
get 'bikes/:id', to: 'bikes#show', as: "bike"
get 'task_lists/:id/edit' => "task_lists#edit", as: "edit_task_list"
get 'time_entries' => "time_entries#index", as: "time_entries"
get 'time_entries/new' => "time_entries#new", as: "new_time_entry"
########################### ###########################
# API Routes # API Routes
scope 'api', :module => :api, defaults: {format: :json} do scope 'api', :module => :api do
scope 'v1', :module => :v1 do scope 'v1', :module => :v1 do
post 'checkin' => "logs#checkin", :as => "api_checkin" post 'checkin' => "logs#checkin", :as => "api_checkin"
post 'checkout' => "logs#checkout", :as => "api_checkout" post 'checkout' => "logs#checkout", :as => "api_checkout"
post 'reset' => "users#password_reset", :as => "api_password_reset" post 'reset' => "users#password_reset", :as => "api_password_reset"
get 'bikes/:id' => "bikes#show", as: "api_bike"
post 'bikes/create' => "bikes#create", as: "api_create_bike"
get 'task_lists/:id' => "task_lists#show", as: "api_task_list"
put 'tasks/update' => "tasks#update", as: "api_update_task"
post 'time_entries/create' => "time_entries#create", as: "api_create_time_entry"
delete 'time_entries/:id' => "time_entries#delete", as: "api_delete_time_entry"
end end
end end

View File

@ -10,6 +10,10 @@ class CreateBikeWheelSizes < ActiveRecord::Migration
t.string :description t.string :description
t.string :tire_common_score t.string :tire_common_score
end end
#create the default undetermined wheel size record
BikeWheelSize.create( twmm: 0, rdmm: 0, rdin: 0, twin: 0, rdfr: 0, twfr: 0, description: "UNDETERMINED", tire_common_score: 0)
ActiveRecord::Base.connection.execute(IO.read(File.join(Rails.root, "db", "seed", "sql", "common_wheel_sizes.sql")))
end end
def down def down

View File

@ -1,11 +0,0 @@
class AddAttachmentAvatarToUsers < ActiveRecord::Migration
def self.up
change_table :users do |t|
t.attachment :avatar
end
end
def self.down
remove_attachment :users, :avatar
end
end

View File

@ -1,11 +0,0 @@
class AddAttachmentPhotoToBikes < ActiveRecord::Migration
def self.up
change_table :bikes do |t|
t.attachment :photo
end
end
def self.down
remove_attachment :bikes, :photo
end
end

View File

@ -1,15 +0,0 @@
class UserEmailCanBeNull < ActiveRecord::Migration
def up
change_table :users do |t|
t.change :email, :string, default: nil, null: true
end
User.where(email: '').update_all(email: nil)
end
def down
change_table :users do |t|
t.change :email, :string, default: '', null: false
end
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20170131164224) do ActiveRecord::Schema.define(:version => 20131019170248) do
create_table "bike_actions", :force => true do |t| create_table "bike_actions", :force => true do |t|
t.string "action", :limit => 128, :null => false t.string "action", :limit => 128, :null => false
@ -47,8 +47,8 @@ ActiveRecord::Schema.define(:version => 20170131164224) do
end end
create_table "bike_wheel_sizes", :force => true do |t| create_table "bike_wheel_sizes", :force => true do |t|
t.integer "twmm" t.string "twmm"
t.integer "rdmm" t.string "rdmm"
t.string "twin" t.string "twin"
t.string "rdin" t.string "rdin"
t.string "twfr" t.string "twfr"
@ -73,10 +73,6 @@ ActiveRecord::Schema.define(:version => 20170131164224) do
t.string "model" t.string "model"
t.integer "shop_id" t.integer "shop_id"
t.integer "bike_wheel_size_id" t.integer "bike_wheel_size_id"
t.string "photo_file_name"
t.string "photo_content_type"
t.integer "photo_file_size"
t.datetime "photo_updated_at"
end end
create_table "credit_conversions", :force => true do |t| create_table "credit_conversions", :force => true do |t|
@ -183,7 +179,7 @@ ActiveRecord::Schema.define(:version => 20170131164224) do
end end
create_table "users", :force => true do |t| create_table "users", :force => true do |t|
t.string "email" t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false t.string "encrypted_password", :default => "", :null => false
t.string "reset_password_token" t.string "reset_password_token"
t.datetime "reset_password_sent_at" t.datetime "reset_password_sent_at"
@ -202,10 +198,6 @@ ActiveRecord::Schema.define(:version => 20170131164224) do
t.string "first_name", :default => "", :null => false t.string "first_name", :default => "", :null => false
t.string "last_name", :default => "", :null => false t.string "last_name", :default => "", :null => false
t.string "username" t.string "username"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
end end
add_index "users", ["bike_id"], :name => "index_users_on_bike_id", :unique => true add_index "users", ["bike_id"], :name => "index_users_on_bike_id", :unique => true

View File

@ -7,6 +7,3 @@ hybrid:
road: road:
id: 3 id: 3
style: ROAD style: ROAD
other:
id: 4
style: OTHER

View File

@ -1,6 +1,3 @@
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (0,0,0,0,0,0,'UNDETERMINED',0);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (54,110,'8 1/2','2','','','',1); INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (54,110,'8 1/2','2','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (62,203,'12','1/2 x 2 1/4','320 ','57','',1); INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (62,203,'12','1/2 x 2 1/4','320 ','57','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,305,'16','1,75 [x 2]','','','',1); INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,305,'16','1,75 [x 2]','','','',1);
@ -38,4 +35,3 @@ INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_co
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (28,630,'27','1 1/8 [1 1/4 fifty]','','','',1); INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (28,630,'27','1 1/8 [1 1/4 fifty]','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (32,630,'27','1 1/4','','','',1); INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (32,630,'27','1 1/4','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (40,635,'28','1 1/2 [1 3/8]','700 ','38B [35B]','',1); INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (40,635,'28','1 1/2 [1 3/8]','700 ','38B [35B]','',1);
COMMIT;

View File

@ -18,13 +18,11 @@ if BikeBrand.all.empty? and BikeModel.all.empty?
# so that the PG table ID sequence is incremented # so that the PG table ID sequence is incremented
# #
# Note the drop(1) which assumes we have a junk PRAGMA line at the top # Note the drop(1) which assumes we have a junk PRAGMA line at the top
['common_wheel_sizes.sql', 'bike_brands_and_models.sql'].each do |sql| load_statements = File.readlines(File.join(Rails.root, 'db', 'seed', 'sql', 'bike_brands_and_models.sql')).drop(1).map do |statement|
load_statements = File.readlines(File.join(Rails.root, 'db', 'seed', 'sql', sql)).drop(1).map do |statement|
statement.sub(/VALUES\(\d+,/, 'VALUES(DEFAULT,') statement.sub(/VALUES\(\d+,/, 'VALUES(DEFAULT,')
end end
ActiveRecord::Base.connection.execute(load_statements.join) ActiveRecord::Base.connection.execute(load_statements.join)
end end
end
if Rails.env.development? if Rails.env.development?
@ -43,7 +41,7 @@ if Rails.env.development?
#create fake bikes #create fake bikes
if Bike.all.empty? if Bike.all.empty?
42.times do |n| 42.times do |n|
FactoryGirl.create(:seed_bike) FactoryGirl.create(:bike)
end end
end end
elsif Rails.env.production? elsif Rails.env.production?

1
doc/README_FOR_APP Normal file
View File

@ -0,0 +1 @@
# No API yet

View File

@ -1,20 +0,0 @@
# Overview
## 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 via the desktop view: <https://www.youtube.com/watch?v=1IchWUdMQ90>.
## Views
The app has two different views. One for "core" volunteers which I consider admins, and another for "customer" volunteers which I consider users. Because of the Netzke/ExtJS framework I used, the current UI is non-intuitive and I feel like is only adequate for admins who can take the time to learn the system. I'm in the process of designing a simpler mobile friendly UI with the essential functionality that's intended for users.
## Deployment
Currently, at the Velocipede collective we have it running on a computer in the shop on the local network. The reason for this, is so that it's only accessible from within the shop. I'm weighing out different methods for ensuring that users can only log in from within the shop (and not from home), such as a shop password that only admins can see and would make visible from within the physical bike shop. I also don't have to worry about security issues while the app is in development. In the future, I'd like to see the app hosted much like <http://freehub.bikekitchen.org/>.
## Ideas
At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donations of old, unused smart phones, connected via local wifi, that can be used as stations to log a volunteer's work and hours. They can be secured by gluing cables into the earphone jacks to deter theft if necessary. This might be another way that the collective can promote reuse of materials.

View File

@ -1,34 +0,0 @@
# Local Developer Setup
1. use rvm
1. allow the .rvmrc file
1. `gem install bundler`
1. `bundle`
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:setup`
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. Link to your extJS folder path under `public/extjs`: (From app root) `ln -s /MY/PATH/extjs/ public/extjs`
1. `rails s`
# Postgres 9.2 Mac OSX Install
1. Install homebrew `ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"`
1. `brew install postgres`
1. First time db initialization `initdb /usr/local/var/postgres -E utf8`
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 database `createdb -U velocipede --owner=velocipede velocipede`
1. Create your test database `createdb -U velocipede --owner=velocipede velocipede_test`
# Testing
1. Install phantomjs `brew install phantomjs`
1. Run tests with `rspec`
# Optional
Add icons
1. Download icons from http://www.famfamfam.com/lab/icons/silk/
1. Link to the icons under `public/images/icons`: (From app root) `ln -s /MY/PATH/famfamfam_silk_icons/icons public/images/icons`

View File

@ -1,21 +0,0 @@
# BikeShed
#
# cp config/database.yml.example config/database.yml
#
# docker-compose -f docker-compose.demo.yml build
# docker-compose -f docker-compose.demo.yml up -d db
# docker-compose -f docker-compose.demo.yml run bikeshed rake db:setup
# docker-compose -f docker-compose.demo.yml up -d bikeshed
db:
image: postgres
ports:
- "5432"
web:
volumes:
- .:/usr/src/app
build: .
ports:
- "8080:3000"
links:
- db

Binary file not shown.

View File

@ -1,20 +0,0 @@
#!/usr/bin/env ruby
require 'fileutils'
extjs_url = "http://my.jasondenney.com/extjs-4.1.1.zip"
download_dir = '/tmp'
dest_dir ='/usr/lib'
extjs_download_path = File.join(download_dir,'extjs-4.1.1.zip')
files = Dir.glob(File.join(dest_dir, 'extjs', '*'))
if files.empty? and Dir.glob(extjs_download_path).empty?
`wget -P #{download_dir} #{extjs_url}`
raise "Failed downloading #{extjs_url}" if Dir.glob(extjs_download_path).empty?
end
if files.empty?
puts `unzip #{extjs_download_path} -d #{dest_dir}/`
FileUtils.mv(File.join(dest_dir, 'ext-4.1.1a'), File.join(dest_dir, 'extjs'))
end
raise "Failed unzipping #{extjs_download_path}" if Dir.glob(File.join(dest_dir,'extjs', '*')).empty?

Some files were not shown because too many files have changed in this diff Show More