mirror of
https://github.com/fspc/BikeShed-1.git
synced 2025-04-04 05:33:22 -04:00
Compare commits
No commits in common. "master" and "v0.0.0" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -6,7 +6,6 @@ tmp/
|
||||
.powrc
|
||||
.rbenv-version
|
||||
.DS_Store
|
||||
public/assets
|
||||
public/system
|
||||
public/extjs
|
||||
public/images/icons
|
||||
config/database.yml
|
||||
.idea/
|
15
.powrc
15
.powrc
@ -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 +0,0 @@
|
||||
velocipede
|
@ -1 +0,0 @@
|
||||
ruby-2.1.1
|
55
.rvmrc
Normal file
55
.rvmrc
Normal 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
|
||||
|
||||
|
31
Dockerfile
31
Dockerfile
@ -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"]
|
42
Gemfile
42
Gemfile
@ -1,7 +1,6 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'rails', '3.2.13'
|
||||
gem 'rake', '< 11.0'
|
||||
|
||||
gem 'netzke-cancan'
|
||||
gem 'netzke-core', '~>0.8.0'
|
||||
@ -13,39 +12,38 @@ gem 'bootstrap-will_paginate', '~> 0.0.6'
|
||||
gem 'cancan'
|
||||
gem 'decent_exposure', '~> 1.0.1'
|
||||
gem 'devise', '~> 2.0.4'
|
||||
gem 'haml-rails'
|
||||
gem 'haml-rails', '~> 0.3.4'
|
||||
gem 'jquery-rails', '~> 2.0'
|
||||
gem 'pg', '~> 0.17.1'
|
||||
gem 'pg'
|
||||
gem 'will_paginate', '~> 3.0.3'
|
||||
gem 'jbuilder', '~> 2.0.3'
|
||||
gem 'paperclip', '~> 4.3'
|
||||
|
||||
# Assets
|
||||
gem 'sass-rails', '~> 3.0'
|
||||
gem 'coffee-rails', '~> 3.2.1'
|
||||
gem 'bootstrap-sass', '~> 3.1.1'
|
||||
# Gems used only for assets and not required
|
||||
# in production environments by default.
|
||||
group :assets do
|
||||
gem 'coffee-rails', '~> 3.2.1'
|
||||
gem "twitter-bootstrap-rails", "~> 2.0.3"
|
||||
|
||||
gem 'momentjs-rails', '>= 2.9.0'
|
||||
gem 'bootstrap3-datetimepicker-rails', '~> 4.17.43'
|
||||
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
|
||||
# gem 'therubyracer'
|
||||
|
||||
gem 'uglifier', '>= 1.0.3'
|
||||
gem 'uglifier', '>= 1.0.3'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
gem 'rspec-rails', '~> 2.14.0'
|
||||
gem 'rspec-rails', '~> 2.8.1'
|
||||
gem 'factory_girl_rails', '~> 1.2'
|
||||
gem 'pry', '~> 0.9.8'
|
||||
gem 'faker', '~> 1.2.0'
|
||||
gem 'colorize'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'shoulda-matchers', '~> 1.0.0'
|
||||
gem 'capybara', '~> 2.2.1'
|
||||
gem 'poltergeist', '~> 1.10.0'
|
||||
gem 'database_cleaner', '~> 1.2.0'
|
||||
gem 'launchy', '~> 2.4.2'
|
||||
gem 'spork', '~> 0.9.2'
|
||||
gem 'capybara', '~> 1.1.2'
|
||||
gem 'turnip', '~> 0.3.0'
|
||||
gem 'database_cleaner'
|
||||
gem 'launchy'
|
||||
gem 'spork'
|
||||
#guard dependency for Mac OS 10
|
||||
gem 'guard-spork', '~> 1.5.1'
|
||||
gem 'guard-rspec', '~> 4.2.6'
|
||||
gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
|
||||
gem 'guard-spork'
|
||||
gem 'guard-rspec'
|
||||
end
|
||||
|
260
Gemfile.lock
260
Gemfile.lock
@ -1,6 +1,6 @@
|
||||
GIT
|
||||
remote: https://github.com/spacemunkay/acts_as_loggable.git
|
||||
revision: 8484dc1f7a58699705c5ffb593ebdf700a9f374a
|
||||
revision: 49e264849ed9018445fb999cacfc55e1e28d7faa
|
||||
specs:
|
||||
acts_as_loggable (0.0.7)
|
||||
activerecord (>= 3.0)
|
||||
@ -36,145 +36,115 @@ GEM
|
||||
activesupport (3.2.13)
|
||||
i18n (= 0.6.1)
|
||||
multi_json (~> 1.0)
|
||||
addressable (2.4.0)
|
||||
arel (3.0.3)
|
||||
bcrypt (3.1.10)
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
bootstrap-sass (3.1.1.1)
|
||||
sass (~> 3.2)
|
||||
bootstrap-will_paginate (0.0.10)
|
||||
addressable (2.3.3)
|
||||
arel (3.0.2)
|
||||
bcrypt-ruby (3.0.1)
|
||||
bootstrap-will_paginate (0.0.9)
|
||||
will_paginate
|
||||
bootstrap3-datetimepicker-rails (4.17.43)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
builder (3.0.4)
|
||||
cancan (1.6.10)
|
||||
capybara (2.2.1)
|
||||
cancan (1.6.9)
|
||||
capybara (1.1.4)
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (~> 2.0)
|
||||
childprocess (0.5.9)
|
||||
selenium-webdriver (~> 2.0)
|
||||
xpath (~> 0.1.4)
|
||||
childprocess (0.3.9)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
climate_control (0.1.0)
|
||||
cliver (0.3.2)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.1.1)
|
||||
coderay (1.0.9)
|
||||
coffee-rails (3.2.2)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (~> 3.2.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script (2.2.0)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.10.0)
|
||||
colorize (0.8.1)
|
||||
database_cleaner (1.2.0)
|
||||
coffee-script-source (1.6.2)
|
||||
commonjs (0.2.6)
|
||||
database_cleaner (0.9.1)
|
||||
decent_exposure (1.0.2)
|
||||
devise (2.0.6)
|
||||
devise (2.0.5)
|
||||
bcrypt-ruby (~> 3.0)
|
||||
orm_adapter (~> 0.0.3)
|
||||
railties (~> 3.1)
|
||||
warden (~> 1.1.1)
|
||||
diff-lcs (1.2.5)
|
||||
diff-lcs (1.1.3)
|
||||
erubis (2.7.0)
|
||||
execjs (2.6.0)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
factory_girl (2.6.4)
|
||||
activesupport (>= 2.3.9)
|
||||
factory_girl_rails (1.7.0)
|
||||
factory_girl (~> 2.6.0)
|
||||
railties (>= 3.0.0)
|
||||
faker (1.2.0)
|
||||
i18n (~> 0.5)
|
||||
ffi (1.9.10)
|
||||
formatador (0.2.5)
|
||||
guard (2.13.0)
|
||||
ffi (1.6.0)
|
||||
formatador (0.2.4)
|
||||
gherkin (2.11.6)
|
||||
json (>= 1.7.6)
|
||||
guard (1.7.0)
|
||||
formatador (>= 0.2.4)
|
||||
listen (>= 2.7, <= 4.0)
|
||||
lumberjack (~> 1.0)
|
||||
nenv (~> 0.1)
|
||||
notiffany (~> 0.0)
|
||||
pry (>= 0.9.12)
|
||||
shellany (~> 0.0)
|
||||
thor (>= 0.18.1)
|
||||
guard-rspec (4.2.10)
|
||||
guard (~> 2.1)
|
||||
rspec (>= 2.14, < 4.0)
|
||||
guard-spork (1.5.1)
|
||||
listen (>= 0.6.0)
|
||||
lumberjack (>= 1.0.2)
|
||||
pry (>= 0.9.10)
|
||||
thor (>= 0.14.6)
|
||||
guard-rspec (1.2.1)
|
||||
guard (>= 1.1)
|
||||
guard-spork (1.5.0)
|
||||
childprocess (>= 0.2.3)
|
||||
guard (>= 1.1)
|
||||
spork (>= 0.8.4)
|
||||
haml (4.0.7)
|
||||
tilt
|
||||
haml-rails (0.4)
|
||||
haml (3.1.8)
|
||||
haml-rails (0.3.5)
|
||||
actionpack (>= 3.1, < 4.1)
|
||||
activesupport (>= 3.1, < 4.1)
|
||||
haml (>= 3.1, < 4.1)
|
||||
haml (~> 3.1)
|
||||
railties (>= 3.1, < 4.1)
|
||||
hike (1.2.3)
|
||||
hike (1.2.1)
|
||||
i18n (0.6.1)
|
||||
jbuilder (2.0.8)
|
||||
activesupport (>= 3.0.0, < 5)
|
||||
multi_json (~> 1.2)
|
||||
journey (1.0.4)
|
||||
jquery-rails (2.3.0)
|
||||
jquery-rails (2.2.1)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.8.3)
|
||||
launchy (2.4.3)
|
||||
json (1.7.7)
|
||||
launchy (2.2.0)
|
||||
addressable (~> 2.3)
|
||||
listen (3.0.6)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9.7)
|
||||
lumberjack (1.0.10)
|
||||
mail (2.5.4)
|
||||
less (2.2.2)
|
||||
commonjs (~> 0.2.6)
|
||||
less-rails (2.2.6)
|
||||
actionpack (>= 3.1)
|
||||
less (~> 2.2.0)
|
||||
libv8 (3.3.10.4)
|
||||
listen (0.7.3)
|
||||
lumberjack (1.0.3)
|
||||
mail (2.5.3)
|
||||
i18n (>= 0.4.0)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mimemagic (0.3.0)
|
||||
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)
|
||||
method_source (0.8.1)
|
||||
mime-types (1.22)
|
||||
multi_json (1.7.2)
|
||||
netzke-basepack (0.8.2)
|
||||
netzke-core (~> 0.8.2)
|
||||
netzke-cancan (0.8.2)
|
||||
cancan
|
||||
netzke-core
|
||||
netzke-core (0.8.4)
|
||||
netzke-core (0.8.3)
|
||||
execjs
|
||||
uglifier
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
notiffany (0.0.8)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
nokogiri (1.5.9)
|
||||
orm_adapter (0.0.7)
|
||||
paperclip (4.3.7)
|
||||
activemodel (>= 3.2.0)
|
||||
activesupport (>= 3.2.0)
|
||||
cocaine (~> 0.5.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)
|
||||
pg (0.15.1)
|
||||
polyglot (0.3.3)
|
||||
pry (0.9.12)
|
||||
coderay (~> 1.0.5)
|
||||
method_source (~> 0.8)
|
||||
slop (~> 3.4)
|
||||
rack (1.4.7)
|
||||
rack-cache (1.6.1)
|
||||
rack (1.4.5)
|
||||
rack-cache (1.2)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.4)
|
||||
rack-ssl (1.3.3)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rails (3.2.13)
|
||||
actionmailer (= 3.2.13)
|
||||
@ -191,58 +161,61 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rake (10.5.0)
|
||||
rb-fsevent (0.9.7)
|
||||
rb-inotify (0.9.7)
|
||||
ffi (>= 0.5.0)
|
||||
rake (10.0.4)
|
||||
rb-fsevent (0.9.3)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-core (2.14.8)
|
||||
rspec-expectations (2.14.5)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.6)
|
||||
rspec-rails (2.14.2)
|
||||
rspec (2.8.0)
|
||||
rspec-core (~> 2.8.0)
|
||||
rspec-expectations (~> 2.8.0)
|
||||
rspec-mocks (~> 2.8.0)
|
||||
rspec-core (2.8.0)
|
||||
rspec-expectations (2.8.0)
|
||||
diff-lcs (~> 1.1.2)
|
||||
rspec-mocks (2.8.0)
|
||||
rspec-rails (2.8.1)
|
||||
actionpack (>= 3.0)
|
||||
activemodel (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
sass (3.4.22)
|
||||
sass-rails (3.2.6)
|
||||
railties (~> 3.2.0)
|
||||
sass (>= 3.1.10)
|
||||
tilt (~> 1.3)
|
||||
shellany (0.0.1)
|
||||
rspec (~> 2.8.0)
|
||||
rubyzip (0.9.9)
|
||||
selenium-webdriver (2.31.0)
|
||||
childprocess (>= 0.2.5)
|
||||
multi_json (~> 1.0)
|
||||
rubyzip
|
||||
websocket (~> 1.0.4)
|
||||
shoulda-matchers (1.0.0)
|
||||
slop (3.6.0)
|
||||
slop (3.4.4)
|
||||
spork (0.9.2)
|
||||
sprockets (2.2.3)
|
||||
sprockets (2.2.2)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
thor (0.19.4)
|
||||
tilt (1.4.1)
|
||||
treetop (1.4.15)
|
||||
therubyracer (0.10.2)
|
||||
libv8 (~> 3.3.10)
|
||||
thor (0.18.1)
|
||||
tilt (1.3.6)
|
||||
treetop (1.4.12)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.46)
|
||||
uglifier (2.7.2)
|
||||
turnip (0.3.1)
|
||||
gherkin (>= 2.5)
|
||||
rspec (~> 2.0)
|
||||
twitter-bootstrap-rails (2.0.9)
|
||||
actionpack (>= 3.1)
|
||||
less-rails (~> 2.2.2)
|
||||
railties (>= 3.1)
|
||||
therubyracer (~> 0.10.1)
|
||||
tzinfo (0.3.37)
|
||||
uglifier (1.3.0)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
multi_json (~> 1.0, >= 1.0.2)
|
||||
warden (1.1.1)
|
||||
rack (>= 1.0)
|
||||
websocket-driver (0.6.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
will_paginate (3.0.7)
|
||||
xpath (2.0.0)
|
||||
websocket (1.0.7)
|
||||
will_paginate (3.0.4)
|
||||
xpath (0.1.4)
|
||||
nokogiri (~> 1.3)
|
||||
|
||||
PLATFORMS
|
||||
@ -250,37 +223,30 @@ PLATFORMS
|
||||
|
||||
DEPENDENCIES
|
||||
acts_as_loggable!
|
||||
bootstrap-sass (~> 3.1.1)
|
||||
bootstrap-will_paginate (~> 0.0.6)
|
||||
bootstrap3-datetimepicker-rails (~> 4.17.43)
|
||||
cancan
|
||||
capybara (~> 2.2.1)
|
||||
capybara (~> 1.1.2)
|
||||
coffee-rails (~> 3.2.1)
|
||||
colorize
|
||||
database_cleaner (~> 1.2.0)
|
||||
database_cleaner
|
||||
decent_exposure (~> 1.0.1)
|
||||
devise (~> 2.0.4)
|
||||
factory_girl_rails (~> 1.2)
|
||||
faker (~> 1.2.0)
|
||||
guard-rspec (~> 4.2.6)
|
||||
guard-spork (~> 1.5.1)
|
||||
haml-rails
|
||||
jbuilder (~> 2.0.3)
|
||||
guard-rspec
|
||||
guard-spork
|
||||
haml-rails (~> 0.3.4)
|
||||
jquery-rails (~> 2.0)
|
||||
launchy (~> 2.4.2)
|
||||
momentjs-rails (>= 2.9.0)
|
||||
launchy
|
||||
netzke-basepack (~> 0.8.0)
|
||||
netzke-cancan
|
||||
netzke-core (~> 0.8.0)
|
||||
paperclip (~> 4.3)
|
||||
pg (~> 0.17.1)
|
||||
poltergeist (~> 1.10.0)
|
||||
pg
|
||||
pry (~> 0.9.8)
|
||||
rails (= 3.2.13)
|
||||
rake (< 11.0)
|
||||
rspec-rails (~> 2.14.0)
|
||||
sass-rails (~> 3.0)
|
||||
rb-fsevent
|
||||
rspec-rails (~> 2.8.1)
|
||||
shoulda-matchers (~> 1.0.0)
|
||||
spork (~> 0.9.2)
|
||||
spork
|
||||
turnip (~> 0.3.0)
|
||||
twitter-bootstrap-rails (~> 2.0.3)
|
||||
uglifier (>= 1.0.3)
|
||||
will_paginate (~> 3.0.3)
|
||||
|
63
README.md
63
README.md
@ -1,48 +1,37 @@
|
||||
# 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
|
||||
|
||||
## 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
|
||||
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. 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`
|
||||
|
||||
You'll likely want to add the following aliases:
|
||||
```
|
||||
alias dm='docker-machine'
|
||||
alias dc='docker-compose'
|
||||
alias dcrw='docker-compose run web'
|
||||
```
|
||||
## Mailcatcher
|
||||
|
||||
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`.
|
||||
Use mailcatcher to see what emails look like in development.
|
||||
Follow the instructions from http://mailcatcher.me/ to create an rvm wrapper so you don't install it all over your gemsets
|
||||
|
||||
# License
|
||||
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
|
||||
|
@ -12,9 +12,4 @@
|
||||
//
|
||||
//= require jquery
|
||||
//= require jquery_ujs
|
||||
//= require bootstrap
|
||||
//= require utils
|
||||
//= require moment
|
||||
//= require bootstrap-datetimepicker
|
||||
//= require jquery.form
|
||||
|
||||
//= require custom_netzke_helpers
|
||||
|
@ -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);
|
||||
}
|
||||
})
|
150
app/assets/javascripts/custom_netzke_helpers.js
Normal file
150
app/assets/javascripts/custom_netzke_helpers.js
Normal file
@ -0,0 +1,150 @@
|
||||
//when signed out, or session expires forward to sign in page
|
||||
Ext.Ajax.on('requestexception', function(conn, response, options) {
|
||||
if (response.status === 401) { window.location = '/users/sign_in'; }
|
||||
}, this);
|
||||
|
||||
Ext.define('Ext.ux.form.field.ColorCombo', {
|
||||
extend:'Ext.form.FieldContainer',
|
||||
mixins:{
|
||||
field:'Ext.form.field.Field'
|
||||
},
|
||||
alias: 'widget.xcolorcombo',
|
||||
|
||||
//configurables
|
||||
combineErrors: true,
|
||||
msgTarget: 'under',
|
||||
layout: 'hbox',
|
||||
readOnly: false,
|
||||
|
||||
// properties
|
||||
colorValue: null,
|
||||
/**
|
||||
* @property dateField
|
||||
* @type Ext.form.field.Date
|
||||
*/
|
||||
colorField: null,
|
||||
|
||||
initComponent: function(){
|
||||
var me = this
|
||||
,i = 0
|
||||
,key
|
||||
,tab;
|
||||
|
||||
me.items = me.items || [];
|
||||
|
||||
me.colorField = Ext.create('Ext.form.field.Trigger', {
|
||||
flex:1,
|
||||
isFormField:false, //exclude from field query's
|
||||
submitValue:false,
|
||||
readOnly: me.readOnly,
|
||||
onTriggerClick: function() {
|
||||
//show renders, so put first
|
||||
me.picker.show();
|
||||
me.picker.alignTo(me.colorField.inputEl);
|
||||
}
|
||||
});
|
||||
me.items.push(me.colorField);
|
||||
|
||||
me.picker = Ext.create('Ext.picker.Color', {
|
||||
renderTo: document.body,
|
||||
floating: true,
|
||||
hidden: true,
|
||||
style: {
|
||||
backgroundColor: "#fff"
|
||||
},
|
||||
listeners: {
|
||||
scope:this,
|
||||
select: function(field, value, opts){
|
||||
me.setValue(value);
|
||||
me.picker.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
me.items.push(me.picker);
|
||||
|
||||
for (; i < me.items.length; i++) {
|
||||
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
|
||||
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
|
||||
me.items[i].on('specialkey', function(field, event){
|
||||
key = event.getKey();
|
||||
tab = key == event.TAB;
|
||||
|
||||
if (tab && me.focussedItem == me.dateField) {
|
||||
event.stopEvent();
|
||||
me.timeField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
me.fireEvent('specialkey', field, event);
|
||||
});
|
||||
}
|
||||
|
||||
me.callParent();
|
||||
|
||||
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
|
||||
this.inputEl = {
|
||||
dom: document.createElement('div'),
|
||||
swallowEvent:function(){}
|
||||
};
|
||||
|
||||
me.initField();
|
||||
},
|
||||
focus:function(){
|
||||
this.callParent(arguments);
|
||||
this.colorField.focus();
|
||||
var me = this;
|
||||
},
|
||||
|
||||
onItemFocus:function(item){
|
||||
if (this.blurTask){
|
||||
this.blurTask.cancel();
|
||||
}
|
||||
this.focussedItem = item;
|
||||
},
|
||||
|
||||
onItemBlur:function(item, e){
|
||||
var me = this;
|
||||
if (item != me.focussedItem){ return; }
|
||||
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
|
||||
me.blurTask = new Ext.util.DelayedTask(function(){
|
||||
me.picker.hide();
|
||||
me.fireEvent('blur', me, e);
|
||||
});
|
||||
me.blurTask.delay(100);
|
||||
},
|
||||
|
||||
getValue: function(){
|
||||
var value = null
|
||||
,color = this.colorField.getSubmitValue();
|
||||
|
||||
if (color){
|
||||
value = this.colorField.getValue();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
getSubmitValue: function(){
|
||||
// var value = this.getValue();
|
||||
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
|
||||
|
||||
var me = this
|
||||
,value = me.getValue();
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
setValue: function(value){
|
||||
this.colorField.setValue(value);
|
||||
},
|
||||
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
|
||||
getSubmitData: function(){
|
||||
var me = this
|
||||
,data = null;
|
||||
|
||||
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
|
||||
data = {};
|
||||
data[me.getName()] = '' + me.getSubmitValue();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
@ -2,79 +2,50 @@ $(document).ready(function(){
|
||||
var MIN_LEN = 3;
|
||||
var MAX_SUBMITS = 3;
|
||||
var submit_count = 0;
|
||||
$("input[name=commit]").click( function(e){
|
||||
console.log("clicked");
|
||||
submit_count += 1;
|
||||
//IDs of contact info
|
||||
var contact_info_ids = [
|
||||
"user_email",
|
||||
"user_user_profiles_attributes_0_addrStreet1",
|
||||
"user_user_profiles_attributes_0_addrCity",
|
||||
"user_user_profiles_attributes_0_addrState",
|
||||
"user_user_profiles_attributes_0_addrZip",
|
||||
"user_user_profiles_attributes_0_phone"
|
||||
];
|
||||
var contact_vals = "";
|
||||
var index = 0;
|
||||
//see if any contact info exists
|
||||
for( var index in contact_info_ids){
|
||||
contact_vals += $("#"+contact_info_ids[index]).val();
|
||||
}
|
||||
if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){
|
||||
|
||||
var checkContacts = function() {
|
||||
submit_count += 1;
|
||||
//IDs of contact info
|
||||
var contact_info_ids = [
|
||||
"user_email",
|
||||
"user_user_profiles_attributes_0_addrStreet1",
|
||||
"user_user_profiles_attributes_0_addrCity",
|
||||
"user_user_profiles_attributes_0_addrState",
|
||||
"user_user_profiles_attributes_0_addrZip",
|
||||
"user_user_profiles_attributes_0_phone"
|
||||
];
|
||||
|
||||
var contact_vals = "";
|
||||
//see if any contact info exists
|
||||
for( var index in contact_info_ids){
|
||||
contact_vals += $("#"+contact_info_ids[index]).val();
|
||||
if( submit_count > MAX_SUBMITS ){
|
||||
alert("Fine.");
|
||||
}
|
||||
if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){
|
||||
return true;
|
||||
|
||||
if( submit_count == MAX_SUBMITS + 1 ){
|
||||
alert("Fine.");
|
||||
}
|
||||
return true;
|
||||
}else{
|
||||
|
||||
}else{
|
||||
|
||||
switch(submit_count){
|
||||
case 1:
|
||||
alert("It appears you have not entered any contact information. " +
|
||||
"Please do.");
|
||||
break;
|
||||
case 2:
|
||||
alert("It is highly recommended that you enter at least one form of" +
|
||||
" contact information. It is in your best interest.");
|
||||
break;
|
||||
case 3:
|
||||
alert("If something happens to your bicycle, we will not be able to" +
|
||||
" notify you. Please enter at least one form of contact.");
|
||||
break;
|
||||
default:
|
||||
alert("Please enter at least one form of contact.");
|
||||
}
|
||||
return false;
|
||||
switch(submit_count){
|
||||
case 1:
|
||||
alert("It appears you have not entered any contact information. " +
|
||||
"Please do.");
|
||||
break;
|
||||
case 2:
|
||||
alert("It is highly recommended that you enter at least one form of" +
|
||||
" contact information. It is in your best interest.");
|
||||
break;
|
||||
case 3:
|
||||
alert("If something happens to your bicycle, we will not be able to" +
|
||||
" notify you. Please enter at least one form of contact.");
|
||||
break;
|
||||
default:
|
||||
alert("Please enter at least one form of contact.");
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
$(document).ready(function(){
|
||||
$("#checkin_menu").show();
|
||||
$("#checkin").click( function(e){
|
||||
var username = $("#user_username").val();
|
||||
var username = $("#user_email").val();
|
||||
var password = $("#user_password").val();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
@ -13,7 +13,7 @@ $(document).ready(function(){
|
||||
complete: function() { },
|
||||
success: function(data) {
|
||||
alert("Checked IN!");
|
||||
$("#user_username").val('');
|
||||
$("#user_email").val('');
|
||||
$("#user_password").val('');
|
||||
},
|
||||
error: function(data,textStatus) {
|
||||
@ -22,7 +22,7 @@ $(document).ready(function(){
|
||||
})
|
||||
});
|
||||
$("#checkout").click( function(e){
|
||||
var username = $("#user_username").val();
|
||||
var username = $("#user_email").val();
|
||||
var password = $("#user_password").val();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
@ -34,8 +34,7 @@ $(document).ready(function(){
|
||||
complete: function() { },
|
||||
success: function(data) {
|
||||
alert("Checked OUT!");
|
||||
|
||||
$("#user_username").val('');
|
||||
$("#user_email").val('');
|
||||
$("#user_password").val('');
|
||||
},
|
||||
error: function(data,textStatus) {
|
||||
|
@ -1,376 +0,0 @@
|
||||
//when signed out, or session expires forward to sign in page
|
||||
Ext.Ajax.on('requestexception', function(conn, response, options) {
|
||||
if (response.status === 401 && response.statusText === "Unauthorized") { window.location = '/users/sign_in'; }
|
||||
}, this);
|
||||
|
||||
//Override default netzke time entry field
|
||||
Ext.define('Ext.ux.form.field.DateTime', {
|
||||
extend:'Ext.form.FieldContainer',
|
||||
mixins:{
|
||||
field:'Ext.form.field.Field'
|
||||
},
|
||||
alias: 'widget.xdatetime',
|
||||
|
||||
//configurables
|
||||
|
||||
combineErrors: true,
|
||||
msgTarget: 'under',
|
||||
layout: 'hbox',
|
||||
readOnly: false,
|
||||
|
||||
dateFormat: 'Y-m-d',
|
||||
dateSubmitFormat: 'Y-m-d',
|
||||
timeFormat: 'H:i:s',
|
||||
timeSubmitFormat: 'H:i:s',
|
||||
dateConfig:{},
|
||||
timeConfig:{},
|
||||
|
||||
|
||||
// properties
|
||||
|
||||
dateValue: null, // Holds the actual date
|
||||
|
||||
dateField: null,
|
||||
|
||||
hourField: null,
|
||||
minuteField: null,
|
||||
ampmField: null,
|
||||
|
||||
initComponent: function(){
|
||||
var me = this
|
||||
,i = 0
|
||||
,key
|
||||
,tab;
|
||||
|
||||
me.items = me.items || [];
|
||||
|
||||
me.dateField = Ext.create('Ext.form.field.Date', Ext.apply({
|
||||
format:me.dateFormat,
|
||||
flex:1,
|
||||
isFormField:false, //exclude from field query's
|
||||
submitValue:false,
|
||||
submitFormat: me.dateSubmitFormat,
|
||||
readOnly: me.readOnly
|
||||
}, me.dateConfig));
|
||||
me.items.push(me.dateField);
|
||||
|
||||
me.hourField = Ext.create('Ext.form.field.Number', {
|
||||
maxWidth: 25,
|
||||
allowBlank: false,
|
||||
allowOnlyWhitespace: false,
|
||||
blankText: "Hour cannot be blank.",
|
||||
allowDecimals: false,
|
||||
maxValue: 12,
|
||||
minValue: 1,
|
||||
maxLength: 2,
|
||||
enforceMaxLength: 2,
|
||||
hideTrigger: true,
|
||||
submitValue:false,
|
||||
flex:1,
|
||||
fieldStyle: "text-align:right;",
|
||||
isFormField:false, //exclude from field query's
|
||||
});
|
||||
me.items.push(me.hourField);
|
||||
|
||||
me.colon = Ext.create('Ext.draw.Text', {
|
||||
text: ':',
|
||||
padding: '3 3 0 3'
|
||||
});
|
||||
me.items.push(me.colon);
|
||||
|
||||
me.minuteField = Ext.create('Ext.form.field.Text', {
|
||||
maxWidth: 30,
|
||||
allowBlank: false,
|
||||
allowOnlyWhitespace: false,
|
||||
blankText: "Minutes cannot be blank.",
|
||||
regex: /[012345]\d/,
|
||||
maxLength: 2,
|
||||
enforceMaxLength: 2,
|
||||
submitValue:false,
|
||||
flex:1,
|
||||
isFormField:false, //exclude from field query's
|
||||
});
|
||||
me.items.push(me.minuteField);
|
||||
|
||||
|
||||
me.ampmField = Ext.create('Ext.form.ComboBox', {
|
||||
maxWidth: 45,
|
||||
value: 'PM',
|
||||
store: ['AM', 'PM'],
|
||||
forceSelection: true,
|
||||
flex:1,
|
||||
editable: false
|
||||
});
|
||||
|
||||
me.items.push(me.ampmField);
|
||||
|
||||
for (; i < me.items.length; i++) {
|
||||
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
|
||||
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
|
||||
me.items[i].on('specialkey', function(field, event){
|
||||
key = event.getKey();
|
||||
tab = key == event.TAB;
|
||||
|
||||
if (tab && me.focussedItem == me.dateField) {
|
||||
event.stopEvent();
|
||||
me.timeField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
me.fireEvent('specialkey', field, event);
|
||||
});
|
||||
}
|
||||
|
||||
me.callParent();
|
||||
|
||||
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
|
||||
this.inputEl = {
|
||||
dom: document.createElement('div'),
|
||||
swallowEvent:function(){}
|
||||
};
|
||||
|
||||
me.initField();
|
||||
},
|
||||
|
||||
focus:function(){
|
||||
this.callParent(arguments);
|
||||
this.dateField.focus();
|
||||
},
|
||||
|
||||
onItemFocus:function(item){
|
||||
if (this.blurTask){
|
||||
this.blurTask.cancel();
|
||||
}
|
||||
this.focussedItem = item;
|
||||
},
|
||||
|
||||
onItemBlur:function(item, e){
|
||||
var me = this;
|
||||
if (item != me.focussedItem){ return; }
|
||||
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
|
||||
me.blurTask = new Ext.util.DelayedTask(function(){
|
||||
me.fireEvent('blur', me, e);
|
||||
});
|
||||
me.blurTask.delay(100);
|
||||
},
|
||||
|
||||
getValue: function(){
|
||||
var value = null
|
||||
,date = this.dateField.getSubmitValue()
|
||||
,time = null
|
||||
,format;
|
||||
|
||||
hour = this.hourField.getValue();
|
||||
minute = this.minuteField.getValue();
|
||||
ampm = this.ampmField.getValue();
|
||||
time = Ext.Date.parse(hour + " " + minute + " " + ampm, 'g i A');
|
||||
time = Ext.Date.format(time, this.timeSubmitFormat);
|
||||
|
||||
if (date){
|
||||
if (time){
|
||||
format = this.getFormat();
|
||||
value = Ext.Date.parse(date + ' ' + time, format);
|
||||
} else {
|
||||
value = this.dateField.getValue();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
getSubmitValue: function(){
|
||||
// var value = this.getValue();
|
||||
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
|
||||
|
||||
var me = this
|
||||
,format = me.getFormat()
|
||||
,value = me.getValue();
|
||||
|
||||
return value ? Ext.Date.format(value, format) : null;
|
||||
},
|
||||
|
||||
setValue: function(value){
|
||||
if (Ext.isString(value) || Ext.isDate(value)){
|
||||
|
||||
if( Ext.isDate(value)){
|
||||
dt = value;
|
||||
}else{
|
||||
dt = new Date(value);
|
||||
value = Ext.Date.parse(value, this.getFormat()); //this.dateTimeFormat
|
||||
}
|
||||
this.dateField.setValue(value);
|
||||
|
||||
hour = Ext.Date.format(dt, 'g');
|
||||
minute = Ext.Date.format(dt, 'i');
|
||||
ampm = Ext.Date.format(dt, 'A');
|
||||
|
||||
this.hourField.setValue(hour);
|
||||
this.minuteField.setRawValue(minute);
|
||||
this.ampmField.setValue(ampm);
|
||||
}
|
||||
},
|
||||
|
||||
getFormat: function(){
|
||||
value = (this.dateField.submitFormat || this.dateField.format) + " " + (this.timeSubmitFormat || this.timeFormat);
|
||||
return value;
|
||||
},
|
||||
|
||||
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
|
||||
getSubmitData: function(){
|
||||
var me = this
|
||||
,data = null;
|
||||
|
||||
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
|
||||
data = {};
|
||||
data[me.getName()] = '' + me.getSubmitValue();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
Ext.define('Ext.ux.form.field.ColorCombo', {
|
||||
extend:'Ext.form.FieldContainer',
|
||||
mixins:{
|
||||
field:'Ext.form.field.Field'
|
||||
},
|
||||
alias: 'widget.xcolorcombo',
|
||||
|
||||
//configurables
|
||||
combineErrors: true,
|
||||
msgTarget: 'under',
|
||||
layout: 'hbox',
|
||||
readOnly: false,
|
||||
|
||||
// properties
|
||||
colorValue: null,
|
||||
/**
|
||||
* @property dateField
|
||||
* @type Ext.form.field.Date
|
||||
*/
|
||||
colorField: null,
|
||||
|
||||
initComponent: function(){
|
||||
var me = this
|
||||
,i = 0
|
||||
,key
|
||||
,tab;
|
||||
|
||||
me.items = me.items || [];
|
||||
|
||||
me.colorField = Ext.create('Ext.form.field.Trigger', {
|
||||
flex:1,
|
||||
isFormField:false, //exclude from field query's
|
||||
submitValue:false,
|
||||
readOnly: me.readOnly,
|
||||
onTriggerClick: function() {
|
||||
//show renders, so put first
|
||||
me.picker.show();
|
||||
me.picker.alignTo(me.colorField.inputEl);
|
||||
}
|
||||
});
|
||||
me.items.push(me.colorField);
|
||||
|
||||
me.picker = Ext.create('Ext.picker.Color', {
|
||||
renderTo: document.body,
|
||||
floating: true,
|
||||
hidden: true,
|
||||
style: {
|
||||
backgroundColor: "#fff"
|
||||
},
|
||||
listeners: {
|
||||
scope:this,
|
||||
select: function(field, value, opts){
|
||||
me.setValue(value);
|
||||
me.picker.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
me.items.push(me.picker);
|
||||
|
||||
for (; i < me.items.length; i++) {
|
||||
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
|
||||
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
|
||||
me.items[i].on('specialkey', function(field, event){
|
||||
key = event.getKey();
|
||||
tab = key == event.TAB;
|
||||
|
||||
if (tab && me.focussedItem == me.dateField) {
|
||||
event.stopEvent();
|
||||
me.timeField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
me.fireEvent('specialkey', field, event);
|
||||
});
|
||||
}
|
||||
|
||||
me.callParent();
|
||||
|
||||
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
|
||||
this.inputEl = {
|
||||
dom: document.createElement('div'),
|
||||
swallowEvent:function(){}
|
||||
};
|
||||
|
||||
me.initField();
|
||||
},
|
||||
focus:function(){
|
||||
this.callParent(arguments);
|
||||
this.colorField.focus();
|
||||
var me = this;
|
||||
},
|
||||
|
||||
onItemFocus:function(item){
|
||||
if (this.blurTask){
|
||||
this.blurTask.cancel();
|
||||
}
|
||||
this.focussedItem = item;
|
||||
},
|
||||
|
||||
onItemBlur:function(item, e){
|
||||
var me = this;
|
||||
if (item != me.focussedItem){ return; }
|
||||
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
|
||||
me.blurTask = new Ext.util.DelayedTask(function(){
|
||||
me.picker.hide();
|
||||
me.fireEvent('blur', me, e);
|
||||
});
|
||||
me.blurTask.delay(100);
|
||||
},
|
||||
|
||||
getValue: function(){
|
||||
var value = null
|
||||
,color = this.colorField.getSubmitValue();
|
||||
|
||||
if (color){
|
||||
value = this.colorField.getValue();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
getSubmitValue: function(){
|
||||
// var value = this.getValue();
|
||||
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
|
||||
|
||||
var me = this
|
||||
,value = me.getValue();
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
setValue: function(value){
|
||||
this.colorField.setValue(value);
|
||||
},
|
||||
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
|
||||
getSubmitData: function(){
|
||||
var me = this
|
||||
,data = null;
|
||||
|
||||
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
|
||||
data = {};
|
||||
data[me.getName()] = '' + me.getSubmitValue();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
$("#index_logout").click(function(){
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
url: $("#index_logout").data("url"),
|
||||
complete: function(){
|
||||
window.location.href="/";
|
||||
}
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -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(", "));
|
||||
});
|
||||
}
|
||||
}
|
13
app/assets/stylesheets/application.css
Normal file
13
app/assets/stylesheets/application.css
Normal 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 .
|
||||
*/
|
@ -1,3 +0,0 @@
|
||||
@import "bootstrap";
|
||||
@import "bootstrap-datetimepicker";
|
||||
@import "frontend";
|
17
app/assets/stylesheets/bootstrap_and_overrides.css.less
vendored
Normal file
17
app/assets/stylesheets/bootstrap_and_overrides.css.less
vendored
Normal 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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -19,7 +19,11 @@ class AppTabPanel < Netzke::Basepack::TabPanel
|
||||
|
||||
#all users
|
||||
# (had to use hash for borders to get the title to display properly)
|
||||
@@app_tab_panel_items = [ :bikes_border ]
|
||||
@@app_tab_panel_items = [ :bikes_border,
|
||||
{ layout: :fit,
|
||||
wrappedComponent: :brands_and_models_border,
|
||||
title: "Brands/Models"}
|
||||
]
|
||||
|
||||
#for users only
|
||||
if not controller.current_user.role?(:admin)
|
||||
|
@ -19,6 +19,6 @@
|
||||
});
|
||||
},
|
||||
onChangeAccountInfo: function(){
|
||||
window.location.href="/users/edit";
|
||||
window.location.href="users/edit";
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ class BikeBrands < Netzke::Basepack::Grid
|
||||
super
|
||||
c.model = "BikeBrand"
|
||||
c.title = "Brands"
|
||||
c.force_fit = true
|
||||
|
||||
c.prohibit_update = true if cannot? :update, BikeBrand
|
||||
c.prohibit_create = true if cannot? :create, BikeBrand
|
||||
|
@ -6,7 +6,6 @@ class BikeLogs < Netzke::Basepack::Grid
|
||||
|
||||
c.model = "ActsAsLoggable::Log"
|
||||
c.title = "Bike History"
|
||||
c.force_fit = true
|
||||
c.data_store = {auto_load: false}
|
||||
c.scope = lambda { |rel| rel.where(:loggable_type => 'Bike',:loggable_id => session[:selected_bike_id]);}
|
||||
c.strong_default_attrs = {
|
||||
@ -19,9 +18,12 @@ class BikeLogs < Netzke::Basepack::Grid
|
||||
|
||||
c.columns = [
|
||||
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
|
||||
{ :name => :end_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
|
||||
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
|
||||
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
|
||||
:description,
|
||||
{ :name => :bike_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::BikeAction.first.id},
|
||||
{ :name => :bike_action__action, :text => 'Action'},
|
||||
{ :name => :created_at, :read_only => true},
|
||||
{ :name => :updated_at, :read_only => true},
|
||||
{ :name => :logged_by, :getter => lambda{ |rec|
|
||||
user = User.find_by_id(rec.logger_id)
|
||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||
@ -46,9 +48,10 @@ class BikeLogs < Netzke::Basepack::Grid
|
||||
def default_fields_for_forms
|
||||
[
|
||||
{ :name => :start_date},
|
||||
{ :name => :end_date},
|
||||
{ :name => :description},
|
||||
#had to hack acts_as_loggable/log.rb to get this to work
|
||||
{ :name => :bike_action__action, :field_label => 'Action', :min_chars => 1 }
|
||||
{ :name => :bike_action__action, :field_label => 'Action'}
|
||||
]
|
||||
end
|
||||
|
||||
|
10
app/components/bike_logs_and_tasks_accordian.rb
Normal file
10
app/components/bike_logs_and_tasks_accordian.rb
Normal file
@ -0,0 +1,10 @@
|
||||
class BikeLogsAndTasksAccordian < Netzke::Basepack::Accordion
|
||||
component :bike_logs
|
||||
component :tasks
|
||||
|
||||
def configure(c)
|
||||
c.prevent_header = true
|
||||
c.items = [ :bike_logs, :tasks ]
|
||||
super
|
||||
end
|
||||
end
|
@ -1,12 +0,0 @@
|
||||
class BikeLowerTabs < Netzke::Basepack::TabPanel
|
||||
component :bike_logs
|
||||
component :tasks
|
||||
component :brands_and_models_border
|
||||
|
||||
def configure(c)
|
||||
c.prevent_header = true
|
||||
c.items = [ :bike_logs, :tasks,
|
||||
{netzke_component: :brands_and_models_border, title: "Brands and Models"} ]
|
||||
super
|
||||
end
|
||||
end
|
@ -4,7 +4,6 @@ class BikeModels < Netzke::Basepack::Grid
|
||||
|
||||
c.model = "BikeModel"
|
||||
c.title = "Models"
|
||||
c.force_fit = true
|
||||
c.data_store = {auto_load: false}
|
||||
c.scope = lambda { |rel| rel.where(:bike_brand_id => session[:selected_bike_brand_id]);}
|
||||
c.strong_default_attrs = {
|
||||
|
@ -2,15 +2,11 @@ class Bikes < Netzke::Basepack::Grid
|
||||
def configure(c)
|
||||
super
|
||||
c.model = "Bike"
|
||||
c.force_fit = true
|
||||
|
||||
# columns with :id set, have :min_chars set in init_component
|
||||
# See: http://stackoverflow.com/questions/17738962/netzke-grid-filtering
|
||||
c.columns = [
|
||||
{ :name => :shop_id, :text => 'Shop ID', :default_value => Bike.last.id.to_i + 1},
|
||||
{ :name => :shop_id, :text => 'Shop ID'},
|
||||
:serial_number,
|
||||
{ :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'},
|
||||
{ :name => :model, :text => 'Model',
|
||||
{ :name => :bike_brand__brand, :text => 'Brand' },
|
||||
{ :name => :bike_model__model, :text => 'Model',
|
||||
:scope => lambda { |rel|
|
||||
if session[:selected_bike_brand_id]
|
||||
rel.where(:bike_brand_id => session[:selected_bike_brand_id])
|
||||
@ -20,25 +16,20 @@ class Bikes < Netzke::Basepack::Grid
|
||||
}
|
||||
},
|
||||
#needs to have type :action or else won't work in grid, because... netzke
|
||||
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block,
|
||||
:default_value => '000000'},
|
||||
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style', :default_value => BikeStyle.first.id},
|
||||
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block},
|
||||
{ :name => :bike_style__style, :text => 'Style' },
|
||||
{ :name => :seat_tube_height, :text => 'Seat Tube (in)'},
|
||||
{ :name => :top_tube_length, :text => 'Top Tube (in)'},
|
||||
{ :name => :bike_wheel_size__display_string, :text => 'Wheel Size'},
|
||||
{ :name => :wheel_size, :text => 'Wheel Size (in)'},
|
||||
:value,
|
||||
{ :id => :bike_condition__condition, :name => :bike_condition__condition, :text => 'Condition', :default_value => BikeCondition.first.id},
|
||||
{ :id => :bike_purpose__purpose, :name => :bike_purpose__purpose, :text => 'Purpose', :default_value => BikePurpose.first.id},
|
||||
{ :name => :bike_condition__condition, :text => 'Condition'},
|
||||
{ :name => :bike_status__status, :text => 'Status'},
|
||||
{ :name => :owner, :getter => lambda { |rec|
|
||||
user = rec.owner
|
||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# Default the sorting to ASC on shop_id
|
||||
c.data_store.sorters = [{ property: 'shop_id', direction: 'ASC' }]
|
||||
|
||||
@bike = Bike.all
|
||||
c.prohibit_update = true if cannot? :update, @bike
|
||||
c.prohibit_create = true if cannot? :create, @bike
|
||||
@ -47,18 +38,18 @@ class Bikes < Netzke::Basepack::Grid
|
||||
def default_fields_for_forms
|
||||
# :field_label MUST be defined in order for search to work
|
||||
[
|
||||
{ :name => :bike_brand__brand, :field_label => 'Brand', :min_chars => 1 },
|
||||
{ :name => :model, :field_label => 'Model'},
|
||||
{ :name => :shop_id, :field_label => 'Shop ID'},
|
||||
{ :name => :serial_number, :field_label => 'Serial Number'},
|
||||
{ :name => :bike_brand__brand, :field_label => 'Brand' },
|
||||
{ :name => :bike_model__model, :field_label => 'Model'},
|
||||
{ :name => "color", :xtype => "xcolorcombo"},
|
||||
{ :name => :bike_style__style, :field_label => 'Style', :min_chars => 1},
|
||||
{ :name => :bike_style__style, :field_label => 'Style' },
|
||||
{ :name => :seat_tube_height, :field_label => 'Seat Tube (in)'},
|
||||
{ :name => :top_tube_length, :field_label => 'Top Tube (in)'},
|
||||
{ :name => :bike_wheel_size__display_string, :field_label => 'Wheel Size'},
|
||||
{ :name => :wheel_size, :field_label => 'Wheel Size (in)'},
|
||||
{ :name => :value, :field_label => 'Value'},
|
||||
{ :name => :bike_condition__condition, :field_label => 'Condition', :min_chars => 1},
|
||||
{ :name => :bike_purpose__purpose, :field_label => 'Purpose', :min_chars => 1}
|
||||
{ :name => :bike_condition__condition, :field_label => 'Condition'},
|
||||
{ :name => :bike_status__status, :field_label => 'Status'}
|
||||
]
|
||||
end
|
||||
#override with nil to remove actions
|
||||
|
@ -3,16 +3,6 @@
|
||||
// calling superclass's initComponent
|
||||
this.callParent();
|
||||
|
||||
//due to Netzke bug, :min_chars attribute doesn't work
|
||||
var min_char_columns = [
|
||||
"bike_brand__brand",
|
||||
"bike_style__style",
|
||||
"bike_condition__condition",
|
||||
"bike_purpose__purpose"]
|
||||
Ext.each(min_char_columns, function(column, index) {
|
||||
Ext.ComponentManager.get(column).editor.minChars = 1;
|
||||
});
|
||||
|
||||
// setting the 'rowclick' event
|
||||
var view = this.getView();
|
||||
view.on('itemclick', function(view, record){
|
||||
|
@ -2,7 +2,7 @@ class BikesBorder < Netzke::Base
|
||||
# Remember regions collapse state and size
|
||||
include Netzke::Basepack::ItemPersistence
|
||||
component :bikes
|
||||
component :bike_lower_tabs
|
||||
component :bike_logs_and_tasks_accordian
|
||||
|
||||
def configure(c)
|
||||
super
|
||||
@ -10,7 +10,7 @@ class BikesBorder < Netzke::Base
|
||||
c.title = "Bikes"
|
||||
c.items = [
|
||||
{ netzke_component: :bikes, region: :center, split: true },
|
||||
{ netzke_component: :bike_lower_tabs, region: :south, height: 300, split: true}
|
||||
{ netzke_component: :bike_logs_and_tasks_accordian, region: :south, height: 300, split: true}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -5,30 +5,14 @@
|
||||
|
||||
// setting the 'rowclick' event
|
||||
var view = this.getComponent('bikes').getView();
|
||||
|
||||
//disable until a bike is clicked
|
||||
var bike_logs_comp = this.queryById('bike_logs');
|
||||
if( bike_logs_comp){
|
||||
bike_logs_comp.disable();
|
||||
}
|
||||
var bike_tasks_comp = this.queryById('tasks');
|
||||
if( bike_tasks_comp ){
|
||||
bike_tasks_comp.disable();
|
||||
}
|
||||
|
||||
view.on('itemclick', function(view, record){
|
||||
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
|
||||
this.selectBike({bike_id: record.get('id')});
|
||||
// query for these components again, can change if not visible because of accordian
|
||||
var bike_logs_comp = this.queryById('bike_logs');
|
||||
var bike_tasks_comp = this.queryById('tasks');
|
||||
if( bike_logs_comp ){
|
||||
bike_logs_comp.getStore().load();
|
||||
bike_logs_comp.enable();
|
||||
if( this.queryById('bike_logs') ){
|
||||
this.queryById('bike_logs').getStore().load();
|
||||
}
|
||||
if( bike_tasks_comp ){
|
||||
bike_tasks_comp.getStore().load();
|
||||
bike_tasks_comp.enable();
|
||||
if( this.queryById('tasks') ){
|
||||
this.queryById('tasks').getStore().load();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ class CheckIns < Netzke::Basepack::Grid
|
||||
super
|
||||
c.header = false
|
||||
c.model = "ActsAsLoggable::Log"
|
||||
c.force_fit = true
|
||||
c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction).
|
||||
where(:loggable_type => "User").
|
||||
where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")).
|
||||
|
@ -1,7 +1,6 @@
|
||||
class Customers < Netzke::Basepack::Grid
|
||||
def configure(c)
|
||||
c.model = "Customer"
|
||||
c.force_fit = true
|
||||
end
|
||||
|
||||
#override with nil to remove actions
|
||||
|
@ -4,6 +4,7 @@
|
||||
this.callParent();
|
||||
this.getView().on('itemclick', function(view, record){
|
||||
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
|
||||
console.log("user: " + record.get('id') );
|
||||
this.selectCustomer({customer_id: record.get('id'), customer_type: 'Customer'});
|
||||
}, this);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ class Logs < Netzke::Basepack::Grid
|
||||
super
|
||||
c.header = false
|
||||
c.model = "ActsAsLoggable::Log"
|
||||
c.force_fit = true
|
||||
c.columns = [
|
||||
:loggable_id,
|
||||
:loggable_type,
|
||||
|
@ -9,7 +9,6 @@ class Tasks < Netzke::Basepack::Grid
|
||||
|
||||
c.header = false
|
||||
c.model = "Task"
|
||||
c.force_fit = true
|
||||
c.scope = lambda{ |rel|
|
||||
if session[:selected_bike_id]
|
||||
rel.where(:task_list_id => Bike.find_by_id(session[:selected_bike_id]).task_list.id)
|
||||
@ -22,21 +21,20 @@ class Tasks < Netzke::Basepack::Grid
|
||||
:task_list_id => task_list_id
|
||||
}
|
||||
c.columns = [
|
||||
:done,
|
||||
:task,
|
||||
:notes
|
||||
:notes,
|
||||
:done
|
||||
]
|
||||
end
|
||||
|
||||
def default_fields_for_forms
|
||||
bike = Bike.find_by_id(session[:selected_bike_id])
|
||||
bike = "Select a Bike First!" if bike.nil?
|
||||
[
|
||||
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Bike Selected", :value => "#{bike.to_s}"},
|
||||
:done,
|
||||
fields = []
|
||||
fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
|
||||
fields.concat( [
|
||||
:task,
|
||||
:notes,
|
||||
]
|
||||
:done
|
||||
])
|
||||
end
|
||||
|
||||
#override with nil to remove actions
|
||||
|
@ -5,7 +5,6 @@ class TransactionLogs < Netzke::Basepack::Grid
|
||||
|
||||
c.model = "ActsAsLoggable::Log"
|
||||
c.title = "Transaction Payments"
|
||||
c.force_fit = true
|
||||
c.data_store = {auto_load: false}
|
||||
c.scope = lambda { |rel| rel.where(:loggable_type => 'Transaction',:loggable_id => session[:selected_transaction_id]);}
|
||||
c.strong_default_attrs = {
|
||||
@ -21,7 +20,7 @@ class TransactionLogs < Netzke::Basepack::Grid
|
||||
c.columns = [
|
||||
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db), :text => 'Date' },
|
||||
{ :name => :description, :text => "Amount"} ,
|
||||
{ :name => :transaction_action__action, :text => 'Method', :default_value => ::ActsAsLoggable::TransactionAction.first.id},
|
||||
{ :name => :transaction_action__action, :text => 'Method'},
|
||||
{ :name => :logged_by, :getter => lambda{ |rec|
|
||||
user = User.find_by_id(rec.logger_id)
|
||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||
@ -50,7 +49,7 @@ class TransactionLogs < Netzke::Basepack::Grid
|
||||
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"},
|
||||
{ :name => :description, :xtype => 'numberfield', :field_label => 'Amount'},
|
||||
#had to hack acts_as_loggable/log.rb to get this to work
|
||||
{ :name => :transaction_action__action, :field_label => 'Payment Method', :min_chars => 1}
|
||||
{ :name => :transaction_action__action, :field_label => 'Payment Method'}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -2,7 +2,6 @@ class Transactions < Netzke::Basepack::Grid
|
||||
def configure(c)
|
||||
super
|
||||
c.model = "Transaction"
|
||||
c.force_fit = true
|
||||
c.strong_default_attrs = {
|
||||
:vendor_id => controller.current_user.id,
|
||||
:customer_id => session[:selected_customer_id],
|
||||
|
@ -4,7 +4,7 @@ class TransactionsBorder < Netzke::Base
|
||||
component :transactions
|
||||
component :transaction_logs
|
||||
#users and customers components are required for the transactions form
|
||||
component :users_and_customers_lower_tabs
|
||||
component :users_and_customers_accordian
|
||||
|
||||
def configure(c)
|
||||
super
|
||||
@ -13,7 +13,7 @@ class TransactionsBorder < Netzke::Base
|
||||
c.items = [
|
||||
{ netzke_component: :transactions, region: :center, height: 300, split: true },
|
||||
{ netzke_component: :transaction_logs, region: :east, width: 300, split: true },
|
||||
{ netzke_component: :users_and_customers_lower_tabs, region: :south, height: 300, split: true }
|
||||
{ netzke_component: :users_and_customers_accordian, region: :south, height: 300, split: true }
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -5,19 +5,10 @@
|
||||
|
||||
// setting the 'rowclick' event
|
||||
var view = this.getComponent('transactions').getView();
|
||||
|
||||
if( this.queryById('transaction_logs')){
|
||||
this.queryById('transaction_logs').disable();
|
||||
}
|
||||
|
||||
view.on('itemclick', function(view, record){
|
||||
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
|
||||
this.selectTransaction({transaction_id: record.get('id')});
|
||||
this.getComponent('transaction_logs').getStore().load();
|
||||
|
||||
if( this.queryById('transaction_logs')){
|
||||
this.queryById('transaction_logs').enable();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
class UserAndProfilesLowerTabs < Netzke::Basepack::TabPanel
|
||||
component :user_profiles
|
||||
component :user_logs
|
||||
component :user_stats
|
||||
|
||||
def configure(c)
|
||||
c.prevent_header = true
|
||||
c.items = [ :user_logs,
|
||||
{ netzke_component: :user_profiles, title: "User Profiles" },
|
||||
{ netzke_component: :user_stats, title: "User Stats" }]
|
||||
super
|
||||
end
|
||||
end
|
@ -34,17 +34,18 @@ class UserLogs < Netzke::Basepack::Grid
|
||||
|
||||
c.model = "ActsAsLoggable::Log"
|
||||
c.title = "User Timesheet"
|
||||
c.force_fit = true
|
||||
c.data_store = user_log_data_store
|
||||
c.scope = user_log_scope
|
||||
c.strong_default_attrs = user_log_strong_default_attrs
|
||||
|
||||
c.columns = [
|
||||
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
|
||||
{ :name => :end_date, :format => "g:ia - D, M j - Y", :default_value => Time.now.to_formatted_s(:db) },
|
||||
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
|
||||
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
|
||||
:description,
|
||||
{ :id => :user_action__action, :name => :user_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::UserAction.all.first.id },
|
||||
{ :name => :user_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::UserAction.all.first.id },
|
||||
:created_at,
|
||||
:updated_at,
|
||||
{ :name => :logged_by, :getter => lambda{ |rec|
|
||||
user = User.find_by_id(rec.logger_id)
|
||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||
@ -64,7 +65,7 @@ class UserLogs < Netzke::Basepack::Grid
|
||||
{ :name => :end_date},
|
||||
{ :name => :description},
|
||||
#had to hack acts_as_loggable/log.rb to get this to work
|
||||
{ :name => :user_action__action, :field_label => 'Action', :min_chars => 1},
|
||||
{ :name => :user_action__action, :field_label => 'Action'},
|
||||
{ :name => :for_bike, :checkboxName => :copy_log, :inputValue => true, :title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [
|
||||
{:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id}
|
||||
]
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
initComponent: function(){
|
||||
// calling superclass's initComponent
|
||||
this.callParent();
|
||||
|
||||
//due to Netzke bug, :min_chars attribute doesn't work
|
||||
var min_char_columns = [
|
||||
"user_action__purpose"]
|
||||
Ext.each(min_char_columns, function(column, index) {
|
||||
Ext.ComponentManager.get(column).editor.minChars = 1;
|
||||
});
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ class UserProfiles < Netzke::Basepack::Grid
|
||||
|
||||
c.model = "UserProfile"
|
||||
c.title = "Profile"
|
||||
c.force_fit = true
|
||||
c.data_store = user_profiles_data_store
|
||||
c.scope = user_profiles_scope
|
||||
c.strong_default_attrs = user_profile_strong_default_attrs
|
||||
|
@ -2,18 +2,16 @@ class UserRoleJoins < Netzke::Basepack::Grid
|
||||
def configure(c)
|
||||
super
|
||||
c.model = "UserRoleJoin"
|
||||
c.force_fit = true
|
||||
c.header = false
|
||||
c.title = "User Roles"
|
||||
c.data_store.sorters = [{ :property => :user__username, :direction => :ASC}]
|
||||
c.columns = [
|
||||
{ :name => :user__username, :text => "Username", :read_only => true},
|
||||
{ :name => :user__username, :text => "Username"},
|
||||
{ :name => :name, :getter => lambda{ |rec|
|
||||
user = User.find_by_id(rec.user_id)
|
||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||
},
|
||||
}
|
||||
},
|
||||
{ :name => :role__role, :text => "Role", :default_value => Role.exists? ? Role.first.id : nil},
|
||||
{ :name => :role__role, :text => "Role"},
|
||||
:created_at,
|
||||
:updated_at,
|
||||
:ends ]
|
||||
@ -21,7 +19,7 @@ class UserRoleJoins < Netzke::Basepack::Grid
|
||||
|
||||
def default_fields_for_forms
|
||||
[
|
||||
{ :name => :user__full_name, :field_label => 'Name'},
|
||||
{ :name => :user__username, :field_label => 'Username' },
|
||||
{ :name => :role__role, :field_label => 'Role' },
|
||||
{ :name => :ends }
|
||||
]
|
||||
|
@ -40,7 +40,7 @@ class UserStats < Netzke::Base
|
||||
|
||||
private
|
||||
def user
|
||||
User.find_by_id(session[:selected_user_id]) || controller.current_user
|
||||
controller.current_user
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -5,7 +5,6 @@ class UserTransactions < Netzke::Basepack::Grid
|
||||
|
||||
c.model = "Transaction"
|
||||
c.title = "Transactions"
|
||||
c.force_fit = true
|
||||
c.scope = lambda { |rel| rel.where(:customer_id => controller.current_user.id, :customer_type => 'User');}
|
||||
c.data_store = { auto_load: true }
|
||||
c.columns = [
|
||||
|
@ -5,18 +5,10 @@
|
||||
|
||||
// setting the 'rowclick' event
|
||||
var view = this.getComponent('user_transactions').getView();
|
||||
|
||||
if( this.queryById('transaction_logs')){
|
||||
this.queryById('transaction_logs').disable();
|
||||
}
|
||||
view.on('itemclick', function(view, record){
|
||||
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
|
||||
this.selectTransaction({transaction_id: record.get('id')});
|
||||
this.getComponent('transaction_logs').getStore().load();
|
||||
|
||||
if( this.queryById('transaction_logs')){
|
||||
this.queryById('transaction_logs').enable();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
@ -12,14 +12,13 @@ class Users < Netzke::Basepack::Grid
|
||||
super
|
||||
c.header = false
|
||||
c.model = "User"
|
||||
c.force_fit = true
|
||||
|
||||
c.columns = [
|
||||
{ :name => :username, :read_only => true },
|
||||
:username,
|
||||
:first_name,
|
||||
:last_name,
|
||||
:email,
|
||||
{ :text => "Bike - Shop ID", :id => :bike__shop_id, :name => :bike__shop_id, :default_value => Bike.exists? ? Bike.first.id : nil }
|
||||
:bike__shop_id
|
||||
]
|
||||
|
||||
c.columns << :reset if can? :manage, User
|
||||
|
@ -2,13 +2,6 @@
|
||||
initComponent: function(){
|
||||
// calling superclass's initComponent
|
||||
this.callParent();
|
||||
|
||||
//due to Netzke bug, :min_chars attribute doesn't work
|
||||
var min_char_columns = ["bike__shop_id"]
|
||||
Ext.each(min_char_columns, function(column, index) {
|
||||
Ext.ComponentManager.get(column).editor.minChars = 1;
|
||||
});
|
||||
|
||||
this.getView().on('itemclick', function(view, record){
|
||||
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
|
||||
this.selectCustomer({customer_id: record.get('id'), customer_type: 'User'});
|
||||
@ -33,7 +26,7 @@
|
||||
Ext.Msg.alert("Success", "New Password: "+data.password);
|
||||
},
|
||||
error: function(data,textStatus) {
|
||||
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["errors"][0]);
|
||||
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
class UsersAndCustomersLowerTabs < Netzke::Basepack::TabPanel
|
||||
class UsersAndCustomersAccordian < Netzke::Basepack::Accordion
|
||||
component :customers
|
||||
component :users
|
||||
|
||||
def configure(c)
|
||||
c.prevent_header = true
|
||||
c.items = [ :users, :customers ]
|
||||
c.items = [ :customers, :users ]
|
||||
super
|
||||
end
|
||||
end
|
@ -2,14 +2,16 @@ class UsersAndProfilesBorder < Netzke::Base
|
||||
# Remember regions collapse state and size
|
||||
include Netzke::Basepack::ItemPersistence
|
||||
component :users
|
||||
component :user_and_profiles_lower_tabs
|
||||
component :user_profiles
|
||||
component :user_logs
|
||||
|
||||
def configure(c)
|
||||
super
|
||||
c.header = false
|
||||
c.items = [
|
||||
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
|
||||
{ netzke_component: :user_and_profiles_lower_tabs, region: :south, height: 300, split: true}
|
||||
{ netzke_component: :user_profiles, region: :south, height: 150, split: true},
|
||||
{ netzke_component: :user_logs, region: :east, split: true}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -2,48 +2,18 @@
|
||||
initComponent: function(){
|
||||
// calling superclass's initComponent
|
||||
this.callParent();
|
||||
var stats = this.queryById('user_stats');
|
||||
var stats = this.getComponent('user_stats');
|
||||
if (stats != undefined){
|
||||
stats.updateStats();
|
||||
}
|
||||
|
||||
if( this.queryById('user_profiles')){
|
||||
this.queryById('user_profiles').disable();
|
||||
}
|
||||
|
||||
if( this.queryById('user_logs')){
|
||||
this.queryById('user_logs').disable();
|
||||
//update user stats
|
||||
var store = this.queryById('user_logs').getStore()
|
||||
store.on('load', function (store, records, operation, success){
|
||||
if( this.queryById('user_stats') ){
|
||||
this.queryById('user_stats').updateStats();
|
||||
}
|
||||
}, this);
|
||||
|
||||
}
|
||||
if( this.queryById('user_stats') ){
|
||||
this.queryById('user_stats').disable();
|
||||
}
|
||||
|
||||
// setting the 'rowclick' event
|
||||
var view = this.getComponent('users').getView();
|
||||
view.on('itemclick', function(view, record){
|
||||
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
|
||||
this.selectUser({user_id: record.get('id')});
|
||||
|
||||
if( this.queryById('user_profiles')){
|
||||
this.queryById('user_profiles').getStore().load();
|
||||
this.queryById('user_profiles').enable();
|
||||
}
|
||||
if( this.queryById('user_logs')){
|
||||
this.queryById('user_logs').getStore().load();
|
||||
this.queryById('user_logs').enable();
|
||||
}
|
||||
if( this.queryById('user_stats') ){
|
||||
this.queryById('user_stats').updateStats();
|
||||
this.queryById('user_stats').enable();
|
||||
}
|
||||
this.getComponent('user_profiles').getStore().load();
|
||||
this.getComponent('user_logs').getStore().load();
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ class Api::V1::BaseController < ActionController::Base
|
||||
private
|
||||
def authenticate_user
|
||||
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] )
|
||||
|
||||
if @current_user.nil?
|
||||
msg = "Username/Password/Token invalid"
|
||||
render :json => {:error => msg }, :status => 401 and return
|
||||
render :json => {:error => msg }, :status => 403 and return
|
||||
end
|
||||
else
|
||||
authenticate_user!
|
||||
|
@ -1,32 +0,0 @@
|
||||
class Api::V1::BikesController < Api::V1::BaseController
|
||||
CANNOT_MANAGE = "You do not have permission to manage bikes."
|
||||
EXPECTED_BIKE = "Expected bike in submitted data"
|
||||
NOT_FOUND = "The bike could not be found."
|
||||
|
||||
before_filter :check_bike_permission, except: :show
|
||||
|
||||
def create
|
||||
if params[:bikes] && bike = params[:bikes].first
|
||||
@bike = Bike.new(bike)
|
||||
if !@bike.save
|
||||
render json: { errors: @bike.errors }, status: 422 and return
|
||||
end
|
||||
else
|
||||
render json: { errors: [EXPECTED_BIKE]}, status: 422 and return
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@bike = Bike.find_by_id(params[:id])
|
||||
if @bike.nil?
|
||||
render json: { errors: [NOT_FOUND] }, status: 404 and return
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def check_bike_permission
|
||||
if cannot? :manage, Bike
|
||||
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
|
||||
end
|
||||
end
|
||||
end
|
@ -1,24 +0,0 @@
|
||||
class Api::V1::TaskListsController < Api::V1::BaseController
|
||||
CANNOT_MANAGE = "You do not have permission to manage this task list."
|
||||
NOT_FOUND = "The task list could not be found."
|
||||
|
||||
before_filter :get_task_list
|
||||
before_filter :check_task_list_permission, except: :show
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
private
|
||||
def get_task_list
|
||||
@task_list = TaskList.find_by_id(params[:id])
|
||||
if @task_list.nil?
|
||||
render json: { errors: [NOT_FOUND] }, status: 404 and return
|
||||
end
|
||||
end
|
||||
|
||||
def check_task_list_permission
|
||||
if cannot? :manage, Bike and @task_list.item != current_user.bike
|
||||
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
|
||||
end
|
||||
end
|
||||
end
|
@ -1,64 +0,0 @@
|
||||
class Api::V1::TasksController < Api::V1::BaseController
|
||||
EXPECTED_TASKS = "Expected a list of tasks in submitted data."
|
||||
CANNOT_MANAGE = "You do not have permission to manage this task."
|
||||
NOT_FOUND = "The task could not be found."
|
||||
|
||||
before_filter :validate_params
|
||||
before_filter :get_tasks
|
||||
before_filter :check_task_permission, except: :show
|
||||
|
||||
def update
|
||||
errors = []
|
||||
@tasks.each do |task_hash|
|
||||
task = task_hash[:record]
|
||||
attrs = task_hash[:new_attributes]
|
||||
task.update_attributes(attrs)
|
||||
if !task.errors.empty?
|
||||
errors << { id: task.id, errors: task.errors }
|
||||
end
|
||||
end
|
||||
|
||||
if !errors.empty?
|
||||
render json: { errors: errors }, status: 422 and return
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def validate_params
|
||||
if params[:tasks].nil? and not params[:tasks].kind_of?(Array)
|
||||
render json: { errors: [EXPECTED_TASKS]}, status: 422 and return
|
||||
end
|
||||
end
|
||||
|
||||
def get_tasks
|
||||
@tasks = []
|
||||
errors = []
|
||||
|
||||
params[:tasks].each do |task|
|
||||
t = Task.find_by_id(task[:id])
|
||||
if t.nil?
|
||||
errors << { id: task[:id], error: NOT_FOUND }
|
||||
else
|
||||
@tasks << { record: t, new_attributes: task }
|
||||
end
|
||||
end
|
||||
|
||||
if !errors.empty?
|
||||
render json: { errors: errors }, status: 404 and return
|
||||
end
|
||||
end
|
||||
|
||||
def check_task_permission
|
||||
errors = []
|
||||
@tasks.each do |task_hash|
|
||||
task = task_hash[:record]
|
||||
if task.task_list.item != current_user.bike
|
||||
errors << { id: task[:id], error: CANNOT_MANAGE }
|
||||
end
|
||||
end
|
||||
|
||||
if cannot? :manage, Bike and !errors.empty?
|
||||
render json: { errors: errors}, status: 403 and return
|
||||
end
|
||||
end
|
||||
end
|
@ -1,33 +0,0 @@
|
||||
class Api::V1::TimeEntriesController < Api::V1::BaseController
|
||||
EXPECTED_TIME_ENTRY = "Expected time entry in submitted data"
|
||||
NOT_FOUND = "Time entry not found"
|
||||
|
||||
def create
|
||||
if params[:time_entries] && time_entry = params[:time_entries].first
|
||||
time_entry.merge!({ loggable_id: current_user.id,
|
||||
logger_id: current_user.id })
|
||||
bike_id = time_entry[:bike_id].to_i
|
||||
|
||||
@time_entry = TimeEntry.new(time_entry.except(:bike_id))
|
||||
|
||||
if bike_id > 0
|
||||
@time_entry.copy_to_bike_history(bike_id)
|
||||
end
|
||||
|
||||
if !@time_entry.save
|
||||
render json: { errors: @time_entry.errors }, status: 422 and return
|
||||
end
|
||||
else
|
||||
render json: { errors: [EXPECTED_TIME_ENTRY]}, status: 422 and return
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
if time_entry = TimeEntry.find_by_id(params[:id])
|
||||
time_entry.delete
|
||||
render nothing: true, status: 204 and return
|
||||
else
|
||||
render json: { errors: [NOT_FOUND]}, status: 404 and return
|
||||
end
|
||||
end
|
||||
end
|
@ -1,27 +1,21 @@
|
||||
require 'securerandom'
|
||||
class Api::V1::UsersController < Api::V1::BaseController
|
||||
CANNOT_MANAGE = "You do not have the permission to manager users"
|
||||
NOT_FOUND = "User not found"
|
||||
NOT_ALLOWED = "Not allowed to reset your own password in this fashion"
|
||||
PASS_LENGTH = 8
|
||||
|
||||
def password_reset
|
||||
if can? :manage, User
|
||||
user = User.find_by_id(params[:user_id])
|
||||
render :json => { "errors" => [NOT_FOUND]}, :status => 404 and return if user.nil?
|
||||
render :json => { "errors" => [NOT_ALLOWED]}, :status => 403 and return if user.id == current_user.id
|
||||
render :json => { "error" => "User not found"}, :status => 404 and return if user.nil?
|
||||
render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_user.id
|
||||
|
||||
new_pass = SecureRandom.hex[0,PASS_LENGTH]
|
||||
new_pass = SecureRandom.hex[0,8]
|
||||
user.password = new_pass
|
||||
user.save
|
||||
render :json => { "password" => new_pass}, :status => 200 and return
|
||||
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
|
||||
|
||||
|
||||
=begin Is this here by accident? Commenting out for now (1/30/14)
|
||||
def checkout
|
||||
#must use @current_user since user may not have signed in
|
||||
if !@current_user.checked_in?
|
||||
@ -31,5 +25,4 @@ class Api::V1::UsersController < Api::V1::BaseController
|
||||
render :nothing => true, :status => 204 and return
|
||||
end
|
||||
end
|
||||
=end
|
||||
end
|
||||
|
@ -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
|
@ -1,7 +0,0 @@
|
||||
class PanelController < ApplicationController
|
||||
|
||||
def index
|
||||
render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke"
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +1,7 @@
|
||||
class SiteController < ApplicationController
|
||||
|
||||
def index
|
||||
@bike = current_user.bike
|
||||
render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application"
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,5 +0,0 @@
|
||||
class TaskListsController < AuthenticatedController
|
||||
def edit
|
||||
@task_list = TaskList.find_by_id(params[:id])
|
||||
end
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
class TimeEntriesController < AuthenticatedController
|
||||
|
||||
def new
|
||||
@bikes = Bike.all.map{ |b| [b.to_s , b.id] }
|
||||
if bike = current_user.bike
|
||||
@bikes.unshift( [bike.to_s, bike.id] )
|
||||
end
|
||||
@bikes.unshift( ["Non-bike work", -1] )
|
||||
end
|
||||
|
||||
def index
|
||||
@user_time_entries = TimeEntry.where(loggable_id: current_user.id)
|
||||
@credits_available = current_user.total_credits
|
||||
@hours_worked = current_user.total_hours
|
||||
end
|
||||
end
|
34
app/controllers/users_controller.rb
Normal file
34
app/controllers/users_controller.rb
Normal 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
|
@ -7,28 +7,4 @@ module ApplicationHelper
|
||||
end
|
||||
link_to(name, '#', id: "add_#{association.to_s.singularize}" , class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
|
||||
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
|
||||
|
@ -1,18 +1,21 @@
|
||||
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!
|
||||
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",
|
||||
:count => resource.errors.count,
|
||||
:resource => resource.class.model_name.human.downcase)
|
||||
|
||||
html = <<-HTML
|
||||
<div class="alert alert-danger">#{sentence}
|
||||
<ul>
|
||||
#{messages.join}
|
||||
</ul>
|
||||
</div>
|
||||
<p>#{sentence}</p>
|
||||
#{messages}
|
||||
HTML
|
||||
|
||||
html.html_safe
|
||||
|
@ -23,7 +23,7 @@ class Ability
|
||||
|
||||
def user
|
||||
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 => "User", :loggable_id => @current_user.id
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
class Bike < ActiveRecord::Base
|
||||
acts_as_loggable
|
||||
attr_accessible :shop_id, :serial_number, :bike_brand_id, :bike_model_id, :model, :color, :bike_style_id,
|
||||
:seat_tube_height, :top_tube_length, :bike_wheel_size_id, :value, :bike_condition_id, :bike_purpose_id, :photo
|
||||
attr_accessible :shop_id, :serial_number, :bike_brand_id, :bike_model_id, :color, :bike_style_id, :seat_tube_height,
|
||||
:top_tube_length, :wheel_size, :value, :bike_condition_id, :bike_status_id
|
||||
|
||||
has_many :transactions
|
||||
|
||||
@ -11,33 +11,33 @@ class Bike < ActiveRecord::Base
|
||||
belongs_to :bike_model
|
||||
belongs_to :bike_style
|
||||
belongs_to :bike_condition
|
||||
belongs_to :bike_purpose
|
||||
belongs_to :bike_wheel_size
|
||||
belongs_to :bike_status
|
||||
|
||||
has_attached_file :photo, :styles => {:thumb => '100x100>'}
|
||||
|
||||
validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
|
||||
validates :serial_number, :length => { :minimum => 3 }
|
||||
validates :model, :length => { :maximum => 50 }
|
||||
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" }
|
||||
#validates :color, :presence => true
|
||||
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" }
|
||||
validates :seat_tube_height, :presence => true, :numericality => true
|
||||
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" }
|
||||
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" }
|
||||
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" }
|
||||
|
||||
validates_attachment :photo, :content_type => {:content_type => %w{ image/jpeg image/gif image/png }},
|
||||
:file_name => {:matches => [/png\Z/, /jpe?g\Z/, /gif\Z/]}
|
||||
validates :shop_id, :presence => true, :uniqueness => true, :length => { :minimum => 3 }
|
||||
validates :serial_number, :uniqueness => true, :length => { :minimum => 3 }
|
||||
validates :bike_brand_id, :presence => true
|
||||
validates :bike_model_id, :presence => true
|
||||
validates :color, :presence => true
|
||||
validates :bike_style_id, :presence => true
|
||||
validates :seat_tube_height, :presence => true
|
||||
validates :top_tube_length, :presence => true
|
||||
validates :wheel_size, :presence => true
|
||||
#validates :value, :presence => true
|
||||
validates :bike_condition_id, :presence => true
|
||||
validates :bike_status_id, :presence => true
|
||||
|
||||
self.per_page = 15
|
||||
|
||||
after_create :create_task_list
|
||||
after_save :create_task_list
|
||||
|
||||
def brand
|
||||
self.bike_brand
|
||||
end
|
||||
|
||||
def model
|
||||
self.bike_model
|
||||
end
|
||||
|
||||
def style
|
||||
self.bike_style
|
||||
end
|
||||
@ -46,12 +46,8 @@ class Bike < ActiveRecord::Base
|
||||
self.bike_condition
|
||||
end
|
||||
|
||||
def purpose
|
||||
self.bike_purpose
|
||||
end
|
||||
|
||||
def wheel_size
|
||||
self.bike_wheel_size
|
||||
def status
|
||||
self.bike_status
|
||||
end
|
||||
|
||||
def to_s
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -1,6 +1,7 @@
|
||||
class BikeModel < ActiveRecord::Base
|
||||
attr_accessible :model, :bike_brand_id
|
||||
|
||||
has_many :bikes
|
||||
belongs_to :bike_brand
|
||||
|
||||
default_scope order('model ASC')
|
||||
|
@ -1,9 +0,0 @@
|
||||
class BikePurpose < ActiveRecord::Base
|
||||
attr_accessible :purpose
|
||||
|
||||
belongs_to :bike
|
||||
|
||||
def to_s
|
||||
self.purpose
|
||||
end
|
||||
end
|
9
app/models/bike_status.rb
Normal file
9
app/models/bike_status.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class BikeStatus < ActiveRecord::Base
|
||||
attr_accessible :status
|
||||
|
||||
belongs_to :bike
|
||||
|
||||
def to_s
|
||||
self.status
|
||||
end
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
class BikeWheelSize < ActiveRecord::Base
|
||||
belongs_to :bike
|
||||
|
||||
def display_string
|
||||
result = []
|
||||
result << "#{twmm}-#{rdmm}" unless twmm.blank? and rdmm.blank?
|
||||
result << "#{rdin}x#{twin}" unless rdin.blank? and twin.blank?
|
||||
result << "#{rdfr}x#{twfr}" unless rdfr.blank? and twfr.blank?
|
||||
result << description unless description.blank?
|
||||
result.join(" | ")
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{twmm}-#{rdmm} #{rdin}x#{twin} #{rdfr}x#{twfr} '#{description}' #{tire_common_score}"
|
||||
end
|
||||
end
|
@ -1,5 +1,5 @@
|
||||
class Task < ActiveRecord::Base
|
||||
attr_accessible :task, :notes, :done, :task_list_id
|
||||
attr_accessible :task, :notes, :done
|
||||
|
||||
belongs_to :task_list
|
||||
|
||||
|
@ -4,7 +4,7 @@ class TaskList < ActiveRecord::Base
|
||||
attr_accessible :item_id, :item_type, :name
|
||||
|
||||
belongs_to :item, :polymorphic => true
|
||||
has_many :tasks, order: "id ASC"
|
||||
has_many :tasks
|
||||
|
||||
after_save :create_default_bike_tasks
|
||||
|
||||
|
@ -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
|
@ -1,6 +1,4 @@
|
||||
class User < ActiveRecord::Base
|
||||
MAX_AVATAR_SIZE_KB = 1024
|
||||
|
||||
acts_as_loggable
|
||||
# Include default devise modules. Others available are:
|
||||
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
|
||||
@ -10,7 +8,7 @@ class User < ActiveRecord::Base
|
||||
# Setup accessible (or protected) attributes for your model
|
||||
attr_accessible :email, :password, :password_confirmation, :remember_me,
|
||||
:first_name, :last_name, :bike_id,
|
||||
:user_profiles_attributes, :username, :avatar
|
||||
:user_profiles_attributes, :username
|
||||
|
||||
has_many :transactions, as: :customer
|
||||
has_many :transaction_logs, through: :transactions, source: :logs
|
||||
@ -22,19 +20,9 @@ class User < ActiveRecord::Base
|
||||
|
||||
belongs_to :bike
|
||||
|
||||
has_attached_file :avatar, :styles => {:thumb => '100x100>'}
|
||||
|
||||
default_scope order('username ASC')
|
||||
|
||||
validates :username, :presence => true, uniqueness: true
|
||||
validates :first_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
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
@ -54,16 +42,10 @@ class User < ActiveRecord::Base
|
||||
roles.include?(role)
|
||||
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
|
||||
|
||||
def completed_build_bikes
|
||||
#default BUILDBIKE/CLASS ID is 5
|
||||
purpose_id = 5
|
||||
status_id = BikeStatus.find_by_status("BUILDBIKE").id
|
||||
Bike.find_by_sql("
|
||||
SELECT *
|
||||
FROM bikes
|
||||
@ -72,7 +54,7 @@ class User < ActiveRecord::Base
|
||||
FROM transactions
|
||||
WHERE customer_id = #{self.id}
|
||||
) AS transactions ON bikes.id = transactions.bike_id
|
||||
WHERE bike_purpose_id = #{purpose_id}")
|
||||
WHERE bike_status_id = #{status_id}")
|
||||
end
|
||||
|
||||
def total_credits
|
||||
@ -80,16 +62,15 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def total_credits_spent
|
||||
log_action_id = 1 #TIME
|
||||
log_action = ::ActsAsLoggable::TransactionAction.find_by_action("TIME")
|
||||
transaction_logs.
|
||||
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)
|
||||
end
|
||||
|
||||
def total_earned_credits
|
||||
volunteer_id = 1
|
||||
staff_id = 3
|
||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
||||
|
||||
# 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
|
||||
@ -98,7 +79,7 @@ class User < ActiveRecord::Base
|
||||
#
|
||||
# The DISTINCT ON, and ORDER BY are important to getting the
|
||||
# single conversion rate that applies to the respective log.
|
||||
::ActsAsLoggable::Log.find_by_sql(["
|
||||
::ActsAsLoggable::Log.find_by_sql("
|
||||
SELECT DISTINCT ON (logs.created_at) start_date, end_date,
|
||||
conversion.conversion, conversion.created_at
|
||||
FROM logs
|
||||
@ -106,26 +87,23 @@ class User < ActiveRecord::Base
|
||||
SELECT conversion, created_at
|
||||
FROM credit_conversions
|
||||
) 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 (log_action_id IN (:credit_actions) AND log_action_type = :log_action_type)
|
||||
ORDER BY logs.created_at, conversion.created_at DESC",
|
||||
{id: self.id,
|
||||
credit_actions: [volunteer_id, staff_id],
|
||||
log_action_type: ::ActsAsLoggable::UserAction.to_s}]).
|
||||
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").
|
||||
sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2)
|
||||
end
|
||||
|
||||
def total_hours
|
||||
log_action_id = 4 #CHECKIN
|
||||
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
|
||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
||||
logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
|
||||
end
|
||||
|
||||
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
|
||||
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( :end_date => current_month_range)
|
||||
.sum { |l| (l.end_date - l.start_date)/3600 }
|
||||
@ -133,31 +111,28 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def checked_in?
|
||||
#default CHECKIN log action is id, yea yea should be a constant
|
||||
log_action_id = 4
|
||||
checked = logs.where( log_action_id: log_action_id).
|
||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
||||
checked = logs.where( log_action_id: log_action.id).
|
||||
where("start_date >= ?", Time.zone.now.beginning_of_day).
|
||||
where("start_date = end_date")
|
||||
!checked.empty?
|
||||
end
|
||||
|
||||
def checkin
|
||||
#default CHECKIN log action is id, yea yea should be a constant
|
||||
log_action_id = 4
|
||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
||||
time = Time.now
|
||||
logs.create( logger_id: self.id,
|
||||
logger_type: self.class.to_s,
|
||||
start_date: time,
|
||||
end_date: time,
|
||||
log_action_id: log_action_id,
|
||||
log_action_type: ::ActsAsLoggable::UserAction.to_s)
|
||||
log_action_id: log_action.id,
|
||||
log_action_type: log_action.class.to_s)
|
||||
save
|
||||
end
|
||||
|
||||
def checkout
|
||||
#default CHECKIN log action is id, yea yea should be a constant
|
||||
log_action_id = 4
|
||||
checked = logs.where( log_action_id: log_action_id).
|
||||
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
|
||||
checked = logs.where( log_action_id: log_action.id).
|
||||
where("start_date >= ?", Time.zone.now.beginning_of_day).
|
||||
where("start_date = end_date").first
|
||||
checked.end_date = Time.now
|
||||
|
@ -1,4 +0,0 @@
|
||||
json.bikes [@bike] do |bike|
|
||||
json.array! bike
|
||||
json.set! :href, api_bike_path(bike)
|
||||
end
|
@ -1,3 +0,0 @@
|
||||
json.bikes [@bike] do |bike|
|
||||
json.array! bike
|
||||
end
|
@ -1,15 +0,0 @@
|
||||
json.task_lists [@task_list] do |tl|
|
||||
json.array! tl
|
||||
json.links do
|
||||
json.bike do
|
||||
json.href api_bike_path(tl.item)
|
||||
json.id tl.item_id
|
||||
end
|
||||
json.tasks tl.tasks do |task|
|
||||
json.id task.id
|
||||
json.done task.done
|
||||
json.notes task.notes
|
||||
json.task task.task
|
||||
end
|
||||
end
|
||||
end
|
@ -1,3 +0,0 @@
|
||||
json.tasks [@tasks.map{|x| x[:record]}] do |task|
|
||||
json.array! task
|
||||
end
|
@ -1,4 +0,0 @@
|
||||
json.time_entries [@time_entry] do |time_entry|
|
||||
json.array! time_entry
|
||||
#json.set! :href, api_time_entry_path(time_entry)
|
||||
end
|
@ -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'
|
@ -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}
|
25
app/views/devise/_links.erb
Normal file
25
app/views/devise/_links.erb
Normal 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 -%>
|
@ -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(' · ').html_safe
|
12
app/views/devise/confirmations/new.html.erb
Normal file
12
app/views/devise/confirmations/new.html.erb
Normal 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" %>
|
16
app/views/devise/passwords/edit.html.erb
Normal file
16
app/views/devise/passwords/edit.html.erb
Normal 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" %>
|
@ -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'
|
12
app/views/devise/passwords/new.html.erb
Normal file
12
app/views/devise/passwords/new.html.erb
Normal 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" %>
|
@ -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'
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user