95 Commits
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
Jason Denney cfeb6fd1d2 Update README.md
Remove mailcatcher instructions since no emails are sent currently.
2013-10-15 16:21:57 -04:00
Jason Denney ec5db646a0 Use models to sort data when possible 2013-09-02 18:24:34 -04:00
Jason Denney de266e72d9 Merge pull request #62 from spacemunkay/denney-css-user-sign-up
Make user signup easier to read & enter
2013-09-02 15:08:59 -07:00
Jason Denney 67c51068cc Make user signup easier to read & enter 2013-09-02 18:08:05 -04:00
Jason Denney 8c6513099d Merge pull request #61 from spacemunkay/denney-move-model-to-bike
Moving bike model to the bikes table
2013-09-02 14:28:06 -07:00
Jason Denney 47f616b7b0 Moving bike model to the bikes table
Leaving the bike_models table and bikes.bike_model_id column as a backup.
2013-09-02 17:26:07 -04:00
Jason Denney 087325c444 Whoops, only create once… not for every update... 2013-09-02 16:23:58 -04:00
Jason Denney 3c4e5c21c2 Merge pull request #59 from spacemunkay/denney-remove-bike-log-end-date
Bike log events are instanteous, remove end_date
2013-09-02 12:28:59 -07:00
Jason Denney 69c01b8225 Bike log events are instanteous, remove end_date 2013-09-02 15:26:08 -04:00
Jason Denney 0aa9c16dbd Merge pull request #58 from spacemunkay/denney-default-sort-order
Set default sort order on users, bikes, userroles
2013-09-02 11:17:08 -07:00
Jason Denney e491c0530c Set default sort order on users, bikes, userroles 2013-09-02 14:16:39 -04:00
Jason Denney 3599cc2bbb Merge pull request #56 from spacemunkay/denney-bike-status-to-purpose
Changing bike_status to bike_purporse
2013-09-02 11:03:21 -07:00
Jason Denney cecb3b1a32 Changing bike_status to bike_purporse 2013-09-02 14:02:18 -04:00
Jason Denney 611b1b1151 Merge pull request #55 from spacemunkay/denney-disable-username-edit
set username to read only
2013-09-02 09:23:32 -07:00
Jason Denney 5e80c8866a set username to read only 2013-09-02 12:22:26 -04:00
Jason Denney 835e73ea97 Merge branch 'master' of github.com:spacemunkay/velocipede 2013-08-28 23:06:14 -04:00
Jason Denney cb2acd19ba ignore public/assets 2013-08-28 23:06:02 -04:00
Jason Denney ee12bfee8a Merge pull request #54 from spacemunkay/denney-autocomplete-max-chars
Changing min_chars for autocomplete to 1 char
2013-08-28 20:03:21 -07:00
Jason Denney 4d008163e1 Changing min_chars for autocomplete to 1 char 2013-08-28 23:01:29 -04:00
Jason Denney 06856f115c Merge pull request #52 from spacemunkay/denney-remove-bike-serial-constraint
Remove unique constraint on bike_serial
2013-08-17 21:56:07 -07:00
Jason Denney a9fb3487a1 Remove unique constraint on bike_serial
bike serial index was already removed.
2013-08-18 00:51:45 -04:00
Jason Denney 869b67d3e4 Merge pull request #50 from spacemunkay/denney-user-roles-by-full-name
User roles added by full name instead of username
2013-08-17 21:04:02 -07:00
Jason Denney cd4f891c95 User roles added by full name instead of username 2013-08-18 00:00:53 -04:00
Jason Denney 71a4bb0af4 Merge pull request #49 from spacemunkay/denney-remove-hours-bike
remove hours, created_at, updated_at in bike logs
2013-08-17 20:43:22 -07:00
Jason Denney f1214ab7e6 remove hours, created_at, updated_at in bike logs 2013-08-17 23:38:07 -04:00
Jason Denney aaa6fda90c Merge pull request #48 from spacemunkay/denney-add-end-date-user-timesheet
Added end date to user timesheet, removed created & updated
2013-08-17 20:23:17 -07:00
Jason Denney 4a7751959c Added end date to user timesheet, removed created & updated 2013-08-17 23:22:07 -04:00
Jason Denney 028a45702e More comments 2013-08-17 14:07:35 -04:00
Jason Denney 1a8b9c9247 Merge pull request #47 from spacemunkay/denney-create-user-sign-in
Changed so that user gets checked in after create.
2013-08-17 11:03:00 -07:00
Jason Denney 4e7f3a1761 Changed so that user gets checked in after create. 2013-08-17 14:01:40 -04:00
Jason Denney a6b41b5560 Merge pull request #46 from spacemunkay/denney-fix-search
Fixing search form for bikes updated to netzke 8.4
2013-08-15 19:07:01 -07:00
Jason Denney 4d7634605c Fixing search form for bikes updated to netzke 8.4
Updated Netzke 8.4, better search styling, case insensitive search, can search associations
Fixed mistake where bikes.condition_id was a string… durrrrrr needs to be integer
2013-08-15 22:05:29 -04:00
Jason Denney cf922be1b1 Quick workflow short term fix 2013-07-28 15:24:02 -04:00
61 changed files with 792 additions and 238 deletions
+1
View File
@@ -7,5 +7,6 @@ tmp/
.rbenv-version
.DS_Store
public/extjs
public/assets
public/images/icons
config/database.yml
+1
View File
@@ -33,6 +33,7 @@ group :development, :test do
gem 'rspec-rails', '~> 2.8.1'
gem 'factory_girl_rails', '~> 1.2'
gem 'pry', '~> 0.9.8'
gem 'faker'
end
group :test do
+9 -6
View File
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/spacemunkay/acts_as_loggable.git
revision: 49e264849ed9018445fb999cacfc55e1e28d7faa
revision: 8484dc1f7a58699705c5ffb593ebdf700a9f374a
specs:
acts_as_loggable (0.0.7)
activerecord (>= 3.0)
@@ -77,6 +77,8 @@ GEM
factory_girl_rails (1.7.0)
factory_girl (~> 2.6.0)
railties (>= 3.0.0)
faker (1.2.0)
i18n (~> 0.5)
ffi (1.6.0)
formatador (0.2.4)
gherkin (2.11.6)
@@ -122,13 +124,13 @@ GEM
treetop (~> 1.4.8)
method_source (0.8.1)
mime-types (1.22)
multi_json (1.7.2)
netzke-basepack (0.8.2)
multi_json (1.8.2)
netzke-basepack (0.8.4)
netzke-core (~> 0.8.2)
netzke-cancan (0.8.2)
cancan
netzke-core
netzke-core (0.8.3)
netzke-core (0.8.4)
execjs
uglifier
nokogiri (1.5.9)
@@ -207,8 +209,8 @@ GEM
less-rails (~> 2.2.2)
railties (>= 3.1)
therubyracer (~> 0.10.1)
tzinfo (0.3.37)
uglifier (1.3.0)
tzinfo (0.3.38)
uglifier (2.1.2)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
warden (1.1.1)
@@ -231,6 +233,7 @@ DEPENDENCIES
decent_exposure (~> 1.0.1)
devise (~> 2.0.4)
factory_girl_rails (~> 1.2)
faker
guard-rspec
guard-spork
haml-rails (~> 0.3.4)
+24 -7
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
1. use rvm
@@ -5,6 +27,8 @@
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.
@@ -19,8 +43,6 @@
1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start`
1. Create your PG user `createuser -d -P velocipede`
1. Create your database `createdb -U velocipede --owner=velocipede velocipede`
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
# Optional
Add icons
@@ -28,11 +50,6 @@
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`
## 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
# License
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
@@ -3,6 +3,232 @@ 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',
dateConfig:{},
timeConfig:{},
// properties
dateValue: null, // Holds the actual date
dateField: null,
hourField: null,
minuteField: null,
ampmField: null,
initComponent: function(){
var me = this
,i = 0
,key
,tab;
me.items = me.items || [];
me.dateField = Ext.create('Ext.form.field.Date', Ext.apply({
format:me.dateFormat,
flex:1,
isFormField:false, //exclude from field query's
submitValue:false,
submitFormat: me.dateSubmitFormat,
readOnly: me.readOnly
}, me.dateConfig));
me.items.push(me.dateField);
me.hourField = Ext.create('Ext.form.field.Number', {
maxWidth: 25,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Hour cannot be blank.",
allowDecimals: false,
maxValue: 12,
minValue: 1,
maxLength: 2,
enforceMaxLength: 2,
hideTrigger: true,
submitValue:false,
flex:1,
fieldStyle: "text-align:right;",
isFormField:false, //exclude from field query's
});
me.items.push(me.hourField);
me.colon = Ext.create('Ext.draw.Text', {
text: ':',
padding: '3 3 0 3'
});
me.items.push(me.colon);
me.minuteField = Ext.create('Ext.form.field.Text', {
maxWidth: 30,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Minutes cannot be blank.",
regex: /[012345]\d/,
maxLength: 2,
enforceMaxLength: 2,
submitValue:false,
flex:1,
isFormField:false, //exclude from field query's
});
me.items.push(me.minuteField);
me.ampmField = Ext.create('Ext.form.ComboBox', {
maxWidth: 45,
value: 'PM',
store: ['AM', 'PM'],
forceSelection: true,
flex:1,
editable: false
});
me.items.push(me.ampmField);
for (; i < me.items.length; i++) {
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
me.items[i].on('specialkey', function(field, event){
key = event.getKey();
tab = key == event.TAB;
if (tab && me.focussedItem == me.dateField) {
event.stopEvent();
me.timeField.focus();
return;
}
me.fireEvent('specialkey', field, event);
});
}
me.callParent();
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
this.inputEl = {
dom: document.createElement('div'),
swallowEvent:function(){}
};
me.initField();
},
focus:function(){
this.callParent(arguments);
this.dateField.focus();
},
onItemFocus:function(item){
if (this.blurTask){
this.blurTask.cancel();
}
this.focussedItem = item;
},
onItemBlur:function(item, e){
var me = this;
if (item != me.focussedItem){ return; }
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
me.blurTask = new Ext.util.DelayedTask(function(){
me.fireEvent('blur', me, e);
});
me.blurTask.delay(100);
},
getValue: function(){
var value = null
,date = this.dateField.getSubmitValue()
,time = null
,format;
hour = this.hourField.getValue();
minute = this.minuteField.getValue();
ampm = this.ampmField.getValue();
time = Ext.Date.parse(hour + " " + minute + " " + ampm, 'g i A');
time = Ext.Date.format(time, this.timeSubmitFormat);
if (date){
if (time){
format = this.getFormat();
value = Ext.Date.parse(date + ' ' + time, format);
} else {
value = this.dateField.getValue();
}
}
return value;
},
getSubmitValue: function(){
// var value = this.getValue();
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
var me = this
,format = me.getFormat()
,value = me.getValue();
return value ? Ext.Date.format(value, format) : null;
},
setValue: function(value){
if (Ext.isString(value) || Ext.isDate(value)){
if( Ext.isDate(value)){
dt = value;
}else{
dt = new Date(value);
value = Ext.Date.parse(value, this.getFormat()); //this.dateTimeFormat
}
this.dateField.setValue(value);
hour = Ext.Date.format(dt, 'g');
minute = Ext.Date.format(dt, 'i');
ampm = Ext.Date.format(dt, 'A');
this.hourField.setValue(hour);
this.minuteField.setRawValue(minute);
this.ampmField.setValue(ampm);
}
},
getFormat: function(){
value = (this.dateField.submitFormat || this.dateField.format) + " " + (this.timeSubmitFormat || this.timeFormat);
return value;
},
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
getSubmitData: function(){
var me = this
,data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getSubmitValue();
}
return data;
}
});
Ext.define('Ext.ux.form.field.ColorCombo', {
extend:'Ext.form.FieldContainer',
mixins:{
+1 -5
View File
@@ -19,11 +19,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel
#all users
# (had to use hash for borders to get the title to display properly)
@@app_tab_panel_items = [ :bikes_border,
{ layout: :fit,
wrappedComponent: :brands_and_models_border,
title: "Brands/Models"}
]
@@app_tab_panel_items = [ :bikes_border ]
#for users only
if not controller.current_user.role?(:admin)
+2 -1
View File
@@ -3,10 +3,11 @@ 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
c.prohibit_delete = true if cannot? :delete, BikeBrand
c.prohibit_delete = true if cannot? :delete, BikeBrand
end
#override with nil to remove actions
+4 -7
View File
@@ -6,6 +6,7 @@ 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 = {
@@ -18,12 +19,9 @@ 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, :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},
{ :name => :end_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
:description,
{ :name => :bike_action__action, :text => 'Action'},
{ :name => :created_at, :read_only => true},
{ :name => :updated_at, :read_only => true},
{ :name => :bike_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::BikeAction.first.id},
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -48,10 +46,9 @@ class BikeLogs < Netzke::Basepack::Grid
def default_fields_for_forms
[
{ :name => :start_date},
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :bike_action__action, :field_label => 'Action'}
{ :name => :bike_action__action, :field_label => 'Action', :min_chars => 1 }
]
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.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 = {
+23 -14
View File
@@ -2,11 +2,15 @@ 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'},
{ :name => :shop_id, :text => 'Shop ID', :default_value => Bike.last.id.to_i + 1},
:serial_number,
{ :name => :bike_brand__brand, :text => 'Brand' },
{ :name => :bike_model__model, :text => 'Model',
{ :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'},
{ :name => :model, :text => 'Model',
:scope => lambda { |rel|
if session[:selected_bike_brand_id]
rel.where(:bike_brand_id => session[:selected_bike_brand_id])
@@ -16,20 +20,25 @@ class Bikes < Netzke::Basepack::Grid
}
},
#needs to have type :action or else won't work in grid, because... netzke
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block},
{ :name => :bike_style__style, :text => 'Style' },
{ :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 => :wheel_size, :text => 'Wheel Size (in)'},
{ :name => :bike_wheel_size__display_string, :text => 'Wheel Size'},
:value,
{ :name => :bike_condition__condition, :text => 'Condition'},
{ :name => :bike_status__status, :text => 'Status'},
{ :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 => :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
@@ -38,18 +47,18 @@ class Bikes < Netzke::Basepack::Grid
def default_fields_for_forms
# :field_label MUST be defined in order for search to work
[
{ :name => :bike_brand__brand, :field_label => 'Brand', :min_chars => 1 },
{ :name => :model, :field_label => 'Model'},
{ :name => :shop_id, :field_label => 'Shop ID'},
{ :name => :serial_number, :field_label => 'Serial Number'},
{ :name => :bike_brand__brand, :field_label => 'Brand' },
{ :name => :bike_model__model, :field_label => 'Model'},
{ :name => "color", :xtype => "xcolorcombo"},
{ :name => :bike_style__style, :field_label => 'Style' },
{ :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 => :wheel_size, :field_label => 'Wheel Size (in)'},
{ :name => :bike_wheel_size__display_string, :field_label => 'Wheel Size'},
{ :name => :value, :field_label => 'Value'},
{ :name => :bike_condition__condition, :field_label => 'Condition'},
{ :name => :bike_status__status, :field_label => 'Status'}
{ :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
@@ -3,6 +3,16 @@
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"bike_brand__brand",
"bike_style__style",
"bike_condition__condition",
"bike_purpose__purpose"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
// setting the 'rowclick' event
var view = this.getView();
view.on('itemclick', function(view, record){
+2 -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_logs_and_tasks_accordian
component :bike_lower_tabs
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_logs_and_tasks_accordian, region: :south, height: 300, split: true}
{ netzke_component: :bike_lower_tabs, region: :south, height: 300, split: true}
]
end
@@ -5,14 +5,30 @@
// 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')});
if( this.queryById('bike_logs') ){
this.queryById('bike_logs').getStore().load();
// 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('tasks') ){
this.queryById('tasks').getStore().load();
if( bike_tasks_comp ){
bike_tasks_comp.getStore().load();
bike_tasks_comp.enable();
}
}, this);
}
+1
View File
@@ -4,6 +4,7 @@ class CheckIns < Netzke::Basepack::Grid
super
c.header = false
c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction).
where(:loggable_type => "User").
where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")).
+1
View File
@@ -1,6 +1,7 @@
class Customers < Netzke::Basepack::Grid
def configure(c)
c.model = "Customer"
c.force_fit = true
end
#override with nil to remove actions
@@ -4,7 +4,6 @@
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);
}
+1
View File
@@ -4,6 +4,7 @@ class Logs < Netzke::Basepack::Grid
super
c.header = false
c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.columns = [
:loggable_id,
:loggable_type,
+7 -4
View File
@@ -2,6 +2,8 @@ class Tasks < Netzke::Basepack::Grid
def configure(c)
super
#disable by default, will be enabled once bike is clicked
c.disabled = true
task_list_id = nil
if session[:selected_bike_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.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)
@@ -21,9 +24,9 @@ class Tasks < Netzke::Basepack::Grid
:task_list_id => task_list_id
}
c.columns = [
:done,
:task,
:notes,
:done
:notes
]
end
@@ -31,9 +34,9 @@ class Tasks < Netzke::Basepack::Grid
fields = []
fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
fields.concat( [
:done,
:task,
:notes,
:done
:notes
])
end
+3 -2
View File
@@ -5,6 +5,7 @@ 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 = {
@@ -20,7 +21,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'},
{ :name => :transaction_action__action, :text => 'Method', :default_value => ::ActsAsLoggable::TransactionAction.first.id},
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
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}"},
{ :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'}
{ :name => :transaction_action__action, :field_label => 'Payment Method', :min_chars => 1}
]
end
+1
View File
@@ -2,6 +2,7 @@ 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_accordian
component :users_and_customers_lower_tabs
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_accordian, region: :south, height: 300, split: true }
{ netzke_component: :users_and_customers_lower_tabs, region: :south, height: 300, split: true }
]
end
@@ -5,10 +5,19 @@
// 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);
}
}
@@ -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
+4 -5
View File
@@ -34,18 +34,17 @@ class UserLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log"
c.title = "User Timesheet"
c.force_fit = true
c.data_store = user_log_data_store
c.scope = user_log_scope
c.strong_default_attrs = user_log_strong_default_attrs
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :format => "g:ia - D, M j - Y", :default_value => Time.now.to_formatted_s(:db) },
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration},
:description,
{ :name => :user_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::UserAction.all.first.id },
:created_at,
:updated_at,
{ :id => :user_action__action, :name => :user_action__action, :text => 'Action', :default_value => ::ActsAsLoggable::UserAction.all.first.id },
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -65,7 +64,7 @@ class UserLogs < Netzke::Basepack::Grid
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action'},
{ :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 => [
{:xtype => 'combo', :no_binding => true, :name => :copy_id, :title => 'Bike', :fieldLabel => 'Bike', :store => bike_store, :value => bike_id}
]
@@ -0,0 +1,13 @@
{
initComponent: function(){
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"user_action__purpose"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
}
}
+1
View File
@@ -19,6 +19,7 @@ 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
+6 -4
View File
@@ -2,16 +2,18 @@ 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"},
{ :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"},
{ :name => :role__role, :text => "Role", :default_value => Role.exists? ? Role.first.id : nil},
:created_at,
:updated_at,
:ends ]
@@ -19,7 +21,7 @@ class UserRoleJoins < Netzke::Basepack::Grid
def default_fields_for_forms
[
{ :name => :user__username, :field_label => 'Username' },
{ :name => :user__full_name, :field_label => 'Name'},
{ :name => :role__role, :field_label => 'Role' },
{ :name => :ends }
]
+1 -1
View File
@@ -40,7 +40,7 @@ class UserStats < Netzke::Base
private
def user
controller.current_user
User.find_by_id(session[:selected_user_id]) || controller.current_user
end
end
+1
View File
@@ -5,6 +5,7 @@ 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,10 +5,18 @@
// 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 -2
View File
@@ -12,13 +12,14 @@ class Users < Netzke::Basepack::Grid
super
c.header = false
c.model = "User"
c.force_fit = true
c.columns = [
:username,
{ :name => :username, :read_only => true },
:first_name,
:last_name,
:email,
: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
@@ -2,6 +2,13 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = ["bike__shop_id"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
this.getView().on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectCustomer({customer_id: record.get('id'), customer_type: 'User'});
@@ -1,10 +1,10 @@
class UsersAndCustomersAccordian < Netzke::Basepack::Accordion
class UsersAndCustomersLowerTabs < Netzke::Basepack::TabPanel
component :customers
component :users
def configure(c)
c.prevent_header = true
c.items = [ :customers, :users ]
c.items = [ :users, :customers ]
super
end
end
+2 -4
View File
@@ -2,16 +2,14 @@ class UsersAndProfilesBorder < Netzke::Base
# Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence
component :users
component :user_profiles
component :user_logs
component :user_and_profiles_lower_tabs
def configure(c)
super
c.header = false
c.items = [
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
{ netzke_component: :user_profiles, region: :south, height: 150, split: true},
{ netzke_component: :user_logs, region: :east, split: true}
{ netzke_component: :user_and_profiles_lower_tabs, region: :south, height: 300, split: true}
]
end
@@ -2,18 +2,48 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
var stats = this.getComponent('user_stats');
var stats = this.queryById('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')});
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);
}
}
+16 -18
View File
@@ -1,43 +1,37 @@
class Bike < ActiveRecord::Base
acts_as_loggable
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
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
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_status
belongs_to :bike_purpose
belongs_to :bike_wheel_size
validates :shop_id, :presence => true, :uniqueness => true, :length => { :minimum => 3 }
validates :serial_number, :uniqueness => true, :length => { :minimum => 3 }
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
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_wheel_size_id, :presence => true
validates :bike_condition_id, :presence => true
validates :bike_status_id, :presence => true
validates :bike_purpose_id, :presence => true
self.per_page = 15
after_save :create_task_list
after_create :create_task_list
def brand
self.bike_brand
end
def model
self.bike_model
end
def style
self.bike_style
end
@@ -46,8 +40,12 @@ class Bike < ActiveRecord::Base
self.bike_condition
end
def status
self.bike_status
def purpose
self.bike_purpose
end
def wheel_size
self.bike_wheel_size
end
def to_s
-1
View File
@@ -1,7 +1,6 @@
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
@@ -0,0 +1,9 @@
class BikePurpose < ActiveRecord::Base
attr_accessible :purpose
belongs_to :bike
def to_s
self.purpose
end
end
-9
View File
@@ -1,9 +0,0 @@
class BikeStatus < ActiveRecord::Base
attr_accessible :status
belongs_to :bike
def to_s
self.status
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
+5 -2
View File
@@ -20,6 +20,8 @@ class User < ActiveRecord::Base
belongs_to :bike
default_scope order('username ASC')
validates :first_name, :presence => true
validates :last_name, :presence => true
@@ -45,7 +47,8 @@ class User < ActiveRecord::Base
### TODO methods below probably belong somewhere else
def completed_build_bikes
status_id = BikeStatus.find_by_status("BUILDBIKE").id
#default BUILDBIKE/CLASS ID is 5
purpose_id = 5
Bike.find_by_sql("
SELECT *
FROM bikes
@@ -54,7 +57,7 @@ class User < ActiveRecord::Base
FROM transactions
WHERE customer_id = #{self.id}
) AS transactions ON bikes.id = transactions.bike_id
WHERE bike_status_id = #{status_id}")
WHERE bike_purpose_id = #{purpose_id}")
end
def total_credits
@@ -1,24 +1,25 @@
%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
.control-group
= f.label :addrStreet1, :class => "control-label"
.controls
= f.text_field :addrStreet1, :class => "control-label"
.control-group
= f.label :addrStreet2, :class => "control-label"
.controls
= f.text_field :addrStreet2, :class => "control-label"
.control-group
= f.label :addrCity, :class => "control-label"
.controls
= f.text_field :addrCity, :class => "control-label"
.control-group
= f.label :addrState, :class => "control-label"
.controls
= f.text_field :addrState, :class => "control-label"
.control-group
= f.label :addrZip, :class => "control-label"
.controls
= f.text_field :addrZip, :class => "control-label"
.control-group
= f.label :phone, :class => "control-label"
.controls
= f.text_field :phone, :class => "control-label"
+33 -31
View File
@@ -1,34 +1,36 @@
= stylesheet_link_tag "bootstrap_and_overrides", :media => "all"
%h2 Sign up
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :class => "form-horizontal"}) do |f|
= devise_error_messages!
%div
= f.label :username
%br/
= f.text_field :username
%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 :email
%br/
= f.email_field :email
%div
- profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles
= f.fields_for :user_profiles, profile_builder do |builder|
= render 'user_profile_fields', f: builder
%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"
.controls
%h2 Sign up
.control-group
= f.label :username, :class => "control-label"
.controls
= f.text_field :username
.control-group
= f.label :first_name, :class => "control-label"
.controls
= f.text_field :first_name
.control-group
= f.label :last_name, :class => "control-label"
.controls
= f.text_field :last_name
.control-group
= f.label :email, :class => "control-label"
.controls
= f.email_field :email
- profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles
= f.fields_for :user_profiles, profile_builder do |builder|
= render 'user_profile_fields', f: builder
.control-group
= f.label :password, :class => "control-label"
.controls
= f.password_field :password
.control-group
= f.label :password_confirmation, :class => "control-label"
.controls
= f.password_field :password_confirmation
.control-group
.controls
= f.submit "Sign up"
= render "links"
+3
View File
@@ -11,6 +11,9 @@
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
}
.x-boundlist-item {
white-space: nowrap;
}
%body
.container
+4 -1
View File
@@ -223,7 +223,10 @@ Devise.setup do |config|
end
#Check in the user if they sign in. (Devise uses Warden)
Warden::Manager.after_authentication do |user,auth,opts|
Warden::Manager.after_set_user do |user,auth,opts|
# this essentially gets called after every netzke request, but alas,
# only using the after_authenticaion callback doesn't get fired after
# user creation.
user.checkin unless user.checked_in?
end
@@ -0,0 +1,15 @@
class BikeConditionIdToInt < ActiveRecord::Migration
def up
#for Postgres
connection.execute(%q{
alter table bikes
alter column bike_condition_id
type integer using cast(bike_condition_id as integer)
})
end
def down
#you really don't want to go back
change_column :bikes, :bike_condition_id, :string
end
end
@@ -0,0 +1,7 @@
class BikeStatusToPurpose < ActiveRecord::Migration
def change
rename_column :bikes, :bike_status_id, :bike_purpose_id
rename_column :bike_statuses, :status, :purpose
rename_table :bike_statuses, :bike_purposes
end
end
@@ -0,0 +1,9 @@
class AddModelToBike < ActiveRecord::Migration
def change
add_column :bikes, :model, :string
change_column :bikes, :bike_model_id, :integer, :null => true
Bike.all.each do |bike|
bike.update_attribute(:model, BikeModel.find_by_id(bike['bike_model_id']).model )
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
+29 -19
View File
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130629152047) do
ActiveRecord::Schema.define(:version => 20131019170248) do
create_table "bike_actions", :force => true do |t|
t.string "action", :limit => 128, :null => false
@@ -34,8 +34,8 @@ ActiveRecord::Schema.define(:version => 20130629152047) do
t.integer "bike_brand_id", :null => false
end
create_table "bike_statuses", :force => true do |t|
t.string "status", :null => false
create_table "bike_purposes", :force => true do |t|
t.string "purpose", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
@@ -46,24 +46,34 @@ ActiveRecord::Schema.define(:version => 20130629152047) do
t.datetime "updated_at", :null => false
end
create_table "bikes", :force => true do |t|
t.string "serial_number"
t.integer "bike_brand_id", :null => false
t.integer "bike_model_id", :null => false
t.string "color"
t.integer "bike_style_id", :null => false
t.float "seat_tube_height"
t.float "top_tube_length"
t.integer "wheel_size"
t.float "value"
t.string "bike_condition_id", :null => false
t.integer "bike_status_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "shop_id"
create_table "bike_wheel_sizes", :force => true 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
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|
t.integer "conversion", :default => 1
+18
View File
@@ -0,0 +1,18 @@
undetermined:
id: 1
purpose: UNDETERMINED
scrap:
id: 2
purpose: SCRAP
parts:
id: 3
purpose: PARTS
storage:
id: 4
purpose: STORAGE
buildbike:
id: 5
purpose: BUILDBIKE
sale:
id: 6
purpose: SALE
-18
View File
@@ -1,18 +0,0 @@
undetermined:
id: 1
status: UNDETERMINED
scrap:
id: 2
status: SCRAP
parts:
id: 3
status: PARTS
storage:
id: 4
status: STORAGE
buildbike:
id: 5
status: BUILDBIKE
sale:
id: 6
status: SALE
+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
if BikeBrand.all.empty? and BikeModel.all.empty?
sql_path = File.join(Rails.root, 'db', 'seed', 'sql', 'bike_brands_and_models.sql')
db_config = Rails.application.config.database_configuration[Rails.env]
cmd = "psql #{db_config["database"]} < #{sql_path}"
system cmd
# Need to use DEFAULT instead of explicit IDs that are used in the sql file,
# so that the PG table ID sequence is incremented
#
# 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
if Rails.env.development?
#create default admin user
#create default users
if User.all.empty?
FactoryGirl.create(:user)
FactoryGirl.create(:staff)
FactoryGirl.create(:bike_admin)
FactoryGirl.create(:admin)
FactoryGirl.create(:user_profile)
u = FactoryGirl.create(:user)
FactoryGirl.create(:user_profile, user_id: u.id)
u = FactoryGirl.create(:staff)
FactoryGirl.create(:user_profile, user_id: u.id)
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
#create fake bikes
+12 -14
View File
@@ -2,21 +2,19 @@
FactoryGirl.define do
factory :bike do
sequence :shop_id do |n|
"Shop ID #{n}"
end
sequence(:shop_id) {|n| n}
sequence :serial_number do |n|
"S/N# #{n}"
"#{Faker::Code.isbn}-#{n}"
end
bike_brand_id 1
bike_model_id 1
color "FFFFFF"
bike_style_id 1
seat_tube_height 5
top_tube_length 6
wheel_size 27
value 100
bike_condition_id 1
bike_status_id 1
bike_brand_id { BikeBrand.all.sample.id }
model { Faker::Commerce.product_name }
color { sprintf("%06X", Random.rand(16777215) ) }
bike_style_id { BikeStyle.all.sample.id }
seat_tube_height { Random.rand(25) }
top_tube_length { Random.rand(25) }
bike_wheel_size_id { BikeWheelSize.all.sample.id }
value { Random.rand(200) }
bike_condition_id { BikeCondition.all.sample.id }
bike_purpose_id { BikePurpose.all.sample.id }
end
end
+6 -6
View File
@@ -1,11 +1,11 @@
FactoryGirl.define do
factory :user_profile do
user_id 1
addrStreet1 "Charles Street"
addrStreet2 "Apt #42"
addrCity "Baltimore"
addrState "MD"
addrZip "21231"
phone "(410)8675309"
addrStreet1 { Faker::Address.street_address }
addrStreet2 { Faker::Address.secondary_address }
addrCity { Faker::Address.city }
addrState { Faker::Address.state_abbr }
addrZip { Faker::Address.zip_code }
phone { Faker::PhoneNumber.cell_phone }
end
end
+1
View File
@@ -1,5 +1,6 @@
FactoryGirl.define do
factory :user do
sequence(:username) { |n| "user_#{n}" }
sequence(:email) { |n| "user_#{n}@example.com" }
password 'password'