Author SHA1 Message Date
Jason Denney ed1f2e6be4 Update schema.rb to make last migration 2013-11-21 19:38:10 -05:00
Jason Denney 852a00881e Velocipede changed their wording to CLASS
Can't find purpose by name, will have to rely on default ID in case wording is changed.
2013-11-21 19:37:30 -05:00
Jason Denney 8cc72c9f48 Added link to "add a bike" video 2013-11-17 22:49:11 -05:00
Jason Denney 90b3043d67 wording 2013-11-17 16:57:29 -05:00
Jason Denney 2ad9fab243 Move some instructions to proper section 2013-11-17 16:55:00 -05:00
Jason Denney 43db139dc7 Adding information to the about section 2013-11-17 16:51:46 -05:00
Jason Denney 2b72094f00 Update read me with youtube video link 2013-11-16 18:42:23 -05:00
Jason Denney 1aa3b16636 Want mm as integer for sorting purposes. 2013-11-11 17:43:17 -05:00
Jason Denney af05d1a0c6 Make migration robust to model changes 2013-11-11 17:43:00 -05:00
Jason Denney 076a59ab45 Make migration robust to model changes 2013-11-11 17:27:51 -05:00
Jason Denney 1075c1a5b3 Merge pull request #98 from spacemunkay/denney-transaction-lower-tabs
Replacing user/customer accordion with tab panel
2013-11-09 08:29:47 -08:00
Jason Denney 65708e743d Replacing user/customer accordion with tab panel 2013-11-09 11:29:08 -05:00
Jason Denney 731d0ed3da Increase width for hour in custom time component 2013-11-09 10:45:25 -05:00
Jason Denney 592049b0e6 Merge pull request #97 from spacemunkay/denney-user-profiles-lower-tabs
Add lower tabs pane instead of splitting
2013-11-09 07:44:46 -08:00
Jason Denney c73d5f955a Add lower tabs pane instead of splitting 2013-11-09 10:44:08 -05:00
Jason Denney 966c36b563 Merge pull request #96 from spacemunkay/denney-move-checks
Move checklist to the left in the tasks view.
2013-11-09 06:48:40 -08:00
Jason Denney 20a350adf2 Move checklist to the left in the tasks view. 2013-11-09 09:48:05 -05:00
Jason Denney bca0903862 Merge pull request #87 from spacemunkay/move-brands-80
Moved brands/models tab to the lower pane of bikes
2013-11-08 21:55:16 -08:00
Jason Denney 5212011256 Merge pull request #95 from spacemunkay/denney-improve-wheel-display
Don't attempt to show a wheel size if it's blank
2013-11-04 06:39:34 -08:00
Jason Denney ea79c167eb Don't attempt to show a wheel size if it's blank 2013-11-04 09:38:53 -05:00
Jason Denney 85129dccaa Merge pull request #94 from spacemunkay/denney-add-faker-data
Denney add faker data
2013-11-03 10:59:08 -08:00
Jason Denney eeefe25227 Make sure grabbing valid ID, better faking 2013-11-03 13:58:26 -05:00
Jason Denney d0b2144b28 Adding better fake seed data 2013-11-03 13:52:43 -05:00
Jason Denney 3ba443397e Merge pull request #93 from spacemunkay/denney-disable-subpanels
Disable sub panels until item clicked
2013-11-03 08:35:32 -08:00
Jason Denney 4a67ebe77e Disable sub panels until item clicked 2013-11-03 11:34:48 -05:00
Jason Denney cda94cf955 Merge pull request #91 from spacemunkay/denney-bike-validation
Don't validate top tube on bike model
2013-11-03 07:11:18 -08:00
Jason Denney 1f9bd4f3a0 Don't validate top tube on bike model 2013-11-03 10:07:53 -05:00
Jason Denney 490c9d84b6 Merge pull request #90 from spacemunkay/denney-custom-time-field
Denney custom time field
2013-11-03 05:49:29 -08:00
Jason Denney 84ed8503d1 Remove console.log 2013-11-03 08:46:30 -05:00
Jason Denney 75f4a7980b Need end date to edit start date, fix type 2013-11-03 08:45:10 -05:00
Jason Denney 16cda6aa86 Remove unused code, add comment 2013-11-03 08:42:30 -05:00
Jason Denney de338276c0 Couldn't edit in columns, assign date correctly 2013-11-03 08:40:37 -05:00
Jason Denney 25808eb27a Boom! Meridian time component 2013-11-03 07:22:57 -05:00
Jason Denney d983647e29 Almost done, need to change minutes to textfield 2013-11-03 07:12:56 -05:00
Jason Denney cb4763c172 So close, just a minor bug left to fix
Can't put a field container within a field container, need to flatten it out.
2013-11-02 19:38:31 -04:00
Jason Denney 3d6f698fb9 Only update shop_id in migration 2013-11-02 15:12:55 -04:00
Jason Denney f7ce96152b Merge pull request #89 from spacemunkay/wheelsize-list
Wheelsize list
2013-10-19 16:34:52 -07:00
Jason Denney 4c8f74acf2 Merge branch 'master' of github.com:spacemunkay/velocipede 2013-10-19 19:26:42 -04:00
Jason Denney c2b8a13db2 Update Gemfile.lock 2013-10-19 19:26:21 -04:00
Jason Denney 77d7fad3ce Merge pull request #88 from spacemunkay/grid-sizing-79
Added forceFit option to all the grids so they resize nicely
2013-10-19 15:30:08 -07:00
Jason Denney 3bea39b53a Merge pull request #75 from spacemunkay/default-dropdown-values-67
Default dropdown values 67
2013-10-19 15:09:51 -07:00
Jason Denney 4eae374aa9 Fix column title while we're at it 2013-10-19 18:08:55 -04:00
Jason Denney dc60630d2d On second thought, don't want to default this one. 2013-10-19 18:08:32 -04:00
root 6e92d343a5 Merge branch 'wheelsize-list' of https://github.com/spacemunkay/velocipede into wheelsize-list 2013-10-19 21:43:17 +00:00
root 2ceb744925 Allow horizontal scrolling for combo list box drop down items 2013-10-19 21:42:29 +00:00
Jason Denney b27d682517 Bug caused by Bike.wheel_size no longer existing 2013-10-19 16:28:09 -04:00
Ron Warholic ba0f84a97c Added forceFit option to all the grids so they resize nicely 2013-10-19 16:19:27 -04:00
Ron Warholic 9300ef83d1 Moved brands/models tab to the lower pane of bikes 2013-10-19 16:13:44 -04:00
Jason Denney 4aed82cf80 Make wheel size an combobox option on bikes 2013-10-19 15:37:01 -04:00
Ron Warholic 83bdaf5973 Removed errant whitespace 2013-10-19 12:53:28 -04:00
Ron Warholic d94d747373 Merge branch 'master' of https://github.com/spacemunkay/velocipede 2013-10-19 12:51:10 -04:00
Ron Warholic da176e00ba Added default values for most add-item forms 2013-10-19 12:50:38 -04:00
Jason Denney f17c168d1d Merge pull request #72 from spacemunkay/sorting-and-shop-id-fix-68-63
Sorting and shop id fix 68 63
2013-10-19 07:12:57 -07:00
Ron Warholic 5e042c2577 Added migration file 2013-10-19 10:03:02 -04:00
Jason Denney bceac0543a Merge pull request #73 from spacemunkay/denney-min-chars
Denney min chars
2013-10-18 21:36:18 -07:00
Jason Denney 3faa19bc0c Adding min chars to forms 2013-10-19 00:32:08 -04:00
Jason Denney bbba67d7d5 remove trailing whitespace 2013-10-19 00:31:36 -04:00
Ron Warholic 6cbe0f8b57 Merge branch 'master' of https://github.com/spacemunkay/velocipede 2013-10-18 23:45:51 -04:00
Ron Warholic 86ada003d6 Fixed default sort and added migration for shop_id => Integer 2013-10-18 23:44:12 -04:00
Jason Denney 9a78478f1a Merge pull request #71 from spacemunkay/sequence-increment-bug-31
Fixed the seeder loading the sql incorrectly; now stubs the index out so...
2013-10-18 19:57:51 -07:00
Jason Denney 8d8058dea0 Comment for bike brands sql insert issue 2013-10-18 22:57:10 -04:00
Ron Warholic 93b8e3b055 Fixed the seeder loading the sql incorrectly; now stubs the index out so it generates the sequence number. 2013-10-18 21:24:44 -04:00
47 changed files with 615 additions and 122 deletions
+1
View File
@@ -33,6 +33,7 @@ group :development, :test do
gem 'rspec-rails', '~> 2.8.1' gem 'rspec-rails', '~> 2.8.1'
gem 'factory_girl_rails', '~> 1.2' gem 'factory_girl_rails', '~> 1.2'
gem 'pry', '~> 0.9.8' gem 'pry', '~> 0.9.8'
gem 'faker'
end end
group :test do group :test do
+6 -3
View File
@@ -1,6 +1,6 @@
GIT GIT
remote: https://github.com/spacemunkay/acts_as_loggable.git remote: https://github.com/spacemunkay/acts_as_loggable.git
revision: 49e264849ed9018445fb999cacfc55e1e28d7faa revision: 8484dc1f7a58699705c5ffb593ebdf700a9f374a
specs: specs:
acts_as_loggable (0.0.7) acts_as_loggable (0.0.7)
activerecord (>= 3.0) activerecord (>= 3.0)
@@ -77,6 +77,8 @@ GEM
factory_girl_rails (1.7.0) factory_girl_rails (1.7.0)
factory_girl (~> 2.6.0) factory_girl (~> 2.6.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faker (1.2.0)
i18n (~> 0.5)
ffi (1.6.0) ffi (1.6.0)
formatador (0.2.4) formatador (0.2.4)
gherkin (2.11.6) gherkin (2.11.6)
@@ -122,7 +124,7 @@ GEM
treetop (~> 1.4.8) treetop (~> 1.4.8)
method_source (0.8.1) method_source (0.8.1)
mime-types (1.22) mime-types (1.22)
multi_json (1.7.9) multi_json (1.8.2)
netzke-basepack (0.8.4) netzke-basepack (0.8.4)
netzke-core (~> 0.8.2) netzke-core (~> 0.8.2)
netzke-cancan (0.8.2) netzke-cancan (0.8.2)
@@ -207,7 +209,7 @@ GEM
less-rails (~> 2.2.2) less-rails (~> 2.2.2)
railties (>= 3.1) railties (>= 3.1)
therubyracer (~> 0.10.1) therubyracer (~> 0.10.1)
tzinfo (0.3.37) tzinfo (0.3.38)
uglifier (2.1.2) uglifier (2.1.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2) multi_json (~> 1.0, >= 1.0.2)
@@ -231,6 +233,7 @@ DEPENDENCIES
decent_exposure (~> 1.0.1) decent_exposure (~> 1.0.1)
devise (~> 2.0.4) devise (~> 2.0.4)
factory_girl_rails (~> 1.2) factory_girl_rails (~> 1.2)
faker
guard-rspec guard-rspec
guard-spork guard-spork
haml-rails (~> 0.3.4) haml-rails (~> 0.3.4)
+24 -2
View File
@@ -1,3 +1,25 @@
# About
A web application for bicycle collectives to track bicycles, bicycle work history, volunteer hours, volunteer work history, and volunteers currently in the shop.
See an overview video of what it looks like and how it works here: https://www.youtube.com/watch?v=0-JjM6d9nK4.
## Instructions/Guides
### Adding a User
To add a user, a user must sign themselves up from the initial login screen. An admin can later log in to grant that user admin privileges afterward, if desired.
### Adding a Bike
How to add a bike to the database: https://www.youtube.com/watch?v=1IchWUdMQ90.
## Views
The app has two different views. One for "core" volunteers which I consider admins, and another for "customer" volunteers which I consider users. Because of the Netzke/ExtJS framework I used, the current UI is non-intuitive and I feel like is only adequate for admins who can take the time to learn the system. I'm in the process of designing a simpler mobile friendly UI with the essential functionality that's intended for users.
## Deployment
Currently, at the Velocipede collective we have it running on a computer in the shop on the local network. The reason for this, is so that it's only accessible from within the shop. I'm weighing out different methods for ensuring that users can only log in from within the shop (and not from home), such as a shop password that only admins can see and would make visible from within the physical bike shop. I also don't have to worry about security issues while the app is in development. In the future, I'd like to see the app hosted much like http://freehub.bikekitchen.org/.
## Ideas
At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donations of old, unused smart phones, connected via local wifi, that can be used as stations to log a volunteer's work and hours. They can be secured by gluing cables into the earphone jacks to deter theft if necessary. This might be another way that the collective can promote reuse of materials.
# Developer Setup # Developer Setup
1. use rvm 1. use rvm
@@ -5,6 +27,8 @@
1. `gem install bundler` 1. `gem install bundler`
1. `bundle` 1. `bundle`
1. Install Postgres (Mac OSX instructions below) 1. Install Postgres (Mac OSX instructions below)
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
1. `rake db:create db:migrate` 1. `rake db:create db:migrate`
1. `rake db:seed` 1. `rake db:seed`
1. Download extJS 4.1 (A version of 4.1 is hosted here: http://my.jasondenney.com/extjs-4.1.1.zip) Latest versions at http://www.sencha.com/products/extjs. Unzip and place where ever you like. 1. Download extJS 4.1 (A version of 4.1 is hosted here: http://my.jasondenney.com/extjs-4.1.1.zip) Latest versions at http://www.sencha.com/products/extjs. Unzip and place where ever you like.
@@ -19,8 +43,6 @@
1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start` 1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start`
1. Create your PG user `createuser -d -P velocipede` 1. Create your PG user `createuser -d -P velocipede`
1. Create your database `createdb -U velocipede --owner=velocipede velocipede` 1. Create your database `createdb -U velocipede --owner=velocipede velocipede`
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
# Optional # Optional
Add icons Add icons
@@ -3,6 +3,232 @@ Ext.Ajax.on('requestexception', function(conn, response, options) {
if (response.status === 401) { window.location = '/users/sign_in'; } if (response.status === 401) { window.location = '/users/sign_in'; }
}, this); }, this);
//Override default netzke time entry field
Ext.define('Ext.ux.form.field.DateTime', {
extend:'Ext.form.FieldContainer',
mixins:{
field:'Ext.form.field.Field'
},
alias: 'widget.xdatetime',
//configurables
combineErrors: true,
msgTarget: 'under',
layout: 'hbox',
readOnly: false,
dateFormat: 'Y-m-d',
dateSubmitFormat: 'Y-m-d',
timeFormat: 'H:i:s',
timeSubmitFormat: 'H:i:s',
dateConfig:{},
timeConfig:{},
// properties
dateValue: null, // Holds the actual date
dateField: null,
hourField: null,
minuteField: null,
ampmField: null,
initComponent: function(){
var me = this
,i = 0
,key
,tab;
me.items = me.items || [];
me.dateField = Ext.create('Ext.form.field.Date', Ext.apply({
format:me.dateFormat,
flex:1,
isFormField:false, //exclude from field query's
submitValue:false,
submitFormat: me.dateSubmitFormat,
readOnly: me.readOnly
}, me.dateConfig));
me.items.push(me.dateField);
me.hourField = Ext.create('Ext.form.field.Number', {
maxWidth: 25,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Hour cannot be blank.",
allowDecimals: false,
maxValue: 12,
minValue: 1,
maxLength: 2,
enforceMaxLength: 2,
hideTrigger: true,
submitValue:false,
flex:1,
fieldStyle: "text-align:right;",
isFormField:false, //exclude from field query's
});
me.items.push(me.hourField);
me.colon = Ext.create('Ext.draw.Text', {
text: ':',
padding: '3 3 0 3'
});
me.items.push(me.colon);
me.minuteField = Ext.create('Ext.form.field.Text', {
maxWidth: 30,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Minutes cannot be blank.",
regex: /[012345]\d/,
maxLength: 2,
enforceMaxLength: 2,
submitValue:false,
flex:1,
isFormField:false, //exclude from field query's
});
me.items.push(me.minuteField);
me.ampmField = Ext.create('Ext.form.ComboBox', {
maxWidth: 45,
value: 'PM',
store: ['AM', 'PM'],
forceSelection: true,
flex:1,
editable: false
});
me.items.push(me.ampmField);
for (; i < me.items.length; i++) {
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
me.items[i].on('specialkey', function(field, event){
key = event.getKey();
tab = key == event.TAB;
if (tab && me.focussedItem == me.dateField) {
event.stopEvent();
me.timeField.focus();
return;
}
me.fireEvent('specialkey', field, event);
});
}
me.callParent();
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
this.inputEl = {
dom: document.createElement('div'),
swallowEvent:function(){}
};
me.initField();
},
focus:function(){
this.callParent(arguments);
this.dateField.focus();
},
onItemFocus:function(item){
if (this.blurTask){
this.blurTask.cancel();
}
this.focussedItem = item;
},
onItemBlur:function(item, e){
var me = this;
if (item != me.focussedItem){ return; }
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
me.blurTask = new Ext.util.DelayedTask(function(){
me.fireEvent('blur', me, e);
});
me.blurTask.delay(100);
},
getValue: function(){
var value = null
,date = this.dateField.getSubmitValue()
,time = null
,format;
hour = this.hourField.getValue();
minute = this.minuteField.getValue();
ampm = this.ampmField.getValue();
time = Ext.Date.parse(hour + " " + minute + " " + ampm, 'g i A');
time = Ext.Date.format(time, this.timeSubmitFormat);
if (date){
if (time){
format = this.getFormat();
value = Ext.Date.parse(date + ' ' + time, format);
} else {
value = this.dateField.getValue();
}
}
return value;
},
getSubmitValue: function(){
// var value = this.getValue();
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
var me = this
,format = me.getFormat()
,value = me.getValue();
return value ? Ext.Date.format(value, format) : null;
},
setValue: function(value){
if (Ext.isString(value) || Ext.isDate(value)){
if( Ext.isDate(value)){
dt = value;
}else{
dt = new Date(value);
value = Ext.Date.parse(value, this.getFormat()); //this.dateTimeFormat
}
this.dateField.setValue(value);
hour = Ext.Date.format(dt, 'g');
minute = Ext.Date.format(dt, 'i');
ampm = Ext.Date.format(dt, 'A');
this.hourField.setValue(hour);
this.minuteField.setRawValue(minute);
this.ampmField.setValue(ampm);
}
},
getFormat: function(){
value = (this.dateField.submitFormat || this.dateField.format) + " " + (this.timeSubmitFormat || this.timeFormat);
return value;
},
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
getSubmitData: function(){
var me = this
,data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getSubmitValue();
}
return data;
}
});
Ext.define('Ext.ux.form.field.ColorCombo', { Ext.define('Ext.ux.form.field.ColorCombo', {
extend:'Ext.form.FieldContainer', extend:'Ext.form.FieldContainer',
mixins:{ mixins:{
+1 -5
View File
@@ -19,11 +19,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel
#all users #all users
# (had to use hash for borders to get the title to display properly) # (had to use hash for borders to get the title to display properly)
@@app_tab_panel_items = [ :bikes_border, @@app_tab_panel_items = [ :bikes_border ]
{ layout: :fit,
wrappedComponent: :brands_and_models_border,
title: "Brands/Models"}
]
#for users only #for users only
if not controller.current_user.role?(:admin) if not controller.current_user.role?(:admin)
+1
View File
@@ -3,6 +3,7 @@ class BikeBrands < Netzke::Basepack::Grid
super super
c.model = "BikeBrand" c.model = "BikeBrand"
c.title = "Brands" c.title = "Brands"
c.force_fit = true
c.prohibit_update = true if cannot? :update, BikeBrand c.prohibit_update = true if cannot? :update, BikeBrand
c.prohibit_create = true if cannot? :create, BikeBrand c.prohibit_create = true if cannot? :create, BikeBrand
+4 -3
View File
@@ -6,6 +6,7 @@ class BikeLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log" c.model = "ActsAsLoggable::Log"
c.title = "Bike History" c.title = "Bike History"
c.force_fit = true
c.data_store = {auto_load: false} c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:loggable_type => 'Bike',:loggable_id => session[:selected_bike_id]);} c.scope = lambda { |rel| rel.where(:loggable_type => 'Bike',:loggable_id => session[:selected_bike_id]);}
c.strong_default_attrs = { c.strong_default_attrs = {
@@ -18,9 +19,9 @@ class BikeLogs < Netzke::Basepack::Grid
c.columns = [ c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) }, { :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) }, { :name => :end_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
:description, :description,
{ :name => :bike_action__action, :text => 'Action'}, { :name => :bike_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::BikeAction.first.id},
{ :name => :logged_by, :getter => lambda{ |rec| { :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id) user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}" user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -47,7 +48,7 @@ class BikeLogs < Netzke::Basepack::Grid
{ :name => :start_date}, { :name => :start_date},
{ :name => :description}, { :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work #had to hack acts_as_loggable/log.rb to get this to work
{ :name => :bike_action__action, :field_label => 'Action'} { :name => :bike_action__action, :field_label => 'Action', :min_chars => 1 }
] ]
end end
@@ -1,10 +0,0 @@
class BikeLogsAndTasksAccordian < Netzke::Basepack::Accordion
component :bike_logs
component :tasks
def configure(c)
c.prevent_header = true
c.items = [ :bike_logs, :tasks ]
super
end
end
+12
View File
@@ -0,0 +1,12 @@
class BikeLowerTabs < Netzke::Basepack::TabPanel
component :bike_logs
component :tasks
component :brands_and_models_border
def configure(c)
c.prevent_header = true
c.items = [ :bike_logs, :tasks,
{netzke_component: :brands_and_models_border, title: "Brands and Models"} ]
super
end
end
+1
View File
@@ -4,6 +4,7 @@ class BikeModels < Netzke::Basepack::Grid
c.model = "BikeModel" c.model = "BikeModel"
c.title = "Models" c.title = "Models"
c.force_fit = true
c.data_store = {auto_load: false} c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:bike_brand_id => session[:selected_bike_brand_id]);} c.scope = lambda { |rel| rel.where(:bike_brand_id => session[:selected_bike_brand_id]);}
c.strong_default_attrs = { c.strong_default_attrs = {
+17 -11
View File
@@ -2,11 +2,12 @@ class Bikes < Netzke::Basepack::Grid
def configure(c) def configure(c)
super super
c.model = "Bike" c.model = "Bike"
c.force_fit = true
# columns with :id set, have :min_chars set in init_component # columns with :id set, have :min_chars set in init_component
# See: http://stackoverflow.com/questions/17738962/netzke-grid-filtering # See: http://stackoverflow.com/questions/17738962/netzke-grid-filtering
c.columns = [ c.columns = [
{ :name => :shop_id, :text => 'Shop ID'}, { :name => :shop_id, :text => 'Shop ID', :default_value => Bike.last.id.to_i + 1},
:serial_number, :serial_number,
{ :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'}, { :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'},
{ :name => :model, :text => 'Model', { :name => :model, :text => 'Model',
@@ -19,20 +20,25 @@ class Bikes < Netzke::Basepack::Grid
} }
}, },
#needs to have type :action or else won't work in grid, because... netzke #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}, { :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block,
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style' }, :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 => :seat_tube_height, :text => 'Seat Tube (in)'},
{ :name => :top_tube_length, :text => 'Top Tube (in)'}, { :name => :top_tube_length, :text => 'Top Tube (in)'},
{ :name => :wheel_size, :text => 'Wheel Size (in)'}, { :name => :bike_wheel_size__display_string, :text => 'Wheel Size'},
:value, :value,
{ :id => :bike_condition__condition, :name => :bike_condition__condition, :text => 'Condition'}, { :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'}, { :id => :bike_purpose__purpose, :name => :bike_purpose__purpose, :text => 'Purpose', :default_value => BikePurpose.first.id},
{ :name => :owner, :getter => lambda { |rec| { :name => :owner, :getter => lambda { |rec|
user = rec.owner user = rec.owner
user.nil? ? "" : "#{user.first_name} #{user.last_name}" user.nil? ? "" : "#{user.first_name} #{user.last_name}"
} }
} }
] ]
# Default the sorting to ASC on shop_id
c.data_store.sorters = [{ property: 'shop_id', direction: 'ASC' }]
@bike = Bike.all @bike = Bike.all
c.prohibit_update = true if cannot? :update, @bike c.prohibit_update = true if cannot? :update, @bike
c.prohibit_create = true if cannot? :create, @bike c.prohibit_create = true if cannot? :create, @bike
@@ -41,18 +47,18 @@ class Bikes < Netzke::Basepack::Grid
def default_fields_for_forms def default_fields_for_forms
# :field_label MUST be defined in order for search to work # :field_label MUST be defined in order for search to work
[ [
{ :name => :bike_brand__brand, :field_label => 'Brand' }, { :name => :bike_brand__brand, :field_label => 'Brand', :min_chars => 1 },
{ :name => :model, :field_label => 'Model'}, { :name => :model, :field_label => 'Model'},
{ :name => :shop_id, :field_label => 'Shop ID'}, { :name => :shop_id, :field_label => 'Shop ID'},
{ :name => :serial_number, :field_label => 'Serial Number'}, { :name => :serial_number, :field_label => 'Serial Number'},
{ :name => "color", :xtype => "xcolorcombo"}, { :name => "color", :xtype => "xcolorcombo"},
{ :name => :bike_style__style, :field_label => 'Style' }, { :name => :bike_style__style, :field_label => 'Style', :min_chars => 1},
{ :name => :seat_tube_height, :field_label => 'Seat Tube (in)'}, { :name => :seat_tube_height, :field_label => 'Seat Tube (in)'},
{ :name => :top_tube_length, :field_label => 'Top Tube (in)'}, { :name => :top_tube_length, :field_label => 'Top Tube (in)'},
{ :name => :wheel_size, :field_label => 'Wheel Size (in)'}, { :name => :bike_wheel_size__display_string, :field_label => 'Wheel Size'},
{ :name => :value, :field_label => 'Value'}, { :name => :value, :field_label => 'Value'},
{ :name => :bike_condition__condition, :field_label => 'Condition'}, { :name => :bike_condition__condition, :field_label => 'Condition', :min_chars => 1},
{ :name => :bike_purpose__purpose, :field_label => 'Purpose'} { :name => :bike_purpose__purpose, :field_label => 'Purpose', :min_chars => 1}
] ]
end end
#override with nil to remove actions #override with nil to remove actions
+2 -2
View File
@@ -2,7 +2,7 @@ class BikesBorder < Netzke::Base
# Remember regions collapse state and size # Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence include Netzke::Basepack::ItemPersistence
component :bikes component :bikes
component :bike_logs_and_tasks_accordian component :bike_lower_tabs
def configure(c) def configure(c)
super super
@@ -10,7 +10,7 @@ class BikesBorder < Netzke::Base
c.title = "Bikes" c.title = "Bikes"
c.items = [ c.items = [
{ netzke_component: :bikes, region: :center, split: true }, { netzke_component: :bikes, region: :center, split: true },
{ netzke_component: :bike_logs_and_tasks_accordian, region: :south, height: 300, split: true} { netzke_component: :bike_lower_tabs, region: :south, height: 300, split: true}
] ]
end end
@@ -5,14 +5,30 @@
// setting the 'rowclick' event // setting the 'rowclick' event
var view = this.getComponent('bikes').getView(); var view = this.getComponent('bikes').getView();
//disable until a bike is clicked
var bike_logs_comp = this.queryById('bike_logs');
if( bike_logs_comp){
bike_logs_comp.disable();
}
var bike_tasks_comp = this.queryById('tasks');
if( bike_tasks_comp ){
bike_tasks_comp.disable();
}
view.on('itemclick', function(view, record){ view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectBike({bike_id: record.get('id')}); this.selectBike({bike_id: record.get('id')});
if( this.queryById('bike_logs') ){ // query for these components again, can change if not visible because of accordian
this.queryById('bike_logs').getStore().load(); var bike_logs_comp = this.queryById('bike_logs');
var bike_tasks_comp = this.queryById('tasks');
if( bike_logs_comp ){
bike_logs_comp.getStore().load();
bike_logs_comp.enable();
} }
if( this.queryById('tasks') ){ if( bike_tasks_comp ){
this.queryById('tasks').getStore().load(); bike_tasks_comp.getStore().load();
bike_tasks_comp.enable();
} }
}, this); }, this);
} }
+1
View File
@@ -4,6 +4,7 @@ class CheckIns < Netzke::Basepack::Grid
super super
c.header = false c.header = false
c.model = "ActsAsLoggable::Log" c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction). c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction).
where(:loggable_type => "User"). where(:loggable_type => "User").
where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")). where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")).
+1
View File
@@ -1,6 +1,7 @@
class Customers < Netzke::Basepack::Grid class Customers < Netzke::Basepack::Grid
def configure(c) def configure(c)
c.model = "Customer" c.model = "Customer"
c.force_fit = true
end end
#override with nil to remove actions #override with nil to remove actions
@@ -4,7 +4,6 @@
this.callParent(); this.callParent();
this.getView().on('itemclick', function(view, record){ this.getView().on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
console.log("user: " + record.get('id') );
this.selectCustomer({customer_id: record.get('id'), customer_type: 'Customer'}); this.selectCustomer({customer_id: record.get('id'), customer_type: 'Customer'});
}, this); }, this);
} }
+1
View File
@@ -4,6 +4,7 @@ class Logs < Netzke::Basepack::Grid
super super
c.header = false c.header = false
c.model = "ActsAsLoggable::Log" c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.columns = [ c.columns = [
:loggable_id, :loggable_id,
:loggable_type, :loggable_type,
+7 -4
View File
@@ -2,6 +2,8 @@ class Tasks < Netzke::Basepack::Grid
def configure(c) def configure(c)
super super
#disable by default, will be enabled once bike is clicked
c.disabled = true
task_list_id = nil task_list_id = nil
if session[:selected_bike_id] if session[:selected_bike_id]
task_list_id = Bike.find_by_id(session[:selected_bike_id]).task_list.id task_list_id = Bike.find_by_id(session[:selected_bike_id]).task_list.id
@@ -9,6 +11,7 @@ class Tasks < Netzke::Basepack::Grid
c.header = false c.header = false
c.model = "Task" c.model = "Task"
c.force_fit = true
c.scope = lambda{ |rel| c.scope = lambda{ |rel|
if session[:selected_bike_id] if session[:selected_bike_id]
rel.where(:task_list_id => Bike.find_by_id(session[:selected_bike_id]).task_list.id) rel.where(:task_list_id => Bike.find_by_id(session[:selected_bike_id]).task_list.id)
@@ -21,9 +24,9 @@ class Tasks < Netzke::Basepack::Grid
:task_list_id => task_list_id :task_list_id => task_list_id
} }
c.columns = [ c.columns = [
:done,
:task, :task,
:notes, :notes
:done
] ]
end end
@@ -31,9 +34,9 @@ class Tasks < Netzke::Basepack::Grid
fields = [] fields = []
fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"} fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
fields.concat( [ fields.concat( [
:done,
:task, :task,
:notes, :notes
:done
]) ])
end end
+3 -2
View File
@@ -5,6 +5,7 @@ class TransactionLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log" c.model = "ActsAsLoggable::Log"
c.title = "Transaction Payments" c.title = "Transaction Payments"
c.force_fit = true
c.data_store = {auto_load: false} c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:loggable_type => 'Transaction',:loggable_id => session[:selected_transaction_id]);} c.scope = lambda { |rel| rel.where(:loggable_type => 'Transaction',:loggable_id => session[:selected_transaction_id]);}
c.strong_default_attrs = { c.strong_default_attrs = {
@@ -20,7 +21,7 @@ class TransactionLogs < Netzke::Basepack::Grid
c.columns = [ c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db), :text => 'Date' }, { :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db), :text => 'Date' },
{ :name => :description, :text => "Amount"} , { :name => :description, :text => "Amount"} ,
{ :name => :transaction_action__action, :text => 'Method'}, { :name => :transaction_action__action, :text => 'Method', :default_value => ::ActsAsLoggable::TransactionAction.first.id},
{ :name => :logged_by, :getter => lambda{ |rec| { :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id) user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}" user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -49,7 +50,7 @@ class TransactionLogs < Netzke::Basepack::Grid
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"}, { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"},
{ :name => :description, :xtype => 'numberfield', :field_label => 'Amount'}, { :name => :description, :xtype => 'numberfield', :field_label => 'Amount'},
#had to hack acts_as_loggable/log.rb to get this to work #had to hack acts_as_loggable/log.rb to get this to work
{ :name => :transaction_action__action, :field_label => 'Payment Method'} { :name => :transaction_action__action, :field_label => 'Payment Method', :min_chars => 1}
] ]
end end
+1
View File
@@ -2,6 +2,7 @@ class Transactions < Netzke::Basepack::Grid
def configure(c) def configure(c)
super super
c.model = "Transaction" c.model = "Transaction"
c.force_fit = true
c.strong_default_attrs = { c.strong_default_attrs = {
:vendor_id => controller.current_user.id, :vendor_id => controller.current_user.id,
:customer_id => session[:selected_customer_id], :customer_id => session[:selected_customer_id],
+2 -2
View File
@@ -4,7 +4,7 @@ class TransactionsBorder < Netzke::Base
component :transactions component :transactions
component :transaction_logs component :transaction_logs
#users and customers components are required for the transactions form #users and customers components are required for the transactions form
component :users_and_customers_accordian component :users_and_customers_lower_tabs
def configure(c) def configure(c)
super super
@@ -13,7 +13,7 @@ class TransactionsBorder < Netzke::Base
c.items = [ c.items = [
{ netzke_component: :transactions, region: :center, height: 300, split: true }, { netzke_component: :transactions, region: :center, height: 300, split: true },
{ netzke_component: :transaction_logs, region: :east, width: 300, split: true }, { netzke_component: :transaction_logs, region: :east, width: 300, split: true },
{ netzke_component: :users_and_customers_accordian, region: :south, height: 300, split: true } { netzke_component: :users_and_customers_lower_tabs, region: :south, height: 300, split: true }
] ]
end end
@@ -5,10 +5,19 @@
// setting the 'rowclick' event // setting the 'rowclick' event
var view = this.getComponent('transactions').getView(); var view = this.getComponent('transactions').getView();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').disable();
}
view.on('itemclick', function(view, record){ view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectTransaction({transaction_id: record.get('id')}); this.selectTransaction({transaction_id: record.get('id')});
this.getComponent('transaction_logs').getStore().load(); this.getComponent('transaction_logs').getStore().load();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').enable();
}
}, this); }, this);
} }
} }
@@ -0,0 +1,13 @@
class UserAndProfilesLowerTabs < Netzke::Basepack::TabPanel
component :user_profiles
component :user_logs
component :user_stats
def configure(c)
c.prevent_header = true
c.items = [ :user_logs,
{ netzke_component: :user_profiles, title: "User Profiles" },
{ netzke_component: :user_stats, title: "User Stats" }]
super
end
end
+2 -1
View File
@@ -34,6 +34,7 @@ class UserLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log" c.model = "ActsAsLoggable::Log"
c.title = "User Timesheet" c.title = "User Timesheet"
c.force_fit = true
c.data_store = user_log_data_store c.data_store = user_log_data_store
c.scope = user_log_scope c.scope = user_log_scope
c.strong_default_attrs = user_log_strong_default_attrs c.strong_default_attrs = user_log_strong_default_attrs
@@ -63,7 +64,7 @@ class UserLogs < Netzke::Basepack::Grid
{ :name => :end_date}, { :name => :end_date},
{ :name => :description}, { :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work #had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action'}, { :name => :user_action__action, :field_label => 'Action', :min_chars => 1},
{ :name => :for_bike, :checkboxName => :copy_log, :inputValue => true, :title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [ { :name => :for_bike, :checkboxName => :copy_log, :inputValue => true, :title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [
{:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id} {:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id}
] ]
+1
View File
@@ -19,6 +19,7 @@ class UserProfiles < Netzke::Basepack::Grid
c.model = "UserProfile" c.model = "UserProfile"
c.title = "Profile" c.title = "Profile"
c.force_fit = true
c.data_store = user_profiles_data_store c.data_store = user_profiles_data_store
c.scope = user_profiles_scope c.scope = user_profiles_scope
c.strong_default_attrs = user_profile_strong_default_attrs c.strong_default_attrs = user_profile_strong_default_attrs
+3 -2
View File
@@ -2,6 +2,7 @@ class UserRoleJoins < Netzke::Basepack::Grid
def configure(c) def configure(c)
super super
c.model = "UserRoleJoin" c.model = "UserRoleJoin"
c.force_fit = true
c.header = false c.header = false
c.title = "User Roles" c.title = "User Roles"
c.data_store.sorters = [{ :property => :user__username, :direction => :ASC}] c.data_store.sorters = [{ :property => :user__username, :direction => :ASC}]
@@ -10,9 +11,9 @@ class UserRoleJoins < Netzke::Basepack::Grid
{ :name => :name, :getter => lambda{ |rec| { :name => :name, :getter => lambda{ |rec|
user = User.find_by_id(rec.user_id) user = User.find_by_id(rec.user_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}" user.nil? ? "" : "#{user.first_name} #{user.last_name}"
} },
}, },
{ :name => :role__role, :text => "Role"}, { :name => :role__role, :text => "Role", :default_value => Role.exists? ? Role.first.id : nil},
:created_at, :created_at,
:updated_at, :updated_at,
:ends ] :ends ]
+1 -1
View File
@@ -40,7 +40,7 @@ class UserStats < Netzke::Base
private private
def user def user
controller.current_user User.find_by_id(session[:selected_user_id]) || controller.current_user
end end
end end
+1
View File
@@ -5,6 +5,7 @@ class UserTransactions < Netzke::Basepack::Grid
c.model = "Transaction" c.model = "Transaction"
c.title = "Transactions" c.title = "Transactions"
c.force_fit = true
c.scope = lambda { |rel| rel.where(:customer_id => controller.current_user.id, :customer_type => 'User');} c.scope = lambda { |rel| rel.where(:customer_id => controller.current_user.id, :customer_type => 'User');}
c.data_store = { auto_load: true } c.data_store = { auto_load: true }
c.columns = [ c.columns = [
@@ -5,10 +5,18 @@
// setting the 'rowclick' event // setting the 'rowclick' event
var view = this.getComponent('user_transactions').getView(); var view = this.getComponent('user_transactions').getView();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').disable();
}
view.on('itemclick', function(view, record){ view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectTransaction({transaction_id: record.get('id')}); this.selectTransaction({transaction_id: record.get('id')});
this.getComponent('transaction_logs').getStore().load(); this.getComponent('transaction_logs').getStore().load();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').enable();
}
}, this); }, this);
} }
} }
+2 -1
View File
@@ -12,13 +12,14 @@ class Users < Netzke::Basepack::Grid
super super
c.header = false c.header = false
c.model = "User" c.model = "User"
c.force_fit = true
c.columns = [ c.columns = [
{ :name => :username, :read_only => true }, { :name => :username, :read_only => true },
:first_name, :first_name,
:last_name, :last_name,
:email, :email,
{ :id => :bike__shop_id, :name => :bike__shop_id} { :text => "Bike - Shop ID", :id => :bike__shop_id, :name => :bike__shop_id, :default_value => Bike.exists? ? Bike.first.id : nil }
] ]
c.columns << :reset if can? :manage, User c.columns << :reset if can? :manage, User
@@ -1,10 +1,10 @@
class UsersAndCustomersAccordian < Netzke::Basepack::Accordion class UsersAndCustomersLowerTabs < Netzke::Basepack::TabPanel
component :customers component :customers
component :users component :users
def configure(c) def configure(c)
c.prevent_header = true c.prevent_header = true
c.items = [ :customers, :users ] c.items = [ :users, :customers ]
super super
end end
end end
+2 -4
View File
@@ -2,16 +2,14 @@ class UsersAndProfilesBorder < Netzke::Base
# Remember regions collapse state and size # Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence include Netzke::Basepack::ItemPersistence
component :users component :users
component :user_profiles component :user_and_profiles_lower_tabs
component :user_logs
def configure(c) def configure(c)
super super
c.header = false c.header = false
c.items = [ c.items = [
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true }, { netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
{ netzke_component: :user_profiles, region: :south, height: 150, split: true}, { netzke_component: :user_and_profiles_lower_tabs, region: :south, height: 300, split: true}
{ netzke_component: :user_logs, region: :east, split: true}
] ]
end end
@@ -2,18 +2,48 @@
initComponent: function(){ initComponent: function(){
// calling superclass's initComponent // calling superclass's initComponent
this.callParent(); this.callParent();
var stats = this.getComponent('user_stats'); var stats = this.queryById('user_stats');
if (stats != undefined){ if (stats != undefined){
stats.updateStats(); stats.updateStats();
} }
if( this.queryById('user_profiles')){
this.queryById('user_profiles').disable();
}
if( this.queryById('user_logs')){
this.queryById('user_logs').disable();
//update user stats
var store = this.queryById('user_logs').getStore()
store.on('load', function (store, records, operation, success){
if( this.queryById('user_stats') ){
this.queryById('user_stats').updateStats();
}
}, this);
}
if( this.queryById('user_stats') ){
this.queryById('user_stats').disable();
}
// setting the 'rowclick' event // setting the 'rowclick' event
var view = this.getComponent('users').getView(); var view = this.getComponent('users').getView();
view.on('itemclick', function(view, record){ view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectUser({user_id: record.get('id')}); this.selectUser({user_id: record.get('id')});
this.getComponent('user_profiles').getStore().load();
this.getComponent('user_logs').getStore().load(); if( this.queryById('user_profiles')){
this.queryById('user_profiles').getStore().load();
this.queryById('user_profiles').enable();
}
if( this.queryById('user_logs')){
this.queryById('user_logs').getStore().load();
this.queryById('user_logs').enable();
}
if( this.queryById('user_stats') ){
this.queryById('user_stats').updateStats();
this.queryById('user_stats').enable();
}
}, this); }, this);
} }
} }
+8 -7
View File
@@ -1,7 +1,7 @@
class Bike < ActiveRecord::Base class Bike < ActiveRecord::Base
acts_as_loggable acts_as_loggable
attr_accessible :shop_id, :serial_number, :bike_brand_id, :model, :color, :bike_style_id, :seat_tube_height, attr_accessible :shop_id, :serial_number, :bike_brand_id, :model, :color, :bike_style_id, :seat_tube_height,
:top_tube_length, :wheel_size, :value, :bike_condition_id, :bike_purpose_id :top_tube_length, :bike_wheel_size_id, :value, :bike_condition_id, :bike_purpose_id
has_many :transactions has_many :transactions
@@ -11,19 +11,16 @@ class Bike < ActiveRecord::Base
belongs_to :bike_style belongs_to :bike_style
belongs_to :bike_condition belongs_to :bike_condition
belongs_to :bike_purpose belongs_to :bike_purpose
belongs_to :bike_wheel_size
default_scope order('shop_id ASC') validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
validates :shop_id, :presence => true, :uniqueness => true, :length => { :minimum => 3 }
validates :serial_number, :length => { :minimum => 3 } validates :serial_number, :length => { :minimum => 3 }
validates :model, :length => { :maximum => 50 } validates :model, :length => { :maximum => 50 }
validates :bike_brand_id, :presence => true validates :bike_brand_id, :presence => true
validates :color, :presence => true validates :color, :presence => true
validates :bike_style_id, :presence => true validates :bike_style_id, :presence => true
validates :seat_tube_height, :presence => true validates :seat_tube_height, :presence => true
validates :top_tube_length, :presence => true validates :bike_wheel_size_id, :presence => true
validates :wheel_size, :presence => true
#validates :value, :presence => true
validates :bike_condition_id, :presence => true validates :bike_condition_id, :presence => true
validates :bike_purpose_id, :presence => true validates :bike_purpose_id, :presence => true
@@ -47,6 +44,10 @@ class Bike < ActiveRecord::Base
self.bike_purpose self.bike_purpose
end end
def wheel_size
self.bike_wheel_size
end
def to_s def to_s
"#{brand} - #{model} - #{style}" "#{brand} - #{model} - #{style}"
end end
+16
View File
@@ -0,0 +1,16 @@
class BikeWheelSize < ActiveRecord::Base
belongs_to :bike
def display_string
result = []
result << "#{twmm}-#{rdmm}" unless twmm.blank? and rdmm.blank?
result << "#{rdin}x#{twin}" unless rdin.blank? and twin.blank?
result << "#{rdfr}x#{twfr}" unless rdfr.blank? and twfr.blank?
result << description unless description.blank?
result.join(" | ")
end
def to_s
"#{twmm}-#{rdmm} #{rdin}x#{twin} #{rdfr}x#{twfr} '#{description}' #{tire_common_score}"
end
end
+2 -1
View File
@@ -47,7 +47,8 @@ class User < ActiveRecord::Base
### TODO methods below probably belong somewhere else ### TODO methods below probably belong somewhere else
def completed_build_bikes def completed_build_bikes
purpose_id = BikePurpose.find_by_purpose("BUILDBIKE").id #default BUILDBIKE/CLASS ID is 5
purpose_id = 5
Bike.find_by_sql(" Bike.find_by_sql("
SELECT * SELECT *
FROM bikes FROM bikes
+3
View File
@@ -11,6 +11,9 @@
body { body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */ padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
} }
.x-boundlist-item {
white-space: nowrap;
}
%body %body
.container .container
@@ -3,8 +3,7 @@ class AddModelToBike < ActiveRecord::Migration
add_column :bikes, :model, :string add_column :bikes, :model, :string
change_column :bikes, :bike_model_id, :integer, :null => true change_column :bikes, :bike_model_id, :integer, :null => true
Bike.all.each do |bike| Bike.all.each do |bike|
bike.model = BikeModel.find_by_id(bike.bike_model_id).model bike.update_attribute(:model, BikeModel.find_by_id(bike['bike_model_id']).model )
bike.save
end end
end end
end end
@@ -0,0 +1,22 @@
class ChangeBikeShopId < ActiveRecord::Migration
# We don't use change_column because we have arbitrary strings we
# need to strip the characters from to make the integer values
def up
bikes = Bike.all
remove_column :bikes, :shop_id
add_column :bikes, :shop_id, :integer, :unique => true
# deconflict shop ids
bikes.each do |bike|
new_id = bike.shop_id.gsub(/[^\d]/, '').to_i rescue 1
while bikes.map(&:shop_id).include? new_id
new_id += 1
end
bike.update_attribute(:shop_id, new_id)
end
end
def down
change_column :bikes, :shop_id, :string
end
end
@@ -0,0 +1,22 @@
class CreateBikeWheelSizes < ActiveRecord::Migration
def up
create_table(:bike_wheel_sizes) do |t|
t.integer :twmm
t.integer :rdmm
t.string :twin
t.string :rdin
t.string :twfr
t.string :rdfr
t.string :description
t.string :tire_common_score
end
#create the default undetermined wheel size record
BikeWheelSize.create( twmm: 0, rdmm: 0, rdin: 0, twin: 0, rdfr: 0, twfr: 0, description: "UNDETERMINED", tire_common_score: 0)
ActiveRecord::Base.connection.execute(IO.read(File.join(Rails.root, "db", "seed", "sql", "common_wheel_sizes.sql")))
end
def down
drop_table :bike_wheel_sizes
end
end
@@ -0,0 +1,32 @@
class AlterBikeWheelToId < ActiveRecord::Migration
def up
add_column :bikes, :bike_wheel_size_id, :integer
undetermined_id = BikeWheelSize.find_by_rdin("0")
Bike.find_each do |bike|
wheel_size = BikeWheelSize.find_by_rdin(bike['wheel_size'].to_s)
if wheel_size.nil?
wheel_size_id = undetermined_id
else
wheel_size_id = wheel_size.id
end
bike.update_attribute(:bike_wheel_size_id, wheel_size_id)
end
remove_column :bikes, :wheel_size
end
def down
add_column :bikes, :wheel_size, :integer
Bike.find_each do |bike|
wheel_size = BikeWheelSize.find_by_id(bike['bike_wheel_size_id'])
bike.update_attribute(:wheel_size, wheel_size["rdin"].to_i)
end
remove_column :bikes, :bike_wheel_size_id
end
end
+27 -18
View File
@@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130902201853) do ActiveRecord::Schema.define(:version => 20131019170248) do
create_table "bike_actions", :force => true do |t| create_table "bike_actions", :force => true do |t|
t.string "action", :limit => 128, :null => false t.string "action", :limit => 128, :null => false
@@ -46,25 +46,34 @@ ActiveRecord::Schema.define(:version => 20130902201853) do
t.datetime "updated_at", :null => false t.datetime "updated_at", :null => false
end end
create_table "bikes", :force => true do |t| create_table "bike_wheel_sizes", :force => true do |t|
t.string "serial_number" t.integer "twmm"
t.integer "bike_brand_id", :null => false t.integer "rdmm"
t.integer "bike_model_id" t.string "twin"
t.string "color" t.string "rdin"
t.integer "bike_style_id", :null => false t.string "twfr"
t.float "seat_tube_height" t.string "rdfr"
t.float "top_tube_length" t.string "description"
t.integer "wheel_size" t.string "tire_common_score"
t.float "value"
t.integer "bike_condition_id", :null => false
t.integer "bike_purpose_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "shop_id"
t.string "model"
end end
add_index "bikes", ["shop_id"], :name => "index_bikes_on_shop_id", :unique => true create_table "bikes", :force => true do |t|
t.string "serial_number"
t.integer "bike_brand_id", :null => false
t.integer "bike_model_id"
t.string "color"
t.integer "bike_style_id", :null => false
t.float "seat_tube_height"
t.float "top_tube_length"
t.float "value"
t.integer "bike_condition_id", :null => false
t.integer "bike_purpose_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "model"
t.integer "shop_id"
t.integer "bike_wheel_size_id"
end
create_table "credit_conversions", :force => true do |t| create_table "credit_conversions", :force => true do |t|
t.integer "conversion", :default => 1 t.integer "conversion", :default => 1
+37
View File
@@ -0,0 +1,37 @@
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (54,110,'8 1/2','2','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (62,203,'12','1/2 x 2 1/4','320 ','57','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,305,'16','1,75 [x 2]','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (32,369,'16','1 1/4','','','Moulton',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,355,'18','1,75 [ x 2]','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (28,406,'20','1 1/8','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (37,406,'20','1 3/8','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,406,'20','1,75 [ x 2]','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,507,'24','1,75 [ x 2]','600 ','50C','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (37,540,'24','1 3/8 A','600 ','35A','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (25,559,'26','1','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (32,559,'26','1,3','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (37,559,'26','1,5','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (40,559,'26','1,6','','','MTB',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (44,559,'26','1,6','','','City/trekking',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,559,'26','1,75 [ x 2]','650 ','50','City/Trekking',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (NULL,NULL,'26','1,9','','','MTB',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (50,'559','26','1,9','','','City/Trekking',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (NULL,NULL,'26','2','','','MTB',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (54,559,'26','2,1','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (57,559,'26','2,3 [2,125]','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (20,571,'','','650 ','20C','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (23,571,'','','650 ','23C','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (28,584,'26','1 1/8 x 1 1/2 B','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (37,584,'26','1 3/8 x 1 1/2 B','650 ','38A','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (37,590,'26','1 3/8','650 [','35] A','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (20,622,'','','700 ','20C','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (23,622,'','','700 ','23C','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (25,622,'','','700 ','25C','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (28,622,'28','1 5/8 x 1 3/8','700 ','28C [700C]','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (32,622,'28','1 1/4 x 1 3/4','700 ','32C [700C Course]','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (37,622,'28','1 3/8 x 1 5/8','700 ','37C [35C]','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (42,622,'28','1,6','700 ','42C [40C]','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (47,622,'28','1,75','700 ','47C [45C]','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (28,630,'27','1 1/8 [1 1/4 fifty]','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (32,630,'27','1 1/4','','','',1);
INSERT INTO bike_wheel_sizes (TWMM, RDMM,RDIN,TWIN,RDFR,TWFR,DESCRIPTION,tire_common_score) values (40,635,'28','1 1/2 [1 3/8]','700 ','38B [35B]','',1);
+17 -10
View File
@@ -14,21 +14,28 @@ end
#Load bike brands and models from sql #Load bike brands and models from sql
if BikeBrand.all.empty? and BikeModel.all.empty? if BikeBrand.all.empty? and BikeModel.all.empty?
sql_path = File.join(Rails.root, 'db', 'seed', 'sql', 'bike_brands_and_models.sql') # Need to use DEFAULT instead of explicit IDs that are used in the sql file,
db_config = Rails.application.config.database_configuration[Rails.env] # so that the PG table ID sequence is incremented
cmd = "psql #{db_config["database"]} < #{sql_path}" #
system cmd # Note the drop(1) which assumes we have a junk PRAGMA line at the top
load_statements = File.readlines(File.join(Rails.root, 'db', 'seed', 'sql', 'bike_brands_and_models.sql')).drop(1).map do |statement|
statement.sub(/VALUES\(\d+,/, 'VALUES(DEFAULT,')
end
ActiveRecord::Base.connection.execute(load_statements.join)
end end
if Rails.env.development? if Rails.env.development?
#create default admin user #create default users
if User.all.empty? if User.all.empty?
FactoryGirl.create(:user) u = FactoryGirl.create(:user)
FactoryGirl.create(:staff) FactoryGirl.create(:user_profile, user_id: u.id)
FactoryGirl.create(:bike_admin) u = FactoryGirl.create(:staff)
FactoryGirl.create(:admin) FactoryGirl.create(:user_profile, user_id: u.id)
FactoryGirl.create(:user_profile) u = FactoryGirl.create(:bike_admin)
FactoryGirl.create(:user_profile, user_id: u.id)
u = FactoryGirl.create(:admin)
FactoryGirl.create(:user_profile, user_id: u.id)
end end
#create fake bikes #create fake bikes
+12 -14
View File
@@ -2,21 +2,19 @@
FactoryGirl.define do FactoryGirl.define do
factory :bike do factory :bike do
sequence :shop_id do |n| sequence(:shop_id) {|n| n}
"Shop ID #{n}"
end
sequence :serial_number do |n| sequence :serial_number do |n|
"S/N# #{n}" "#{Faker::Code.isbn}-#{n}"
end end
bike_brand_id 1 bike_brand_id { BikeBrand.all.sample.id }
bike_model_id 1 model { Faker::Commerce.product_name }
color "FFFFFF" color { sprintf("%06X", Random.rand(16777215) ) }
bike_style_id 1 bike_style_id { BikeStyle.all.sample.id }
seat_tube_height 5 seat_tube_height { Random.rand(25) }
top_tube_length 6 top_tube_length { Random.rand(25) }
wheel_size 27 bike_wheel_size_id { BikeWheelSize.all.sample.id }
value 100 value { Random.rand(200) }
bike_condition_id 1 bike_condition_id { BikeCondition.all.sample.id }
bike_purpose_id 1 bike_purpose_id { BikePurpose.all.sample.id }
end end
end end
+6 -6
View File
@@ -1,11 +1,11 @@
FactoryGirl.define do FactoryGirl.define do
factory :user_profile do factory :user_profile do
user_id 1 user_id 1
addrStreet1 "Charles Street" addrStreet1 { Faker::Address.street_address }
addrStreet2 "Apt #42" addrStreet2 { Faker::Address.secondary_address }
addrCity "Baltimore" addrCity { Faker::Address.city }
addrState "MD" addrState { Faker::Address.state_abbr }
addrZip "21231" addrZip { Faker::Address.zip_code }
phone "(410)8675309" phone { Faker::PhoneNumber.cell_phone }
end end
end end
+1
View File
@@ -1,5 +1,6 @@
FactoryGirl.define do FactoryGirl.define do
factory :user do factory :user do
sequence(:username) { |n| "user_#{n}" } sequence(:username) { |n| "user_#{n}" }
sequence(:email) { |n| "user_#{n}@example.com" } sequence(:email) { |n| "user_#{n}@example.com" }
password 'password' password 'password'