mirror of
https://github.com/fspc/BikeShed-1.git
synced 2026-09-17 00:41:38 -04:00
Compare commits
263
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc6802e5fb | ||
|
|
e48924a228 | ||
|
|
1887840507 | ||
|
|
0629972dca | ||
|
|
6daff093eb | ||
|
|
43faa79f60 | ||
|
|
9e282eda7c | ||
|
|
02fba62499 | ||
|
|
4fe8865aca | ||
|
|
fd5d4798b0 | ||
|
|
3e480981c7 | ||
|
|
523135ed9f | ||
|
|
5e95ce542c | ||
|
|
a8e0c87d37 | ||
|
|
1b2c0fa378 | ||
|
|
785e096aea | ||
|
|
5c4007a1be | ||
|
|
b9b6ed5ca0 | ||
|
|
0605d0f0d9 | ||
|
|
071815b9fd | ||
|
|
ca3026e7c5 | ||
|
|
5e0d678ea1 | ||
|
|
d94c436ba5 | ||
|
|
24facd4dee | ||
|
|
894423e66f | ||
|
|
4ce4bd6597 | ||
|
|
2e21e327ca | ||
|
|
57d72c04a7 | ||
|
|
4224f0de40 | ||
|
|
3d71add637 | ||
|
|
7dd556d718 | ||
|
|
338905878e | ||
|
|
7897795e8b | ||
|
|
00511c5b64 | ||
|
|
1191014471 | ||
|
|
c0a9d8c1e5 | ||
|
|
82f38fe35c | ||
|
|
2321197973 | ||
|
|
069a11f480 | ||
|
|
b30203b750 | ||
|
|
3e56f2eb19 | ||
|
|
2d65ebb45f | ||
|
|
80b2b7ff60 | ||
|
|
98b136e4f4 | ||
|
|
9527725e07 | ||
|
|
8f637348cf | ||
|
|
062ed9c2d1 | ||
|
|
124ce5901d | ||
|
|
1f9322ff3d | ||
|
|
b1ddcfb867 | ||
|
|
64e907b6f7 | ||
|
|
e2f442889e | ||
|
|
3b904f95f2 | ||
|
|
656f0f12e4 | ||
|
|
30d818ac87 | ||
|
|
dc0a567a27 | ||
|
|
523046b042 | ||
|
|
9231c91e6c | ||
|
|
14869cf8b6 | ||
|
|
b88bf545d2 | ||
|
|
20964eb268 | ||
|
|
b90e101d69 | ||
|
|
7b11da9583 | ||
|
|
64d4186b57 | ||
|
|
c6a3ed9991 | ||
|
|
d0c05a0244 | ||
|
|
55974feb4c | ||
|
|
e867366525 | ||
|
|
fb02646092 | ||
|
|
75832a3910 | ||
|
|
2d09aba26a | ||
|
|
e694dfbddd | ||
|
|
e19b80faec | ||
|
|
38a716b132 | ||
|
|
8110baf255 | ||
|
|
9fa0017d36 | ||
|
|
e0041662d3 | ||
|
|
c575fface9 | ||
|
|
83a4e4e9c1 | ||
|
|
f17eba810e | ||
|
|
4b5133fd02 | ||
|
|
9edb90203c | ||
|
|
8e112ca936 | ||
|
|
8c5ff986a2 | ||
|
|
f10b2adad6 | ||
|
|
bd6872413e | ||
|
|
a8bdb52536 | ||
|
|
0f890e12e9 | ||
|
|
61472e0fb6 | ||
|
|
ecb5083f1c | ||
|
|
a84b0e0fe7 | ||
|
|
2b863b8d8a | ||
|
|
a35bd2f22b | ||
|
|
c9d2da3e50 | ||
|
|
df3c29a899 | ||
|
|
a11f5a1b91 | ||
|
|
2a6e10ee9d | ||
|
|
ed1f2e6be4 | ||
|
|
852a00881e | ||
|
|
8cc72c9f48 | ||
|
|
90b3043d67 | ||
|
|
2ad9fab243 | ||
|
|
43db139dc7 | ||
|
|
2b72094f00 | ||
|
|
1aa3b16636 | ||
|
|
af05d1a0c6 | ||
|
|
076a59ab45 | ||
|
|
1075c1a5b3 | ||
|
|
65708e743d | ||
|
|
731d0ed3da | ||
|
|
592049b0e6 | ||
|
|
c73d5f955a | ||
|
|
966c36b563 | ||
|
|
20a350adf2 | ||
|
|
bca0903862 | ||
|
|
5212011256 | ||
|
|
ea79c167eb | ||
|
|
85129dccaa | ||
|
|
eeefe25227 | ||
|
|
d0b2144b28 | ||
|
|
3ba443397e | ||
|
|
4a67ebe77e | ||
|
|
cda94cf955 | ||
|
|
1f9bd4f3a0 | ||
|
|
490c9d84b6 | ||
|
|
84ed8503d1 | ||
|
|
75f4a7980b | ||
|
|
16cda6aa86 | ||
|
|
de338276c0 | ||
|
|
25808eb27a | ||
|
|
d983647e29 | ||
|
|
cb4763c172 | ||
|
|
3d6f698fb9 | ||
|
|
f7ce96152b | ||
|
|
4c8f74acf2 | ||
|
|
c2b8a13db2 | ||
|
|
77d7fad3ce | ||
|
|
3bea39b53a | ||
|
|
4eae374aa9 | ||
|
|
dc60630d2d | ||
|
|
6e92d343a5 | ||
|
|
2ceb744925 | ||
|
|
b27d682517 | ||
|
|
ba0f84a97c | ||
|
|
9300ef83d1 | ||
|
|
4aed82cf80 | ||
|
|
83bdaf5973 | ||
|
|
d94d747373 | ||
|
|
da176e00ba | ||
|
|
f17c168d1d | ||
|
|
5e042c2577 | ||
|
|
bceac0543a | ||
|
|
3faa19bc0c | ||
|
|
bbba67d7d5 | ||
|
|
6cbe0f8b57 | ||
|
|
86ada003d6 | ||
|
|
9a78478f1a | ||
|
|
8d8058dea0 | ||
|
|
93b8e3b055 | ||
|
|
cfeb6fd1d2 | ||
|
|
ec5db646a0 | ||
|
|
de266e72d9 | ||
|
|
67c51068cc | ||
|
|
8c6513099d | ||
|
|
47f616b7b0 | ||
|
|
087325c444 | ||
|
|
3c4e5c21c2 | ||
|
|
69c01b8225 | ||
|
|
0aa9c16dbd | ||
|
|
e491c0530c | ||
|
|
3599cc2bbb | ||
|
|
cecb3b1a32 | ||
|
|
611b1b1151 | ||
|
|
5e80c8866a | ||
|
|
835e73ea97 | ||
|
|
cb2acd19ba | ||
|
|
ee12bfee8a | ||
|
|
4d008163e1 | ||
|
|
06856f115c | ||
|
|
a9fb3487a1 | ||
|
|
869b67d3e4 | ||
|
|
cd4f891c95 | ||
|
|
71a4bb0af4 | ||
|
|
f1214ab7e6 | ||
|
|
aaa6fda90c | ||
|
|
4a7751959c | ||
|
|
028a45702e | ||
|
|
1a8b9c9247 | ||
|
|
4e7f3a1761 | ||
|
|
a6b41b5560 | ||
|
|
4d7634605c | ||
|
|
cf922be1b1 | ||
|
|
d755f9971e | ||
|
|
52e67a1182 | ||
|
|
7736ac5c34 | ||
|
|
bccd5f10d7 | ||
|
|
f0ce8b7171 | ||
|
|
238a3af7d5 | ||
|
|
fee421cabe | ||
|
|
f713c6bacd | ||
|
|
ac24711289 | ||
|
|
b89873d0e5 | ||
|
|
d431024f01 | ||
|
|
ff60fdd67f | ||
|
|
03abb963ac | ||
|
|
cd41735cee | ||
|
|
0f89cf057a | ||
|
|
8172b04805 | ||
|
|
9cb4ed42dc | ||
|
|
7c85e2901f | ||
|
|
2c548ff63c | ||
|
|
1defca3668 | ||
|
|
88d3160e29 | ||
|
|
edff478204 | ||
|
|
84ca3a6f96 | ||
|
|
51caa93afa | ||
|
|
7a935c250b | ||
|
|
854c8efaa9 | ||
|
|
b450baa3dc | ||
|
|
fa51dd25f3 | ||
|
|
c9e94d8e30 | ||
|
|
c5b16139f5 | ||
|
|
6d76385d11 | ||
|
|
f6f932f63a | ||
|
|
a18e34c8f1 | ||
|
|
b25a4520f9 | ||
|
|
adeca0e08a | ||
|
|
db1732f7bf | ||
|
|
d5662284ab | ||
|
|
0ef4e81474 | ||
|
|
761ab81b56 | ||
|
|
687cb33974 | ||
|
|
7f029f2a83 | ||
|
|
c7974c52e8 | ||
|
|
8c864e2006 | ||
|
|
6d2e7686d6 | ||
|
|
9021294c4b | ||
|
|
a07d6345e8 | ||
|
|
db6b7ff7da | ||
|
|
75734a075c | ||
|
|
73bb35de88 | ||
|
|
8c5132814f | ||
|
|
2e08f655ea | ||
|
|
b67062fb32 | ||
|
|
3c90c13c3b | ||
|
|
19adc354a1 | ||
|
|
145a731ed2 | ||
|
|
a00c1ed925 | ||
|
|
14973e8334 | ||
|
|
d7755f00ed | ||
|
|
c504252541 | ||
|
|
65c8cf8cf1 | ||
|
|
81217f1ea8 | ||
|
|
bc1eeea16e | ||
|
|
f34def2c09 | ||
|
|
8f988b2cf1 | ||
|
|
1e589322f4 | ||
|
|
be1f52568a | ||
|
|
3f1093bca5 | ||
|
|
a614b28558 | ||
|
|
6f6902dd82 | ||
|
|
a083ed715b | ||
|
|
91bb2ea43e |
+2
-2
@@ -6,5 +6,5 @@ tmp/
|
|||||||
.powrc
|
.powrc
|
||||||
.rbenv-version
|
.rbenv-version
|
||||||
.DS_Store
|
.DS_Store
|
||||||
public/extjs
|
public/assets
|
||||||
public/images/icons
|
config/database.yml
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
velocipede
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ruby-2.1.1
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
|
||||||
# development environment upon cd'ing into the directory
|
|
||||||
|
|
||||||
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
|
||||||
environment_id="ruby-1.9.3-p374@velocipede"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Uncomment following line if you want options to be set only for given project.
|
|
||||||
#
|
|
||||||
# PROJECT_JRUBY_OPTS=( --1.9 )
|
|
||||||
|
|
||||||
#
|
|
||||||
# First we attempt to load the desired environment directly from the environment
|
|
||||||
# file. This is very fast and efficient compared to running through the entire
|
|
||||||
# CLI and selector. If you want feedback on which environment was used then
|
|
||||||
# insert the word 'use' after --create as this triggers verbose mode.
|
|
||||||
#
|
|
||||||
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
|
||||||
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
|
||||||
then
|
|
||||||
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
|
||||||
|
|
||||||
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
|
||||||
then
|
|
||||||
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# If the environment file has not yet been created, use the RVM CLI to select.
|
|
||||||
if ! rvm --create use "$environment_id"
|
|
||||||
then
|
|
||||||
echo "Failed to create RVM environment '${environment_id}'."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
|
||||||
# it be automatically loaded. Uncomment the following and adjust the filename if
|
|
||||||
# necessary.
|
|
||||||
#
|
|
||||||
# filename=".gems"
|
|
||||||
# if [[ -s "$filename" ]]
|
|
||||||
# then
|
|
||||||
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
|
||||||
# fi
|
|
||||||
|
|
||||||
# If you use bundler, this might be useful to you:
|
|
||||||
# if command -v bundle && [[ -s Gemfile ]]
|
|
||||||
# then
|
|
||||||
# bundle install
|
|
||||||
# fi
|
|
||||||
|
|
||||||
|
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
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 bundle install
|
||||||
|
|
||||||
|
COPY install_extjs.rb /usr/src/app/
|
||||||
|
RUN /usr/src/app/install_extjs.rb
|
||||||
|
|
||||||
|
COPY install_famfamfam.rb /usr/src/app/
|
||||||
|
RUN /usr/src/app/install_famfamfam.rb
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["rails", "server", "-b", "0.0.0.0"]
|
||||||
@@ -14,13 +14,13 @@ gem 'decent_exposure', '~> 1.0.1'
|
|||||||
gem 'devise', '~> 2.0.4'
|
gem 'devise', '~> 2.0.4'
|
||||||
gem 'haml-rails', '~> 0.3.4'
|
gem 'haml-rails', '~> 0.3.4'
|
||||||
gem 'jquery-rails', '~> 2.0'
|
gem 'jquery-rails', '~> 2.0'
|
||||||
gem 'sqlite3', '~> 1.3.5'
|
gem 'pg', '~> 0.17.1'
|
||||||
gem 'will_paginate', '~> 3.0.3'
|
gem 'will_paginate', '~> 3.0.3'
|
||||||
|
gem 'jbuilder', '~> 2.0.3'
|
||||||
|
|
||||||
# Gems used only for assets and not required
|
# Gems used only for assets and not required
|
||||||
# in production environments by default.
|
# in production environments by default.
|
||||||
group :assets do
|
group :assets do
|
||||||
gem 'sass-rails', '~> 3.2.3'
|
|
||||||
gem 'coffee-rails', '~> 3.2.1'
|
gem 'coffee-rails', '~> 3.2.1'
|
||||||
gem "twitter-bootstrap-rails", "~> 2.0.3"
|
gem "twitter-bootstrap-rails", "~> 2.0.3"
|
||||||
|
|
||||||
@@ -31,20 +31,20 @@ group :assets do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'rspec-rails', '~> 2.8.1'
|
gem 'rspec-rails', '~> 2.14.0'
|
||||||
gem 'factory_girl_rails', '~> 1.2'
|
gem 'factory_girl_rails', '~> 1.2'
|
||||||
gem 'pry', '~> 0.9.8'
|
gem 'pry', '~> 0.9.8'
|
||||||
|
gem 'faker', '~> 1.2.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'shoulda-matchers', '~> 1.0.0'
|
gem 'shoulda-matchers', '~> 1.0.0'
|
||||||
gem 'capybara', '~> 1.1.2'
|
gem 'capybara', '~> 2.2.1'
|
||||||
gem 'turnip', '~> 0.3.0'
|
gem 'poltergeist', '~> 1.5.0'
|
||||||
gem 'database_cleaner'
|
gem 'database_cleaner', '~> 1.2.0'
|
||||||
gem 'launchy'
|
gem 'launchy', '~> 2.4.2'
|
||||||
gem 'spork'
|
gem 'spork', '~> 0.9.2'
|
||||||
#guard dependency for Mac OS 10
|
#guard dependency for Mac OS 10
|
||||||
gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
|
gem 'guard-spork', '~> 1.5.1'
|
||||||
gem 'guard-spork'
|
gem 'guard-rspec', '~> 4.2.6'
|
||||||
gem 'guard-rspec'
|
|
||||||
end
|
end
|
||||||
|
|||||||
+115
-99
@@ -1,6 +1,6 @@
|
|||||||
GIT
|
GIT
|
||||||
remote: https://github.com/spacemunkay/acts_as_loggable.git
|
remote: https://github.com/spacemunkay/acts_as_loggable.git
|
||||||
revision: 6033fe5a44878bb374f73fe473fed9a3396ddc74
|
revision: 8484dc1f7a58699705c5ffb593ebdf700a9f374a
|
||||||
specs:
|
specs:
|
||||||
acts_as_loggable (0.0.7)
|
acts_as_loggable (0.0.7)
|
||||||
activerecord (>= 3.0)
|
activerecord (>= 3.0)
|
||||||
@@ -36,60 +36,65 @@ GEM
|
|||||||
activesupport (3.2.13)
|
activesupport (3.2.13)
|
||||||
i18n (= 0.6.1)
|
i18n (= 0.6.1)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
addressable (2.3.3)
|
addressable (2.4.0)
|
||||||
arel (3.0.2)
|
arel (3.0.3)
|
||||||
bcrypt-ruby (3.0.1)
|
bcrypt (3.1.10)
|
||||||
bootstrap-will_paginate (0.0.9)
|
bcrypt-ruby (3.1.5)
|
||||||
|
bcrypt (>= 3.1.3)
|
||||||
|
bootstrap-will_paginate (0.0.10)
|
||||||
will_paginate
|
will_paginate
|
||||||
builder (3.0.4)
|
builder (3.0.4)
|
||||||
cancan (1.6.9)
|
cancan (1.6.10)
|
||||||
capybara (1.1.4)
|
capybara (2.2.1)
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rack-test (>= 0.5.4)
|
rack-test (>= 0.5.4)
|
||||||
selenium-webdriver (~> 2.0)
|
xpath (~> 2.0)
|
||||||
xpath (~> 0.1.4)
|
childprocess (0.5.9)
|
||||||
childprocess (0.3.9)
|
|
||||||
ffi (~> 1.0, >= 1.0.11)
|
ffi (~> 1.0, >= 1.0.11)
|
||||||
coderay (1.0.9)
|
cliver (0.3.2)
|
||||||
|
coderay (1.1.1)
|
||||||
coffee-rails (3.2.2)
|
coffee-rails (3.2.2)
|
||||||
coffee-script (>= 2.2.0)
|
coffee-script (>= 2.2.0)
|
||||||
railties (~> 3.2.0)
|
railties (~> 3.2.0)
|
||||||
coffee-script (2.2.0)
|
coffee-script (2.4.1)
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.6.2)
|
coffee-script-source (1.10.0)
|
||||||
commonjs (0.2.6)
|
commonjs (0.2.7)
|
||||||
database_cleaner (0.9.1)
|
database_cleaner (1.2.0)
|
||||||
decent_exposure (1.0.2)
|
decent_exposure (1.0.2)
|
||||||
devise (2.0.5)
|
devise (2.0.6)
|
||||||
bcrypt-ruby (~> 3.0)
|
bcrypt-ruby (~> 3.0)
|
||||||
orm_adapter (~> 0.0.3)
|
orm_adapter (~> 0.0.3)
|
||||||
railties (~> 3.1)
|
railties (~> 3.1)
|
||||||
warden (~> 1.1.1)
|
warden (~> 1.1.1)
|
||||||
diff-lcs (1.1.3)
|
diff-lcs (1.2.5)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
execjs (1.4.0)
|
execjs (2.6.0)
|
||||||
multi_json (~> 1.0)
|
|
||||||
factory_girl (2.6.4)
|
factory_girl (2.6.4)
|
||||||
activesupport (>= 2.3.9)
|
activesupport (>= 2.3.9)
|
||||||
factory_girl_rails (1.7.0)
|
factory_girl_rails (1.7.0)
|
||||||
factory_girl (~> 2.6.0)
|
factory_girl (~> 2.6.0)
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
ffi (1.6.0)
|
faker (1.2.0)
|
||||||
formatador (0.2.4)
|
i18n (~> 0.5)
|
||||||
gherkin (2.11.6)
|
ffi (1.9.10)
|
||||||
json (>= 1.7.6)
|
formatador (0.2.5)
|
||||||
guard (1.7.0)
|
guard (2.13.0)
|
||||||
formatador (>= 0.2.4)
|
formatador (>= 0.2.4)
|
||||||
listen (>= 0.6.0)
|
listen (>= 2.7, <= 4.0)
|
||||||
lumberjack (>= 1.0.2)
|
lumberjack (~> 1.0)
|
||||||
pry (>= 0.9.10)
|
nenv (~> 0.1)
|
||||||
thor (>= 0.14.6)
|
notiffany (~> 0.0)
|
||||||
guard-rspec (1.2.1)
|
pry (>= 0.9.12)
|
||||||
guard (>= 1.1)
|
shellany (~> 0.0)
|
||||||
guard-spork (1.5.0)
|
thor (>= 0.18.1)
|
||||||
|
guard-rspec (4.2.10)
|
||||||
|
guard (~> 2.1)
|
||||||
|
rspec (>= 2.14, < 4.0)
|
||||||
|
guard-spork (1.5.1)
|
||||||
childprocess (>= 0.2.3)
|
childprocess (>= 0.2.3)
|
||||||
guard (>= 1.1)
|
guard (>= 1.1)
|
||||||
spork (>= 0.8.4)
|
spork (>= 0.8.4)
|
||||||
@@ -99,14 +104,17 @@ GEM
|
|||||||
activesupport (>= 3.1, < 4.1)
|
activesupport (>= 3.1, < 4.1)
|
||||||
haml (~> 3.1)
|
haml (~> 3.1)
|
||||||
railties (>= 3.1, < 4.1)
|
railties (>= 3.1, < 4.1)
|
||||||
hike (1.2.1)
|
hike (1.2.3)
|
||||||
i18n (0.6.1)
|
i18n (0.6.1)
|
||||||
|
jbuilder (2.0.8)
|
||||||
|
activesupport (>= 3.0.0, < 5)
|
||||||
|
multi_json (~> 1.2)
|
||||||
journey (1.0.4)
|
journey (1.0.4)
|
||||||
jquery-rails (2.2.1)
|
jquery-rails (2.3.0)
|
||||||
railties (>= 3.0, < 5.0)
|
railties (>= 3.0, < 5.0)
|
||||||
thor (>= 0.14, < 2.0)
|
thor (>= 0.14, < 2.0)
|
||||||
json (1.7.7)
|
json (1.8.3)
|
||||||
launchy (2.2.0)
|
launchy (2.4.3)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
less (2.2.2)
|
less (2.2.2)
|
||||||
commonjs (~> 0.2.6)
|
commonjs (~> 0.2.6)
|
||||||
@@ -114,36 +122,49 @@ GEM
|
|||||||
actionpack (>= 3.1)
|
actionpack (>= 3.1)
|
||||||
less (~> 2.2.0)
|
less (~> 2.2.0)
|
||||||
libv8 (3.3.10.4)
|
libv8 (3.3.10.4)
|
||||||
listen (0.7.3)
|
listen (3.0.6)
|
||||||
lumberjack (1.0.3)
|
rb-fsevent (>= 0.9.3)
|
||||||
mail (2.5.3)
|
rb-inotify (>= 0.9.7)
|
||||||
i18n (>= 0.4.0)
|
lumberjack (1.0.10)
|
||||||
|
mail (2.5.4)
|
||||||
mime-types (~> 1.16)
|
mime-types (~> 1.16)
|
||||||
treetop (~> 1.4.8)
|
treetop (~> 1.4.8)
|
||||||
method_source (0.8.1)
|
method_source (0.8.2)
|
||||||
mime-types (1.22)
|
mime-types (1.25.1)
|
||||||
multi_json (1.7.2)
|
mini_portile2 (2.0.0)
|
||||||
netzke-basepack (0.8.2)
|
multi_json (1.11.2)
|
||||||
|
nenv (0.3.0)
|
||||||
|
netzke-basepack (0.8.4)
|
||||||
netzke-core (~> 0.8.2)
|
netzke-core (~> 0.8.2)
|
||||||
netzke-cancan (0.8.2)
|
netzke-cancan (0.8.2)
|
||||||
cancan
|
cancan
|
||||||
netzke-core
|
netzke-core
|
||||||
netzke-core (0.8.3)
|
netzke-core (0.8.4)
|
||||||
execjs
|
execjs
|
||||||
uglifier
|
uglifier
|
||||||
nokogiri (1.5.9)
|
nokogiri (1.6.7.2)
|
||||||
|
mini_portile2 (~> 2.0.0.rc2)
|
||||||
|
notiffany (0.0.8)
|
||||||
|
nenv (~> 0.1)
|
||||||
|
shellany (~> 0.0)
|
||||||
orm_adapter (0.0.7)
|
orm_adapter (0.0.7)
|
||||||
polyglot (0.3.3)
|
pg (0.17.1)
|
||||||
pry (0.9.12)
|
poltergeist (1.5.1)
|
||||||
coderay (~> 1.0.5)
|
capybara (~> 2.1)
|
||||||
|
cliver (~> 0.3.1)
|
||||||
|
multi_json (~> 1.0)
|
||||||
|
websocket-driver (>= 0.2.0)
|
||||||
|
polyglot (0.3.5)
|
||||||
|
pry (0.9.12.6)
|
||||||
|
coderay (~> 1.0)
|
||||||
method_source (~> 0.8)
|
method_source (~> 0.8)
|
||||||
slop (~> 3.4)
|
slop (~> 3.4)
|
||||||
rack (1.4.5)
|
rack (1.4.7)
|
||||||
rack-cache (1.2)
|
rack-cache (1.6.1)
|
||||||
rack (>= 0.4)
|
rack (>= 0.4)
|
||||||
rack-ssl (1.3.3)
|
rack-ssl (1.3.4)
|
||||||
rack
|
rack
|
||||||
rack-test (0.6.2)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rails (3.2.13)
|
rails (3.2.13)
|
||||||
actionmailer (= 3.2.13)
|
actionmailer (= 3.2.13)
|
||||||
@@ -160,66 +181,60 @@ GEM
|
|||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
rdoc (~> 3.4)
|
rdoc (~> 3.4)
|
||||||
thor (>= 0.14.6, < 2.0)
|
thor (>= 0.14.6, < 2.0)
|
||||||
rake (10.0.4)
|
rake (10.5.0)
|
||||||
|
rb-fsevent (0.9.7)
|
||||||
|
rb-inotify (0.9.7)
|
||||||
|
ffi (>= 0.5.0)
|
||||||
rdoc (3.12.2)
|
rdoc (3.12.2)
|
||||||
json (~> 1.4)
|
json (~> 1.4)
|
||||||
rspec (2.8.0)
|
rspec (2.14.1)
|
||||||
rspec-core (~> 2.8.0)
|
rspec-core (~> 2.14.0)
|
||||||
rspec-expectations (~> 2.8.0)
|
rspec-expectations (~> 2.14.0)
|
||||||
rspec-mocks (~> 2.8.0)
|
rspec-mocks (~> 2.14.0)
|
||||||
rspec-core (2.8.0)
|
rspec-core (2.14.8)
|
||||||
rspec-expectations (2.8.0)
|
rspec-expectations (2.14.5)
|
||||||
diff-lcs (~> 1.1.2)
|
diff-lcs (>= 1.1.3, < 2.0)
|
||||||
rspec-mocks (2.8.0)
|
rspec-mocks (2.14.6)
|
||||||
rspec-rails (2.8.1)
|
rspec-rails (2.14.2)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
|
activemodel (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
railties (>= 3.0)
|
railties (>= 3.0)
|
||||||
rspec (~> 2.8.0)
|
rspec-core (~> 2.14.0)
|
||||||
rubyzip (0.9.9)
|
rspec-expectations (~> 2.14.0)
|
||||||
sass (3.2.7)
|
rspec-mocks (~> 2.14.0)
|
||||||
sass-rails (3.2.6)
|
shellany (0.0.1)
|
||||||
railties (~> 3.2.0)
|
|
||||||
sass (>= 3.1.10)
|
|
||||||
tilt (~> 1.3)
|
|
||||||
selenium-webdriver (2.31.0)
|
|
||||||
childprocess (>= 0.2.5)
|
|
||||||
multi_json (~> 1.0)
|
|
||||||
rubyzip
|
|
||||||
websocket (~> 1.0.4)
|
|
||||||
shoulda-matchers (1.0.0)
|
shoulda-matchers (1.0.0)
|
||||||
slop (3.4.4)
|
slop (3.6.0)
|
||||||
spork (0.9.2)
|
spork (0.9.2)
|
||||||
sprockets (2.2.2)
|
sprockets (2.2.3)
|
||||||
hike (~> 1.2)
|
hike (~> 1.2)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
rack (~> 1.0)
|
rack (~> 1.0)
|
||||||
tilt (~> 1.1, != 1.3.0)
|
tilt (~> 1.1, != 1.3.0)
|
||||||
sqlite3 (1.3.7)
|
|
||||||
therubyracer (0.10.2)
|
therubyracer (0.10.2)
|
||||||
libv8 (~> 3.3.10)
|
libv8 (~> 3.3.10)
|
||||||
thor (0.18.1)
|
thor (0.19.1)
|
||||||
tilt (1.3.6)
|
tilt (1.4.1)
|
||||||
treetop (1.4.12)
|
treetop (1.4.15)
|
||||||
polyglot
|
polyglot
|
||||||
polyglot (>= 0.3.1)
|
polyglot (>= 0.3.1)
|
||||||
turnip (0.3.1)
|
|
||||||
gherkin (>= 2.5)
|
|
||||||
rspec (~> 2.0)
|
|
||||||
twitter-bootstrap-rails (2.0.9)
|
twitter-bootstrap-rails (2.0.9)
|
||||||
actionpack (>= 3.1)
|
actionpack (>= 3.1)
|
||||||
less-rails (~> 2.2.2)
|
less-rails (~> 2.2.2)
|
||||||
railties (>= 3.1)
|
railties (>= 3.1)
|
||||||
therubyracer (~> 0.10.1)
|
therubyracer (~> 0.10.1)
|
||||||
tzinfo (0.3.37)
|
tzinfo (0.3.46)
|
||||||
uglifier (1.3.0)
|
uglifier (2.7.2)
|
||||||
execjs (>= 0.3.0)
|
execjs (>= 0.3.0)
|
||||||
multi_json (~> 1.0, >= 1.0.2)
|
json (>= 1.8.0)
|
||||||
warden (1.1.1)
|
warden (1.1.1)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
websocket (1.0.7)
|
websocket-driver (0.6.3)
|
||||||
will_paginate (3.0.4)
|
websocket-extensions (>= 0.1.0)
|
||||||
xpath (0.1.4)
|
websocket-extensions (0.1.2)
|
||||||
|
will_paginate (3.0.7)
|
||||||
|
xpath (2.0.0)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.3)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
@@ -229,28 +244,29 @@ DEPENDENCIES
|
|||||||
acts_as_loggable!
|
acts_as_loggable!
|
||||||
bootstrap-will_paginate (~> 0.0.6)
|
bootstrap-will_paginate (~> 0.0.6)
|
||||||
cancan
|
cancan
|
||||||
capybara (~> 1.1.2)
|
capybara (~> 2.2.1)
|
||||||
coffee-rails (~> 3.2.1)
|
coffee-rails (~> 3.2.1)
|
||||||
database_cleaner
|
database_cleaner (~> 1.2.0)
|
||||||
decent_exposure (~> 1.0.1)
|
decent_exposure (~> 1.0.1)
|
||||||
devise (~> 2.0.4)
|
devise (~> 2.0.4)
|
||||||
factory_girl_rails (~> 1.2)
|
factory_girl_rails (~> 1.2)
|
||||||
guard-rspec
|
faker (~> 1.2.0)
|
||||||
guard-spork
|
guard-rspec (~> 4.2.6)
|
||||||
|
guard-spork (~> 1.5.1)
|
||||||
haml-rails (~> 0.3.4)
|
haml-rails (~> 0.3.4)
|
||||||
|
jbuilder (~> 2.0.3)
|
||||||
jquery-rails (~> 2.0)
|
jquery-rails (~> 2.0)
|
||||||
launchy
|
launchy (~> 2.4.2)
|
||||||
netzke-basepack (~> 0.8.0)
|
netzke-basepack (~> 0.8.0)
|
||||||
netzke-cancan
|
netzke-cancan
|
||||||
netzke-core (~> 0.8.0)
|
netzke-core (~> 0.8.0)
|
||||||
|
pg (~> 0.17.1)
|
||||||
|
poltergeist (~> 1.5.0)
|
||||||
pry (~> 0.9.8)
|
pry (~> 0.9.8)
|
||||||
rails (= 3.2.13)
|
rails (= 3.2.13)
|
||||||
rspec-rails (~> 2.8.1)
|
rspec-rails (~> 2.14.0)
|
||||||
sass-rails (~> 3.2.3)
|
|
||||||
shoulda-matchers (~> 1.0.0)
|
shoulda-matchers (~> 1.0.0)
|
||||||
spork
|
spork (~> 0.9.2)
|
||||||
sqlite3 (~> 1.3.5)
|
|
||||||
turnip (~> 0.3.0)
|
|
||||||
twitter-bootstrap-rails (~> 2.0.3)
|
twitter-bootstrap-rails (~> 2.0.3)
|
||||||
uglifier (>= 1.0.3)
|
uglifier (>= 1.0.3)
|
||||||
will_paginate (~> 3.0.3)
|
will_paginate (~> 3.0.3)
|
||||||
|
|||||||
@@ -1,22 +1,44 @@
|
|||||||
|
# About
|
||||||
|
A web application for bicycle collectives to track bicycles, bicycle work history, volunteer hours, volunteer work history, and volunteers currently in the shop.
|
||||||
|
|
||||||
|
See an overview video of what the desktop view looks like and how it works here: https://www.youtube.com/watch?v=0-JjM6d9nK4.
|
||||||
|
|
||||||
|
## Overview/Instructions/Guides
|
||||||
|
|
||||||
|
See [guides](doc/guides.md)
|
||||||
|
|
||||||
# Developer Setup
|
# Developer Setup
|
||||||
|
|
||||||
1. use rvm
|
## Running with Docker (recommended)
|
||||||
1. allow the .rvmrc file
|
|
||||||
1. `gem install bundler`
|
|
||||||
1. `bundle`
|
|
||||||
1. `rake db:create db:migrate db:seed`
|
|
||||||
1. Download extJS (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`
|
|
||||||
|
|
||||||
# Optional
|
1. These instructions haven't been tested, please provide corrections!
|
||||||
Add icons
|
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:create db:migrate`
|
||||||
|
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.
|
||||||
|
|
||||||
1. Download icons from http://www.famfamfam.com/lab/icons/silk/
|
### Developer Workflow
|
||||||
1. Link to the icons under `public/images/icons`: (From app root) `ln -s /MY/PATH/famfamfam_silk_icons/icons public/images/icons`
|
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`.
|
||||||
|
|
||||||
|
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.
|
# License
|
||||||
Follow the instructions from http://mailcatcher.me/ to create an rvm wrapper so you don't install it all over your gemsets
|
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
<a href="http://madewithloveinbaltimore.org">Made with ♥ in Baltimore</a>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ require File.expand_path('../config/application', __FILE__)
|
|||||||
|
|
||||||
Velocipede::Application.load_tasks
|
Velocipede::Application.load_tasks
|
||||||
|
|
||||||
|
unless Rails.env.production?
|
||||||
|
|
||||||
namespace :spec do
|
namespace :spec do
|
||||||
desc 'Run all acceptance specs'
|
desc 'Run all acceptance specs'
|
||||||
RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
|
RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
|
||||||
@@ -25,3 +27,5 @@ desc 'Run all specs'
|
|||||||
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
|
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
|
||||||
t.pattern = '**/*{_spec.rb,.feature}'
|
t.pattern = '**/*{_spec.rb,.feature}'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|||||||
@@ -12,5 +12,9 @@
|
|||||||
//
|
//
|
||||||
//= require jquery
|
//= require jquery
|
||||||
//= require jquery_ujs
|
//= require jquery_ujs
|
||||||
//= require twitter/bootstrap
|
//= require twitter/bootstrap/bootstrap-button
|
||||||
//= require_tree .
|
//= require twitter/bootstrap/bootstrap-modal
|
||||||
|
//= require utils
|
||||||
|
//= require bootstrap-datepicker
|
||||||
|
//= require bootstrap-timepicker
|
||||||
|
//= require jquery-date-format
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
$('.btn').button();
|
||||||
|
|
||||||
|
$("#add_bike_submit").click(function(){
|
||||||
|
|
||||||
|
json_data = { bikes: [{
|
||||||
|
serial_number: $("#serial_number").val(),
|
||||||
|
bike_brand_id: parseInt($("#bike_brand_id").val()),
|
||||||
|
shop_id: parseInt($("#shop_id").val()),
|
||||||
|
model: $("#model").val(),
|
||||||
|
bike_style_id: parseInt($('input[name=bike_style]:checked').val()),
|
||||||
|
seat_tube_height: parseFloat($("#seat_tube_height").val()),
|
||||||
|
bike_condition_id: parseInt($('input[name=bike_condition]:checked').val()),
|
||||||
|
bike_purpose_id: 1,
|
||||||
|
bike_wheel_size_id: parseInt($("#bike_wheel_size_id").val()),
|
||||||
|
}]};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: $("#add_bike_submit").data("url"),
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify(json_data),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
window.location = data.bikes[0].id + "?add_bike=1";
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
+474
@@ -0,0 +1,474 @@
|
|||||||
|
/* =========================================================
|
||||||
|
* bootstrap-datepicker.js
|
||||||
|
* http://www.eyecon.ro/bootstrap-datepicker
|
||||||
|
* =========================================================
|
||||||
|
* Copyright 2012 Stefan Petre
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* ========================================================= */
|
||||||
|
|
||||||
|
!function( $ ) {
|
||||||
|
|
||||||
|
// Picker object
|
||||||
|
|
||||||
|
var Datepicker = function(element, options){
|
||||||
|
this.element = $(element);
|
||||||
|
this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
|
||||||
|
this.picker = $(DPGlobal.template)
|
||||||
|
.appendTo('body')
|
||||||
|
.on({
|
||||||
|
click: $.proxy(this.click, this)//,
|
||||||
|
//mousedown: $.proxy(this.mousedown, this)
|
||||||
|
});
|
||||||
|
this.isInput = this.element.is('input');
|
||||||
|
this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
|
||||||
|
|
||||||
|
if (this.isInput) {
|
||||||
|
this.element.on({
|
||||||
|
focus: $.proxy(this.show, this),
|
||||||
|
//blur: $.proxy(this.hide, this),
|
||||||
|
keyup: $.proxy(this.update, this)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (this.component){
|
||||||
|
this.component.on('click', $.proxy(this.show, this));
|
||||||
|
} else {
|
||||||
|
this.element.on('click', $.proxy(this.show, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
|
||||||
|
if (typeof this.minViewMode === 'string') {
|
||||||
|
switch (this.minViewMode) {
|
||||||
|
case 'months':
|
||||||
|
this.minViewMode = 1;
|
||||||
|
break;
|
||||||
|
case 'years':
|
||||||
|
this.minViewMode = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.minViewMode = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
|
||||||
|
if (typeof this.viewMode === 'string') {
|
||||||
|
switch (this.viewMode) {
|
||||||
|
case 'months':
|
||||||
|
this.viewMode = 1;
|
||||||
|
break;
|
||||||
|
case 'years':
|
||||||
|
this.viewMode = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.viewMode = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.startViewMode = this.viewMode;
|
||||||
|
this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
|
||||||
|
this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
|
||||||
|
this.onRender = options.onRender;
|
||||||
|
this.fillDow();
|
||||||
|
this.fillMonths();
|
||||||
|
this.update();
|
||||||
|
this.showMode();
|
||||||
|
};
|
||||||
|
|
||||||
|
Datepicker.prototype = {
|
||||||
|
constructor: Datepicker,
|
||||||
|
|
||||||
|
show: function(e) {
|
||||||
|
this.picker.show();
|
||||||
|
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
|
||||||
|
this.place();
|
||||||
|
$(window).on('resize', $.proxy(this.place, this));
|
||||||
|
if (e ) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (!this.isInput) {
|
||||||
|
}
|
||||||
|
var that = this;
|
||||||
|
$(document).on('mousedown', function(ev){
|
||||||
|
if ($(ev.target).closest('.datepicker').length == 0) {
|
||||||
|
that.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'show',
|
||||||
|
date: this.date
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function(){
|
||||||
|
this.picker.hide();
|
||||||
|
$(window).off('resize', this.place);
|
||||||
|
this.viewMode = this.startViewMode;
|
||||||
|
this.showMode();
|
||||||
|
if (!this.isInput) {
|
||||||
|
$(document).off('mousedown', this.hide);
|
||||||
|
}
|
||||||
|
//this.set();
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'hide',
|
||||||
|
date: this.date
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function() {
|
||||||
|
var formated = DPGlobal.formatDate(this.date, this.format);
|
||||||
|
if (!this.isInput) {
|
||||||
|
if (this.component){
|
||||||
|
this.element.find('input').prop('value', formated);
|
||||||
|
}
|
||||||
|
this.element.data('date', formated);
|
||||||
|
} else {
|
||||||
|
this.element.prop('value', formated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setValue: function(newDate) {
|
||||||
|
if (typeof newDate === 'string') {
|
||||||
|
this.date = DPGlobal.parseDate(newDate, this.format);
|
||||||
|
} else {
|
||||||
|
this.date = new Date(newDate);
|
||||||
|
}
|
||||||
|
this.set();
|
||||||
|
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
|
||||||
|
this.fill();
|
||||||
|
},
|
||||||
|
|
||||||
|
place: function(){
|
||||||
|
var offset = this.component ? this.component.offset() : this.element.offset();
|
||||||
|
this.picker.css({
|
||||||
|
top: offset.top + this.height,
|
||||||
|
left: offset.left
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function(newDate){
|
||||||
|
this.date = DPGlobal.parseDate(
|
||||||
|
typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
|
||||||
|
this.format
|
||||||
|
);
|
||||||
|
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
|
||||||
|
this.fill();
|
||||||
|
},
|
||||||
|
|
||||||
|
fillDow: function(){
|
||||||
|
var dowCnt = this.weekStart;
|
||||||
|
var html = '<tr>';
|
||||||
|
while (dowCnt < this.weekStart + 7) {
|
||||||
|
html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
|
||||||
|
}
|
||||||
|
html += '</tr>';
|
||||||
|
this.picker.find('.datepicker-days thead').append(html);
|
||||||
|
},
|
||||||
|
|
||||||
|
fillMonths: function(){
|
||||||
|
var html = '';
|
||||||
|
var i = 0
|
||||||
|
while (i < 12) {
|
||||||
|
html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
|
||||||
|
}
|
||||||
|
this.picker.find('.datepicker-months td').append(html);
|
||||||
|
},
|
||||||
|
|
||||||
|
fill: function() {
|
||||||
|
var d = new Date(this.viewDate),
|
||||||
|
year = d.getFullYear(),
|
||||||
|
month = d.getMonth(),
|
||||||
|
currentDate = this.date.valueOf();
|
||||||
|
this.picker.find('.datepicker-days th:eq(1)')
|
||||||
|
.text(DPGlobal.dates.months[month]+' '+year);
|
||||||
|
var prevMonth = new Date(year, month-1, 28,0,0,0,0),
|
||||||
|
day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
|
||||||
|
prevMonth.setDate(day);
|
||||||
|
prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
|
||||||
|
var nextMonth = new Date(prevMonth);
|
||||||
|
nextMonth.setDate(nextMonth.getDate() + 42);
|
||||||
|
nextMonth = nextMonth.valueOf();
|
||||||
|
var html = [];
|
||||||
|
var clsName,
|
||||||
|
prevY,
|
||||||
|
prevM;
|
||||||
|
while(prevMonth.valueOf() < nextMonth) {
|
||||||
|
if (prevMonth.getDay() === this.weekStart) {
|
||||||
|
html.push('<tr>');
|
||||||
|
}
|
||||||
|
clsName = this.onRender(prevMonth);
|
||||||
|
prevY = prevMonth.getFullYear();
|
||||||
|
prevM = prevMonth.getMonth();
|
||||||
|
if ((prevM < month && prevY === year) || prevY < year) {
|
||||||
|
clsName += ' old';
|
||||||
|
} else if ((prevM > month && prevY === year) || prevY > year) {
|
||||||
|
clsName += ' new';
|
||||||
|
}
|
||||||
|
if (prevMonth.valueOf() === currentDate) {
|
||||||
|
clsName += ' active';
|
||||||
|
}
|
||||||
|
html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
|
||||||
|
if (prevMonth.getDay() === this.weekEnd) {
|
||||||
|
html.push('</tr>');
|
||||||
|
}
|
||||||
|
prevMonth.setDate(prevMonth.getDate()+1);
|
||||||
|
}
|
||||||
|
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
|
||||||
|
var currentYear = this.date.getFullYear();
|
||||||
|
|
||||||
|
var months = this.picker.find('.datepicker-months')
|
||||||
|
.find('th:eq(1)')
|
||||||
|
.text(year)
|
||||||
|
.end()
|
||||||
|
.find('span').removeClass('active');
|
||||||
|
if (currentYear === year) {
|
||||||
|
months.eq(this.date.getMonth()).addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
html = '';
|
||||||
|
year = parseInt(year/10, 10) * 10;
|
||||||
|
var yearCont = this.picker.find('.datepicker-years')
|
||||||
|
.find('th:eq(1)')
|
||||||
|
.text(year + '-' + (year + 9))
|
||||||
|
.end()
|
||||||
|
.find('td');
|
||||||
|
year -= 1;
|
||||||
|
for (var i = -1; i < 11; i++) {
|
||||||
|
html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
|
||||||
|
year += 1;
|
||||||
|
}
|
||||||
|
yearCont.html(html);
|
||||||
|
},
|
||||||
|
|
||||||
|
click: function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
var target = $(e.target).closest('span, td, th');
|
||||||
|
if (target.length === 1) {
|
||||||
|
switch(target[0].nodeName.toLowerCase()) {
|
||||||
|
case 'th':
|
||||||
|
switch(target[0].className) {
|
||||||
|
case 'switch':
|
||||||
|
this.showMode(1);
|
||||||
|
break;
|
||||||
|
case 'prev':
|
||||||
|
case 'next':
|
||||||
|
this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
|
||||||
|
this.viewDate,
|
||||||
|
this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
|
||||||
|
DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
|
||||||
|
);
|
||||||
|
this.fill();
|
||||||
|
this.set();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'span':
|
||||||
|
if (target.is('.month')) {
|
||||||
|
var month = target.parent().find('span').index(target);
|
||||||
|
this.viewDate.setMonth(month);
|
||||||
|
} else {
|
||||||
|
var year = parseInt(target.text(), 10)||0;
|
||||||
|
this.viewDate.setFullYear(year);
|
||||||
|
}
|
||||||
|
if (this.viewMode !== 0) {
|
||||||
|
this.date = new Date(this.viewDate);
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'changeDate',
|
||||||
|
date: this.date,
|
||||||
|
viewMode: DPGlobal.modes[this.viewMode].clsName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.showMode(-1);
|
||||||
|
this.fill();
|
||||||
|
this.set();
|
||||||
|
break;
|
||||||
|
case 'td':
|
||||||
|
if (target.is('.day') && !target.is('.disabled')){
|
||||||
|
var day = parseInt(target.text(), 10)||1;
|
||||||
|
var month = this.viewDate.getMonth();
|
||||||
|
if (target.is('.old')) {
|
||||||
|
month -= 1;
|
||||||
|
} else if (target.is('.new')) {
|
||||||
|
month += 1;
|
||||||
|
}
|
||||||
|
var year = this.viewDate.getFullYear();
|
||||||
|
this.date = new Date(year, month, day,0,0,0,0);
|
||||||
|
this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
|
||||||
|
this.fill();
|
||||||
|
this.set();
|
||||||
|
this.element.trigger({
|
||||||
|
type: 'changeDate',
|
||||||
|
date: this.date,
|
||||||
|
viewMode: DPGlobal.modes[this.viewMode].clsName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mousedown: function(e){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
showMode: function(dir) {
|
||||||
|
if (dir) {
|
||||||
|
this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
|
||||||
|
}
|
||||||
|
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.datepicker = function ( option, val ) {
|
||||||
|
return this.each(function () {
|
||||||
|
var $this = $(this),
|
||||||
|
data = $this.data('datepicker'),
|
||||||
|
options = typeof option === 'object' && option;
|
||||||
|
if (!data) {
|
||||||
|
$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
|
||||||
|
}
|
||||||
|
if (typeof option === 'string') data[option](val);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.datepicker.defaults = {
|
||||||
|
onRender: function(date) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$.fn.datepicker.Constructor = Datepicker;
|
||||||
|
|
||||||
|
var DPGlobal = {
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
clsName: 'days',
|
||||||
|
navFnc: 'Month',
|
||||||
|
navStep: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clsName: 'months',
|
||||||
|
navFnc: 'FullYear',
|
||||||
|
navStep: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clsName: 'years',
|
||||||
|
navFnc: 'FullYear',
|
||||||
|
navStep: 10
|
||||||
|
}],
|
||||||
|
dates:{
|
||||||
|
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
||||||
|
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||||
|
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
|
||||||
|
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
||||||
|
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||||
|
},
|
||||||
|
isLeapYear: function (year) {
|
||||||
|
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
|
||||||
|
},
|
||||||
|
getDaysInMonth: function (year, month) {
|
||||||
|
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
|
||||||
|
},
|
||||||
|
parseFormat: function(format){
|
||||||
|
var separator = format.match(/[.\/\-\s].*?/),
|
||||||
|
parts = format.split(/\W+/);
|
||||||
|
if (!separator || !parts || parts.length === 0){
|
||||||
|
throw new Error("Invalid date format.");
|
||||||
|
}
|
||||||
|
return {separator: separator, parts: parts};
|
||||||
|
},
|
||||||
|
parseDate: function(date, format) {
|
||||||
|
var parts = date.split(format.separator),
|
||||||
|
date = new Date(),
|
||||||
|
val;
|
||||||
|
date.setHours(0);
|
||||||
|
date.setMinutes(0);
|
||||||
|
date.setSeconds(0);
|
||||||
|
date.setMilliseconds(0);
|
||||||
|
if (parts.length === format.parts.length) {
|
||||||
|
var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
|
||||||
|
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
||||||
|
val = parseInt(parts[i], 10)||1;
|
||||||
|
switch(format.parts[i]) {
|
||||||
|
case 'dd':
|
||||||
|
case 'd':
|
||||||
|
day = val;
|
||||||
|
date.setDate(val);
|
||||||
|
break;
|
||||||
|
case 'mm':
|
||||||
|
case 'm':
|
||||||
|
month = val - 1;
|
||||||
|
date.setMonth(val - 1);
|
||||||
|
break;
|
||||||
|
case 'yy':
|
||||||
|
year = 2000 + val;
|
||||||
|
date.setFullYear(2000 + val);
|
||||||
|
break;
|
||||||
|
case 'yyyy':
|
||||||
|
year = val;
|
||||||
|
date.setFullYear(val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
date = new Date(year, month, day, 0 ,0 ,0);
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
},
|
||||||
|
formatDate: function(date, format){
|
||||||
|
var val = {
|
||||||
|
d: date.getDate(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
yy: date.getFullYear().toString().substring(2),
|
||||||
|
yyyy: date.getFullYear()
|
||||||
|
};
|
||||||
|
val.dd = (val.d < 10 ? '0' : '') + val.d;
|
||||||
|
val.mm = (val.m < 10 ? '0' : '') + val.m;
|
||||||
|
var date = [];
|
||||||
|
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
||||||
|
date.push(val[format.parts[i]]);
|
||||||
|
}
|
||||||
|
return date.join(format.separator);
|
||||||
|
},
|
||||||
|
headTemplate: '<thead>'+
|
||||||
|
'<tr>'+
|
||||||
|
'<th class="prev">‹</th>'+
|
||||||
|
'<th colspan="5" class="switch"></th>'+
|
||||||
|
'<th class="next">›</th>'+
|
||||||
|
'</tr>'+
|
||||||
|
'</thead>',
|
||||||
|
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
|
||||||
|
};
|
||||||
|
DPGlobal.template = '<div class="datepicker dropdown-menu">'+
|
||||||
|
'<div class="datepicker-days">'+
|
||||||
|
'<table class=" table-condensed">'+
|
||||||
|
DPGlobal.headTemplate+
|
||||||
|
'<tbody></tbody>'+
|
||||||
|
'</table>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="datepicker-months">'+
|
||||||
|
'<table class="table-condensed">'+
|
||||||
|
DPGlobal.headTemplate+
|
||||||
|
DPGlobal.contTemplate+
|
||||||
|
'</table>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="datepicker-years">'+
|
||||||
|
'<table class="table-condensed">'+
|
||||||
|
DPGlobal.headTemplate+
|
||||||
|
DPGlobal.contTemplate+
|
||||||
|
'</table>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
}( window.jQuery );
|
||||||
+1097
File diff suppressed because it is too large
Load Diff
@@ -1,5 +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) { window.location = '/users/sign_in'; }
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
$(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){
|
||||||
|
|
||||||
|
if( submit_count > MAX_SUBMITS ){
|
||||||
|
alert("Fine.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
$(document).ready(function(){
|
||||||
|
$("#checkin_menu").show();
|
||||||
|
$("#checkin").click( function(e){
|
||||||
|
var username = $("#user_username").val();
|
||||||
|
var password = $("#user_password").val();
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/api/v1/checkin',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
processData: false,
|
||||||
|
data: JSON.stringify({"username": username, "password": password }),
|
||||||
|
complete: function() { },
|
||||||
|
success: function(data) {
|
||||||
|
alert("Checked IN!");
|
||||||
|
$("#user_username").val('');
|
||||||
|
$("#user_password").val('');
|
||||||
|
},
|
||||||
|
error: function(data,textStatus) {
|
||||||
|
alert( "Error: " + JSON.parse(data.responseText)["error"]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
$("#checkout").click( function(e){
|
||||||
|
var username = $("#user_username").val();
|
||||||
|
var password = $("#user_password").val();
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/api/v1/checkout',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
processData: false,
|
||||||
|
data: JSON.stringify({"username": username, "password": password }),
|
||||||
|
complete: function() { },
|
||||||
|
success: function(data) {
|
||||||
|
alert("Checked OUT!");
|
||||||
|
|
||||||
|
$("#user_username").val('');
|
||||||
|
$("#user_password").val('');
|
||||||
|
},
|
||||||
|
error: function(data,textStatus) {
|
||||||
|
alert( "Error: " + JSON.parse(data.responseText)["error"]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
+445
@@ -0,0 +1,445 @@
|
|||||||
|
var DateFormat = {};
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
var daysInWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
var shortMonthsInYear = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
var longMonthsInYear = ['January', 'February', 'March', 'April', 'May', 'June',
|
||||||
|
'July', 'August', 'September', 'October', 'November', 'December'];
|
||||||
|
var shortMonthsToNumber = { 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
|
||||||
|
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12' };
|
||||||
|
|
||||||
|
var YYYYMMDD_MATCHER = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;
|
||||||
|
|
||||||
|
$.format = (function() {
|
||||||
|
function numberToLongDay(value) {
|
||||||
|
// 0 to Sunday
|
||||||
|
// 1 to Monday
|
||||||
|
return daysInWeek[parseInt(value, 10)] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberToShortMonth(value) {
|
||||||
|
// 1 to Jan
|
||||||
|
// 2 to Feb
|
||||||
|
var monthArrayIndex = parseInt(value, 10) - 1;
|
||||||
|
return shortMonthsInYear[monthArrayIndex] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberToLongMonth(value) {
|
||||||
|
// 1 to January
|
||||||
|
// 2 to February
|
||||||
|
var monthArrayIndex = parseInt(value, 10) - 1;
|
||||||
|
return longMonthsInYear[monthArrayIndex] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortMonthToNumber(value) {
|
||||||
|
// Jan to 01
|
||||||
|
// Feb to 02
|
||||||
|
return shortMonthsToNumber[value] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTime(value) {
|
||||||
|
// 10:54:50.546
|
||||||
|
// => hour: 10, minute: 54, second: 50, millis: 546
|
||||||
|
// 10:54:50
|
||||||
|
// => hour: 10, minute: 54, second: 50, millis: ''
|
||||||
|
var time = value,
|
||||||
|
values,
|
||||||
|
subValues,
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
second,
|
||||||
|
millis = '',
|
||||||
|
delimited,
|
||||||
|
timeArray;
|
||||||
|
|
||||||
|
if(time.indexOf('.') !== -1) {
|
||||||
|
delimited = time.split('.');
|
||||||
|
// split time and milliseconds
|
||||||
|
time = delimited[0];
|
||||||
|
millis = delimited[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
timeArray = time.split(':');
|
||||||
|
|
||||||
|
if(timeArray.length === 3) {
|
||||||
|
hour = timeArray[0];
|
||||||
|
minute = timeArray[1];
|
||||||
|
// '20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 20
|
||||||
|
// '20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 20
|
||||||
|
second = timeArray[2].replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// '01:10:20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 01:10:20
|
||||||
|
// '01:10:20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
// => 01:10:20
|
||||||
|
time = time.replace(/\s.+/, '').replace(/[a-z]/gi, '');
|
||||||
|
return {
|
||||||
|
time: time,
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
second: second,
|
||||||
|
millis: millis
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { time : '', hour : '', minute : '', second : '', millis : '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function padding(value, length) {
|
||||||
|
var paddingCount = length - String(value).length;
|
||||||
|
for(var i = 0; i < paddingCount; i++) {
|
||||||
|
value = '0' + value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
parseDate: function(value) {
|
||||||
|
var parsedDate = {
|
||||||
|
date: null,
|
||||||
|
year: null,
|
||||||
|
month: null,
|
||||||
|
dayOfMonth: null,
|
||||||
|
dayOfWeek: null,
|
||||||
|
time: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if(typeof value == 'number') {
|
||||||
|
return this.parseDate(new Date(value));
|
||||||
|
} else if(typeof value.getFullYear == 'function') {
|
||||||
|
parsedDate.year = String(value.getFullYear());
|
||||||
|
// d = new Date(1900, 1, 1) // 1 for Feb instead of Jan.
|
||||||
|
// => Thu Feb 01 1900 00:00:00
|
||||||
|
parsedDate.month = String(value.getMonth() + 1);
|
||||||
|
parsedDate.dayOfMonth = String(value.getDate());
|
||||||
|
parsedDate.time = parseTime(value.toTimeString());
|
||||||
|
} else if(value.search(YYYYMMDD_MATCHER) != -1) {
|
||||||
|
/* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */
|
||||||
|
values = value.split(/[T\+-]/);
|
||||||
|
parsedDate.year = values[0];
|
||||||
|
parsedDate.month = values[1];
|
||||||
|
parsedDate.dayOfMonth = values[2];
|
||||||
|
parsedDate.time = parseTime(values[3].split('.')[0]);
|
||||||
|
} else {
|
||||||
|
values = value.split(' ');
|
||||||
|
switch (values.length) {
|
||||||
|
case 6:
|
||||||
|
/* Wed Jan 13 10:43:41 CET 2010 */
|
||||||
|
parsedDate.year = values[5];
|
||||||
|
parsedDate.month = shortMonthToNumber(values[1]);
|
||||||
|
parsedDate.dayOfMonth = values[2];
|
||||||
|
parsedDate.time = parseTime(values[3]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
/* 2009-12-18 10:54:50.546 */
|
||||||
|
subValues = values[0].split('-');
|
||||||
|
parsedDate.year = subValues[0];
|
||||||
|
parsedDate.month = subValues[1];
|
||||||
|
parsedDate.dayOfMonth = subValues[2];
|
||||||
|
parsedDate.time = parseTime(values[1]);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
/* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */
|
||||||
|
case 9:
|
||||||
|
/* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */
|
||||||
|
case 10:
|
||||||
|
/* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */
|
||||||
|
parsedDate.year = values[3];
|
||||||
|
parsedDate.month = shortMonthToNumber(values[1]);
|
||||||
|
parsedDate.dayOfMonth = values[2];
|
||||||
|
parsedDate.time = parseTime(values[4]);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */
|
||||||
|
subValues = values[0].split('');
|
||||||
|
parsedDate.year = subValues[0] + subValues[1] + subValues[2] + subValues[3];
|
||||||
|
parsedDate.month = subValues[5] + subValues[6];
|
||||||
|
parsedDate.dayOfMonth = subValues[8] + subValues[9];
|
||||||
|
parsedDate.time = parseTime(subValues[13] + subValues[14] + subValues[15] + subValues[16] + subValues[17] + subValues[18] + subValues[19] + subValues[20]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsedDate.date = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth);
|
||||||
|
parsedDate.dayOfWeek = String(parsedDate.date.getDay());
|
||||||
|
|
||||||
|
return parsedDate;
|
||||||
|
},
|
||||||
|
|
||||||
|
date : function(value, format) {
|
||||||
|
try {
|
||||||
|
var parsedDate = this.parseDate(value);
|
||||||
|
|
||||||
|
if(parsedDate === null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var date = parsedDate.date,
|
||||||
|
year = parsedDate.year,
|
||||||
|
month = parsedDate.month,
|
||||||
|
dayOfMonth = parsedDate.dayOfMonth,
|
||||||
|
dayOfWeek = parsedDate.dayOfWeek,
|
||||||
|
time = parsedDate.time;
|
||||||
|
|
||||||
|
var pattern = '',
|
||||||
|
retValue = '',
|
||||||
|
unparsedRest = '',
|
||||||
|
inQuote = false;
|
||||||
|
|
||||||
|
/* Issue 1 - variable scope issue in format.date (Thanks jakemonO) */
|
||||||
|
for(var i = 0; i < format.length; i++) {
|
||||||
|
var currentPattern = format.charAt(i);
|
||||||
|
// Look-Ahead Right (LALR)
|
||||||
|
var nextRight = format.charAt(i + 1);
|
||||||
|
|
||||||
|
if (inQuote) {
|
||||||
|
if (currentPattern == "'") {
|
||||||
|
retValue += (pattern === '') ? "'" : pattern;
|
||||||
|
pattern = '';
|
||||||
|
inQuote = false;
|
||||||
|
} else {
|
||||||
|
pattern += currentPattern;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pattern += currentPattern;
|
||||||
|
unparsedRest = '';
|
||||||
|
switch (pattern) {
|
||||||
|
case 'ddd':
|
||||||
|
retValue += numberToLongDay(dayOfWeek);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'dd':
|
||||||
|
if(nextRight === 'd') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += padding(dayOfMonth, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
if(nextRight === 'd') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += parseInt(dayOfMonth, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
if(dayOfMonth == 1 || dayOfMonth == 21 || dayOfMonth == 31) {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'st';
|
||||||
|
} else if(dayOfMonth == 2 || dayOfMonth == 22) {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'nd';
|
||||||
|
} else if(dayOfMonth == 3 || dayOfMonth == 23) {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'rd';
|
||||||
|
} else {
|
||||||
|
dayOfMonth = parseInt(dayOfMonth, 10) + 'th';
|
||||||
|
}
|
||||||
|
retValue += dayOfMonth;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'MMMM':
|
||||||
|
retValue += numberToLongMonth(month);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'MMM':
|
||||||
|
if(nextRight === 'M') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += numberToShortMonth(month);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'MM':
|
||||||
|
if(nextRight === 'M') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += padding(month, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
if(nextRight === 'M') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += parseInt(month, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'y':
|
||||||
|
case 'yyy':
|
||||||
|
if(nextRight === 'y') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += pattern;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'yy':
|
||||||
|
if(nextRight === 'y') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += String(year).slice(-2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'yyyy':
|
||||||
|
retValue += year;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'HH':
|
||||||
|
retValue += padding(time.hour, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
if(nextRight === 'H') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += parseInt(time.hour, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'hh':
|
||||||
|
/* time.hour is '00' as string == is used instead of === */
|
||||||
|
hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
|
||||||
|
: time.hour - 12);
|
||||||
|
retValue += padding(hour, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
if(nextRight === 'h') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
|
||||||
|
: time.hour - 12);
|
||||||
|
retValue += parseInt(hour, 10);
|
||||||
|
// Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21
|
||||||
|
// retValue = parseInt(retValue, 10);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'mm':
|
||||||
|
retValue += padding(time.minute, 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
if(nextRight === 'm') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += time.minute;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'ss':
|
||||||
|
/* ensure only seconds are added to the return string */
|
||||||
|
retValue += padding(time.second.substring(0, 2), 2);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if(nextRight === 's') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += time.second;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
case 'SS':
|
||||||
|
if(nextRight === 'S') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retValue += pattern;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'SSS':
|
||||||
|
retValue += time.millis.substring(0, 3);
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
retValue += time.hour >= 12 ? 'PM' : 'AM';
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
retValue += time.hour >= 12 ? 'p.m.' : 'a.m.';
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
case "'":
|
||||||
|
pattern = '';
|
||||||
|
inQuote = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
retValue += currentPattern;
|
||||||
|
pattern = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retValue += unparsedRest;
|
||||||
|
return retValue;
|
||||||
|
} catch (e) {
|
||||||
|
if(console && console.log) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* JavaScript Pretty Date
|
||||||
|
* Copyright (c) 2011 John Resig (ejohn.org)
|
||||||
|
* Licensed under the MIT and GPL licenses.
|
||||||
|
*
|
||||||
|
* Takes an ISO time and returns a string representing how long ago the date
|
||||||
|
* represents
|
||||||
|
*
|
||||||
|
* ('2008-01-28T20:24:17Z') // => '2 hours ago'
|
||||||
|
* ('2008-01-27T22:24:17Z') // => 'Yesterday'
|
||||||
|
* ('2008-01-26T22:24:17Z') // => '2 days ago'
|
||||||
|
* ('2008-01-14T22:24:17Z') // => '2 weeks ago'
|
||||||
|
* ('2007-12-15T22:24:17Z') // => 'more than 5 weeks ago'
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
prettyDate : function(time) {
|
||||||
|
var date;
|
||||||
|
var diff;
|
||||||
|
var day_diff;
|
||||||
|
|
||||||
|
if(typeof time === 'string' || typeof time === 'number') {
|
||||||
|
date = new Date(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof time === 'object') {
|
||||||
|
date = new Date(time.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
diff = (((new Date()).getTime() - date.getTime()) / 1000);
|
||||||
|
|
||||||
|
day_diff = Math.floor(diff / 86400);
|
||||||
|
|
||||||
|
if(isNaN(day_diff) || day_diff < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(diff < 60) {
|
||||||
|
return 'just now';
|
||||||
|
} else if(diff < 120) {
|
||||||
|
return '1 minute ago';
|
||||||
|
} else if(diff < 3600) {
|
||||||
|
return Math.floor(diff / 60) + ' minutes ago';
|
||||||
|
} else if(diff < 7200) {
|
||||||
|
return '1 hour ago';
|
||||||
|
} else if(diff < 86400) {
|
||||||
|
return Math.floor(diff / 3600) + ' hours ago';
|
||||||
|
} else if(day_diff === 1) {
|
||||||
|
return 'Yesterday';
|
||||||
|
} else if(day_diff < 7) {
|
||||||
|
return day_diff + ' days ago';
|
||||||
|
} else if(day_diff < 31) {
|
||||||
|
return Math.ceil(day_diff / 7) + ' weeks ago';
|
||||||
|
} else if(day_diff >= 31) {
|
||||||
|
return 'more than 5 weeks ago';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toBrowserTimeZone : function(value, format) {
|
||||||
|
return this.date(new Date(value), format || 'MM/dd/yyyy HH:mm:ss');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
}(DateFormat));
|
||||||
|
;// require dateFormat.js
|
||||||
|
// please check `dist/jquery.dateFormat.js` for a complete version
|
||||||
|
(function($) {
|
||||||
|
$.format = DateFormat.format;
|
||||||
|
}(jQuery));
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
//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;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
$("#index_logout").click(function(){
|
||||||
|
$.ajax({
|
||||||
|
type: "DELETE",
|
||||||
|
url: $("#index_logout").data("url"),
|
||||||
|
complete: function(){
|
||||||
|
window.location.href="/";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
$(".task_list_task").click(function(){
|
||||||
|
$("#update_tasks_submit").removeClass("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#update_tasks_submit").click(function(){
|
||||||
|
|
||||||
|
tasks = [];
|
||||||
|
$(".task_list_task").each(function(){
|
||||||
|
tasks.push({
|
||||||
|
id: parseInt($(this).data("id")),
|
||||||
|
done: $(this).is(":checked")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
json_data = { tasks: tasks };
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: $("#update_tasks_submit").data("url"),
|
||||||
|
type: "PUT",
|
||||||
|
data: JSON.stringify(json_data),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
//should re-render via JS, but for now reload
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
alert("An error occured updating tasks");
|
||||||
|
//displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
$(document).ready(function(){
|
||||||
|
|
||||||
|
var currentdate = new Date();
|
||||||
|
$("#date_id").datepicker().on('changeDate', function(ev){
|
||||||
|
$("#date_id").datepicker('hide');
|
||||||
|
});
|
||||||
|
$("#date_id").datepicker('setValue', currentdate);
|
||||||
|
|
||||||
|
$("#start_time_id").timepicker();
|
||||||
|
$("#end_time_id").timepicker();
|
||||||
|
|
||||||
|
$("#add_time_entry_submit").click(function(){
|
||||||
|
date = $("#date_id").val();
|
||||||
|
start_date = new Date(date + " " + $("#start_time_id").val());
|
||||||
|
end_date = new Date(date + " " + $("#end_time_id").val());
|
||||||
|
|
||||||
|
forward = $("#add_time_entry_submit").data("forward");
|
||||||
|
|
||||||
|
// If a bike is selected, forward to the bike
|
||||||
|
// checklist.
|
||||||
|
bike_id = parseInt($("#bike_id").val());
|
||||||
|
if( bike_id > 0 ){
|
||||||
|
forward = "/task_lists/" + bike_id + "/edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: Ideally, we'd submit the dates as ISO, but I can't figure out
|
||||||
|
// how to get Netzke to render UTC dates correctly (it calls to_json
|
||||||
|
// somewhere and drops off the timezone). For the time being, save dates
|
||||||
|
// in locale like Netzke.
|
||||||
|
json_data = { time_entries: [{
|
||||||
|
start_date: $.format.date(start_date, "dd-MM-yyyy hh:mm a"),
|
||||||
|
end_date: $.format.date(end_date, "dd-MM-yyyy hh:mm a"),
|
||||||
|
log_action_id: parseInt($('input[name=action_id]:checked').val()),
|
||||||
|
bike_id: bike_id,
|
||||||
|
description: $("#description_id").val(),
|
||||||
|
}]};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: $("#add_time_entry_submit").data("url"),
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify(json_data),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
window.location = forward;
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".work_entry-delete-btn").click(function(){
|
||||||
|
row = $(this).closest("tr");
|
||||||
|
entry_id = row.data("id");
|
||||||
|
start_date = row.data("start_date");
|
||||||
|
duration = row.data("duration");
|
||||||
|
description = row.data("description");
|
||||||
|
$("#work_entry_start_date").html(start_date);
|
||||||
|
$("#work_entry_duration").html(duration);
|
||||||
|
$("#work_entry_description").html(description);
|
||||||
|
$("#confirmation_delete").data("entry_id", entry_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#confirmation_delete").click(function(){
|
||||||
|
entry_id = $(this).data("entry_id");
|
||||||
|
url = $("#confirmation_delete").data("url") + entry_id;
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: "delete",
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(data, status, xhr){
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error: function(data, status ){
|
||||||
|
displayFormErrors(data.responseJSON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
function displayFormErrors(data){
|
||||||
|
if( data.errors != undefined ){
|
||||||
|
$.each( data.errors, function( field, errorMsg) {
|
||||||
|
$("#"+field).parents(".control-group").addClass("error");
|
||||||
|
$("#"+field).siblings(".help-block").html(errorMsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,5 +9,7 @@
|
|||||||
* compiled file, but it's generally better to create a new file per style scope.
|
* compiled file, but it's generally better to create a new file per style scope.
|
||||||
*
|
*
|
||||||
*= require_self
|
*= require_self
|
||||||
*= require_tree .
|
*= require datepicker
|
||||||
|
*= require bootstrap-timepicker
|
||||||
|
*= require bootstrap_and_overrides
|
||||||
*/
|
*/
|
||||||
|
|||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
/*!
|
||||||
|
* Timepicker Component for Twitter Bootstrap
|
||||||
|
*
|
||||||
|
* Copyright 2013 Joris de Wit
|
||||||
|
*
|
||||||
|
* Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
.bootstrap-timepicker {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before {
|
||||||
|
left: auto;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after {
|
||||||
|
left: auto;
|
||||||
|
right: 13px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker .add-on {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker .add-on i {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu.open {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu:before {
|
||||||
|
border-bottom: 7px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-left: 7px solid transparent;
|
||||||
|
border-right: 7px solid transparent;
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.dropdown-menu:after {
|
||||||
|
border-bottom: 6px solid #FFFFFF;
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-left:before {
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-left:after {
|
||||||
|
left: 7px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-right:before {
|
||||||
|
right: 6px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-right:after {
|
||||||
|
right: 7px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-top:before {
|
||||||
|
top: -7px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-top:after {
|
||||||
|
top: -6px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-bottom:before {
|
||||||
|
bottom: -7px;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-top: 7px solid #999;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget.timepicker-orient-bottom:after {
|
||||||
|
bottom: -6px;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-top: 6px solid #ffffff;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget a.btn,
|
||||||
|
.bootstrap-timepicker-widget input {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td {
|
||||||
|
text-align: center;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td:not(.separator) {
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td span {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td a {
|
||||||
|
border: 1px transparent solid;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px 0;
|
||||||
|
outline: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #eee;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td a i {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget table td input {
|
||||||
|
width: 25px;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker-widget .modal-content {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
@media (min-width: 767px) {
|
||||||
|
.bootstrap-timepicker-widget.modal {
|
||||||
|
width: 200px;
|
||||||
|
margin-left: -100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.bootstrap-timepicker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.bootstrap-timepicker .dropdown-menu {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,26 @@
|
|||||||
@import "twitter/bootstrap/bootstrap";
|
@import "twitter/bootstrap/bootstrap";
|
||||||
|
body {
|
||||||
|
padding-top: 60px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
@import "twitter/bootstrap/responsive";
|
@import "twitter/bootstrap/responsive";
|
||||||
|
|
||||||
// Set the correct sprite paths
|
// Set the correct sprite paths
|
||||||
@iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
|
@iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
|
||||||
@iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png');
|
@iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png');
|
||||||
|
|
||||||
|
// Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines)
|
||||||
|
// Note: If you use asset_path() here, your compiled boostrap_and_overrides.css will not
|
||||||
|
// have the proper paths. So for now we use the absolute path.
|
||||||
|
@fontAwesomeEotPath: '/assets/fontawesome-webfont.eot';
|
||||||
|
@fontAwesomeWoffPath: '/assets/fontawesome-webfont.woff';
|
||||||
|
@fontAwesomeTtfPath: '/assets/fontawesome-webfont.ttf';
|
||||||
|
@fontAwesomeSvgPath: '/assets/fontawesome-webfont.svg';
|
||||||
|
|
||||||
|
// Font Awesome
|
||||||
|
@import "fontawesome";
|
||||||
|
|
||||||
// Your custom LESS stylesheets goes here
|
// Your custom LESS stylesheets goes here
|
||||||
//
|
//
|
||||||
// Since bootstrap was imported above you have access to its mixins which
|
// Since bootstrap was imported above you have access to its mixins which
|
||||||
@@ -15,3 +31,17 @@
|
|||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
// @linkColor: #ff0000;
|
// @linkColor: #ff0000;
|
||||||
|
|
||||||
|
[data-toggle="buttons-radio"] > .btn > input[type="radio"],
|
||||||
|
[data-toggle="buttons"] > .btn > input[type="checkbox"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group.error .btn-group > .btn {
|
||||||
|
color: #b94a48;
|
||||||
|
border-color: #b94a48;
|
||||||
|
}
|
||||||
|
|||||||
Executable
+182
@@ -0,0 +1,182 @@
|
|||||||
|
/*!
|
||||||
|
* Datepicker for Bootstrap
|
||||||
|
*
|
||||||
|
* Copyright 2012 Stefan Petre
|
||||||
|
* Licensed under the Apache License v2.0
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.datepicker {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 4px;
|
||||||
|
margin-top: 1px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
/*.dow {
|
||||||
|
border-top: 1px solid #ddd !important;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
}
|
||||||
|
.datepicker:before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
border-left: 7px solid transparent;
|
||||||
|
border-right: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid #ccc;
|
||||||
|
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||||
|
position: absolute;
|
||||||
|
top: -7px;
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
.datepicker:after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
border-bottom: 6px solid #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 7px;
|
||||||
|
}
|
||||||
|
.datepicker > div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.datepicker table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.datepicker td,
|
||||||
|
.datepicker th {
|
||||||
|
text-align: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.datepicker td.day:hover {
|
||||||
|
background: #eeeeee;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.datepicker td.day.disabled {
|
||||||
|
color: #eeeeee;
|
||||||
|
}
|
||||||
|
.datepicker td.old,
|
||||||
|
.datepicker td.new {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
.datepicker td.active,
|
||||||
|
.datepicker td.active:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #006dcc;
|
||||||
|
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||||
|
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
|
||||||
|
border-color: #0044cc #0044cc #002a80;
|
||||||
|
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||||
|
*background-color: #0044cc;
|
||||||
|
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
|
||||||
|
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.datepicker td.active:hover,
|
||||||
|
.datepicker td.active:hover:hover,
|
||||||
|
.datepicker td.active:focus,
|
||||||
|
.datepicker td.active:hover:focus,
|
||||||
|
.datepicker td.active:active,
|
||||||
|
.datepicker td.active:hover:active,
|
||||||
|
.datepicker td.active.active,
|
||||||
|
.datepicker td.active:hover.active,
|
||||||
|
.datepicker td.active.disabled,
|
||||||
|
.datepicker td.active:hover.disabled,
|
||||||
|
.datepicker td.active[disabled],
|
||||||
|
.datepicker td.active:hover[disabled] {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #0044cc;
|
||||||
|
*background-color: #003bb3;
|
||||||
|
}
|
||||||
|
.datepicker td.active:active,
|
||||||
|
.datepicker td.active:hover:active,
|
||||||
|
.datepicker td.active.active,
|
||||||
|
.datepicker td.active:hover.active {
|
||||||
|
background-color: #003399 \9;
|
||||||
|
}
|
||||||
|
.datepicker td span {
|
||||||
|
display: block;
|
||||||
|
width: 47px;
|
||||||
|
height: 54px;
|
||||||
|
line-height: 54px;
|
||||||
|
float: left;
|
||||||
|
margin: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.datepicker td span:hover {
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
.datepicker td span.active {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #006dcc;
|
||||||
|
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||||
|
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||||
|
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
|
||||||
|
border-color: #0044cc #0044cc #002a80;
|
||||||
|
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||||
|
*background-color: #0044cc;
|
||||||
|
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
|
||||||
|
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.datepicker td span.active:hover,
|
||||||
|
.datepicker td span.active:focus,
|
||||||
|
.datepicker td span.active:active,
|
||||||
|
.datepicker td span.active.active,
|
||||||
|
.datepicker td span.active.disabled,
|
||||||
|
.datepicker td span.active[disabled] {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #0044cc;
|
||||||
|
*background-color: #003bb3;
|
||||||
|
}
|
||||||
|
.datepicker td span.active:active,
|
||||||
|
.datepicker td span.active.active {
|
||||||
|
background-color: #003399 \9;
|
||||||
|
}
|
||||||
|
.datepicker td span.old {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
.datepicker th.switch {
|
||||||
|
width: 145px;
|
||||||
|
}
|
||||||
|
.datepicker th.next,
|
||||||
|
.datepicker th.prev {
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
.datepicker thead tr:first-child th {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.datepicker thead tr:first-child th:hover {
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
.input-append.date .add-on i,
|
||||||
|
.input-prepend.date .add-on i {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
@@ -1,45 +1,56 @@
|
|||||||
class AppTabPanel < Netzke::Basepack::TabPanel
|
class AppTabPanel < Netzke::Basepack::TabPanel
|
||||||
|
|
||||||
|
|
||||||
action :sign_out do |c|
|
action :sign_out do |c|
|
||||||
c.icon = :door_out
|
c.icon = :door_out
|
||||||
c.text = "Sign out #{controller.current_user.email}" if controller.current_user
|
c.text = "Exit"
|
||||||
|
end
|
||||||
|
|
||||||
|
action :check_out do |c|
|
||||||
|
c.icon = :door_out
|
||||||
|
c.text = "CHECK OUT" if controller.current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
action :change_account_info do |c|
|
||||||
|
c.icon = :user_edit
|
||||||
|
c.text = "Change Email/Password"
|
||||||
end
|
end
|
||||||
|
|
||||||
def configure(c)
|
def configure(c)
|
||||||
|
|
||||||
#all users
|
#all users
|
||||||
# (had to use hash for borders to get the title to display properly)
|
# (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
|
#for users only
|
||||||
if controller.current_user.role?(:user)
|
if not controller.current_user.role?(:admin)
|
||||||
# (had to use hash for borders to get the title to display properly)
|
|
||||||
@@app_tab_panel_items.concat [{ layout: :fit,
|
@@app_tab_panel_items.concat [{ layout: :fit,
|
||||||
wrappedComponent: :user_profile_border,
|
wrappedComponent: :user_profile_border,
|
||||||
title: "Profile"},
|
title: "My Profile"},
|
||||||
{ layout: :fit,
|
{ layout: :fit,
|
||||||
wrappedComponent: :user_transactions_border,
|
wrappedComponent: :user_transactions_border,
|
||||||
title: "Transactions"}
|
title: "My Transactions"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
#for admins
|
#for admins
|
||||||
if controller.current_user.role?(:admin)
|
if can? :manage, User
|
||||||
# (had to use hash for borders to get the title to display properly)
|
|
||||||
@@app_tab_panel_items.concat [{ layout: :fit,
|
@@app_tab_panel_items.concat [{ layout: :fit,
|
||||||
wrappedComponent: :users_and_profiles_border,
|
wrappedComponent: :users_and_profiles_border,
|
||||||
title: "Users/Profiles"},
|
title: "Users/Profiles"}]
|
||||||
{ layout: :fit,
|
end
|
||||||
|
if can? :manage, Transaction
|
||||||
|
@@app_tab_panel_items.concat [{ layout: :fit,
|
||||||
wrappedComponent: :transactions_border,
|
wrappedComponent: :transactions_border,
|
||||||
title: "Transactions"},
|
title: "Users/Transactions"}]
|
||||||
:logs,
|
end
|
||||||
:user_roles]
|
if can? :manage, Role
|
||||||
|
@@app_tab_panel_items.concat [{ layout: :fit,
|
||||||
|
wrappedComponent: :user_role_joins,
|
||||||
|
title: "User Roles"},
|
||||||
|
]
|
||||||
|
end
|
||||||
|
if can? :manage, ::ActsAsLoggable::Log.all
|
||||||
|
@@app_tab_panel_items.concat [:check_ins, :logs]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@app_tab_panel_items.each do |item|
|
@@app_tab_panel_items.each do |item|
|
||||||
if item.kind_of?(Symbol)
|
if item.kind_of?(Symbol)
|
||||||
self.class.component item
|
self.class.component item
|
||||||
@@ -47,9 +58,8 @@ class AppTabPanel < Netzke::Basepack::TabPanel
|
|||||||
self.class.component item[:wrappedComponent]
|
self.class.component item[:wrappedComponent]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
c.prevent_header = true
|
c.prevent_header = true
|
||||||
c.tbar = [:sign_out]
|
c.tbar = [:sign_out, :check_out, :change_account_info]
|
||||||
c.items = @@app_tab_panel_items
|
c.items = @@app_tab_panel_items
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
@@ -58,6 +68,5 @@ class AppTabPanel < Netzke::Basepack::TabPanel
|
|||||||
#gets js from app_tab_panel/javascripts/sign_out.js
|
#gets js from app_tab_panel/javascripts/sign_out.js
|
||||||
c.mixin :sign_out
|
c.mixin :sign_out
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,20 @@
|
|||||||
url: '/users/sign_out',
|
url: '/users/sign_out',
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
onCheckOut: function(){
|
||||||
|
Ext.Ajax.request({
|
||||||
|
url: '/api/v1/checkout',
|
||||||
|
method: 'POST',
|
||||||
|
success: function(response, opts) {
|
||||||
|
Ext.Ajax.request({
|
||||||
|
url: '/users/sign_out',
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onChangeAccountInfo: function(){
|
||||||
|
window.location.href="/users/edit";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ class BikeBrands < Netzke::Basepack::Grid
|
|||||||
super
|
super
|
||||||
c.model = "BikeBrand"
|
c.model = "BikeBrand"
|
||||||
c.title = "Brands"
|
c.title = "Brands"
|
||||||
|
c.force_fit = true
|
||||||
|
|
||||||
c.prohibit_update = true if cannot? :update, BikeBrand
|
c.prohibit_update = true if cannot? :update, BikeBrand
|
||||||
c.prohibit_create = true if cannot? :create, BikeBrand
|
c.prohibit_create = true if cannot? :create, BikeBrand
|
||||||
|
|||||||
+12
-10
@@ -6,6 +6,7 @@ class BikeLogs < Netzke::Basepack::Grid
|
|||||||
|
|
||||||
c.model = "ActsAsLoggable::Log"
|
c.model = "ActsAsLoggable::Log"
|
||||||
c.title = "Bike History"
|
c.title = "Bike History"
|
||||||
|
c.force_fit = true
|
||||||
c.data_store = {auto_load: false}
|
c.data_store = {auto_load: false}
|
||||||
c.scope = lambda { |rel| rel.where(:loggable_type => 'Bike',:loggable_id => session[:selected_bike_id]);}
|
c.scope = lambda { |rel| rel.where(:loggable_type => 'Bike',:loggable_id => session[:selected_bike_id]);}
|
||||||
c.strong_default_attrs = {
|
c.strong_default_attrs = {
|
||||||
@@ -18,12 +19,9 @@ class BikeLogs < Netzke::Basepack::Grid
|
|||||||
|
|
||||||
c.columns = [
|
c.columns = [
|
||||||
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
|
{ :name => :start_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 => :end_date, :format => "g:ia - D, M j - Y", :width => 165, :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,
|
:description,
|
||||||
{ :name => :bike_action__action, :text => 'Action'},
|
{ :name => :bike_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::BikeAction.first.id},
|
||||||
{ :name => :created_at, :read_only => true},
|
|
||||||
{ :name => :updated_at, :read_only => true},
|
|
||||||
{ :name => :logged_by, :getter => lambda{ |rec|
|
{ :name => :logged_by, :getter => lambda{ |rec|
|
||||||
user = User.find_by_id(rec.logger_id)
|
user = User.find_by_id(rec.logger_id)
|
||||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||||
@@ -31,26 +29,30 @@ class BikeLogs < Netzke::Basepack::Grid
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@bike_logs = ::ActsAsLoggable::Log.where(:loggable_type => "Bike").all
|
||||||
|
c.prohibit_update = true if cannot? :update, @bike_logs
|
||||||
|
c.prohibit_create = true if cannot? :create, @bike_logs
|
||||||
|
c.prohibit_delete = true if cannot? :delete, @bike_logs
|
||||||
|
=begin
|
||||||
#TODO: fix GUI so it actually respects this
|
#TODO: fix GUI so it actually respects this
|
||||||
current_bike = Bike.find_by_id(session[:selected_bike_id])
|
current_bike = Bike.find_by_id(session[:selected_bike_id])
|
||||||
if cannot? :update, current_bike
|
if cannot? :update, current_bike
|
||||||
# if you can't update the bike, you can't do anything to the log
|
# if you can't update the bike, you can't do anything to the log
|
||||||
c.prohibit_update = c.prohibit_create = c.prohibit_delete = true
|
c.prohibit_update = c.prohibit_create = c.prohibit_delete = true
|
||||||
end
|
end
|
||||||
|
=end
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_fields_for_forms
|
def default_fields_for_forms
|
||||||
[
|
[
|
||||||
{ :name => :start_date},
|
{ :name => :start_date},
|
||||||
{ :name => :end_date},
|
|
||||||
{ :name => :description},
|
{ :name => :description},
|
||||||
#had to hack acts_as_loggable/log.rb to get this to work
|
#had to hack acts_as_loggable/log.rb to get this to work
|
||||||
{ :name => :bike_action__action, :field_label => 'Action'}
|
{ :name => :bike_action__action, :field_label => 'Action', :min_chars => 1 }
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
=begin
|
|
||||||
#override with nil to remove actions
|
#override with nil to remove actions
|
||||||
def default_bbar
|
def default_bbar
|
||||||
bbar = [ :search ]
|
bbar = [ :search ]
|
||||||
@@ -58,5 +60,5 @@ class BikeLogs < Netzke::Basepack::Grid
|
|||||||
bbar.concat [ :add_in_form ] if can? :create, ::ActsAsLoggable::Log
|
bbar.concat [ :add_in_form ] if can? :create, ::ActsAsLoggable::Log
|
||||||
bbar
|
bbar
|
||||||
end
|
end
|
||||||
=end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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,6 +4,7 @@ class BikeModels < Netzke::Basepack::Grid
|
|||||||
|
|
||||||
c.model = "BikeModel"
|
c.model = "BikeModel"
|
||||||
c.title = "Models"
|
c.title = "Models"
|
||||||
|
c.force_fit = true
|
||||||
c.data_store = {auto_load: false}
|
c.data_store = {auto_load: false}
|
||||||
c.scope = lambda { |rel| rel.where(:bike_brand_id => session[:selected_bike_brand_id]);}
|
c.scope = lambda { |rel| rel.where(:bike_brand_id => session[:selected_bike_brand_id]);}
|
||||||
c.strong_default_attrs = {
|
c.strong_default_attrs = {
|
||||||
|
|||||||
+40
-11
@@ -2,12 +2,15 @@ class Bikes < Netzke::Basepack::Grid
|
|||||||
def configure(c)
|
def configure(c)
|
||||||
super
|
super
|
||||||
c.model = "Bike"
|
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 = [
|
c.columns = [
|
||||||
{ :name => :shop_id, :text => 'Shop ID'},
|
{ :name => :shop_id, :text => 'Shop ID', :default_value => Bike.last.id.to_i + 1},
|
||||||
:serial_number,
|
:serial_number,
|
||||||
{ :name => :bike_brand__brand, :text => 'Brand' },
|
{ :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'},
|
||||||
{ :name => :bike_model__model, :text => 'Model',
|
{ :name => :model, :text => 'Model',
|
||||||
:scope => lambda { |rel|
|
:scope => lambda { |rel|
|
||||||
if session[:selected_bike_brand_id]
|
if session[:selected_bike_brand_id]
|
||||||
rel.where(:bike_brand_id => session[:selected_bike_brand_id])
|
rel.where(:bike_brand_id => session[:selected_bike_brand_id])
|
||||||
@@ -16,22 +19,48 @@ class Bikes < Netzke::Basepack::Grid
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
:color,
|
#needs to have type :action or else won't work in grid, because... netzke
|
||||||
{ :name => :bike_style__style, :text => 'Style' },
|
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block,
|
||||||
:seat_tube_height,
|
:default_value => '000000'},
|
||||||
:top_tube_length,
|
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style', :default_value => BikeStyle.first.id},
|
||||||
:wheel_size,
|
{ :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'},
|
||||||
:value,
|
:value,
|
||||||
{ :name => :bike_condition__condition, :text => 'Condition'},
|
{ :id => :bike_condition__condition, :name => :bike_condition__condition, :text => 'Condition', :default_value => BikeCondition.first.id},
|
||||||
{ :name => :bike_status__status, :text => 'Status'},
|
{ :id => :bike_purpose__purpose, :name => :bike_purpose__purpose, :text => 'Purpose', :default_value => BikePurpose.first.id},
|
||||||
{ :name => :owner, :getter => lambda { |rec|
|
{ :name => :owner, :getter => lambda { |rec|
|
||||||
user = rec.owner
|
user = rec.owner
|
||||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
c.prohibit_delete = true if cannot? :delete, @bike
|
||||||
|
end
|
||||||
|
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 => "color", :xtype => "xcolorcombo"},
|
||||||
|
{ :name => :bike_style__style, :field_label => 'Style', :min_chars => 1},
|
||||||
|
{ :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 => :value, :field_label => 'Value'},
|
||||||
|
{ :name => :bike_condition__condition, :field_label => 'Condition', :min_chars => 1},
|
||||||
|
{ :name => :bike_purpose__purpose, :field_label => 'Purpose', :min_chars => 1}
|
||||||
|
]
|
||||||
|
end
|
||||||
#override with nil to remove actions
|
#override with nil to remove actions
|
||||||
def default_bbar
|
def default_bbar
|
||||||
[ :apply, :add_in_form, :search ]
|
[ :apply, :add_in_form, :search ]
|
||||||
|
|||||||
@@ -3,11 +3,24 @@
|
|||||||
// calling superclass's initComponent
|
// calling superclass's initComponent
|
||||||
this.callParent();
|
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
|
// setting the 'rowclick' event
|
||||||
var view = this.getView();
|
var view = this.getView();
|
||||||
view.on('itemclick', function(view, record){
|
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!
|
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
|
||||||
this.selectBikeBrand({bike_brand_id: record.get('bike_brand__brand')});
|
this.selectBikeBrand({bike_brand_id: record.get('bike_brand__brand')});
|
||||||
}, this);
|
}, this);
|
||||||
|
},
|
||||||
|
colorBlock: function(value){
|
||||||
|
return Ext.String.format('<div style="display:inline-block">{1}</div><div style="background-color:#{0};width:50px;height:10px;display:inline-block;margin:0 5px 0 5px;border:solid;border-color:gray;border-width:1px;"></div>', value, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ class BikesBorder < Netzke::Base
|
|||||||
# Remember regions collapse state and size
|
# Remember regions collapse state and size
|
||||||
include Netzke::Basepack::ItemPersistence
|
include Netzke::Basepack::ItemPersistence
|
||||||
component :bikes
|
component :bikes
|
||||||
component :bike_logs_and_tasks_accordian
|
component :bike_lower_tabs
|
||||||
|
|
||||||
def configure(c)
|
def configure(c)
|
||||||
super
|
super
|
||||||
@@ -10,7 +10,7 @@ class BikesBorder < Netzke::Base
|
|||||||
c.title = "Bikes"
|
c.title = "Bikes"
|
||||||
c.items = [
|
c.items = [
|
||||||
{ netzke_component: :bikes, region: :center, split: true },
|
{ netzke_component: :bikes, region: :center, split: true },
|
||||||
{ netzke_component: :bike_logs_and_tasks_accordian, region: :south, height: 300, split: true}
|
{ netzke_component: :bike_lower_tabs, region: :south, height: 300, split: true}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,30 @@
|
|||||||
|
|
||||||
// setting the 'rowclick' event
|
// setting the 'rowclick' event
|
||||||
var view = this.getComponent('bikes').getView();
|
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){
|
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!
|
// 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')});
|
this.selectBike({bike_id: record.get('id')});
|
||||||
if( this.queryById('bike_logs') ){
|
// query for these components again, can change if not visible because of accordian
|
||||||
this.queryById('bike_logs').getStore().load();
|
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('tasks') ){
|
if( bike_tasks_comp ){
|
||||||
this.queryById('tasks').getStore().load();
|
bike_tasks_comp.getStore().load();
|
||||||
|
bike_tasks_comp.enable();
|
||||||
}
|
}
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
class CheckIns < Netzke::Basepack::Grid
|
||||||
|
|
||||||
|
def configure(c)
|
||||||
|
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")).
|
||||||
|
where("start_date >= ?", Time.zone.now.beginning_of_day);
|
||||||
|
}
|
||||||
|
c.columns = [
|
||||||
|
{ :name => :name, :getter => lambda{ |rec|
|
||||||
|
user = User.find_by_id(rec.loggable_id)
|
||||||
|
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ :name => "Status", :getter => lambda{ |rec| rec.start_date == rec.end_date ? "Checked In" : "Checked Out" } },
|
||||||
|
:start_date,
|
||||||
|
:end_date,
|
||||||
|
]
|
||||||
|
c.prohibit_update = true
|
||||||
|
c.prohibit_create = true
|
||||||
|
c.prohibit_delete = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_bbar
|
||||||
|
[ :search ]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
class Customers < Netzke::Basepack::Grid
|
class Customers < Netzke::Basepack::Grid
|
||||||
def configure(c)
|
def configure(c)
|
||||||
c.model = "Customer"
|
c.model = "Customer"
|
||||||
|
c.force_fit = true
|
||||||
end
|
end
|
||||||
|
|
||||||
#override with nil to remove actions
|
#override with nil to remove actions
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
this.callParent();
|
this.callParent();
|
||||||
this.getView().on('itemclick', function(view, record){
|
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!
|
// 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.selectCustomer({customer_id: record.get('id'), customer_type: 'Customer'});
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class Logs < Netzke::Basepack::Grid
|
|||||||
super
|
super
|
||||||
c.header = false
|
c.header = false
|
||||||
c.model = "ActsAsLoggable::Log"
|
c.model = "ActsAsLoggable::Log"
|
||||||
|
c.force_fit = true
|
||||||
c.columns = [
|
c.columns = [
|
||||||
:loggable_id,
|
:loggable_id,
|
||||||
:loggable_type,
|
:loggable_type,
|
||||||
@@ -22,4 +23,25 @@ class Logs < Netzke::Basepack::Grid
|
|||||||
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration}
|
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def default_fields_for_forms
|
||||||
|
[
|
||||||
|
:loggable_id,
|
||||||
|
:loggable_type,
|
||||||
|
:logger_id,
|
||||||
|
:logger_type,
|
||||||
|
:context,
|
||||||
|
:start_date,
|
||||||
|
:end_date,
|
||||||
|
:description,
|
||||||
|
:log_action_id,
|
||||||
|
:log_action_type,
|
||||||
|
:created_at,
|
||||||
|
:updated_at
|
||||||
|
].map{ |x| { :name => x, :field_label => x.to_s.humanize} }
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_bbar
|
||||||
|
[ :apply, :add_in_form, :search ]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Tasks < Netzke::Basepack::Grid
|
|||||||
|
|
||||||
c.header = false
|
c.header = false
|
||||||
c.model = "Task"
|
c.model = "Task"
|
||||||
|
c.force_fit = true
|
||||||
c.scope = lambda{ |rel|
|
c.scope = lambda{ |rel|
|
||||||
if session[:selected_bike_id]
|
if session[:selected_bike_id]
|
||||||
rel.where(:task_list_id => Bike.find_by_id(session[:selected_bike_id]).task_list.id)
|
rel.where(:task_list_id => Bike.find_by_id(session[:selected_bike_id]).task_list.id)
|
||||||
@@ -21,20 +22,21 @@ class Tasks < Netzke::Basepack::Grid
|
|||||||
:task_list_id => task_list_id
|
:task_list_id => task_list_id
|
||||||
}
|
}
|
||||||
c.columns = [
|
c.columns = [
|
||||||
|
:done,
|
||||||
:task,
|
:task,
|
||||||
:notes,
|
:notes
|
||||||
:done
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_fields_for_forms
|
def default_fields_for_forms
|
||||||
fields = []
|
bike = Bike.find_by_id(session[:selected_bike_id])
|
||||||
fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
|
bike = "Select a Bike First!" if bike.nil?
|
||||||
fields.concat( [
|
[
|
||||||
|
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Bike Selected", :value => "#{bike.to_s}"},
|
||||||
|
:done,
|
||||||
:task,
|
:task,
|
||||||
:notes,
|
:notes,
|
||||||
:done
|
]
|
||||||
])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#override with nil to remove actions
|
#override with nil to remove actions
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class TransactionLogs < Netzke::Basepack::Grid
|
|||||||
|
|
||||||
c.model = "ActsAsLoggable::Log"
|
c.model = "ActsAsLoggable::Log"
|
||||||
c.title = "Transaction Payments"
|
c.title = "Transaction Payments"
|
||||||
|
c.force_fit = true
|
||||||
c.data_store = {auto_load: false}
|
c.data_store = {auto_load: false}
|
||||||
c.scope = lambda { |rel| rel.where(:loggable_type => 'Transaction',:loggable_id => session[:selected_transaction_id]);}
|
c.scope = lambda { |rel| rel.where(:loggable_type => 'Transaction',:loggable_id => session[:selected_transaction_id]);}
|
||||||
c.strong_default_attrs = {
|
c.strong_default_attrs = {
|
||||||
@@ -20,7 +21,7 @@ class TransactionLogs < Netzke::Basepack::Grid
|
|||||||
c.columns = [
|
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 => :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 => :description, :text => "Amount"} ,
|
||||||
{ :name => :transaction_action__action, :text => 'Method'},
|
{ :name => :transaction_action__action, :text => 'Method', :default_value => ::ActsAsLoggable::TransactionAction.first.id},
|
||||||
{ :name => :logged_by, :getter => lambda{ |rec|
|
{ :name => :logged_by, :getter => lambda{ |rec|
|
||||||
user = User.find_by_id(rec.logger_id)
|
user = User.find_by_id(rec.logger_id)
|
||||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||||
@@ -29,28 +30,27 @@ class TransactionLogs < Netzke::Basepack::Grid
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
c.prohibit_update = true if cannot? :update, ::ActsAsLoggable::Log
|
@transaction_logs = ::ActsAsLoggable::Log.where(:loggable_type => "Transaction").all
|
||||||
c.prohibit_create = true if cannot? :create, ::ActsAsLoggable::Log
|
c.prohibit_update = true if cannot? :update, @transaction_logs
|
||||||
c.prohibit_delete = true if cannot? :delete, ::ActsAsLoggable::Log
|
c.prohibit_create = true if cannot? :create, @transaction_logs
|
||||||
|
c.prohibit_delete = true if cannot? :delete, @transaction_logs
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_fields_for_forms
|
def default_fields_for_forms
|
||||||
customer = nil
|
customer = "No Customer Selected"
|
||||||
item = nil
|
item = "No Item Selected"
|
||||||
if session[:selected_transaction_id]
|
|
||||||
trans = Transaction.find_by_id(session[:selected_transaction_id])
|
trans = Transaction.find_by_id(session[:selected_transaction_id])
|
||||||
|
if trans
|
||||||
customer = trans.customer
|
customer = trans.customer
|
||||||
item = trans.item
|
item = trans.item
|
||||||
end
|
end
|
||||||
customer = "No Customer Selected" if customer.nil?
|
|
||||||
item = "No Item Selected" if item.nil?
|
|
||||||
[
|
[
|
||||||
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment from:", :value => "#{customer.to_s}"},
|
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment from:", :value => "#{customer.to_s}"},
|
||||||
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"},
|
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"},
|
||||||
{ :name => :description, :xtype => 'numberfield', :field_label => 'Amount'},
|
{ :name => :description, :xtype => 'numberfield', :field_label => 'Amount'},
|
||||||
#had to hack acts_as_loggable/log.rb to get this to work
|
#had to hack acts_as_loggable/log.rb to get this to work
|
||||||
{ :name => :transaction_action__action, :field_label => 'Payment Method'}
|
{ :name => :transaction_action__action, :field_label => 'Payment Method', :min_chars => 1}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ class TransactionLogs < Netzke::Basepack::Grid
|
|||||||
#override with nil to remove actions
|
#override with nil to remove actions
|
||||||
def default_bbar
|
def default_bbar
|
||||||
bbar = [ :search ]
|
bbar = [ :search ]
|
||||||
bbar.concat [ :apply ] if can? :update, ::ActsAsLoggable::Log
|
bbar.concat [ :apply ] if can? :update, @transaction_logs
|
||||||
bbar.concat [:add_in_form ] if can? :create, ::ActsAsLoggable::Log
|
bbar.concat [:add_in_form ] if can? :create, @transaction_logs
|
||||||
bbar
|
bbar
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ class Transactions < Netzke::Basepack::Grid
|
|||||||
def configure(c)
|
def configure(c)
|
||||||
super
|
super
|
||||||
c.model = "Transaction"
|
c.model = "Transaction"
|
||||||
|
c.force_fit = true
|
||||||
c.strong_default_attrs = {
|
c.strong_default_attrs = {
|
||||||
:vendor_id => controller.current_user.id,
|
:vendor_id => controller.current_user.id,
|
||||||
:customer_id => session[:selected_customer_id],
|
:customer_id => session[:selected_customer_id],
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class TransactionsBorder < Netzke::Base
|
|||||||
component :transactions
|
component :transactions
|
||||||
component :transaction_logs
|
component :transaction_logs
|
||||||
#users and customers components are required for the transactions form
|
#users and customers components are required for the transactions form
|
||||||
component :users_and_customers_accordian
|
component :users_and_customers_lower_tabs
|
||||||
|
|
||||||
def configure(c)
|
def configure(c)
|
||||||
super
|
super
|
||||||
@@ -13,7 +13,7 @@ class TransactionsBorder < Netzke::Base
|
|||||||
c.items = [
|
c.items = [
|
||||||
{ netzke_component: :transactions, region: :center, height: 300, split: true },
|
{ netzke_component: :transactions, region: :center, height: 300, split: true },
|
||||||
{ netzke_component: :transaction_logs, region: :east, width: 300, split: true },
|
{ netzke_component: :transaction_logs, region: :east, width: 300, split: true },
|
||||||
{ netzke_component: :users_and_customers_accordian, region: :south, height: 300, split: true }
|
{ netzke_component: :users_and_customers_lower_tabs, region: :south, height: 300, split: true }
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,19 @@
|
|||||||
|
|
||||||
// setting the 'rowclick' event
|
// setting the 'rowclick' event
|
||||||
var view = this.getComponent('transactions').getView();
|
var view = this.getComponent('transactions').getView();
|
||||||
|
|
||||||
|
if( this.queryById('transaction_logs')){
|
||||||
|
this.queryById('transaction_logs').disable();
|
||||||
|
}
|
||||||
|
|
||||||
view.on('itemclick', function(view, record){
|
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!
|
// 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.selectTransaction({transaction_id: record.get('id')});
|
||||||
this.getComponent('transaction_logs').getStore().load();
|
this.getComponent('transaction_logs').getStore().load();
|
||||||
|
|
||||||
|
if( this.queryById('transaction_logs')){
|
||||||
|
this.queryById('transaction_logs').enable();
|
||||||
|
}
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
+15
-10
@@ -14,10 +14,16 @@ class UserLogs < Netzke::Basepack::Grid
|
|||||||
:copy_action_id => 4
|
:copy_action_id => 4
|
||||||
}
|
}
|
||||||
|
|
||||||
if can? :manage, ::ActsAsLoggable::Log
|
#this seems inefficient
|
||||||
|
if can? :manage, ::ActsAsLoggable::Log.where(:loggable_type => "User").all
|
||||||
#admins and staff
|
#admins and staff
|
||||||
user_log_scope = lambda { |rel| rel.where(:loggable_type => 'User',:loggable_id => session[:selected_user_id]);}
|
|
||||||
user_log_strong_default_attrs.merge!( { :loggable_id => session[:selected_user_id] } )
|
#if selected user nil, then the admin is viewing their own profile
|
||||||
|
selected_user = User.find_by_id(session[:selected_user_id])
|
||||||
|
selected_user = controller.current_user if selected_user.nil?
|
||||||
|
|
||||||
|
user_log_scope = lambda { |rel| rel.where(:loggable_type => 'User',:loggable_id => selected_user.id );}
|
||||||
|
user_log_strong_default_attrs.merge!( { :loggable_id => selected_user.id } )
|
||||||
user_log_data_store = {auto_load: true }
|
user_log_data_store = {auto_load: true }
|
||||||
else
|
else
|
||||||
#just users
|
#just users
|
||||||
@@ -27,19 +33,18 @@ class UserLogs < Netzke::Basepack::Grid
|
|||||||
end
|
end
|
||||||
|
|
||||||
c.model = "ActsAsLoggable::Log"
|
c.model = "ActsAsLoggable::Log"
|
||||||
c.title = "User History"
|
c.title = "User Timesheet"
|
||||||
|
c.force_fit = true
|
||||||
c.data_store = user_log_data_store
|
c.data_store = user_log_data_store
|
||||||
c.scope = user_log_scope
|
c.scope = user_log_scope
|
||||||
c.strong_default_attrs = user_log_strong_default_attrs
|
c.strong_default_attrs = user_log_strong_default_attrs
|
||||||
|
|
||||||
c.columns = [
|
c.columns = [
|
||||||
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
|
{ :name => :start_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 => :end_date, :format => "g:ia - D, M j - Y", :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},
|
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
|
||||||
:description,
|
:description,
|
||||||
{ :name => :user_action__action, :text => 'Action' },
|
{ :id => :user_action__action, :name => :user_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::UserAction.all.first.id },
|
||||||
:created_at,
|
|
||||||
:updated_at,
|
|
||||||
{ :name => :logged_by, :getter => lambda{ |rec|
|
{ :name => :logged_by, :getter => lambda{ |rec|
|
||||||
user = User.find_by_id(rec.logger_id)
|
user = User.find_by_id(rec.logger_id)
|
||||||
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
|
||||||
@@ -53,13 +58,13 @@ class UserLogs < Netzke::Basepack::Grid
|
|||||||
bike_store = Bike.all.map { |b| [b.id, b.shop_id] }
|
bike_store = Bike.all.map { |b| [b.id, b.shop_id] }
|
||||||
current_user ||= User.find_by_id(session[:selected_user_id]) || controller.current_user
|
current_user ||= User.find_by_id(session[:selected_user_id]) || controller.current_user
|
||||||
bike_id = current_user.bike.nil? ? nil : current_user.bike.id
|
bike_id = current_user.bike.nil? ? nil : current_user.bike.id
|
||||||
action_id = current_user.user_role.id
|
|
||||||
[
|
[
|
||||||
|
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Log for:", :value => "#{current_user.to_s}"},
|
||||||
{ :name => :start_date},
|
{ :name => :start_date},
|
||||||
{ :name => :end_date},
|
{ :name => :end_date},
|
||||||
{ :name => :description},
|
{ :name => :description},
|
||||||
#had to hack acts_as_loggable/log.rb to get this to work
|
#had to hack acts_as_loggable/log.rb to get this to work
|
||||||
{ :name => :user_action__action, :field_label => 'Action', :value => action_id},
|
{ :name => :user_action__action, :field_label => 'Action', :min_chars => 1},
|
||||||
{ :name => :for_bike, :checkboxName => :copy_log, :inputValue => true, :title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [
|
{ :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}
|
{:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
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,6 +19,7 @@ class UserProfiles < Netzke::Basepack::Grid
|
|||||||
|
|
||||||
c.model = "UserProfile"
|
c.model = "UserProfile"
|
||||||
c.title = "Profile"
|
c.title = "Profile"
|
||||||
|
c.force_fit = true
|
||||||
c.data_store = user_profiles_data_store
|
c.data_store = user_profiles_data_store
|
||||||
c.scope = user_profiles_scope
|
c.scope = user_profiles_scope
|
||||||
c.strong_default_attrs = user_profile_strong_default_attrs
|
c.strong_default_attrs = user_profile_strong_default_attrs
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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 => :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},
|
||||||
|
:created_at,
|
||||||
|
:updated_at,
|
||||||
|
:ends ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_fields_for_forms
|
||||||
|
[
|
||||||
|
{ :name => :user__full_name, :field_label => 'Name'},
|
||||||
|
{ :name => :role__role, :field_label => 'Role' },
|
||||||
|
{ :name => :ends }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
#override with nil to remove actions
|
||||||
|
def default_bbar
|
||||||
|
[ :apply, :add_in_form, :search ]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
class UserRoles < Netzke::Basepack::Grid
|
|
||||||
|
|
||||||
def configure(c)
|
|
||||||
super
|
|
||||||
c.model = "UserRole"
|
|
||||||
c.title = "User Roles"
|
|
||||||
c.columns = [ :role, :created_at, :updated_at, :ends ]
|
|
||||||
end
|
|
||||||
|
|
||||||
#override with nil to remove actions
|
|
||||||
def default_bbar
|
|
||||||
[ :apply, :add_in_form, :search ]
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -2,14 +2,29 @@ class UserStats < Netzke::Base
|
|||||||
|
|
||||||
def body_content(user)
|
def body_content(user)
|
||||||
bike = user.bike
|
bike = user.bike
|
||||||
%Q(
|
completed = user.completed_build_bikes
|
||||||
<div id="user_stats_page">
|
#TODO I think it's time to switch to a template
|
||||||
|
body = ""
|
||||||
|
body += %Q(<div id="user_stats_page">)
|
||||||
|
body += %Q(
|
||||||
|
<p>Username: #{user.username}</p>
|
||||||
|
<p>Total Credits Earned: #{user.total_earned_credits}</p>
|
||||||
|
<p>Credits Available: #{user.total_credits}</p>
|
||||||
|
<p>Credits Spent: #{user.total_credits_spent}</p>
|
||||||
<p>Total Hours Worked: #{user.total_hours}</p>
|
<p>Total Hours Worked: #{user.total_hours}</p>
|
||||||
<p>Hours worked in #{Time.now.strftime('%B')}: #{user.current_month_hours}</p>
|
<p>Hours worked in #{Time.now.strftime('%B')}: #{user.current_month_hours}</p>
|
||||||
<p>Current bike Shop ID: #{bike.shop_id if bike}</p>
|
<p>Current bike Shop ID: #{bike.shop_id if bike}</p>
|
||||||
<p>Current bike S/N: #{bike.serial_number if bike}</p>
|
<p>Current bike S/N: #{bike.serial_number if bike}</p>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
|
unless completed.empty?
|
||||||
|
body += %Q(<p>Previously built bikes (#{completed.count}):</p>)
|
||||||
|
body += %Q(<ul>)
|
||||||
|
completed.each do |b|
|
||||||
|
body += %Q(<li>#{b.shop_id}</li>)
|
||||||
|
end
|
||||||
|
body += %Q(</ul>)
|
||||||
|
end
|
||||||
|
body += %Q(</div>)
|
||||||
end
|
end
|
||||||
|
|
||||||
js_configure do |c|
|
js_configure do |c|
|
||||||
@@ -25,7 +40,7 @@ class UserStats < Netzke::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
def user
|
def user
|
||||||
controller.current_user
|
User.find_by_id(session[:selected_user_id]) || controller.current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class UserTransactions < Netzke::Basepack::Grid
|
|||||||
|
|
||||||
c.model = "Transaction"
|
c.model = "Transaction"
|
||||||
c.title = "Transactions"
|
c.title = "Transactions"
|
||||||
|
c.force_fit = true
|
||||||
c.scope = lambda { |rel| rel.where(:customer_id => controller.current_user.id, :customer_type => 'User');}
|
c.scope = lambda { |rel| rel.where(:customer_id => controller.current_user.id, :customer_type => 'User');}
|
||||||
c.data_store = { auto_load: true }
|
c.data_store = { auto_load: true }
|
||||||
c.columns = [
|
c.columns = [
|
||||||
|
|||||||
@@ -5,10 +5,18 @@
|
|||||||
|
|
||||||
// setting the 'rowclick' event
|
// setting the 'rowclick' event
|
||||||
var view = this.getComponent('user_transactions').getView();
|
var view = this.getComponent('user_transactions').getView();
|
||||||
|
|
||||||
|
if( this.queryById('transaction_logs')){
|
||||||
|
this.queryById('transaction_logs').disable();
|
||||||
|
}
|
||||||
view.on('itemclick', function(view, record){
|
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!
|
// 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.selectTransaction({transaction_id: record.get('id')});
|
||||||
this.getComponent('transaction_logs').getStore().load();
|
this.getComponent('transaction_logs').getStore().load();
|
||||||
|
|
||||||
|
if( this.queryById('transaction_logs')){
|
||||||
|
this.queryById('transaction_logs').enable();
|
||||||
|
}
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-3
@@ -1,17 +1,28 @@
|
|||||||
class Users < Netzke::Basepack::Grid
|
class Users < Netzke::Basepack::Grid
|
||||||
|
include Netzke::Basepack::ActionColumn
|
||||||
|
|
||||||
|
column :reset do |c|
|
||||||
|
c.type = :action
|
||||||
|
c.actions = [{name: :reset_password, icon: :lock_break}]
|
||||||
|
c.header = ""
|
||||||
|
c.width = 20
|
||||||
|
end
|
||||||
|
|
||||||
def configure(c)
|
def configure(c)
|
||||||
super
|
super
|
||||||
c.header = false
|
c.header = false
|
||||||
c.model = "User"
|
c.model = "User"
|
||||||
|
c.force_fit = true
|
||||||
|
|
||||||
c.columns = [
|
c.columns = [
|
||||||
|
{ :name => :username, :read_only => true },
|
||||||
:first_name,
|
:first_name,
|
||||||
:last_name,
|
:last_name,
|
||||||
:nickname,
|
|
||||||
:email,
|
:email,
|
||||||
:user_role__role,
|
{ :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
|
||||||
end
|
end
|
||||||
|
|
||||||
#override with nil to remove actions
|
#override with nil to remove actions
|
||||||
|
|||||||
@@ -2,10 +2,41 @@
|
|||||||
initComponent: function(){
|
initComponent: function(){
|
||||||
// calling superclass's initComponent
|
// calling superclass's initComponent
|
||||||
this.callParent();
|
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){
|
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!
|
// 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: 'User'});
|
this.selectCustomer({customer_id: record.get('id'), customer_type: 'User'});
|
||||||
}, this);
|
}, this);
|
||||||
|
},
|
||||||
|
onResetPassword: function(record){
|
||||||
|
user = record.data;
|
||||||
|
Ext.Msg.confirm(
|
||||||
|
"Reset Password",
|
||||||
|
"Are you sure you want to reset "+user.first_name+" "+user.last_name+"'s password?",
|
||||||
|
function(butt_id){
|
||||||
|
if( butt_id === "yes" ){
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/api/v1/reset',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
processData: false,
|
||||||
|
data: JSON.stringify({"user_id": user.id}),
|
||||||
|
complete: function() { },
|
||||||
|
success: function(data) {
|
||||||
|
Ext.Msg.alert("Success", "New Password: "+data.password);
|
||||||
|
},
|
||||||
|
error: function(data,textStatus) {
|
||||||
|
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["errors"][0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
class UsersAndCustomersAccordian < Netzke::Basepack::Accordion
|
class UsersAndCustomersLowerTabs < Netzke::Basepack::TabPanel
|
||||||
component :customers
|
component :customers
|
||||||
component :users
|
component :users
|
||||||
|
|
||||||
def configure(c)
|
def configure(c)
|
||||||
c.prevent_header = true
|
c.prevent_header = true
|
||||||
c.items = [ :customers, :users ]
|
c.items = [ :users, :customers ]
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2,16 +2,14 @@ class UsersAndProfilesBorder < Netzke::Base
|
|||||||
# Remember regions collapse state and size
|
# Remember regions collapse state and size
|
||||||
include Netzke::Basepack::ItemPersistence
|
include Netzke::Basepack::ItemPersistence
|
||||||
component :users
|
component :users
|
||||||
component :user_profiles
|
component :user_and_profiles_lower_tabs
|
||||||
component :user_logs
|
|
||||||
|
|
||||||
def configure(c)
|
def configure(c)
|
||||||
super
|
super
|
||||||
c.header = false
|
c.header = false
|
||||||
c.items = [
|
c.items = [
|
||||||
{ netzke_component: :users, header: "Users", region: :center, width: 300, split: true },
|
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
|
||||||
{ netzke_component: :user_profiles, region: :south, height: 150, split: true},
|
{ netzke_component: :user_and_profiles_lower_tabs, region: :south, height: 300, split: true}
|
||||||
{ netzke_component: :user_logs, region: :east, split: true}
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,48 @@
|
|||||||
initComponent: function(){
|
initComponent: function(){
|
||||||
// calling superclass's initComponent
|
// calling superclass's initComponent
|
||||||
this.callParent();
|
this.callParent();
|
||||||
var stats = this.getComponent('user_stats');
|
var stats = this.queryById('user_stats');
|
||||||
if (stats != undefined){
|
if (stats != undefined){
|
||||||
stats.updateStats();
|
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
|
// setting the 'rowclick' event
|
||||||
var view = this.getComponent('users').getView();
|
var view = this.getComponent('users').getView();
|
||||||
view.on('itemclick', function(view, record){
|
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!
|
// 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')});
|
this.selectUser({user_id: record.get('id')});
|
||||||
this.getComponent('user_profiles').getStore().load();
|
|
||||||
this.getComponent('user_logs').getStore().load();
|
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);
|
}, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
class Api::V1::BaseController < ActionController::Base
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
before_filter :authenticate_user
|
||||||
|
|
||||||
|
private
|
||||||
|
def authenticate_user
|
||||||
|
if params[:username]
|
||||||
|
user = User.find_for_database_authentication( :username => params[:username] )
|
||||||
|
@current_user = user if user && user.valid_password?( params[:password] )
|
||||||
|
|
||||||
|
if @current_user.nil?
|
||||||
|
msg = "Username/Password/Token invalid"
|
||||||
|
render :json => {:error => msg }, :status => 401 and return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
authenticate_user!
|
||||||
|
@current_user = current_user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
class Api::V1::BikesController < Api::V1::BaseController
|
||||||
|
CANNOT_MANAGE = "You do not have permission to manage bikes."
|
||||||
|
EXPECTED_BIKE = "Expected bike in submitted data"
|
||||||
|
NOT_FOUND = "The bike could not be found."
|
||||||
|
|
||||||
|
before_filter :check_bike_permission, except: :show
|
||||||
|
|
||||||
|
def create
|
||||||
|
if params[:bikes] && bike = params[:bikes].first
|
||||||
|
@bike = Bike.new(bike)
|
||||||
|
if !@bike.save
|
||||||
|
render json: { errors: @bike.errors }, status: 422 and return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
render json: { errors: [EXPECTED_BIKE]}, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@bike = Bike.find_by_id(params[:id])
|
||||||
|
if @bike.nil?
|
||||||
|
render json: { errors: [NOT_FOUND] }, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def check_bike_permission
|
||||||
|
if cannot? :manage, Bike
|
||||||
|
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
class Api::V1::LogsController < Api::V1::BaseController
|
||||||
|
|
||||||
|
def checkin
|
||||||
|
#must use @current_user since user may not have signed in
|
||||||
|
if @current_user.checked_in?
|
||||||
|
render :json => { "error" => "You are already checked in."}, :status => 404 and return
|
||||||
|
else
|
||||||
|
@current_user.checkin
|
||||||
|
render :nothing => true, :status => 204 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkout
|
||||||
|
#must use @current_user since user may not have signed in
|
||||||
|
if !@current_user.checked_in?
|
||||||
|
render :json => { "error" => "You were not even checked in."}, :status => 404 and return
|
||||||
|
else
|
||||||
|
@current_user.checkout
|
||||||
|
render :nothing => true, :status => 204 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
class Api::V1::TaskListsController < Api::V1::BaseController
|
||||||
|
CANNOT_MANAGE = "You do not have permission to manage this task list."
|
||||||
|
NOT_FOUND = "The task list could not be found."
|
||||||
|
|
||||||
|
before_filter :get_task_list
|
||||||
|
before_filter :check_task_list_permission, except: :show
|
||||||
|
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def get_task_list
|
||||||
|
@task_list = TaskList.find_by_id(params[:id])
|
||||||
|
if @task_list.nil?
|
||||||
|
render json: { errors: [NOT_FOUND] }, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_task_list_permission
|
||||||
|
if cannot? :manage, Bike and @task_list.item != current_user.bike
|
||||||
|
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
class Api::V1::TasksController < Api::V1::BaseController
|
||||||
|
EXPECTED_TASKS = "Expected a list of tasks in submitted data."
|
||||||
|
CANNOT_MANAGE = "You do not have permission to manage this task."
|
||||||
|
NOT_FOUND = "The task could not be found."
|
||||||
|
|
||||||
|
before_filter :validate_params
|
||||||
|
before_filter :get_tasks
|
||||||
|
before_filter :check_task_permission, except: :show
|
||||||
|
|
||||||
|
def update
|
||||||
|
errors = []
|
||||||
|
@tasks.each do |task_hash|
|
||||||
|
task = task_hash[:record]
|
||||||
|
attrs = task_hash[:new_attributes]
|
||||||
|
task.update_attributes(attrs)
|
||||||
|
if !task.errors.empty?
|
||||||
|
errors << { id: task.id, errors: task.errors }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !errors.empty?
|
||||||
|
render json: { errors: errors }, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def validate_params
|
||||||
|
if params[:tasks].nil? and not params[:tasks].kind_of?(Array)
|
||||||
|
render json: { errors: [EXPECTED_TASKS]}, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_tasks
|
||||||
|
@tasks = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
params[:tasks].each do |task|
|
||||||
|
t = Task.find_by_id(task[:id])
|
||||||
|
if t.nil?
|
||||||
|
errors << { id: task[:id], error: NOT_FOUND }
|
||||||
|
else
|
||||||
|
@tasks << { record: t, new_attributes: task }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !errors.empty?
|
||||||
|
render json: { errors: errors }, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_task_permission
|
||||||
|
errors = []
|
||||||
|
@tasks.each do |task_hash|
|
||||||
|
task = task_hash[:record]
|
||||||
|
if task.task_list.item != current_user.bike
|
||||||
|
errors << { id: task[:id], error: CANNOT_MANAGE }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if cannot? :manage, Bike and !errors.empty?
|
||||||
|
render json: { errors: errors}, status: 403 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
class Api::V1::TimeEntriesController < Api::V1::BaseController
|
||||||
|
EXPECTED_TIME_ENTRY = "Expected time entry in submitted data"
|
||||||
|
NOT_FOUND = "Time entry not found"
|
||||||
|
|
||||||
|
def create
|
||||||
|
if params[:time_entries] && time_entry = params[:time_entries].first
|
||||||
|
time_entry.merge!({ loggable_id: current_user.id,
|
||||||
|
logger_id: current_user.id })
|
||||||
|
bike_id = time_entry[:bike_id].to_i
|
||||||
|
|
||||||
|
@time_entry = TimeEntry.new(time_entry.except(:bike_id))
|
||||||
|
|
||||||
|
if bike_id > 0
|
||||||
|
@time_entry.copy_to_bike_history(bike_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
if !@time_entry.save
|
||||||
|
render json: { errors: @time_entry.errors }, status: 422 and return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
render json: { errors: [EXPECTED_TIME_ENTRY]}, status: 422 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete
|
||||||
|
if time_entry = TimeEntry.find_by_id(params[:id])
|
||||||
|
time_entry.delete
|
||||||
|
render nothing: true, status: 204 and return
|
||||||
|
else
|
||||||
|
render json: { errors: [NOT_FOUND]}, status: 404 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
new_pass = SecureRandom.hex[0,PASS_LENGTH]
|
||||||
|
user.password = new_pass
|
||||||
|
user.save
|
||||||
|
render :json => { "password" => new_pass}, :status => 200 and return
|
||||||
|
else
|
||||||
|
render :json => { "errors" => [CANNOT_MANAGE]}, :status => 403 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
=begin Is this here by accident? Commenting out for now (1/30/14)
|
||||||
|
def checkout
|
||||||
|
#must use @current_user since user may not have signed in
|
||||||
|
if !@current_user.checked_in?
|
||||||
|
render :json => { "error" => "You were not even checked in."}, :status => 404 and return
|
||||||
|
else
|
||||||
|
@current_user.checkout
|
||||||
|
render :nothing => true, :status => 204 and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
=end
|
||||||
|
end
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class BikesController < AuthenticatedController
|
||||||
|
|
||||||
|
def new
|
||||||
|
@brands = BikeBrand.all.map{ |b| [b.brand, b.id] }
|
||||||
|
@brands.unshift( ["Select a brand", -1] )
|
||||||
|
@wheel_sizes = BikeWheelSize.all.map{ |w| [w.display_string, w.id] }
|
||||||
|
@wheel_sizes.unshift( ["Select a wheel size", -1] )
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@bike = Bike.find_by_id(params[:id])
|
||||||
|
@task_list_id = @bike.task_list.id
|
||||||
|
@show_add_bike = true if params[:add_bike]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
class PanelController < ApplicationController
|
||||||
|
|
||||||
|
def index
|
||||||
|
render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
class SiteController < ApplicationController
|
class SiteController < ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application"
|
@bike = current_user.bike
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class TaskListsController < AuthenticatedController
|
||||||
|
def edit
|
||||||
|
@task_list = TaskList.find_by_id(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class TimeEntriesController < AuthenticatedController
|
||||||
|
|
||||||
|
def new
|
||||||
|
@bikes = Bike.all.map{ |b| [b.to_s , b.id] }
|
||||||
|
if bike = current_user.bike
|
||||||
|
@bikes.unshift( [bike.to_s, bike.id] )
|
||||||
|
end
|
||||||
|
@bikes.unshift( ["Non-bike work", -1] )
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@user_time_entries = TimeEntry.where(loggable_id: current_user.id)
|
||||||
|
@credits_available = current_user.total_credits
|
||||||
|
@hours_worked = current_user.total_hours
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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?
|
||||||
|
|
||||||
|
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
|
||||||
|
<p>#{sentence}</p>
|
||||||
|
#{messages}
|
||||||
|
HTML
|
||||||
|
|
||||||
|
html.html_safe
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,7 +3,9 @@ class Ability
|
|||||||
|
|
||||||
def initialize(current_user)
|
def initialize(current_user)
|
||||||
@current_user = current_user
|
@current_user = current_user
|
||||||
self.send(current_user.role.to_sym)
|
current_user.roles.each do |role|
|
||||||
|
self.send(role.role.to_sym)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin
|
def admin
|
||||||
@@ -21,8 +23,8 @@ class Ability
|
|||||||
|
|
||||||
def user
|
def user
|
||||||
can :read, :all
|
can :read, :all
|
||||||
can :update, Bike, :id => @current_user.bike_id unless @current_user.bike.nil?
|
can :manage, @current_user.bike unless @current_user.bike.nil?
|
||||||
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "Bike", :loggable_id => @current_user.bike_id }
|
can :manage, ::ActsAsLoggable::Log, :loggable_type => "Bike", :loggable_id => @current_user.bike_id
|
||||||
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "User", :loggable_id => @current_user.id }
|
can :manage, ::ActsAsLoggable::Log, :loggable_type => "User", :loggable_id => @current_user.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+21
-23
@@ -1,43 +1,37 @@
|
|||||||
class Bike < ActiveRecord::Base
|
class Bike < ActiveRecord::Base
|
||||||
acts_as_loggable
|
acts_as_loggable
|
||||||
attr_accessible :shop_id, :serial_number, :bike_brand_id, :bike_model_id, :color, :bike_style_id, :seat_tube_height,
|
attr_accessible :shop_id, :serial_number, :bike_brand_id, :model, :color, :bike_style_id, :seat_tube_height,
|
||||||
:top_tube_length, :wheel_size, :value, :bike_condition_id, :bike_status_id
|
:top_tube_length, :bike_wheel_size_id, :value, :bike_condition_id, :bike_purpose_id
|
||||||
|
|
||||||
has_many :transactions
|
has_many :transactions
|
||||||
|
|
||||||
has_one :owner, :class_name => 'User'
|
has_one :owner, :class_name => 'User'
|
||||||
has_one :task_list, :as => :item, :dependent => :destroy
|
has_one :task_list, :as => :item, :dependent => :destroy
|
||||||
belongs_to :bike_brand
|
belongs_to :bike_brand
|
||||||
belongs_to :bike_model
|
|
||||||
belongs_to :bike_style
|
belongs_to :bike_style
|
||||||
belongs_to :bike_condition
|
belongs_to :bike_condition
|
||||||
belongs_to :bike_status
|
belongs_to :bike_purpose
|
||||||
|
belongs_to :bike_wheel_size
|
||||||
|
|
||||||
validates :shop_id, :presence => true, :uniqueness => true, :length => { :minimum => 3 }
|
validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
|
||||||
validates :serial_number, :uniqueness => true, :length => { :minimum => 3 }
|
validates :serial_number, :length => { :minimum => 3 }
|
||||||
validates :bike_brand_id, :presence => true
|
validates :model, :length => { :maximum => 50 }
|
||||||
validates :bike_model_id, :presence => true
|
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" }
|
||||||
validates :color, :presence => true
|
#validates :color, :presence => true
|
||||||
validates :bike_style_id, :presence => true
|
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" }
|
||||||
validates :seat_tube_height, :presence => true
|
validates :seat_tube_height, :presence => true, :numericality => true
|
||||||
validates :top_tube_length, :presence => true
|
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" }
|
||||||
validates :wheel_size, :presence => true
|
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" }
|
||||||
#validates :value, :presence => true
|
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" }
|
||||||
validates :bike_condition_id, :presence => true
|
|
||||||
validates :bike_status_id, :presence => true
|
|
||||||
|
|
||||||
self.per_page = 15
|
self.per_page = 15
|
||||||
|
|
||||||
after_save :create_task_list
|
after_create :create_task_list
|
||||||
|
|
||||||
def brand
|
def brand
|
||||||
self.bike_brand
|
self.bike_brand
|
||||||
end
|
end
|
||||||
|
|
||||||
def model
|
|
||||||
self.bike_model
|
|
||||||
end
|
|
||||||
|
|
||||||
def style
|
def style
|
||||||
self.bike_style
|
self.bike_style
|
||||||
end
|
end
|
||||||
@@ -46,8 +40,12 @@ class Bike < ActiveRecord::Base
|
|||||||
self.bike_condition
|
self.bike_condition
|
||||||
end
|
end
|
||||||
|
|
||||||
def status
|
def purpose
|
||||||
self.bike_status
|
self.bike_purpose
|
||||||
|
end
|
||||||
|
|
||||||
|
def wheel_size
|
||||||
|
self.bike_wheel_size
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
class BikeModel < ActiveRecord::Base
|
class BikeModel < ActiveRecord::Base
|
||||||
attr_accessible :model, :bike_brand_id
|
attr_accessible :model, :bike_brand_id
|
||||||
|
|
||||||
has_many :bikes
|
|
||||||
belongs_to :bike_brand
|
belongs_to :bike_brand
|
||||||
|
|
||||||
default_scope order('model ASC')
|
default_scope order('model ASC')
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
class BikePurpose < ActiveRecord::Base
|
||||||
|
attr_accessible :purpose
|
||||||
|
|
||||||
|
belongs_to :bike
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
self.purpose
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
class BikeStatus < ActiveRecord::Base
|
|
||||||
attr_accessible :status
|
|
||||||
|
|
||||||
belongs_to :bike
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
self.status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
class CreditConversion < ActiveRecord::Base
|
||||||
|
attr_accessible :conversion
|
||||||
|
end
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class Role < ActiveRecord::Base
|
||||||
|
attr_accessible :role
|
||||||
|
|
||||||
|
has_many :user_role_joins
|
||||||
|
has_many :users, through: :user_role_joins
|
||||||
|
validates_uniqueness_of :role
|
||||||
|
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
self.role
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
self.role == other.role
|
||||||
|
end
|
||||||
|
end
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
class Task < ActiveRecord::Base
|
class Task < ActiveRecord::Base
|
||||||
attr_accessible :task, :notes, :done
|
attr_accessible :task, :notes, :done, :task_list_id
|
||||||
|
|
||||||
belongs_to :task_list
|
belongs_to :task_list
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class TaskList < ActiveRecord::Base
|
|||||||
attr_accessible :item_id, :item_type, :name
|
attr_accessible :item_id, :item_type, :name
|
||||||
|
|
||||||
belongs_to :item, :polymorphic => true
|
belongs_to :item, :polymorphic => true
|
||||||
has_many :tasks
|
has_many :tasks, order: "id ASC"
|
||||||
|
|
||||||
after_save :create_default_bike_tasks
|
after_save :create_default_bike_tasks
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
class TimeEntry < ActsAsLoggable::Log
|
||||||
|
default_scope where( loggable_type: "User",
|
||||||
|
logger_type: "User",
|
||||||
|
log_action_type: "ActsAsLoggable::UserAction").where("log_action_id != 4").order("start_date DESC")
|
||||||
|
|
||||||
|
def copy_to_bike_history(bike_id)
|
||||||
|
self.assign_attributes({
|
||||||
|
copy_log: true,
|
||||||
|
copy_type: 'Bike',
|
||||||
|
copy_id: bike_id,
|
||||||
|
copy_action_type: 'ActsAsLoggable::BikeAction',
|
||||||
|
copy_action_id: 4
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def duration
|
||||||
|
end_date - start_date
|
||||||
|
end
|
||||||
|
|
||||||
|
def duration_in_hours
|
||||||
|
(duration / 1.hour).round(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
log_action.try(:action)
|
||||||
|
end
|
||||||
|
end
|
||||||
+109
-10
@@ -7,16 +7,21 @@ class User < ActiveRecord::Base
|
|||||||
|
|
||||||
# Setup accessible (or protected) attributes for your model
|
# Setup accessible (or protected) attributes for your model
|
||||||
attr_accessible :email, :password, :password_confirmation, :remember_me,
|
attr_accessible :email, :password, :password_confirmation, :remember_me,
|
||||||
:first_name, :last_name, :nickname, :user_role_id, :bike_id,
|
:first_name, :last_name, :bike_id,
|
||||||
:user_profiles_attributes
|
:user_profiles_attributes, :username
|
||||||
|
|
||||||
has_many :transactions
|
has_many :transactions, as: :customer
|
||||||
|
has_many :transaction_logs, through: :transactions, source: :logs
|
||||||
has_many :user_profiles
|
has_many :user_profiles
|
||||||
accepts_nested_attributes_for :user_profiles, allow_destroy: false
|
accepts_nested_attributes_for :user_profiles, allow_destroy: false
|
||||||
|
|
||||||
has_one :user_role
|
has_many :user_role_joins, :conditions => ["ends IS NULL OR ends > ?", Time.now]
|
||||||
|
has_many :roles, through: :user_role_joins
|
||||||
|
|
||||||
belongs_to :bike
|
belongs_to :bike
|
||||||
|
|
||||||
|
default_scope order('username ASC')
|
||||||
|
|
||||||
validates :first_name, :presence => true
|
validates :first_name, :presence => true
|
||||||
validates :last_name, :presence => true
|
validates :last_name, :presence => true
|
||||||
|
|
||||||
@@ -24,29 +29,123 @@ class User < ActiveRecord::Base
|
|||||||
"#{first_name} #{last_name}"
|
"#{first_name} #{last_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def email_required?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def full_name
|
def full_name
|
||||||
to_s
|
to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def role
|
def role?(role)
|
||||||
user_role.role
|
if role.kind_of?(String) or role.kind_of?(Symbol)
|
||||||
|
role = Role.find_by_role(role.to_s)
|
||||||
|
end
|
||||||
|
roles.include?(role)
|
||||||
end
|
end
|
||||||
|
|
||||||
def role?(role)
|
### TODO methods below probably belong somewhere else
|
||||||
user_role.to_s == role.to_s
|
|
||||||
|
def completed_build_bikes
|
||||||
|
#default BUILDBIKE/CLASS ID is 5
|
||||||
|
purpose_id = 5
|
||||||
|
Bike.find_by_sql("
|
||||||
|
SELECT *
|
||||||
|
FROM bikes
|
||||||
|
INNER JOIN(
|
||||||
|
SELECT *
|
||||||
|
FROM transactions
|
||||||
|
WHERE customer_id = #{self.id}
|
||||||
|
) AS transactions ON bikes.id = transactions.bike_id
|
||||||
|
WHERE bike_purpose_id = #{purpose_id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_credits
|
||||||
|
total_earned_credits - total_credits_spent
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_credits_spent
|
||||||
|
log_action_id = 1 #TIME
|
||||||
|
transaction_logs.
|
||||||
|
where( "log_action_id = ? AND log_action_type = ?",
|
||||||
|
log_action_id, ::ActsAsLoggable::TransactionAction.to_s).
|
||||||
|
sum{ |r| r.description.to_i }.round(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_earned_credits
|
||||||
|
volunteer_id = 1
|
||||||
|
staff_id = 3
|
||||||
|
|
||||||
|
# Find the first credit conversion which has a created_at date before the
|
||||||
|
# log's created_at date and join it to the log's row so we can calculate
|
||||||
|
# the credits earned from that log entry (each log could have a different
|
||||||
|
# conversion rate)
|
||||||
|
#
|
||||||
|
# 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(["
|
||||||
|
SELECT DISTINCT ON (logs.created_at) start_date, end_date,
|
||||||
|
conversion.conversion, conversion.created_at
|
||||||
|
FROM logs
|
||||||
|
INNER JOIN(
|
||||||
|
SELECT conversion, created_at
|
||||||
|
FROM credit_conversions
|
||||||
|
) AS conversion ON logs.created_at > conversion.created_at
|
||||||
|
WHERE logs.loggable_id = :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}]).
|
||||||
|
sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_hours
|
def total_hours
|
||||||
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
|
log_action_id = 4 #CHECKIN
|
||||||
|
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_month_hours
|
def current_month_hours
|
||||||
|
log_action_id = 4 #CHECKIN
|
||||||
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month
|
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month
|
||||||
current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month)
|
current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month)
|
||||||
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id)
|
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s)
|
||||||
.where( :start_date => current_month_range)
|
.where( :start_date => current_month_range)
|
||||||
.where( :end_date => current_month_range)
|
.where( :end_date => current_month_range)
|
||||||
.sum { |l| (l.end_date - l.start_date)/3600 }
|
.sum { |l| (l.end_date - l.start_date)/3600 }
|
||||||
.round(2)
|
.round(2)
|
||||||
end
|
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).
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
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).
|
||||||
|
where("start_date >= ?", Time.zone.now.beginning_of_day).
|
||||||
|
where("start_date = end_date").first
|
||||||
|
checked.end_date = Time.now
|
||||||
|
checked.save
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
class UserProfile < ActiveRecord::Base
|
class UserProfile < ActiveRecord::Base
|
||||||
# Setup accessible (or protected) attributes for your model
|
|
||||||
attr_accessible :user_id, :addrStreet1, :addrStreet2, :addrCity,
|
attr_accessible :user_id, :addrStreet1, :addrStreet2, :addrCity,
|
||||||
:addrState, :addrZip, :phone
|
:addrState, :addrZip, :phone
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ class UserProfile < ActiveRecord::Base
|
|||||||
#validates :addrCity , :presence => true
|
#validates :addrCity , :presence => true
|
||||||
#validates :addrState , :presence => true
|
#validates :addrState , :presence => true
|
||||||
#validates :addrZip , :presence => true
|
#validates :addrZip , :presence => true
|
||||||
validates :phone, :presence => true
|
#validates :phone, :presence => true
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
[addrStreet1, addrStreet2, addrCity, addrState, addrZip, phone].join(" - ")
|
[addrStreet1, addrStreet2, addrCity, addrState, addrZip, phone].join(" - ")
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
class UserRole < ActiveRecord::Base
|
|
||||||
attr_accessible :role
|
|
||||||
|
|
||||||
belongs_to :user
|
|
||||||
|
|
||||||
self.per_page = 15
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
self.role
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class UserRoleJoin < ActiveRecord::Base
|
||||||
|
self.table_name = :user_role_joins
|
||||||
|
attr_accessible :role_id, :user_id, :ends
|
||||||
|
|
||||||
|
belongs_to :user
|
||||||
|
belongs_to :role
|
||||||
|
|
||||||
|
validate :role_id, presence: true, numericality: true
|
||||||
|
validate :user_id, presence: true, numericality: true
|
||||||
|
validates_uniqueness_of :user_id, :scope => :role_id
|
||||||
|
|
||||||
|
self.per_page = 15
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
json.bikes [@bike] do |bike|
|
||||||
|
json.array! bike
|
||||||
|
json.set! :href, api_bike_path(bike)
|
||||||
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
json.bikes [@bike] do |bike|
|
||||||
|
json.array! bike
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
json.task_lists [@task_list] do |tl|
|
||||||
|
json.array! tl
|
||||||
|
json.links do
|
||||||
|
json.bike do
|
||||||
|
json.href api_bike_path(tl.item)
|
||||||
|
json.id tl.item_id
|
||||||
|
end
|
||||||
|
json.tasks tl.tasks do |task|
|
||||||
|
json.id task.id
|
||||||
|
json.done task.done
|
||||||
|
json.notes task.notes
|
||||||
|
json.task task.task
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
json.tasks [@tasks.map{|x| x[:record]}] do |task|
|
||||||
|
json.array! task
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
json.time_entries [@time_entry] do |time_entry|
|
||||||
|
json.array! time_entry
|
||||||
|
#json.set! :href, api_time_entry_path(time_entry)
|
||||||
|
end
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
%a{ class: "btn btn-default btn-lg", href: root_path}
|
||||||
|
%span{ class:"icon-home"}
|
||||||
|
%h2 Add Bike
|
||||||
|
|
||||||
|
%p
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "shop_id", placeholder: "Shop ID", type: "number", min:0, class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
= select_tag(:bike_brand_id, options_for_select(@brands))
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "model", placeholder: "Model", type: "text", class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "serial_number", placeholder: "Serial Number", type: "text", class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
.btn-group{ "data-toggle" => "buttons-radio"}
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 3} RD
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 1} MTN
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 2} HYB
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_style", value: 4} OTHER
|
||||||
|
%input{ id: "bike_style_id", type: "hidden"}
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
= select_tag(:bike_wheel_size_id, options_for_select(@wheel_sizes), id: :bike_wheel_size_id)
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
.btn-group{ "data-toggle" => "buttons-radio"}
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 2} Poor
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 3} Fair
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 4} Good
|
||||||
|
%label{ class: "btn btn-default"}
|
||||||
|
%input{ type: "radio", name: "bike_condition", value: 5} Excellent
|
||||||
|
%input{ id: "bike_condition_id", type: "hidden"}
|
||||||
|
.help-block
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "seat_tube_height", placeholder: "Seat Tube (cm)", type: "number", min: 0, max: 100, class: "input-lg" }
|
||||||
|
.help-block
|
||||||
|
-# Commenting this out until description is added to Bike
|
||||||
|
%p
|
||||||
|
%input{id: "bike_description", placeholder: "Short description", type: "text", class: "input-lg" }
|
||||||
|
.control-group
|
||||||
|
.controls
|
||||||
|
%input{id: "add_bike_submit", value: "Add Bike", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_bike_path}"}
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
- if @show_add_bike
|
||||||
|
%p
|
||||||
|
%a{class: "btn btn-lg btn-block btn-info", href: new_bike_path} Add Another Bike?
|
||||||
|
|
||||||
|
%a{ class: "btn btn-default btn-lg", href: root_path}
|
||||||
|
%span{ class:"icon-home"}
|
||||||
|
%h2 #{@bike.shop_id}: #{@bike.bike_brand}
|
||||||
|
%h2 #{@bike.model}
|
||||||
|
|
||||||
|
%dl.dl-horizontal
|
||||||
|
%dt Type
|
||||||
|
%dd #{@bike.bike_style}
|
||||||
|
%dt Wheel Size
|
||||||
|
%dd #{@bike.bike_wheel_size.display_string}
|
||||||
|
%dt Condition
|
||||||
|
%dd #{@bike.bike_condition}
|
||||||
|
%dt Seat Tube (cm)
|
||||||
|
%dd #{@bike.seat_tube_height}
|
||||||
|
%dt Purpose
|
||||||
|
%dd #{@bike.bike_purpose}
|
||||||
|
- if !@bike.value.nil?
|
||||||
|
%dt Value
|
||||||
|
%dd #{@bike.value}
|
||||||
|
- if !@bike.color.nil?
|
||||||
|
%dt Color
|
||||||
|
%dd{ style: "width: 50px; background-color: ##{@bike.color}; border: black; border-width: 1px; border-style: solid;"} #{@bike.color}
|
||||||
|
%a{class: "btn btn-lg btn-block btn-primary", href: edit_task_list_path(@task_list_id) } View Checklist
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
<%- if controller_name != 'sessions' %>
|
<%- if controller_name != 'sessions' %>
|
||||||
<%= link_to "Sign in", new_session_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Sign in", new_session_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
||||||
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Sign up", new_registration_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
|
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
|
||||||
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
||||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
|
<p>
|
||||||
|
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "btn btn-block btn-default" %><br />
|
||||||
|
</p>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
||||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
|
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
||||||
<%- if devise_mapping.omniauthable? %>
|
<%- if devise_mapping.omniauthable? %>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<h2>Forgot your password?</h2>
|
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
|
||||||
|
|
||||||
|
<h2>Forgot your password?</h2>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
|
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
|
||||||
<%= devise_error_messages! %>
|
<%= devise_error_messages! %>
|
||||||
|
|
||||||
<div><%= f.label :email %><br />
|
<div class="form-group">
|
||||||
<%= f.email_field :email %></div>
|
<%= f.email_field :email, placeholder: "Email", class: "form-control input-lg" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div><%= f.submit "Send me reset password instructions" %></div>
|
<div><%= f.submit "Reset Password", class:"btn btn-lg btn-primary"%></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render "links" %>
|
<%= render "links" %>
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
|
%p
|
||||||
%fieldset
|
%fieldset
|
||||||
= f.label :addrStreet1
|
.form-group
|
||||||
%br/
|
= f.text_field :addrStreet1, placeholder: "Street Address Line 1", :class => "form-control input-lg"
|
||||||
= f.text_field :addrStreet1
|
.form-group
|
||||||
%br/
|
= f.text_field :addrStreet2, placeholder: "Street Address Line 2",:class => "form-control input-lg"
|
||||||
= f.label :addrStreet2
|
.form-group
|
||||||
%br/
|
= f.text_field :addrCity, placeholder: "City", :class => "form-control input-lg"
|
||||||
= f.text_field :addrStreet2
|
.form-group
|
||||||
%br/
|
= f.text_field :addrState, placeholder: "State Abbreviation", :class => "form-control input-lg"
|
||||||
= f.label :addrCity
|
.form-group
|
||||||
%br/
|
= f.text_field :addrZip, placeholder: "Zip Code", :class => "form-control input-lg"
|
||||||
= f.text_field :addrCity
|
.form-group
|
||||||
%br/
|
= f.text_field :phone, placeholder: "Phone", :class => "form-control input-lg"
|
||||||
= f.label :addrState
|
|
||||||
%br/
|
|
||||||
= f.text_field :addrState
|
|
||||||
%br/
|
|
||||||
= f.label :addrZip
|
|
||||||
%br/
|
|
||||||
= f.text_field :addrZip
|
|
||||||
%br/
|
|
||||||
= f.label :phone
|
|
||||||
%br/
|
|
||||||
= f.text_field :phone
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user