Author SHA1 Message Date
Jason Denney 01d1dcdcf1 WIP, got a component enabling/disabling on select. 2013-04-25 21:06:35 -04:00
161 changed files with 614 additions and 5283 deletions
-2
View File
@@ -7,6 +7,4 @@ tmp/
.rbenv-version
.DS_Store
public/extjs
public/assets
public/images/icons
config/database.yml
+1
View File
@@ -1 +1,2 @@
--colour
-rturnip
-1
View File
@@ -1 +0,0 @@
velocipede
-1
View File
@@ -1 +0,0 @@
ruby-1.9.3-p374
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# This is an RVM Project .rvmrc file, used to automatically load the ruby
# development environment upon cd'ing into the directory
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
environment_id="ruby-1.9.3-p374@velocipede"
#
# Uncomment following line if you want options to be set only for given project.
#
# PROJECT_JRUBY_OPTS=( --1.9 )
#
# First we attempt to load the desired environment directly from the environment
# file. This is very fast and efficient compared to running through the entire
# CLI and selector. If you want feedback on which environment was used then
# insert the word 'use' after --create as this triggers verbose mode.
#
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
then
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
then
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
fi
else
# If the environment file has not yet been created, use the RVM CLI to select.
if ! rvm --create use "$environment_id"
then
echo "Failed to create RVM environment '${environment_id}'."
return 1
fi
fi
#
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
# it be automatically loaded. Uncomment the following and adjust the filename if
# necessary.
#
# filename=".gems"
# if [[ -s "$filename" ]]
# then
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
# fi
# If you use bundler, this might be useful to you:
# if command -v bundle && [[ -s Gemfile ]]
# then
# bundle install
# fi
+10 -11
View File
@@ -14,13 +14,13 @@ gem 'decent_exposure', '~> 1.0.1'
gem 'devise', '~> 2.0.4'
gem 'haml-rails', '~> 0.3.4'
gem 'jquery-rails', '~> 2.0'
gem 'pg', '~> 0.17.1'
gem 'sqlite3', '~> 1.3.5'
gem 'will_paginate', '~> 3.0.3'
gem 'jbuilder', '~> 2.0.3'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem "twitter-bootstrap-rails", "~> 2.0.3"
@@ -31,21 +31,20 @@ group :assets do
end
group :development, :test do
gem 'rspec-rails', '~> 2.14.0'
gem 'rspec-rails', '~> 2.8.1'
gem 'factory_girl_rails', '~> 1.2'
gem 'pry', '~> 0.9.8'
gem 'faker', '~> 1.2.0'
end
group :test do
gem 'shoulda-matchers', '~> 1.0.0'
gem 'capybara', '~> 2.2.1'
gem 'poltergeist', '~> 1.5.0'
gem 'database_cleaner', '~> 1.2.0'
gem 'launchy', '~> 2.4.2'
gem 'spork', '~> 0.9.2'
gem 'capybara', '~> 1.1.2'
gem 'turnip', '~> 0.3.0'
gem 'database_cleaner'
gem 'launchy'
gem 'spork'
#guard dependency for Mac OS 10
gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
gem 'guard-spork', '~> 1.5.1'
gem 'guard-rspec', '~> 4.2.6'
gem 'guard-spork'
gem 'guard-rspec'
end
+90 -99
View File
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/spacemunkay/acts_as_loggable.git
revision: 8484dc1f7a58699705c5ffb593ebdf700a9f374a
revision: 6033fe5a44878bb374f73fe473fed9a3396ddc74
specs:
acts_as_loggable (0.0.7)
activerecord (>= 3.0)
@@ -36,62 +36,60 @@ GEM
activesupport (3.2.13)
i18n (= 0.6.1)
multi_json (~> 1.0)
addressable (2.3.5)
arel (3.0.3)
bcrypt-ruby (3.1.2)
bootstrap-will_paginate (0.0.10)
addressable (2.3.3)
arel (3.0.2)
bcrypt-ruby (3.0.1)
bootstrap-will_paginate (0.0.9)
will_paginate
builder (3.0.4)
cancan (1.6.10)
capybara (2.2.1)
cancan (1.6.9)
capybara (1.1.4)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
celluloid (0.15.2)
timers (~> 1.1.0)
childprocess (0.4.0)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11)
cliver (0.3.2)
coderay (1.1.0)
coderay (1.0.9)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.7.0)
commonjs (0.2.7)
database_cleaner (1.2.0)
coffee-script-source (1.6.2)
commonjs (0.2.6)
database_cleaner (0.9.1)
decent_exposure (1.0.2)
devise (2.0.6)
devise (2.0.5)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3)
railties (~> 3.1)
warden (~> 1.1.1)
diff-lcs (1.2.5)
diff-lcs (1.1.3)
erubis (2.7.0)
execjs (2.0.2)
execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (2.6.4)
activesupport (>= 2.3.9)
factory_girl_rails (1.7.0)
factory_girl (~> 2.6.0)
railties (>= 3.0.0)
faker (1.2.0)
i18n (~> 0.5)
ffi (1.9.3)
ffi (1.6.0)
formatador (0.2.4)
guard (2.4.0)
gherkin (2.11.6)
json (>= 1.7.6)
guard (1.7.0)
formatador (>= 0.2.4)
listen (~> 2.1)
lumberjack (~> 1.0)
pry (>= 0.9.12)
thor (>= 0.18.1)
guard-rspec (4.2.6)
guard (~> 2.1)
rspec (>= 2.14, < 4.0)
guard-spork (1.5.1)
listen (>= 0.6.0)
lumberjack (>= 1.0.2)
pry (>= 0.9.10)
thor (>= 0.14.6)
guard-rspec (1.2.1)
guard (>= 1.1)
guard-spork (1.5.0)
childprocess (>= 0.2.3)
guard (>= 1.1)
spork (>= 0.8.4)
@@ -101,17 +99,14 @@ GEM
activesupport (>= 3.1, < 4.1)
haml (~> 3.1)
railties (>= 3.1, < 4.1)
hike (1.2.3)
hike (1.2.1)
i18n (0.6.1)
jbuilder (2.0.3)
activesupport (>= 3.0.0)
multi_json (>= 1.2.0)
journey (1.0.4)
jquery-rails (2.3.0)
jquery-rails (2.2.1)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.1)
launchy (2.4.2)
json (1.7.7)
launchy (2.2.0)
addressable (~> 2.3)
less (2.2.2)
commonjs (~> 0.2.6)
@@ -119,38 +114,28 @@ GEM
actionpack (>= 3.1)
less (~> 2.2.0)
libv8 (3.3.10.4)
listen (2.4.1)
celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
lumberjack (1.0.4)
mail (2.5.4)
listen (0.7.3)
lumberjack (1.0.3)
mail (2.5.3)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.5.2)
multi_json (1.8.4)
netzke-basepack (0.8.4)
method_source (0.8.1)
mime-types (1.22)
multi_json (1.7.2)
netzke-basepack (0.8.2)
netzke-core (~> 0.8.2)
netzke-cancan (0.8.2)
cancan
netzke-core
netzke-core (0.8.4)
netzke-core (0.8.3)
execjs
uglifier
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
nokogiri (1.5.9)
orm_adapter (0.0.7)
pg (0.17.1)
poltergeist (1.5.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyglot (0.3.3)
pry (0.9.12.6)
coderay (~> 1.0)
pry (0.9.12)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.4)
rack (1.4.5)
@@ -175,58 +160,66 @@ GEM
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.1.1)
rb-fsevent (0.9.4)
rb-inotify (0.9.3)
ffi (>= 0.5.0)
rake (10.0.4)
rdoc (3.12.2)
json (~> 1.4)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.7)
rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.5)
rspec-rails (2.14.1)
rspec (2.8.0)
rspec-core (~> 2.8.0)
rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.8.0)
rspec-core (2.8.0)
rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.8.0)
rspec-rails (2.8.1)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec (~> 2.8.0)
rubyzip (0.9.9)
sass (3.2.7)
sass-rails (3.2.6)
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)
slop (3.4.7)
slop (3.4.4)
spork (0.9.2)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.7)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thor (0.18.1)
tilt (1.4.1)
timers (1.1.0)
treetop (1.4.15)
tilt (1.3.6)
treetop (1.4.12)
polyglot
polyglot (>= 0.3.1)
turnip (0.3.1)
gherkin (>= 2.5)
rspec (~> 2.0)
twitter-bootstrap-rails (2.0.9)
actionpack (>= 3.1)
less-rails (~> 2.2.2)
railties (>= 3.1)
therubyracer (~> 0.10.1)
tzinfo (0.3.38)
uglifier (2.4.0)
tzinfo (0.3.37)
uglifier (1.3.0)
execjs (>= 0.3.0)
json (>= 1.8.0)
multi_json (~> 1.0, >= 1.0.2)
warden (1.1.1)
rack (>= 1.0)
websocket-driver (0.3.2)
will_paginate (3.0.5)
xpath (2.0.0)
websocket (1.0.7)
will_paginate (3.0.4)
xpath (0.1.4)
nokogiri (~> 1.3)
PLATFORMS
@@ -236,30 +229,28 @@ DEPENDENCIES
acts_as_loggable!
bootstrap-will_paginate (~> 0.0.6)
cancan
capybara (~> 2.2.1)
capybara (~> 1.1.2)
coffee-rails (~> 3.2.1)
database_cleaner (~> 1.2.0)
database_cleaner
decent_exposure (~> 1.0.1)
devise (~> 2.0.4)
factory_girl_rails (~> 1.2)
faker (~> 1.2.0)
guard-rspec (~> 4.2.6)
guard-spork (~> 1.5.1)
guard-rspec
guard-spork
haml-rails (~> 0.3.4)
jbuilder (~> 2.0.3)
jquery-rails (~> 2.0)
launchy (~> 2.4.2)
launchy
netzke-basepack (~> 0.8.0)
netzke-cancan
netzke-core (~> 0.8.0)
pg (~> 0.17.1)
poltergeist (~> 1.5.0)
pry (~> 0.9.8)
rails (= 3.2.13)
rb-fsevent
rspec-rails (~> 2.14.0)
rspec-rails (~> 2.8.1)
sass-rails (~> 3.2.3)
shoulda-matchers (~> 1.0.0)
spork (~> 0.9.2)
spork
sqlite3 (~> 1.3.5)
turnip (~> 0.3.0)
twitter-bootstrap-rails (~> 2.0.3)
uglifier (>= 1.0.3)
will_paginate (~> 3.0.3)
+6 -45
View File
@@ -1,61 +1,22 @@
# About
A web application for bicycle collectives to track bicycles, bicycle work history, volunteer hours, volunteer work history, and volunteers currently in the shop.
See an overview video of what it looks like and how it works here: https://www.youtube.com/watch?v=0-JjM6d9nK4.
## Instructions/Guides
### Adding a User
To add a user, a user must sign themselves up from the initial login screen. An admin can later log in to grant that user admin privileges afterward, if desired.
### Adding a Bike
How to add a bike to the database: https://www.youtube.com/watch?v=1IchWUdMQ90.
## Views
The app has two different views. One for "core" volunteers which I consider admins, and another for "customer" volunteers which I consider users. Because of the Netzke/ExtJS framework I used, the current UI is non-intuitive and I feel like is only adequate for admins who can take the time to learn the system. I'm in the process of designing a simpler mobile friendly UI with the essential functionality that's intended for users.
## Deployment
Currently, at the Velocipede collective we have it running on a computer in the shop on the local network. The reason for this, is so that it's only accessible from within the shop. I'm weighing out different methods for ensuring that users can only log in from within the shop (and not from home), such as a shop password that only admins can see and would make visible from within the physical bike shop. I also don't have to worry about security issues while the app is in development. In the future, I'd like to see the app hosted much like http://freehub.bikekitchen.org/.
## Ideas
At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donations of old, unused smart phones, connected via local wifi, that can be used as stations to log a volunteer's work and hours. They can be secured by gluing cables into the earphone jacks to deter theft if necessary. This might be another way that the collective can promote reuse of materials.
# Developer Setup
1. use rvm
1. allow the .rvmrc file
1. `gem install bundler`
1. `bundle`
1. Install Postgres (Mac OSX instructions below)
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
1. `rake db:create db:migrate`
1. `rake db:seed`
1. Download extJS 4.1 (A version of 4.1 is hosted here: http://my.jasondenney.com/extjs-4.1.1.zip) Latest versions at http://www.sencha.com/products/extjs. Unzip and place where ever you like.
1. `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`
# Postgres 9.2 Mac OSX Install
1. Install homebrew `ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"`
1. `brew install postgres`
1. First time db initialization `initdb /usr/local/var/postgres -E utf8`
1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start`
1. Create your PG user `createuser -d -P velocipede`
1. Create your database `createdb -U velocipede --owner=velocipede velocipede`
# Testing
1. Install phantomjs `brew install phantomjs`
1. Run tests with `rspec`
# Optional
Add icons
1. Download icons from http://www.famfamfam.com/lab/icons/silk/
1. Link to the icons under `public/images/icons`: (From app root) `ln -s /MY/PATH/famfamfam_silk_icons/icons public/images/icons`
# License
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
<a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a>
## Mailcatcher
Use mailcatcher to see what emails look like in development.
Follow the instructions from http://mailcatcher.me/ to create an rvm wrapper so you don't install it all over your gemsets
+5 -9
View File
@@ -12,20 +12,16 @@ require File.expand_path('../config/application', __FILE__)
Velocipede::Application.load_tasks
unless Rails.env.production?
namespace :spec do
namespace :spec do
desc 'Run all acceptance specs'
RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
t.pattern = '**/*.feature'
end
end
end
Rake.application.remove_task 'spec'
Rake.application.remove_task 'spec'
desc 'Run all specs'
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
desc 'Run all specs'
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
t.pattern = '**/*{_spec.rb,.feature}'
end
end
+2 -5
View File
@@ -12,8 +12,5 @@
//
//= require jquery
//= require jquery_ujs
//= require twitter/bootstrap/bootstrap-button
//= require twitter/bootstrap/bootstrap-modal
//= require utils
//= require bootstrap-datepicker
//= require bootstrap-timepicker
//= require twitter/bootstrap
//= require_tree .
-31
View File
@@ -1,31 +0,0 @@
$('.btn').button();
$("#add_bike_submit").click(function(){
json_data = { bikes: [{
serial_number: $("#serial_number").val(),
bike_brand_id: parseInt($("#bike_brand_id").val()),
shop_id: parseInt($("#shop_id").val()),
model: $("#model").val(),
bike_style_id: parseInt($('input[name=bike_style]:checked').val()),
seat_tube_height: parseFloat($("#seat_tube_height").val()),
bike_condition_id: parseInt($('input[name=bike_condition]:checked').val()),
bike_purpose_id: 1,
bike_wheel_size_id: parseInt($("#bike_wheel_size_id").val()),
}]};
$.ajax({
url: $("#add_bike_submit").data("url"),
type: "POST",
data: JSON.stringify(json_data),
contentType: 'application/json',
dataType: "json",
success: function(data, status, xhr){
window.location = data.bikes[0].id + "?add_bike=1";
},
error: function(data, status ){
displayFormErrors(data.responseJSON);
}
});
});
-474
View File
@@ -1,474 +0,0 @@
/* =========================================================
* bootstrap-datepicker.js
* http://www.eyecon.ro/bootstrap-datepicker
* =========================================================
* Copyright 2012 Stefan Petre
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
!function( $ ) {
// Picker object
var Datepicker = function(element, options){
this.element = $(element);
this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
this.picker = $(DPGlobal.template)
.appendTo('body')
.on({
click: $.proxy(this.click, this)//,
//mousedown: $.proxy(this.mousedown, this)
});
this.isInput = this.element.is('input');
this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
if (this.isInput) {
this.element.on({
focus: $.proxy(this.show, this),
//blur: $.proxy(this.hide, this),
keyup: $.proxy(this.update, this)
});
} else {
if (this.component){
this.component.on('click', $.proxy(this.show, this));
} else {
this.element.on('click', $.proxy(this.show, this));
}
}
this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
if (typeof this.minViewMode === 'string') {
switch (this.minViewMode) {
case 'months':
this.minViewMode = 1;
break;
case 'years':
this.minViewMode = 2;
break;
default:
this.minViewMode = 0;
break;
}
}
this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
if (typeof this.viewMode === 'string') {
switch (this.viewMode) {
case 'months':
this.viewMode = 1;
break;
case 'years':
this.viewMode = 2;
break;
default:
this.viewMode = 0;
break;
}
}
this.startViewMode = this.viewMode;
this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
this.onRender = options.onRender;
this.fillDow();
this.fillMonths();
this.update();
this.showMode();
};
Datepicker.prototype = {
constructor: Datepicker,
show: function(e) {
this.picker.show();
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
this.place();
$(window).on('resize', $.proxy(this.place, this));
if (e ) {
e.stopPropagation();
e.preventDefault();
}
if (!this.isInput) {
}
var that = this;
$(document).on('mousedown', function(ev){
if ($(ev.target).closest('.datepicker').length == 0) {
that.hide();
}
});
this.element.trigger({
type: 'show',
date: this.date
});
},
hide: function(){
this.picker.hide();
$(window).off('resize', this.place);
this.viewMode = this.startViewMode;
this.showMode();
if (!this.isInput) {
$(document).off('mousedown', this.hide);
}
//this.set();
this.element.trigger({
type: 'hide',
date: this.date
});
},
set: function() {
var formated = DPGlobal.formatDate(this.date, this.format);
if (!this.isInput) {
if (this.component){
this.element.find('input').prop('value', formated);
}
this.element.data('date', formated);
} else {
this.element.prop('value', formated);
}
},
setValue: function(newDate) {
if (typeof newDate === 'string') {
this.date = DPGlobal.parseDate(newDate, this.format);
} else {
this.date = new Date(newDate);
}
this.set();
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
this.fill();
},
place: function(){
var offset = this.component ? this.component.offset() : this.element.offset();
this.picker.css({
top: offset.top + this.height,
left: offset.left
});
},
update: function(newDate){
this.date = DPGlobal.parseDate(
typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
this.format
);
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
this.fill();
},
fillDow: function(){
var dowCnt = this.weekStart;
var html = '<tr>';
while (dowCnt < this.weekStart + 7) {
html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
}
html += '</tr>';
this.picker.find('.datepicker-days thead').append(html);
},
fillMonths: function(){
var html = '';
var i = 0
while (i < 12) {
html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
}
this.picker.find('.datepicker-months td').append(html);
},
fill: function() {
var d = new Date(this.viewDate),
year = d.getFullYear(),
month = d.getMonth(),
currentDate = this.date.valueOf();
this.picker.find('.datepicker-days th:eq(1)')
.text(DPGlobal.dates.months[month]+' '+year);
var prevMonth = new Date(year, month-1, 28,0,0,0,0),
day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
prevMonth.setDate(day);
prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
var nextMonth = new Date(prevMonth);
nextMonth.setDate(nextMonth.getDate() + 42);
nextMonth = nextMonth.valueOf();
var html = [];
var clsName,
prevY,
prevM;
while(prevMonth.valueOf() < nextMonth) {
if (prevMonth.getDay() === this.weekStart) {
html.push('<tr>');
}
clsName = this.onRender(prevMonth);
prevY = prevMonth.getFullYear();
prevM = prevMonth.getMonth();
if ((prevM < month && prevY === year) || prevY < year) {
clsName += ' old';
} else if ((prevM > month && prevY === year) || prevY > year) {
clsName += ' new';
}
if (prevMonth.valueOf() === currentDate) {
clsName += ' active';
}
html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
if (prevMonth.getDay() === this.weekEnd) {
html.push('</tr>');
}
prevMonth.setDate(prevMonth.getDate()+1);
}
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
var currentYear = this.date.getFullYear();
var months = this.picker.find('.datepicker-months')
.find('th:eq(1)')
.text(year)
.end()
.find('span').removeClass('active');
if (currentYear === year) {
months.eq(this.date.getMonth()).addClass('active');
}
html = '';
year = parseInt(year/10, 10) * 10;
var yearCont = this.picker.find('.datepicker-years')
.find('th:eq(1)')
.text(year + '-' + (year + 9))
.end()
.find('td');
year -= 1;
for (var i = -1; i < 11; i++) {
html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
year += 1;
}
yearCont.html(html);
},
click: function(e) {
e.stopPropagation();
e.preventDefault();
var target = $(e.target).closest('span, td, th');
if (target.length === 1) {
switch(target[0].nodeName.toLowerCase()) {
case 'th':
switch(target[0].className) {
case 'switch':
this.showMode(1);
break;
case 'prev':
case 'next':
this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
this.viewDate,
this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
);
this.fill();
this.set();
break;
}
break;
case 'span':
if (target.is('.month')) {
var month = target.parent().find('span').index(target);
this.viewDate.setMonth(month);
} else {
var year = parseInt(target.text(), 10)||0;
this.viewDate.setFullYear(year);
}
if (this.viewMode !== 0) {
this.date = new Date(this.viewDate);
this.element.trigger({
type: 'changeDate',
date: this.date,
viewMode: DPGlobal.modes[this.viewMode].clsName
});
}
this.showMode(-1);
this.fill();
this.set();
break;
case 'td':
if (target.is('.day') && !target.is('.disabled')){
var day = parseInt(target.text(), 10)||1;
var month = this.viewDate.getMonth();
if (target.is('.old')) {
month -= 1;
} else if (target.is('.new')) {
month += 1;
}
var year = this.viewDate.getFullYear();
this.date = new Date(year, month, day,0,0,0,0);
this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
this.fill();
this.set();
this.element.trigger({
type: 'changeDate',
date: this.date,
viewMode: DPGlobal.modes[this.viewMode].clsName
});
}
break;
}
}
},
mousedown: function(e){
e.stopPropagation();
e.preventDefault();
},
showMode: function(dir) {
if (dir) {
this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
}
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
}
};
$.fn.datepicker = function ( option, val ) {
return this.each(function () {
var $this = $(this),
data = $this.data('datepicker'),
options = typeof option === 'object' && option;
if (!data) {
$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
}
if (typeof option === 'string') data[option](val);
});
};
$.fn.datepicker.defaults = {
onRender: function(date) {
return '';
}
};
$.fn.datepicker.Constructor = Datepicker;
var DPGlobal = {
modes: [
{
clsName: 'days',
navFnc: 'Month',
navStep: 1
},
{
clsName: 'months',
navFnc: 'FullYear',
navStep: 1
},
{
clsName: 'years',
navFnc: 'FullYear',
navStep: 10
}],
dates:{
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
},
isLeapYear: function (year) {
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
},
getDaysInMonth: function (year, month) {
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
},
parseFormat: function(format){
var separator = format.match(/[.\/\-\s].*?/),
parts = format.split(/\W+/);
if (!separator || !parts || parts.length === 0){
throw new Error("Invalid date format.");
}
return {separator: separator, parts: parts};
},
parseDate: function(date, format) {
var parts = date.split(format.separator),
date = new Date(),
val;
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
if (parts.length === format.parts.length) {
var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
val = parseInt(parts[i], 10)||1;
switch(format.parts[i]) {
case 'dd':
case 'd':
day = val;
date.setDate(val);
break;
case 'mm':
case 'm':
month = val - 1;
date.setMonth(val - 1);
break;
case 'yy':
year = 2000 + val;
date.setFullYear(2000 + val);
break;
case 'yyyy':
year = val;
date.setFullYear(val);
break;
}
}
date = new Date(year, month, day, 0 ,0 ,0);
}
return date;
},
formatDate: function(date, format){
var val = {
d: date.getDate(),
m: date.getMonth() + 1,
yy: date.getFullYear().toString().substring(2),
yyyy: date.getFullYear()
};
val.dd = (val.d < 10 ? '0' : '') + val.d;
val.mm = (val.m < 10 ? '0' : '') + val.m;
var date = [];
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
date.push(val[format.parts[i]]);
}
return date.join(format.separator);
},
headTemplate: '<thead>'+
'<tr>'+
'<th class="prev">&lsaquo;</th>'+
'<th colspan="5" class="switch"></th>'+
'<th class="next">&rsaquo;</th>'+
'</tr>'+
'</thead>',
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
};
DPGlobal.template = '<div class="datepicker dropdown-menu">'+
'<div class="datepicker-days">'+
'<table class=" table-condensed">'+
DPGlobal.headTemplate+
'<tbody></tbody>'+
'</table>'+
'</div>'+
'<div class="datepicker-months">'+
'<table class="table-condensed">'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
'</table>'+
'</div>'+
'<div class="datepicker-years">'+
'<table class="table-condensed">'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
'</table>'+
'</div>'+
'</div>';
}( window.jQuery );
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
//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);
@@ -1,51 +0,0 @@
$(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;
}
});
});
-46
View File
@@ -1,46 +0,0 @@
$(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"]);
}
})
});
});
-379
View File
@@ -1,379 +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);
//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',
submitFormat: 'c',
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();
console.log(value);
console.log(format);
console.log("formatted: " + Ext.Date.format(value, this.submitFormat));
return value ? Ext.Date.format(value, this.submitFormat) : 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;
}
});
-9
View File
@@ -1,9 +0,0 @@
$("#index_logout").click(function(){
$.ajax({
type: "DELETE",
url: $("#index_logout").data("url"),
complete: function(){
window.location.href="/";
}
});
});
-32
View File
@@ -1,32 +0,0 @@
$(".task_list_task").click(function(){
$("#update_tasks_submit").removeClass("disabled");
});
$("#update_tasks_submit").click(function(){
tasks = [];
$(".task_list_task").each(function(){
tasks.push({
id: parseInt($(this).data("id")),
done: $(this).is(":checked")
});
});
json_data = { tasks: tasks };
$.ajax({
url: $("#update_tasks_submit").data("url"),
type: "PUT",
data: JSON.stringify(json_data),
contentType: 'application/json',
dataType: "json",
success: function(data, status, xhr){
//should re-render via JS, but for now reload
location.reload();
},
error: function(data, status ){
alert("An error occured updating tasks");
//displayFormErrors(data.responseJSON);
}
});
});
-76
View File
@@ -1,76 +0,0 @@
$(document).ready(function(){
var currentdate = new Date();
$("#date_id").datepicker().on('changeDate', function(ev){
$("#date_id").datepicker('hide');
});
$("#date_id").datepicker('setValue', currentdate);
$("#start_time_id").timepicker();
$("#end_time_id").timepicker();
$("#add_time_entry_submit").click(function(){
date = $("#date_id").val();
start_date = new Date(date + " " + $("#start_time_id").val());
end_date = new Date(date + " " + $("#end_time_id").val());
forward = $("#add_time_entry_submit").data("forward");
// If a bike is selected, forward to the bike
// checklist.
bike_id = parseInt($("#bike_id").val());
if( bike_id > 0 ){
forward = "/task_lists/" + bike_id + "/edit";
}
json_data = { time_entries: [{
start_date: start_date.toISOString(),
end_date: end_date.toISOString(),
log_action_id: parseInt($('input[name=action_id]:checked').val()),
bike_id: bike_id,
description: $("#description_id").val(),
}]};
$.ajax({
url: $("#add_time_entry_submit").data("url"),
type: "POST",
data: JSON.stringify(json_data),
contentType: 'application/json',
dataType: "json",
success: function(data, status, xhr){
window.location = forward;
},
error: function(data, status ){
displayFormErrors(data.responseJSON);
}
});
});
$(".work_entry-delete-btn").click(function(){
row = $(this).closest("tr");
entry_id = row.data("id");
start_date = row.data("start_date");
duration = row.data("duration");
description = row.data("description");
$("#work_entry_start_date").html(start_date);
$("#work_entry_duration").html(duration);
$("#work_entry_description").html(description);
$("#confirmation_delete").data("entry_id", entry_id);
});
$("#confirmation_delete").click(function(){
entry_id = $(this).data("entry_id");
url = $("#confirmation_delete").data("url") + entry_id;
$.ajax({
url: url,
type: "delete",
contentType: 'application/json',
success: function(data, status, xhr){
location.reload();
},
error: function(data, status ){
displayFormErrors(data.responseJSON);
}
});
});
});
-8
View File
@@ -1,8 +0,0 @@
function displayFormErrors(data){
if( data.errors != undefined ){
$.each( data.errors, function( field, errorMsg) {
$("#"+field).parents(".control-group").addClass("error");
$("#"+field).siblings(".help-block").html(errorMsg);
});
}
}
+1 -3
View File
@@ -9,7 +9,5 @@
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require datepicker
*= require bootstrap-timepicker
*= require bootstrap_and_overrides
*= require_tree .
*/
-146
View File
@@ -1,146 +0,0 @@
/*!
* Timepicker Component for Twitter Bootstrap
*
* Copyright 2013 Joris de Wit
*
* Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
.bootstrap-timepicker {
position: relative;
}
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu {
left: auto;
right: 0;
}
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before {
left: auto;
right: 12px;
}
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after {
left: auto;
right: 13px;
}
.bootstrap-timepicker .add-on {
cursor: pointer;
}
.bootstrap-timepicker .add-on i {
display: inline-block;
width: 16px;
height: 16px;
}
.bootstrap-timepicker-widget.dropdown-menu {
padding: 4px;
}
.bootstrap-timepicker-widget.dropdown-menu.open {
display: inline-block;
}
.bootstrap-timepicker-widget.dropdown-menu:before {
border-bottom: 7px solid rgba(0, 0, 0, 0.2);
border-left: 7px solid transparent;
border-right: 7px solid transparent;
content: "";
display: inline-block;
position: absolute;
}
.bootstrap-timepicker-widget.dropdown-menu:after {
border-bottom: 6px solid #FFFFFF;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
content: "";
display: inline-block;
position: absolute;
}
.bootstrap-timepicker-widget.timepicker-orient-left:before {
left: 6px;
}
.bootstrap-timepicker-widget.timepicker-orient-left:after {
left: 7px;
}
.bootstrap-timepicker-widget.timepicker-orient-right:before {
right: 6px;
}
.bootstrap-timepicker-widget.timepicker-orient-right:after {
right: 7px;
}
.bootstrap-timepicker-widget.timepicker-orient-top:before {
top: -7px;
}
.bootstrap-timepicker-widget.timepicker-orient-top:after {
top: -6px;
}
.bootstrap-timepicker-widget.timepicker-orient-bottom:before {
bottom: -7px;
border-bottom: 0;
border-top: 7px solid #999;
}
.bootstrap-timepicker-widget.timepicker-orient-bottom:after {
bottom: -6px;
border-bottom: 0;
border-top: 6px solid #ffffff;
}
.bootstrap-timepicker-widget a.btn,
.bootstrap-timepicker-widget input {
border-radius: 4px;
}
.bootstrap-timepicker-widget table {
width: 100%;
margin: 0;
}
.bootstrap-timepicker-widget table td {
text-align: center;
height: 30px;
margin: 0;
padding: 2px;
}
.bootstrap-timepicker-widget table td:not(.separator) {
min-width: 30px;
}
.bootstrap-timepicker-widget table td span {
width: 100%;
}
.bootstrap-timepicker-widget table td a {
border: 1px transparent solid;
width: 100%;
display: inline-block;
margin: 0;
padding: 8px 0;
outline: 0;
color: #333;
}
.bootstrap-timepicker-widget table td a:hover {
text-decoration: none;
background-color: #eee;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
border-color: #ddd;
}
.bootstrap-timepicker-widget table td a i {
margin-top: 2px;
font-size: 18px;
}
.bootstrap-timepicker-widget table td input {
width: 25px;
margin: 0;
text-align: center;
}
.bootstrap-timepicker-widget .modal-content {
padding: 4px;
}
@media (min-width: 767px) {
.bootstrap-timepicker-widget.modal {
width: 200px;
margin-left: -100px;
}
}
@media (max-width: 767px) {
.bootstrap-timepicker {
width: 100%;
}
.bootstrap-timepicker .dropdown-menu {
width: 100%;
}
}
-30
View File
@@ -1,26 +1,10 @@
@import "twitter/bootstrap/bootstrap";
body {
padding-top: 60px;
padding-left: 20px;
}
@import "twitter/bootstrap/responsive";
// Set the correct sprite paths
@iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
@iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png');
// 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
//
// Since bootstrap was imported above you have access to its mixins which
@@ -31,17 +15,3 @@ body {
//
// Example:
// @linkColor: #ff0000;
[data-toggle="buttons-radio"] > .btn > input[type="radio"],
[data-toggle="buttons"] > .btn > input[type="checkbox"] {
display: none;
}
.inline-block {
display: inline-block;
}
.control-group.error .btn-group > .btn {
color: #b94a48;
border-color: #b94a48;
}
-182
View File
@@ -1,182 +0,0 @@
/*!
* Datepicker for Bootstrap
*
* Copyright 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.datepicker {
top: 0;
left: 0;
padding: 4px;
margin-top: 1px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
/*.dow {
border-top: 1px solid #ddd !important;
}*/
}
.datepicker:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
position: absolute;
top: -7px;
left: 6px;
}
.datepicker:after {
content: '';
display: inline-block;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #ffffff;
position: absolute;
top: -6px;
left: 7px;
}
.datepicker > div {
display: none;
}
.datepicker table {
width: 100%;
margin: 0;
}
.datepicker td,
.datepicker th {
text-align: center;
width: 20px;
height: 20px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.datepicker td.day:hover {
background: #eeeeee;
cursor: pointer;
}
.datepicker td.day.disabled {
color: #eeeeee;
}
.datepicker td.old,
.datepicker td.new {
color: #999999;
}
.datepicker td.active,
.datepicker td.active:hover {
color: #ffffff;
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
*background-color: #0044cc;
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker td.active:hover,
.datepicker td.active:hover:hover,
.datepicker td.active:focus,
.datepicker td.active:hover:focus,
.datepicker td.active:active,
.datepicker td.active:hover:active,
.datepicker td.active.active,
.datepicker td.active:hover.active,
.datepicker td.active.disabled,
.datepicker td.active:hover.disabled,
.datepicker td.active[disabled],
.datepicker td.active:hover[disabled] {
color: #ffffff;
background-color: #0044cc;
*background-color: #003bb3;
}
.datepicker td.active:active,
.datepicker td.active:hover:active,
.datepicker td.active.active,
.datepicker td.active:hover.active {
background-color: #003399 \9;
}
.datepicker td span {
display: block;
width: 47px;
height: 54px;
line-height: 54px;
float: left;
margin: 2px;
cursor: pointer;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.datepicker td span:hover {
background: #eeeeee;
}
.datepicker td span.active {
color: #ffffff;
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
*background-color: #0044cc;
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker td span.active:hover,
.datepicker td span.active:focus,
.datepicker td span.active:active,
.datepicker td span.active.active,
.datepicker td span.active.disabled,
.datepicker td span.active[disabled] {
color: #ffffff;
background-color: #0044cc;
*background-color: #003bb3;
}
.datepicker td span.active:active,
.datepicker td span.active.active {
background-color: #003399 \9;
}
.datepicker td span.old {
color: #999999;
}
.datepicker th.switch {
width: 145px;
}
.datepicker th.next,
.datepicker th.prev {
font-size: 21px;
}
.datepicker thead tr:first-child th {
cursor: pointer;
}
.datepicker thead tr:first-child th:hover {
background: #eeeeee;
}
.input-append.date .add-on i,
.input-prepend.date .add-on i {
display: block;
cursor: pointer;
width: 16px;
height: 16px;
}
+23 -32
View File
@@ -1,56 +1,45 @@
class AppTabPanel < Netzke::Basepack::TabPanel
action :sign_out do |c|
c.icon = :door_out
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"
c.text = "Sign out #{controller.current_user.email}" if controller.current_user
end
def configure(c)
#all users
# (had to use hash for borders to get the title to display properly)
@@app_tab_panel_items = [ :bikes_border ]
@@app_tab_panel_items = [ :bikes_border,
{ layout: :fit,
wrappedComponent: :brands_and_models_border,
title: "Brands/Models"}
]
#for users only
if not controller.current_user.role?(:admin)
#for users
if controller.current_user.role?(:user)
# (had to use hash for borders to get the title to display properly)
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :user_profile_border,
title: "My Profile"},
title: "Profile"},
{ layout: :fit,
wrappedComponent: :user_transactions_border,
title: "My Transactions"}
title: "Transactions"}
]
end
#for admins
if can? :manage, User
if 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,
wrappedComponent: :users_and_profiles_border,
title: "Users/Profiles"}]
end
if can? :manage, Transaction
@@app_tab_panel_items.concat [{ layout: :fit,
title: "Users/Profiles"},
{ layout: :fit,
wrappedComponent: :transactions_border,
title: "Users/Transactions"}]
end
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]
title: "Transactions"},
:logs,
:user_roles]
end
@@app_tab_panel_items.each do |item|
if item.kind_of?(Symbol)
self.class.component item
@@ -58,8 +47,9 @@ class AppTabPanel < Netzke::Basepack::TabPanel
self.class.component item[:wrappedComponent]
end
end
c.prevent_header = true
c.tbar = [:sign_out, :check_out, :change_account_info]
c.tbar = [:sign_out]
c.items = @@app_tab_panel_items
super
end
@@ -68,5 +58,6 @@ class AppTabPanel < Netzke::Basepack::TabPanel
#gets js from app_tab_panel/javascripts/sign_out.js
c.mixin :sign_out
end
end
@@ -5,20 +5,5 @@
url: '/users/sign_out',
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";
}
}
-1
View File
@@ -3,7 +3,6 @@ class BikeBrands < Netzke::Basepack::Grid
super
c.model = "BikeBrand"
c.title = "Brands"
c.force_fit = true
c.prohibit_update = true if cannot? :update, BikeBrand
c.prohibit_create = true if cannot? :create, BikeBrand
+33 -14
View File
@@ -6,7 +6,6 @@ class BikeLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log"
c.title = "Bike History"
c.force_fit = true
c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:loggable_type => 'Bike',:loggable_id => session[:selected_bike_id]);}
c.strong_default_attrs = {
@@ -19,36 +18,57 @@ class BikeLogs < Netzke::Basepack::Grid
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
:description,
{ :name => :bike_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::BikeAction.first.id},
{ :name => :bike_action__action, :text => 'Action'},
{ :name => :created_at, :read_only => true},
{ :name => :updated_at, :read_only => true},
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
}
]
@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
current_bike = Bike.find_by_id(session[:selected_bike_id])
if cannot? :update, current_bike
if can? :manage, current_bike
puts "\n\n\nCAN MANAGE!\n\n\n"
c.bbar = default_bbar
else
puts "\n\n\nCANNNOT MANAGE!\n\n\n"
# 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
c.bbar = [ :search ]
end
=end
end
endpoint :set_read_only do |params, this|
current_bike = Bike.find_by_id(session[:selected_bike_id])
puts "\n\n\nREAD!\n\n\n"
if can? :manage, current_bike
puts "\n\n\nCAN MANAGE!\n\n\n"
#calls this.setDisabled()
this.set_disabled(false)
#sets the return value of this function
#this.netzke_set_result(true)
else
puts "\n\n\nCANNNOT MANAGE!\n\n\n"
#sets the return value of this function
#this.netzke_set_result(true)
this.set_disabled(true)
end
end
def default_fields_for_forms
[
{ :name => :start_date},
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :bike_action__action, :field_label => 'Action', :min_chars => 1 }
{ :name => :bike_action__action, :field_label => 'Action'}
]
end
@@ -56,9 +76,8 @@ class BikeLogs < Netzke::Basepack::Grid
#override with nil to remove actions
def default_bbar
bbar = [ :search ]
bbar.concat [ :apply ] if can? :update, ::ActsAsLoggable::Log
bbar.concat [ :add_in_form ] if can? :create, ::ActsAsLoggable::Log
bbar.concat [ :apply ] #if can? :update, ::ActsAsLoggable::Log
bbar.concat [ :add_in_form ] #if can? :create, ::ActsAsLoggable::Log
bbar
end
end
@@ -0,0 +1,10 @@
class BikeLogsAndTasksAccordian < Netzke::Basepack::Accordion
component :bike_logs
component :tasks
def configure(c)
c.prevent_header = true
c.items = [ :bike_logs, :tasks ]
super
end
end
-12
View File
@@ -1,12 +0,0 @@
class BikeLowerTabs < Netzke::Basepack::TabPanel
component :bike_logs
component :tasks
component :brands_and_models_border
def configure(c)
c.prevent_header = true
c.items = [ :bike_logs, :tasks,
{netzke_component: :brands_and_models_border, title: "Brands and Models"} ]
super
end
end
-1
View File
@@ -4,7 +4,6 @@ class BikeModels < Netzke::Basepack::Grid
c.model = "BikeModel"
c.title = "Models"
c.force_fit = true
c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:bike_brand_id => session[:selected_bike_brand_id]);}
c.strong_default_attrs = {
+11 -40
View File
@@ -2,15 +2,12 @@ class Bikes < Netzke::Basepack::Grid
def configure(c)
super
c.model = "Bike"
c.force_fit = true
# columns with :id set, have :min_chars set in init_component
# See: http://stackoverflow.com/questions/17738962/netzke-grid-filtering
c.columns = [
{ :name => :shop_id, :text => 'Shop ID', :default_value => Bike.last.id.to_i + 1},
{ :name => :shop_id, :text => 'Shop ID'},
:serial_number,
{ :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'},
{ :name => :model, :text => 'Model',
{ :name => :bike_brand__brand, :text => 'Brand' },
{ :name => :bike_model__model, :text => 'Model',
:scope => lambda { |rel|
if session[:selected_bike_brand_id]
rel.where(:bike_brand_id => session[:selected_bike_brand_id])
@@ -19,48 +16,22 @@ class Bikes < Netzke::Basepack::Grid
end
}
},
#needs to have type :action or else won't work in grid, because... netzke
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block,
:default_value => '000000'},
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style', :default_value => BikeStyle.first.id},
{ :name => :seat_tube_height, :text => 'Seat Tube (in)'},
{ :name => :top_tube_length, :text => 'Top Tube (in)'},
{ :name => :bike_wheel_size__display_string, :text => 'Wheel Size'},
:color,
{ :name => :bike_style__style, :text => 'Style' },
:seat_tube_height,
:top_tube_length,
:wheel_size,
:value,
{ :id => :bike_condition__condition, :name => :bike_condition__condition, :text => 'Condition', :default_value => BikeCondition.first.id},
{ :id => :bike_purpose__purpose, :name => :bike_purpose__purpose, :text => 'Purpose', :default_value => BikePurpose.first.id},
{ :name => :bike_condition__condition, :text => 'Condition'},
{ :name => :bike_status__status, :text => 'Status'},
{ :name => :owner, :getter => lambda { |rec|
user = rec.owner
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
}
]
# Default the sorting to ASC on shop_id
c.data_store.sorters = [{ property: 'shop_id', direction: 'ASC' }]
@bike = Bike.all
c.prohibit_update = true if cannot? :update, @bike
c.prohibit_create = true if cannot? :create, @bike
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
def default_bbar
[ :apply, :add_in_form, :search ]
@@ -3,24 +3,11 @@
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"bike_brand__brand",
"bike_style__style",
"bike_condition__condition",
"bike_purpose__purpose"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
// setting the 'rowclick' event
var view = this.getView();
view.on('itemclick', function(view, record){
// 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);
},
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 -2
View File
@@ -2,7 +2,7 @@ class BikesBorder < Netzke::Base
# Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence
component :bikes
component :bike_lower_tabs
component :bike_logs_and_tasks_accordian
def configure(c)
super
@@ -10,7 +10,7 @@ class BikesBorder < Netzke::Base
c.title = "Bikes"
c.items = [
{ netzke_component: :bikes, region: :center, split: true },
{ netzke_component: :bike_lower_tabs, region: :south, height: 300, split: true}
{ netzke_component: :bike_logs_and_tasks_accordian, region: :south, height: 300, split: true}
]
end
@@ -5,30 +5,19 @@
// setting the 'rowclick' event
var view = this.getComponent('bikes').getView();
//disable until a bike is clicked
var bike_logs_comp = this.queryById('bike_logs');
if( bike_logs_comp){
bike_logs_comp.disable();
}
var bike_tasks_comp = this.queryById('tasks');
if( bike_tasks_comp ){
bike_tasks_comp.disable();
}
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectBike({bike_id: record.get('id')});
// query for these components again, can change if not visible because of accordian
var bike_logs_comp = this.queryById('bike_logs');
var bike_tasks_comp = this.queryById('tasks');
if( bike_logs_comp ){
bike_logs_comp.getStore().load();
bike_logs_comp.enable();
if( this.queryById('bike_logs') ){
this.queryById('bike_logs').getStore().load();
this.queryById('bike_logs').setReadOnly({}, function(arg, arg1, arg2){
console.log(arg);
console.log(this.getDockedItems());
});
this.queryById('bike_logs').setDisabled(true);
}
if( bike_tasks_comp ){
bike_tasks_comp.getStore().load();
bike_tasks_comp.enable();
if( this.queryById('tasks') ){
this.queryById('tasks').getStore().load();
}
}, this);
}
-32
View File
@@ -1,32 +0,0 @@
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
View File
@@ -1,7 +1,6 @@
class Customers < Netzke::Basepack::Grid
def configure(c)
c.model = "Customer"
c.force_fit = true
end
#override with nil to remove actions
@@ -4,6 +4,7 @@
this.callParent();
this.getView().on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
console.log("user: " + record.get('id') );
this.selectCustomer({customer_id: record.get('id'), customer_type: 'Customer'});
}, this);
}
-22
View File
@@ -4,7 +4,6 @@ class Logs < Netzke::Basepack::Grid
super
c.header = false
c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.columns = [
:loggable_id,
:loggable_type,
@@ -23,25 +22,4 @@ class Logs < Netzke::Basepack::Grid
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration}
]
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
+7 -9
View File
@@ -9,7 +9,6 @@ class Tasks < Netzke::Basepack::Grid
c.header = false
c.model = "Task"
c.force_fit = true
c.scope = lambda{ |rel|
if session[:selected_bike_id]
rel.where(:task_list_id => Bike.find_by_id(session[:selected_bike_id]).task_list.id)
@@ -22,21 +21,20 @@ class Tasks < Netzke::Basepack::Grid
:task_list_id => task_list_id
}
c.columns = [
:done,
:task,
:notes
:notes,
:done
]
end
def default_fields_for_forms
bike = Bike.find_by_id(session[:selected_bike_id])
bike = "Select a Bike First!" if bike.nil?
[
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Bike Selected", :value => "#{bike.to_s}"},
:done,
fields = []
fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
fields.concat( [
:task,
:notes,
]
:done
])
end
#override with nil to remove actions
+12 -12
View File
@@ -5,7 +5,6 @@ class TransactionLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log"
c.title = "Transaction Payments"
c.force_fit = true
c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:loggable_type => 'Transaction',:loggable_id => session[:selected_transaction_id]);}
c.strong_default_attrs = {
@@ -21,7 +20,7 @@ class TransactionLogs < Netzke::Basepack::Grid
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db), :text => 'Date' },
{ :name => :description, :text => "Amount"} ,
{ :name => :transaction_action__action, :text => 'Method', :default_value => ::ActsAsLoggable::TransactionAction.first.id},
{ :name => :transaction_action__action, :text => 'Method'},
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -30,27 +29,28 @@ class TransactionLogs < Netzke::Basepack::Grid
}
]
@transaction_logs = ::ActsAsLoggable::Log.where(:loggable_type => "Transaction").all
c.prohibit_update = true if cannot? :update, @transaction_logs
c.prohibit_create = true if cannot? :create, @transaction_logs
c.prohibit_delete = true if cannot? :delete, @transaction_logs
c.prohibit_update = true if cannot? :update, ::ActsAsLoggable::Log
c.prohibit_create = true if cannot? :create, ::ActsAsLoggable::Log
c.prohibit_delete = true if cannot? :delete, ::ActsAsLoggable::Log
end
def default_fields_for_forms
customer = "No Customer Selected"
item = "No Item Selected"
customer = nil
item = nil
if session[:selected_transaction_id]
trans = Transaction.find_by_id(session[:selected_transaction_id])
if trans
customer = trans.customer
item = trans.item
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 for:", :value => "#{item.to_s}"},
{ :name => :description, :xtype => 'numberfield', :field_label => 'Amount'},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :transaction_action__action, :field_label => 'Payment Method', :min_chars => 1}
{ :name => :transaction_action__action, :field_label => 'Payment Method'}
]
end
@@ -58,8 +58,8 @@ class TransactionLogs < Netzke::Basepack::Grid
#override with nil to remove actions
def default_bbar
bbar = [ :search ]
bbar.concat [ :apply ] if can? :update, @transaction_logs
bbar.concat [:add_in_form ] if can? :create, @transaction_logs
bbar.concat [ :apply ] if can? :update, ::ActsAsLoggable::Log
bbar.concat [:add_in_form ] if can? :create, ::ActsAsLoggable::Log
bbar
end
-1
View File
@@ -2,7 +2,6 @@ class Transactions < Netzke::Basepack::Grid
def configure(c)
super
c.model = "Transaction"
c.force_fit = true
c.strong_default_attrs = {
:vendor_id => controller.current_user.id,
:customer_id => session[:selected_customer_id],
+2 -2
View File
@@ -4,7 +4,7 @@ class TransactionsBorder < Netzke::Base
component :transactions
component :transaction_logs
#users and customers components are required for the transactions form
component :users_and_customers_lower_tabs
component :users_and_customers_accordian
def configure(c)
super
@@ -13,7 +13,7 @@ class TransactionsBorder < Netzke::Base
c.items = [
{ netzke_component: :transactions, region: :center, height: 300, split: true },
{ netzke_component: :transaction_logs, region: :east, width: 300, split: true },
{ netzke_component: :users_and_customers_lower_tabs, region: :south, height: 300, split: true }
{ netzke_component: :users_and_customers_accordian, region: :south, height: 300, split: true }
]
end
@@ -5,19 +5,10 @@
// setting the 'rowclick' event
var view = this.getComponent('transactions').getView();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').disable();
}
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectTransaction({transaction_id: record.get('id')});
this.getComponent('transaction_logs').getStore().load();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').enable();
}
}, this);
}
}
@@ -1,13 +0,0 @@
class UserAndProfilesLowerTabs < Netzke::Basepack::TabPanel
component :user_profiles
component :user_logs
component :user_stats
def configure(c)
c.prevent_header = true
c.items = [ :user_logs,
{ netzke_component: :user_profiles, title: "User Profiles" },
{ netzke_component: :user_stats, title: "User Stats" }]
super
end
end
+10 -15
View File
@@ -14,16 +14,10 @@ class UserLogs < Netzke::Basepack::Grid
:copy_action_id => 4
}
#this seems inefficient
if can? :manage, ::ActsAsLoggable::Log.where(:loggable_type => "User").all
if can? :manage, ::ActsAsLoggable::Log
#admins and staff
#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_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] } )
user_log_data_store = {auto_load: true }
else
#just users
@@ -33,18 +27,19 @@ class UserLogs < Netzke::Basepack::Grid
end
c.model = "ActsAsLoggable::Log"
c.title = "User Timesheet"
c.force_fit = true
c.title = "User History"
c.data_store = user_log_data_store
c.scope = user_log_scope
c.strong_default_attrs = user_log_strong_default_attrs
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :format => "g:ia - D, M j - Y", :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
:description,
{ :id => :user_action__action, :name => :user_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::UserAction.all.first.id },
{ :name => :user_action__action, :text => 'Action' },
:created_at,
:updated_at,
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -58,13 +53,13 @@ class UserLogs < Netzke::Basepack::Grid
bike_store = Bike.all.map { |b| [b.id, b.shop_id] }
current_user ||= User.find_by_id(session[:selected_user_id]) || controller.current_user
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 => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action', :min_chars => 1},
{ :name => :user_action__action, :field_label => 'Action', :value => action_id},
{ :name => :for_bike, :checkboxName => :copy_log, :inputValue => true, :title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [
{:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id}
]
@@ -1,13 +0,0 @@
{
initComponent: function(){
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"user_action__purpose"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
}
}
-1
View File
@@ -19,7 +19,6 @@ class UserProfiles < Netzke::Basepack::Grid
c.model = "UserProfile"
c.title = "Profile"
c.force_fit = true
c.data_store = user_profiles_data_store
c.scope = user_profiles_scope
c.strong_default_attrs = user_profile_strong_default_attrs
-35
View File
@@ -1,35 +0,0 @@
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
+15
View File
@@ -0,0 +1,15 @@
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
+4 -19
View File
@@ -2,29 +2,14 @@ class UserStats < Netzke::Base
def body_content(user)
bike = user.bike
completed = user.completed_build_bikes
#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>
%Q(
<div id="user_stats_page">
<p>Total Hours Worked: #{user.total_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 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
js_configure do |c|
@@ -40,7 +25,7 @@ class UserStats < Netzke::Base
private
def user
User.find_by_id(session[:selected_user_id]) || controller.current_user
controller.current_user
end
end
-1
View File
@@ -5,7 +5,6 @@ class UserTransactions < Netzke::Basepack::Grid
c.model = "Transaction"
c.title = "Transactions"
c.force_fit = true
c.scope = lambda { |rel| rel.where(:customer_id => controller.current_user.id, :customer_type => 'User');}
c.data_store = { auto_load: true }
c.columns = [
@@ -5,18 +5,10 @@
// setting the 'rowclick' event
var view = this.getComponent('user_transactions').getView();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').disable();
}
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectTransaction({transaction_id: record.get('id')});
this.getComponent('transaction_logs').getStore().load();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').enable();
}
}, this);
}
}
+3 -14
View File
@@ -1,28 +1,17 @@
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)
super
c.header = false
c.model = "User"
c.force_fit = true
c.columns = [
{ :name => :username, :read_only => true },
:first_name,
:last_name,
:nickname,
:email,
{ :text => "Bike - Shop ID", :id => :bike__shop_id, :name => :bike__shop_id, :default_value => Bike.exists? ? Bike.first.id : nil }
:user_role__role,
:bike__shop_id
]
c.columns << :reset if can? :manage, User
end
#override with nil to remove actions
@@ -2,41 +2,10 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = ["bike__shop_id"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
this.getView().on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
console.log("user: " + record.get('id') );
this.selectCustomer({customer_id: record.get('id'), customer_type: 'User'});
}, 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]);
}
});
}
});
}
}
@@ -1,10 +1,10 @@
class UsersAndCustomersLowerTabs < Netzke::Basepack::TabPanel
class UsersAndCustomersAccordian < Netzke::Basepack::Accordion
component :customers
component :users
def configure(c)
c.prevent_header = true
c.items = [ :users, :customers ]
c.items = [ :customers, :users ]
super
end
end
+5 -3
View File
@@ -2,14 +2,16 @@ class UsersAndProfilesBorder < Netzke::Base
# Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence
component :users
component :user_and_profiles_lower_tabs
component :user_profiles
component :user_logs
def configure(c)
super
c.header = false
c.items = [
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
{ netzke_component: :user_and_profiles_lower_tabs, region: :south, height: 300, split: true}
{ netzke_component: :users, header: "Users", region: :center, width: 300, split: true },
{ netzke_component: :user_profiles, region: :south, height: 150, split: true},
{ netzke_component: :user_logs, region: :east, split: true}
]
end
@@ -2,48 +2,18 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
var stats = this.queryById('user_stats');
var stats = this.getComponent('user_stats');
if (stats != undefined){
stats.updateStats();
}
if( this.queryById('user_profiles')){
this.queryById('user_profiles').disable();
}
if( this.queryById('user_logs')){
this.queryById('user_logs').disable();
//update user stats
var store = this.queryById('user_logs').getStore()
store.on('load', function (store, records, operation, success){
if( this.queryById('user_stats') ){
this.queryById('user_stats').updateStats();
}
}, this);
}
if( this.queryById('user_stats') ){
this.queryById('user_stats').disable();
}
// setting the 'rowclick' event
var view = this.getComponent('users').getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectUser({user_id: record.get('id')});
if( this.queryById('user_profiles')){
this.queryById('user_profiles').getStore().load();
this.queryById('user_profiles').enable();
}
if( this.queryById('user_logs')){
this.queryById('user_logs').getStore().load();
this.queryById('user_logs').enable();
}
if( this.queryById('user_stats') ){
this.queryById('user_stats').updateStats();
this.queryById('user_stats').enable();
}
this.getComponent('user_profiles').getStore().load();
this.getComponent('user_logs').getStore().load();
}, this);
}
}
-22
View File
@@ -1,22 +0,0 @@
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
@@ -1,32 +0,0 @@
class Api::V1::BikesController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have permission to manage bikes."
EXPECTED_BIKE = "Expected bike in submitted data"
NOT_FOUND = "The bike could not be found."
before_filter :check_bike_permission, except: :show
def create
if params[:bikes] && bike = params[:bikes].first
@bike = Bike.new(bike)
if !@bike.save
render json: { errors: @bike.errors }, status: 422 and return
end
else
render json: { errors: [EXPECTED_BIKE]}, status: 422 and return
end
end
def show
@bike = Bike.find_by_id(params[:id])
if @bike.nil?
render json: { errors: [NOT_FOUND] }, status: 404 and return
end
end
private
def check_bike_permission
if cannot? :manage, Bike
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
end
end
end
-22
View File
@@ -1,22 +0,0 @@
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
@@ -1,24 +0,0 @@
class Api::V1::TaskListsController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have permission to manage this task list."
NOT_FOUND = "The task list could not be found."
before_filter :get_task_list
before_filter :check_task_list_permission, except: :show
def show
end
private
def get_task_list
@task_list = TaskList.find_by_id(params[:id])
if @task_list.nil?
render json: { errors: [NOT_FOUND] }, status: 404 and return
end
end
def check_task_list_permission
if cannot? :manage, Bike and @task_list.item != current_user.bike
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
end
end
end
@@ -1,64 +0,0 @@
class Api::V1::TasksController < Api::V1::BaseController
EXPECTED_TASKS = "Expected a list of tasks in submitted data."
CANNOT_MANAGE = "You do not have permission to manage this task."
NOT_FOUND = "The task could not be found."
before_filter :validate_params
before_filter :get_tasks
before_filter :check_task_permission, except: :show
def update
errors = []
@tasks.each do |task_hash|
task = task_hash[:record]
attrs = task_hash[:new_attributes]
task.update_attributes(attrs)
if !task.errors.empty?
errors << { id: task.id, errors: task.errors }
end
end
if !errors.empty?
render json: { errors: errors }, status: 422 and return
end
end
private
def validate_params
if params[:tasks].nil? and not params[:tasks].kind_of?(Array)
render json: { errors: [EXPECTED_TASKS]}, status: 422 and return
end
end
def get_tasks
@tasks = []
errors = []
params[:tasks].each do |task|
t = Task.find_by_id(task[:id])
if t.nil?
errors << { id: task[:id], error: NOT_FOUND }
else
@tasks << { record: t, new_attributes: task }
end
end
if !errors.empty?
render json: { errors: errors }, status: 404 and return
end
end
def check_task_permission
errors = []
@tasks.each do |task_hash|
task = task_hash[:record]
if task.task_list.item != current_user.bike
errors << { id: task[:id], error: CANNOT_MANAGE }
end
end
if cannot? :manage, Bike and !errors.empty?
render json: { errors: errors}, status: 403 and return
end
end
end
@@ -1,33 +0,0 @@
class Api::V1::TimeEntriesController < Api::V1::BaseController
EXPECTED_TIME_ENTRY = "Expected time entry in submitted data"
NOT_FOUND = "Time entry not found"
def create
if params[:time_entries] && time_entry = params[:time_entries].first
time_entry.merge!({ loggable_id: current_user.id,
logger_id: current_user.id })
bike_id = time_entry[:bike_id].to_i
@time_entry = TimeEntry.new(time_entry.except(:bike_id))
if bike_id > 0
@time_entry.copy_to_bike_history(bike_id)
end
if !@time_entry.save
render json: { errors: @time_entry.errors }, status: 422 and return
end
else
render json: { errors: [EXPECTED_TIME_ENTRY]}, status: 422 and return
end
end
def delete
if time_entry = TimeEntry.find_by_id(params[:id])
time_entry.delete
render nothing: true, status: 204 and return
else
render json: { errors: [NOT_FOUND]}, status: 404 and return
end
end
end
@@ -1,35 +0,0 @@
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
-16
View File
@@ -1,16 +0,0 @@
class BikesController < AuthenticatedController
def new
@brands = BikeBrand.all.map{ |b| [b.brand, b.id] }
@brands.unshift( ["Select a brand", -1] )
@wheel_sizes = BikeWheelSize.all.map{ |w| [w.display_string, w.id] }
@wheel_sizes.unshift( ["Select a wheel size", -1] )
end
def show
@bike = Bike.find_by_id(params[:id])
@task_list_id = @bike.task_list.id
@show_add_bike = true if params[:add_bike]
end
end
-7
View File
@@ -1,7 +0,0 @@
class PanelController < ApplicationController
def index
render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke"
end
end
+2 -1
View File
@@ -1,6 +1,7 @@
class SiteController < ApplicationController
def index
@bike = current_user.bike
render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application"
end
end
-5
View File
@@ -1,5 +0,0 @@
class TaskListsController < AuthenticatedController
def edit
@task_list = TaskList.find_by_id(params[:id])
end
end
@@ -1,16 +0,0 @@
class TimeEntriesController < AuthenticatedController
def new
@bikes = Bike.all.map{ |b| [b.to_s , b.id] }
if bike = current_user.bike
@bikes.unshift( [bike.to_s, bike.id] )
end
@bikes.unshift( ["Non-bike work", -1] )
end
def index
@user_time_entries = TimeEntry.where(loggable_id: current_user.id)
@credits_available = current_user.total_credits
@hours_worked = current_user.total_hours
end
end
-23
View File
@@ -1,23 +0,0 @@
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 -5
View File
@@ -3,9 +3,7 @@ class Ability
def initialize(current_user)
@current_user = current_user
current_user.roles.each do |role|
self.send(role.role.to_sym)
end
self.send(current_user.role.to_sym)
end
def admin
@@ -24,7 +22,7 @@ class Ability
def user
can :read, :all
can :manage, Bike, :id => @current_user.bike_id unless @current_user.bike.nil?
can :manage, ::ActsAsLoggable::Log, :loggable_type => "Bike", :loggable_id => @current_user.bike_id
can :manage, ::ActsAsLoggable::Log, :loggable_type => "User", :loggable_id => @current_user.id
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "Bike", :loggable_id => @current_user.bike_id }
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "User", :loggable_id => @current_user.id }
end
end
+23 -21
View File
@@ -1,37 +1,43 @@
class Bike < ActiveRecord::Base
acts_as_loggable
attr_accessible :shop_id, :serial_number, :bike_brand_id, :model, :color, :bike_style_id, :seat_tube_height,
:top_tube_length, :bike_wheel_size_id, :value, :bike_condition_id, :bike_purpose_id
attr_accessible :shop_id, :serial_number, :bike_brand_id, :bike_model_id, :color, :bike_style_id, :seat_tube_height,
:top_tube_length, :wheel_size, :value, :bike_condition_id, :bike_status_id
has_many :transactions
has_one :owner, :class_name => 'User'
has_one :task_list, :as => :item, :dependent => :destroy
belongs_to :bike_brand
belongs_to :bike_model
belongs_to :bike_style
belongs_to :bike_condition
belongs_to :bike_purpose
belongs_to :bike_wheel_size
belongs_to :bike_status
validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
validates :serial_number, :length => { :minimum => 3 }
validates :model, :length => { :maximum => 50 }
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" }
#validates :color, :presence => true
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" }
validates :seat_tube_height, :presence => true, :numericality => true
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" }
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" }
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" }
validates :shop_id, :presence => true, :uniqueness => true, :length => { :minimum => 3 }
validates :serial_number, :uniqueness => true, :length => { :minimum => 3 }
validates :bike_brand_id, :presence => true
validates :bike_model_id, :presence => true
validates :color, :presence => true
validates :bike_style_id, :presence => true
validates :seat_tube_height, :presence => true
validates :top_tube_length, :presence => true
validates :wheel_size, :presence => true
#validates :value, :presence => true
validates :bike_condition_id, :presence => true
validates :bike_status_id, :presence => true
self.per_page = 15
after_create :create_task_list
after_save :create_task_list
def brand
self.bike_brand
end
def model
self.bike_model
end
def style
self.bike_style
end
@@ -40,12 +46,8 @@ class Bike < ActiveRecord::Base
self.bike_condition
end
def purpose
self.bike_purpose
end
def wheel_size
self.bike_wheel_size
def status
self.bike_status
end
def to_s
+1
View File
@@ -1,6 +1,7 @@
class BikeModel < ActiveRecord::Base
attr_accessible :model, :bike_brand_id
has_many :bikes
belongs_to :bike_brand
default_scope order('model ASC')
-9
View File
@@ -1,9 +0,0 @@
class BikePurpose < ActiveRecord::Base
attr_accessible :purpose
belongs_to :bike
def to_s
self.purpose
end
end
+9
View File
@@ -0,0 +1,9 @@
class BikeStatus < ActiveRecord::Base
attr_accessible :status
belongs_to :bike
def to_s
self.status
end
end
-16
View File
@@ -1,16 +0,0 @@
class BikeWheelSize < ActiveRecord::Base
belongs_to :bike
def display_string
result = []
result << "#{twmm}-#{rdmm}" unless twmm.blank? and rdmm.blank?
result << "#{rdin}x#{twin}" unless rdin.blank? and twin.blank?
result << "#{rdfr}x#{twfr}" unless rdfr.blank? and twfr.blank?
result << description unless description.blank?
result.join(" | ")
end
def to_s
"#{twmm}-#{rdmm} #{rdin}x#{twin} #{rdfr}x#{twfr} '#{description}' #{tire_common_score}"
end
end
-3
View File
@@ -1,3 +0,0 @@
class CreditConversion < ActiveRecord::Base
attr_accessible :conversion
end
-16
View File
@@ -1,16 +0,0 @@
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
View File
@@ -1,5 +1,5 @@
class Task < ActiveRecord::Base
attr_accessible :task, :notes, :done, :task_list_id
attr_accessible :task, :notes, :done
belongs_to :task_list
+1 -1
View File
@@ -4,7 +4,7 @@ class TaskList < ActiveRecord::Base
attr_accessible :item_id, :item_type, :name
belongs_to :item, :polymorphic => true
has_many :tasks, order: "id ASC"
has_many :tasks
after_save :create_default_bike_tasks
-27
View File
@@ -1,27 +0,0 @@
class TimeEntry < ActsAsLoggable::Log
default_scope where( loggable_type: "User",
logger_type: "User",
log_action_type: "ActsAsLoggable::UserAction").where("log_action_id != 4").order("start_date DESC")
def copy_to_bike_history(bike_id)
self.assign_attributes({
copy_log: true,
copy_type: 'Bike',
copy_id: bike_id,
copy_action_type: 'ActsAsLoggable::BikeAction',
copy_action_id: 4
})
end
def duration
end_date - start_date
end
def duration_in_hours
(duration / 1.hour).round(2)
end
def type
log_action.try(:action)
end
end
+11 -110
View File
@@ -7,21 +7,16 @@ class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :bike_id,
:user_profiles_attributes, :username
:first_name, :last_name, :nickname, :user_role_id, :bike_id,
:user_profiles_attributes
has_many :transactions, as: :customer
has_many :transaction_logs, through: :transactions, source: :logs
has_many :transactions
has_many :user_profiles
accepts_nested_attributes_for :user_profiles, allow_destroy: false
has_many :user_role_joins, :conditions => ["ends IS NULL OR ends > ?", Time.now]
has_many :roles, through: :user_role_joins
has_one :user_role
belongs_to :bike
default_scope order('username ASC')
validates :first_name, :presence => true
validates :last_name, :presence => true
@@ -29,123 +24,29 @@ class User < ActiveRecord::Base
"#{first_name} #{last_name}"
end
def email_required?
false
end
def full_name
to_s
end
def role
user_role.role
end
def 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
### TODO methods below probably belong somewhere else
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)
user_role.to_s == role.to_s
end
def total_hours
log_action_id = 4 #CHECKIN
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
end
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
current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month)
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s)
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id)
.where( :start_date => current_month_range)
.where( :end_date => current_month_range)
.sum { |l| (l.end_date - l.start_date)/3600 }
.round(2)
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
+2 -1
View File
@@ -1,4 +1,5 @@
class UserProfile < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :user_id, :addrStreet1, :addrStreet2, :addrCity,
:addrState, :addrZip, :phone
@@ -9,7 +10,7 @@ class UserProfile < ActiveRecord::Base
#validates :addrCity , :presence => true
#validates :addrState , :presence => true
#validates :addrZip , :presence => true
#validates :phone, :presence => true
validates :phone, :presence => true
def to_s
[addrStreet1, addrStreet2, addrCity, addrState, addrZip, phone].join(" - ")
+11
View File
@@ -0,0 +1,11 @@
class UserRole < ActiveRecord::Base
attr_accessible :role
belongs_to :user
self.per_page = 15
def to_s
self.role
end
end
-13
View File
@@ -1,13 +0,0 @@
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
@@ -1,4 +0,0 @@
json.bikes [@bike] do |bike|
json.array! bike
json.set! :href, api_bike_path(bike)
end
@@ -1,3 +0,0 @@
json.bikes [@bike] do |bike|
json.array! bike
end
@@ -1,15 +0,0 @@
json.task_lists [@task_list] do |tl|
json.array! tl
json.links do
json.bike do
json.href api_bike_path(tl.item)
json.id tl.item_id
end
json.tasks tl.tasks do |task|
json.id task.id
json.done task.done
json.notes task.notes
json.task task.task
end
end
end
@@ -1,3 +0,0 @@
json.tasks [@tasks.map{|x| x[:record]}] do |task|
json.array! task
end
@@ -1,4 +0,0 @@
json.time_entries [@time_entry] do |time_entry|
json.array! time_entry
#json.set! :href, api_time_entry_path(time_entry)
end
-62
View File
@@ -1,62 +0,0 @@
%a{ class: "btn btn-default btn-lg", href: root_path}
%span{ class:"icon-home"}
%h2 Add Bike
%p
.control-group
.controls
%input{id: "shop_id", placeholder: "Shop ID", type: "number", min:0, class: "input-lg" }
.help-block
.control-group
.controls
= select_tag(:bike_brand_id, options_for_select(@brands))
.help-block
.control-group
.controls
%input{id: "model", placeholder: "Model", type: "text", class: "input-lg" }
.help-block
.control-group
.controls
%input{id: "serial_number", placeholder: "Serial Number", type: "text", class: "input-lg" }
.help-block
.control-group
.controls
.btn-group{ "data-toggle" => "buttons-radio"}
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 3} RD
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 1} MTN
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 2} HYB
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 4} OTHER
%input{ id: "bike_style_id", type: "hidden"}
.help-block
.control-group
.controls
= select_tag(:bike_wheel_size_id, options_for_select(@wheel_sizes), id: :bike_wheel_size_id)
.help-block
.control-group
.controls
.btn-group{ "data-toggle" => "buttons-radio"}
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 2} Poor
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 3} Fair
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 4} Good
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 5} Excellent
%input{ id: "bike_condition_id", type: "hidden"}
.help-block
.control-group
.controls
%input{id: "seat_tube_height", placeholder: "Seat Tube (cm)", type: "number", min: 0, max: 100, class: "input-lg" }
.help-block
-# Commenting this out until description is added to Bike
%p
%input{id: "bike_description", placeholder: "Short description", type: "text", class: "input-lg" }
.control-group
.controls
%input{id: "add_bike_submit", value: "Add Bike", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_bike_path}"}
-27
View File
@@ -1,27 +0,0 @@
- if @show_add_bike
%p
%a{class: "btn btn-lg btn-block btn-info", href: new_bike_path} Add Another Bike?
%a{ class: "btn btn-default btn-lg", href: root_path}
%span{ class:"icon-home"}
%h2 #{@bike.shop_id}: #{@bike.bike_brand}
%h2 #{@bike.model}
%dl.dl-horizontal
%dt Type
%dd #{@bike.bike_style}
%dt Wheel Size
%dd #{@bike.bike_wheel_size.display_string}
%dt Condition
%dd #{@bike.bike_condition}
%dt Seat Tube (cm)
%dd #{@bike.seat_tube_height}
%dt Purpose
%dd #{@bike.bike_purpose}
- if !@bike.value.nil?
%dt Value
%dd #{@bike.value}
- if !@bike.color.nil?
%dt Color
%dd{ style: "width: 50px; background-color: ##{@bike.color}; border: black; border-width: 1px; border-style: solid;"} #{@bike.color}
%a{class: "btn btn-lg btn-block btn-primary", href: edit_task_list_path(@task_list_id) } View Checklist
+5 -12
View File
@@ -1,28 +1,21 @@
<%- if controller_name != 'sessions' %>
<p>
<%= link_to "Sign in", new_session_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<%= link_to "Sign in", new_session_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<p>
<%= link_to "Sign up", new_registration_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<p>
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<p>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.omniauthable? %>
+4 -8
View File
@@ -1,16 +1,12 @@
<%= 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| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.email_field :email, placeholder: "Email", class: "form-control input-lg" %>
</div>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.submit "Reset Password", class:"btn btn-lg btn-primary"%></div>
<div><%= f.submit "Send me reset password instructions" %></div>
<% end %>
<%= render "links" %>
@@ -1,14 +1,24 @@
%p
%fieldset
.form-group
= f.text_field :addrStreet1, placeholder: "Street Address Line 1", :class => "form-control input-lg"
.form-group
= f.text_field :addrStreet2, placeholder: "Street Address Line 2",:class => "form-control input-lg"
.form-group
= f.text_field :addrCity, placeholder: "City", :class => "form-control input-lg"
.form-group
= f.text_field :addrState, placeholder: "State Abbreviation", :class => "form-control input-lg"
.form-group
= f.text_field :addrZip, placeholder: "Zip Code", :class => "form-control input-lg"
.form-group
= f.text_field :phone, placeholder: "Phone", :class => "form-control input-lg"
%fieldset
= f.label :addrStreet1
%br/
= f.text_field :addrStreet1
%br/
= f.label :addrStreet2
%br/
= f.text_field :addrStreet2
%br/
= f.label :addrCity
%br/
= f.text_field :addrCity
%br/
= 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
@@ -1,5 +1,3 @@
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+28 -19
View File
@@ -1,23 +1,32 @@
= stylesheet_link_tag "bootstrap_and_overrides", :media => "all"
%h2 Sign up
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= devise_error_messages!
.controls
%h2 Sign up
.form-group
= f.text_field :username, placeholder: "Username", :class => "form-control input-lg"
.form-group
= f.text_field :first_name, placeholder: "First Name", :class => "form-control input-lg"
.form-group
= f.text_field :last_name, placeholder: "Last Name", :class => "form-control input-lg"
.form-group
= f.email_field :email, placeholder: "E-mail", :class => "form-control input-lg"
- profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles
= f.fields_for :user_profiles, profile_builder do |builder|
%div
= f.label :first_name
%br/
= f.text_field :first_name
%div
= f.label :last_name
%br/
= f.text_field :last_name
%div
= f.label :nickname
%br/
= f.text_field :nickname
%div
= f.label :email
%br/
= f.email_field :email
%div
= f.fields_for :user_profiles, UserProfile.new do |builder|
= render 'user_profile_fields', f: builder
.form-group
= f.password_field :password, placeholder: "Password", :class => "form-control input-lg"
.form-group
= f.password_field :password_confirmation, placeholder: "Password Confirmation", :class => "form-control input-lg"
.form-group
= f.submit "Sign up", class:"btn btn-lg btn-primary"
%div
= f.label :password
%br/
= f.password_field :password
%div
= f.label :password_confirmation
%br/
= f.password_field :password_confirmation
%div= f.submit "Sign up"
= render "links"

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