Author SHA1 Message Date
Jason Denney a7bdfd7a04 Merge pull request #107 from spacemunkay/brigitte-sass
Updated Sign In Page
2017-01-08 11:37:09 -08:00
Brigitte Warner 91a5b7c64c remove unneeded sass-cache 2014-08-07 20:55:44 -04:00
Brigitte Warner 2148fec9f4 add vendor and sass-cache to gitignore 2014-08-07 20:46:26 -04:00
Brigitte Warner d9ed9feec1 update sign in page with bootstrap 3 2014-08-07 20:43:26 -04:00
Brigitte Warner c2e2ce5b1b update ruby and gem versions 2014-07-24 20:42:37 -04:00
Brigitte Warner e1e49cbbb2 update less to sass 2014-07-24 20:42:09 -04:00
Jason Denney fd5d4798b0 Merge pull request #103 from spacemunkay/denney-fix-user-add-bike
Fix User being able to add bikes
2014-06-12 22:26:30 -04:00
Jason Denney 3e480981c7 Fix User being able to add bikes 2014-06-12 22:24:00 -04:00
Jason Denney 523135ed9f Merge pull request #102 from spacemunkay/denney-fix-edit-redirect
root path to user edit
2014-06-12 21:25:29 -04:00
Jason Denney 5e95ce542c root path to user edit 2014-06-12 21:24:02 -04:00
Jason Denney a8e0c87d37 Merge pull request #101 from spacemunkay/mobile-ui
Mobile UI
2014-05-04 20:55:41 -04:00
Jason Denney 1b2c0fa378 Submit dates in locale format for time being
Added jquery date format plugin
2014-05-04 20:43:46 -04:00
Jason Denney 785e096aea Giving up on checkin tests for now 2014-05-04 11:26:37 -04:00
Jason Denney 5c4007a1be Show add another bike link after creating bike 2014-05-04 11:14:50 -04:00
Jason Denney b9b6ed5ca0 Remove stray puts in spec 2014-05-04 10:56:29 -04:00
Jason Denney 0605d0f0d9 Fixed creating bike work time entry & added spec 2014-05-04 10:56:14 -04:00
Jason Denney 071815b9fd Fix app layout for mobile devices 2014-05-04 10:34:55 -04:00
Jason Denney ca3026e7c5 Added delete time entry feature spec 2014-05-03 22:53:27 -04:00
Jason Denney 5e0d678ea1 Finish time entry delete JS/html 2014-05-03 22:08:23 -04:00
Jason Denney d94c436ba5 Added api/time_entry/delete method 2014-05-03 22:06:56 -04:00
Jason Denney 24facd4dee Time entry actions had the wrong IDs... 2014-05-03 21:48:35 -04:00
Jason Denney 894423e66f Fixed bug in user credits available
Personal work was counting toward credits, now it’s possible to add a
column to user actions to denote if a user action can count to credits.
2014-05-03 21:41:33 -04:00
Jason Denney 4ce4bd6597 WIP, rigged up JS/html to delete work entries 2014-05-03 19:29:57 -04:00
Jason Denney 2e21e327ca Home page link to time entries 2014-05-03 19:29:22 -04:00
Jason Denney 57d72c04a7 Fix TimeEntry scope 2014-05-03 19:28:56 -04:00
Jason Denney 4224f0de40 Added Time entries index page 2014-04-27 18:59:50 -04:00
Jason Denney 3d71add637 WIP, adding time entry index page 2014-04-27 15:46:42 -04:00
Jason Denney 7dd556d718 Fixed forwarding on time entry creation 2014-04-27 15:46:13 -04:00
Jason Denney 338905878e feature spec for adding new time entry 2014-04-27 15:07:21 -04:00
Jason Denney 7897795e8b Correct spec description 2014-04-27 14:39:32 -04:00
Jason Denney 00511c5b64 Finish Api/TimeEntries spec, fix bike_id handling 2014-04-27 14:39:11 -04:00
Jason Denney 1191014471 WIP, cleaned up api/time_entries, added TimeEntry
* Added scoped TimeEntry model
* Removed setting defaults in TimeEntry controller
* Still working on tests…
2014-04-26 20:06:47 -04:00
Jason Denney c0a9d8c1e5 Return time entry on successful create 2014-04-20 22:56:48 -04:00
Jason Denney 82f38fe35c Actually merge time_entry defaults 2014-04-20 22:56:26 -04:00
Jason Denney 2321197973 If bike selected, forward to bike checklist 2014-04-20 22:56:04 -04:00
Jason Denney 069a11f480 WIP, adding JS and api time entry create 2014-04-19 21:06:29 -04:00
Jason Denney b30203b750 Allow user select bike worked when adding time 2014-04-19 16:48:10 -04:00
Jason Denney 3e56f2eb19 Updating edit task list page w/ home & bike link 2014-04-19 16:25:15 -04:00
Jason Denney 2d65ebb45f JS Formatting 2014-04-19 16:22:54 -04:00
Jason Denney 80b2b7ff60 Added New Time Entry Front End 2014-04-19 16:20:14 -04:00
Jason Denney 98b136e4f4 Add link to view bike check list when showing bike 2014-03-16 13:50:13 -04:00
Jason Denney 9527725e07 Adding makeshift home button, Added view your bike link 2014-03-16 13:34:56 -04:00
Jason Denney 8f637348cf Fix check in/ check out 2014-03-12 23:35:09 -04:00
Jason Denney 062ed9c2d1 Added tests for user checkin/checkout on index 2014-03-12 21:52:37 -04:00
Jason Denney 124ce5901d Update spec tests for registration page 2014-03-12 21:29:01 -04:00
Jason Denney 1f9322ff3d Fixed weird spec sign_in issues, and render json 2014-03-09 22:49:30 -04:00
Jason Denney b1ddcfb867 Redirect correctly after creating a bike 2014-03-09 22:48:59 -04:00
Jason Denney 64e907b6f7 Should authenticate by user username, not email 2014-03-09 22:48:35 -04:00
Jason Denney e2f442889e Added tasks controller, spec, JS, apitesthelper
Had an issue with calling render_views in the specs to render the
jbuilder json templates and also getting the devise sign_in method to
work, ended up adding a helper to add the username/password for a user
2014-03-09 22:30:41 -04:00
Jason Denney 3b904f95f2 Adding api task_lists#show and spec, edit.haml updates 2014-03-09 19:20:00 -04:00
Jason Denney 656f0f12e4 WIP, creating TaskList Controller and edit action & view 2014-03-08 15:34:58 -05:00
Jason Denney 30d818ac87 Draft of the task list api controller 2014-03-08 15:04:27 -05:00
Jason Denney dc0a567a27 Added bike show api method 2014-03-08 15:03:52 -05:00
Jason Denney 523046b042 Add create jbuilder template 2014-03-01 14:15:49 -05:00
Jason Denney 9231c91e6c Update README.md 2014-03-01 11:12:10 -05:00
Jason Denney 14869cf8b6 Modifed bikes create to use jbuilder & follow jsonapi.org 2014-02-26 00:06:25 -05:00
Jason Denney b88bf545d2 Added jbuilder to gemfile 2014-02-26 00:05:02 -05:00
Jason Denney 20964eb268 Update feature for new bike view to pass 2014-02-10 00:09:11 -05:00
Jason Denney b90e101d69 Whoops! copypasta in seed yml 2014-02-09 22:01:57 -05:00
Jason Denney 7b11da9583 Forward url on add bike/ don't display value if nil 2014-02-09 22:01:42 -05:00
Jason Denney 64d4186b57 Bike show view styling
TBD add class for color block
2014-02-09 19:18:16 -05:00
Jason Denney c6a3ed9991 Added bike show and show view 2014-02-09 19:11:08 -05:00
Jason Denney d0c05a0244 Fix validations for creating a bike, view respond to errors 2014-02-09 18:23:35 -05:00
Jason Denney 55974feb4c Parse float for seat tube height 2014-02-09 18:22:33 -05:00
Jason Denney e867366525 Made feature specs for creating a bike 2014-02-08 16:56:34 -05:00
Jason Denney fb02646092 Moved registrations specs to features 2014-02-08 16:56:21 -05:00
Jason Denney 75832a3910 Add poltergeist to gem file, and limited version dependecies 2014-02-08 16:54:50 -05:00
Jason Denney 2d09aba26a Added submit js to bikes/new view
* Added displayFormErrors utility
* Added 4th option to bike styles “OTHER”
2014-02-08 14:21:20 -05:00
Jason Denney e694dfbddd Don't clobber development database, durrrr 2014-02-08 14:17:09 -05:00
Jason Denney e19b80faec Update README.md 2014-02-08 11:46:48 -05:00
Jason Denney 38a716b132 Added User password_reset spec and some refactor
*Make returned error in errors array
*Use constants
2014-02-04 23:47:30 -05:00
Jason Denney 8110baf255 Removing unnecessary render 2014-02-03 00:16:13 -05:00
Jason Denney 9fa0017d36 Fixed error response codes, finished bikes#create spec 2014-02-03 00:14:37 -05:00
Jason Denney e0041662d3 WIP, adding api/bikes_controller#create 2014-02-02 17:11:13 -05:00
Jason Denney c575fface9 Commenting out description on bike form for now 2014-02-02 17:10:32 -05:00
Jason Denney 83a4e4e9c1 Make bike color not required 2014-02-02 17:10:17 -05:00
Jason Denney f17eba810e WTF? Is this needed? Commenting this out for now
users#checkout  copy pasta perhaps?
2014-02-02 17:09:49 -05:00
Jason Denney 4b5133fd02 Fix unauthorized return code 2014-02-02 17:09:13 -05:00
Jason Denney 9edb90203c Remove whitespace 2014-02-02 09:45:18 -05:00
Jason Denney 8e112ca936 Adding logs_controller_spec, finally test coverage!!!!! 2014-02-02 09:45:01 -05:00
Jason Denney 8c5ff986a2 Update spec, remove turnip, to get tests working 2014-02-02 09:44:18 -05:00
Jason Denney f10b2adad6 Convert .rvmrc to ruby version and gemset 2014-02-02 09:43:22 -05:00
Jason Denney bd6872413e Make year dynamically updated 2014-01-21 23:35:11 -05:00
Jason Denney a8bdb52536 Improved Add Bike view
Worked out some bootstrap overrides issues
2014-01-19 19:32:18 -05:00
Jason Denney 0f890e12e9 Added bikes views
*Added new bike template
2014-01-18 18:25:21 -05:00
Jason Denney 61472e0fb6 Hide admin view button on small devices 2014-01-18 15:20:34 -05:00
Jason Denney ecb5083f1c Created new dashboard index
*Added js for logging out button
*WIP
2014-01-18 15:16:23 -05:00
Jason Denney a84b0e0fe7 Moving Netzke Admin view to Panel controller
*Moved custom_netzke_helpers code to panel.js
*Added panel controller and index and netzke layout
2014-01-18 15:14:19 -05:00
Jason Denney 2b863b8d8a Updated Forget Password, other minor changes 2014-01-18 12:11:43 -05:00
Jason Denney a35bd2f22b make sign up mobile friendly 2014-01-15 00:33:32 -05:00
Jason Denney c9d2da3e50 Make sign up links like buttons 2014-01-15 00:00:18 -05:00
Jason Denney df3c29a899 Use twitter bootstrap classes on login 2014-01-12 14:24:57 -05:00
Jason Denney a11f5a1b91 Needed to add task_list_id as attr_accessible
Also improved labels for adding task
2013-11-21 20:36:35 -05:00
Jason Denney 2a6e10ee9d Disabling by default was just annoying 2013-11-21 20:35:23 -05:00
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
Jason Denney d755f9971e Update README.md 2013-06-29 17:20:53 -04:00
Jason Denney 52e67a1182 Adding Made in Bmore to Login 2013-06-29 17:18:51 -04:00
Jason Denney 7736ac5c34 Added credit tracking and stats
Added credit_conversions table so conversions can change over time
Added default credit conversion fixture
Added User#transaction_logs
Added User#total_credits (earned minus spent)
Added User#total_credits_spent (through transaction_logs)
Added User#total_earned_credits (complicated query which finds the conversion rate for a log depending on its created_at date)
2013-06-29 15:19:38 -04:00
Jason Denney bccd5f10d7 Updated seeds.rb for PG 2013-06-29 11:45:19 -04:00
Jason Denney f0ce8b7171 Switch DB from sqlite to Postgres
-Postgres for production
-Migrations don't translate well between sqlite and postgres, better to just develop using the same database as production.
-Fixed migrations to work with Postgres
-Updated Readme for PG install instructions
-database config moved to .example
2013-06-25 23:50:34 -04:00
Jason Denney 238a3af7d5 minor change to check for seeding admin 2013-06-25 22:31:29 -04:00
Jason Denney fee421cabe not optimal production config, but it works 2013-06-25 22:24:21 -04:00
Jason Denney f713c6bacd Forgot to remove nickname from registration form 2013-06-25 22:06:43 -04:00
Jason Denney ac24711289 Rename User History to User Timesheet 2013-06-13 21:14:42 -04:00
Jason Denney b89873d0e5 Updating schema file w/ removed user nick 2013-06-13 21:12:34 -04:00
Jason Denney d431024f01 Adding bike_admin fixture, fixed seeds.rb 2013-06-13 21:11:54 -04:00
Jason Denney ff60fdd67f Removing nickname from user 2013-06-13 20:59:01 -04:00
Jason Denney 03abb963ac Merging Master 2013-06-11 00:38:20 -04:00
Jason Denney cd41735cee Bug in User#completed_build_bikes query. inner 2013-06-11 00:35:15 -04:00
Jason Denney 0f89cf057a Display User roles by username, then full name
Also set the default_fields_for_forms
2013-06-11 00:34:45 -04:00
Jason Denney 8172b04805 2013-06-11 00:34:01 -04:00
Jason Denney 9cb4ed42dc Update README.md 2013-06-10 23:49:39 -04:00
Jason Denney 7c85e2901f Changes needed to run in produciton 2013-06-02 21:50:22 -04:00
Jason Denney 2c548ff63c User#completed_build_bikes method, and stats page list 2013-06-01 17:11:57 -04:00
Jason Denney 1defca3668 Fixing some bike logs permissions 2013-06-01 16:32:33 -04:00
Jason Denney 88d3160e29 Fixing logs add_in_form and search 2013-06-01 14:47:20 -04:00
Jason Denney edff478204 Reordering Admin tab panel, putting logs last 2013-06-01 14:46:59 -04:00
Jason Denney 84ca3a6f96 Search form requires :field_label when overriding default_fields_for_forms 2013-06-01 14:10:18 -04:00
Jason Denney 51caa93afa Some js wasn't getting loaded at login. and other fixes 2013-06-01 11:39:42 -04:00
jnm 7a935c250b Merge pull request #27 from spacemunkay/denney-fix-some-cancan
Fixing some cancan permission issues… i think
2013-05-30 19:21:37 -07:00
Jason Denney 854c8efaa9 only admins can see checkins 2013-05-30 22:19:00 -04:00
Jason Denney b450baa3dc merging master 2013-05-30 22:17:18 -04:00
jnm fa51dd25f3 Merge pull request #24 from spacemunkay/denney-reset-password
Denney reset password
2013-05-30 19:13:49 -07:00
Jason Denney c9e94d8e30 Merge pull request #21 from spacemunkay/denney-new-user-form
Denney new user form
2013-05-30 19:09:10 -07:00
John N. Milner c5b16139f5 Merge branch 'master' into denney-new-user-form
Conflicts:
	db/schema.rb
2013-05-30 22:03:53 -04:00
Jason Denney 6d76385d11 Merge pull request #20 from spacemunkay/jnm-check-ins-view
New view to show the current day's check ins
2013-05-30 18:39:20 -07:00
Jason Denney f6f932f63a Fixing some cancan permission issues… i think 2013-05-30 21:35:39 -04:00
jnm a18e34c8f1 Merge pull request #22 from spacemunkay/denney-color-picker
Denney color picker
2013-05-30 17:21:35 -07:00
John N. Milner b25a4520f9 Correct log criteria for Check Ins view 2013-05-30 19:52:57 -04:00
John N. Milner adeca0e08a Corrected Check Ins view to show the checked-in user, not the user who logged the check in 2013-05-30 19:30:40 -04:00
Jason Denney db1732f7bf Bug in user_stats queries 2013-05-28 01:34:55 -04:00
Jason Denney d5662284ab Added link for users to update email/password 2013-05-28 00:27:34 -04:00
Jason Denney 0ef4e81474 Added reset password action column to users comp. 2013-05-28 00:27:03 -04:00
Jason Denney 761ab81b56 Added reset password api method 2013-05-28 00:25:23 -04:00
Jason Denney 687cb33974 Added color blocks to grid view 2013-05-26 19:52:40 -04:00
Jason Denney 7f029f2a83 Removing commented js 2013-05-26 13:33:35 -04:00
Jason Denney c7974c52e8 Adding color picker for bikes view/forms 2013-05-26 13:31:52 -04:00
Jason Denney 8c864e2006 Measurements will be in inches for the time being 2013-05-25 12:43:55 -04:00
Jason Denney 6d2e7686d6 Remove unneeded if 2013-05-25 11:13:52 -04:00
Jason Denney 9021294c4b Auth by username instead of email
Added username to User table/model
2013-05-25 11:08:58 -04:00
Jason Denney a07d6345e8 Mildly better looking signup page w/ errors. 2013-05-25 10:25:12 -04:00
Jason Denney db6b7ff7da Adjusting how javascript is loaded
Now js is loaded per controller
2013-05-25 10:12:03 -04:00
Jason Denney 75734a075c Moving new_user.js to devise/registrations.s 2013-05-25 09:59:49 -04:00
Jason Denney 73bb35de88 Moving bootstrap.js 2013-05-25 09:58:43 -04:00
Jason Denney 8c5132814f New User Sign up now annoys user to input contact info 2013-05-25 09:02:26 -04:00
Jason Denney 2e08f655ea Registration form now remembers field values for nested form errors 2013-05-25 09:00:12 -04:00
John N. Milner b67062fb32 New view to show the current day's check ins 2013-05-23 23:56:18 -04:00
jnm 3c90c13c3b Merge pull request #15 from spacemunkay/denney-add-users-roles
Changing UserRole to UserRoleJoin
2013-05-23 18:03:59 -07:00
jnm 19adc354a1 Merge pull request #18 from spacemunkay/denney-check-in
Denney check in
2013-05-23 18:01:07 -07:00
Jason Denney 145a731ed2 Remove puts 2013-05-19 16:44:28 -04:00
Jason Denney a00c1ed925 Finished Checkin/out feature for now
-Checkin/out api methods can be used while signed in
-Added checkout button in Netzke App, which also signs out in addition to checking out.
2013-05-19 16:41:43 -04:00
Jason Denney 14973e8334 Merge pull request #17 from spacemunkay/denney-bootstrap-css
Add bootstrap css to login page
2013-05-19 12:45:00 -07:00
Jason Denney d7755f00ed Initial stab at check-in feature 2013-05-19 15:43:44 -04:00
Jason Denney c504252541 Add bootstrap css to login page 2013-05-16 23:40:36 -04:00
Jason Denney 65c8cf8cf1 Fix setting default log action 2013-05-16 22:44:27 -04:00
Jason Denney 81217f1ea8 Give better names to columns, add last name col 2013-05-16 22:41:31 -04:00
Jason Denney bc1eeea16e Make the title of user roles tab 2013-05-16 22:41:00 -04:00
darmock f34def2c09 Store role_id as an integer instead of a string 2013-05-10 03:30:54 -04:00
John N. Milner 8f988b2cf1 Use self.table_name = instead of set_table_name to avoid deprecation warning 2013-05-10 03:21:13 -04:00
Jason Denney 1e589322f4 Update README.md
Adding link to ExtJS 4.1 download
2013-05-08 10:52:46 -03:00
Jason Denney be1f52568a Update README.md
Adding link to license
2013-05-08 10:48:34 -03:00
Jason Denney 3f1093bca5 ExtJS version is important 2013-05-07 21:34:38 -04:00
Jason Denney a614b28558 Make it possible to limit roles by date 2013-04-25 21:38:55 -04:00
Jason Denney 6f6902dd82 Update Ability to get user roles, not role 2013-04-25 21:25:49 -04:00
Jason Denney a083ed715b Renamed UserRoles to UserRole Joins, Needed ID column in user_role_joins table 2013-04-24 07:56:02 -04:00
Jason Denney 91bb2ea43e Changing UserRole to UserRoleJoin
Beware, this breaks the view for some reason
2013-04-23 23:21:17 -04:00
164 changed files with 5841 additions and 728 deletions
+4
View File
@@ -7,4 +7,8 @@ tmp/
.rbenv-version
.DS_Store
public/extjs
public/assets
public/images/icons
config/database.yml
vendor/
.sass-cache/
-1
View File
@@ -1,2 +1 @@
--colour
-rturnip
+1
View File
@@ -0,0 +1 @@
velocipede
+1
View File
@@ -0,0 +1 @@
2.1.1
-55
View File
@@ -1,55 +0,0 @@
#!/usr/bin/env bash
# This is an RVM Project .rvmrc file, used to automatically load the ruby
# development environment upon cd'ing into the directory
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
environment_id="ruby-1.9.3-p374@velocipede"
#
# Uncomment following line if you want options to be set only for given project.
#
# PROJECT_JRUBY_OPTS=( --1.9 )
#
# First we attempt to load the desired environment directly from the environment
# file. This is very fast and efficient compared to running through the entire
# CLI and selector. If you want feedback on which environment was used then
# insert the word 'use' after --create as this triggers verbose mode.
#
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
then
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
then
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
fi
else
# If the environment file has not yet been created, use the RVM CLI to select.
if ! rvm --create use "$environment_id"
then
echo "Failed to create RVM environment '${environment_id}'."
return 1
fi
fi
#
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
# it be automatically loaded. Uncomment the following and adjust the filename if
# necessary.
#
# filename=".gems"
# if [[ -s "$filename" ]]
# then
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
# fi
# If you use bundler, this might be useful to you:
# if command -v bundle && [[ -s Gemfile ]]
# then
# bundle install
# fi
+24 -24
View File
@@ -1,47 +1,47 @@
source 'https://rubygems.org'
gem 'rails', '3.2.13'
gem 'rails', '3.2.19'
gem 'netzke-cancan'
gem 'netzke-core', '~>0.8.0'
gem 'netzke-basepack', '~>0.8.0'
gem 'netzke-core'
gem 'netzke-basepack'
gem 'acts_as_loggable', :git => 'https://github.com/spacemunkay/acts_as_loggable.git'
gem 'bootstrap-will_paginate', '~> 0.0.6'
gem 'bootstrap-will_paginate'
gem 'cancan'
gem 'decent_exposure', '~> 1.0.1'
gem 'devise', '~> 2.0.4'
gem 'haml-rails', '~> 0.3.4'
gem 'jquery-rails', '~> 2.0'
gem 'sqlite3', '~> 1.3.5'
gem 'will_paginate', '~> 3.0.3'
gem 'haml-rails'
gem 'jquery-rails'
gem 'pg', '~> 0.17.1'
gem 'will_paginate'
gem 'jbuilder'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem "twitter-bootstrap-rails", "~> 2.0.3"
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer'
gem 'uglifier', '>= 1.0.3'
gem 'coffee-rails'
# Use SCSS for stylesheets
gem 'sass-rails'
# Use bootstrap-sass for stylesheets
gem 'bootstrap-sass'
gem 'uglifier'
end
group :development, :test do
gem 'rspec-rails', '~> 2.8.1'
gem 'factory_girl_rails', '~> 1.2'
gem 'pry', '~> 0.9.8'
gem 'rspec-rails'
gem 'factory_girl_rails'
gem 'pry'
gem 'faker'
end
group :test do
gem 'shoulda-matchers', '~> 1.0.0'
gem 'capybara', '~> 1.1.2'
gem 'turnip', '~> 0.3.0'
gem 'database_cleaner'
gem 'launchy'
# gem 'shoulda-matchers', '~> 1.0.0'
gem 'capybara', '~> 2.2.1'
gem 'poltergeist', '~> 1.5.0'
gem 'database_cleaner', '~> 1.2.0'
gem 'launchy', '~> 2.4.2'
gem 'spork'
#guard dependency for Mac OS 10
gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
+159 -151
View File
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/spacemunkay/acts_as_loggable.git
revision: 6033fe5a44878bb374f73fe473fed9a3396ddc74
revision: 8484dc1f7a58699705c5ffb593ebdf700a9f374a
specs:
acts_as_loggable (0.0.7)
activerecord (>= 3.0)
@@ -9,12 +9,12 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actionmailer (3.2.13)
actionpack (= 3.2.13)
mail (~> 2.5.3)
actionpack (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
actionmailer (3.2.19)
actionpack (= 3.2.19)
mail (~> 2.5.4)
actionpack (3.2.19)
activemodel (= 3.2.19)
activesupport (= 3.2.19)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
@@ -22,204 +22,210 @@ GEM
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.13)
activesupport (= 3.2.13)
activemodel (3.2.19)
activesupport (= 3.2.19)
builder (~> 3.0.0)
activerecord (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
activerecord (3.2.19)
activemodel (= 3.2.19)
activesupport (= 3.2.19)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
activesupport (3.2.13)
i18n (= 0.6.1)
activeresource (3.2.19)
activemodel (= 3.2.19)
activesupport (= 3.2.19)
activesupport (3.2.19)
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
addressable (2.3.3)
arel (3.0.2)
bcrypt-ruby (3.0.1)
bootstrap-will_paginate (0.0.9)
addressable (2.3.6)
arel (3.0.3)
bcrypt (3.1.7)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
bootstrap-sass (3.2.0.0)
sass (~> 3.2)
bootstrap-will_paginate (0.0.10)
will_paginate
builder (3.0.4)
cancan (1.6.9)
capybara (1.1.4)
cancan (1.6.10)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.3.9)
xpath (~> 2.0)
celluloid (0.15.2)
timers (~> 1.1.0)
childprocess (0.5.3)
ffi (~> 1.0, >= 1.0.11)
coderay (1.0.9)
cliver (0.3.2)
coderay (1.1.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script (2.3.0)
coffee-script-source
execjs
coffee-script-source (1.6.2)
commonjs (0.2.6)
database_cleaner (0.9.1)
coffee-script-source (1.7.1)
database_cleaner (1.2.0)
decent_exposure (1.0.2)
devise (2.0.5)
devise (2.0.6)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3)
railties (~> 3.1)
warden (~> 1.1.1)
diff-lcs (1.1.3)
diff-lcs (1.2.5)
erubis (2.7.0)
execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (2.6.4)
activesupport (>= 2.3.9)
factory_girl_rails (1.7.0)
factory_girl (~> 2.6.0)
execjs (2.2.1)
factory_girl (4.4.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.4.1)
factory_girl (~> 4.4.0)
railties (>= 3.0.0)
ffi (1.6.0)
formatador (0.2.4)
gherkin (2.11.6)
json (>= 1.7.6)
guard (1.7.0)
faker (1.4.2)
i18n (~> 0.5)
ffi (1.9.3)
formatador (0.2.5)
guard (2.6.1)
formatador (>= 0.2.4)
listen (>= 0.6.0)
lumberjack (>= 1.0.2)
pry (>= 0.9.10)
thor (>= 0.14.6)
guard-rspec (1.2.1)
guard (>= 1.1)
guard-spork (1.5.0)
listen (~> 2.7)
lumberjack (~> 1.0)
pry (>= 0.9.12)
thor (>= 0.18.1)
guard-rspec (4.3.1)
guard (~> 2.1)
rspec (>= 2.14, < 4.0)
guard-spork (1.5.1)
childprocess (>= 0.2.3)
guard (>= 1.1)
spork (>= 0.8.4)
haml (3.1.8)
haml-rails (0.3.5)
haml (4.0.5)
tilt
haml-rails (0.4)
actionpack (>= 3.1, < 4.1)
activesupport (>= 3.1, < 4.1)
haml (~> 3.1)
haml (>= 3.1, < 4.1)
railties (>= 3.1, < 4.1)
hike (1.2.1)
i18n (0.6.1)
hike (1.2.3)
i18n (0.6.11)
jbuilder (2.1.3)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
journey (1.0.4)
jquery-rails (2.2.1)
jquery-rails (3.1.1)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.7.7)
launchy (2.2.0)
json (1.8.1)
launchy (2.4.2)
addressable (~> 2.3)
less (2.2.2)
commonjs (~> 0.2.6)
less-rails (2.2.6)
actionpack (>= 3.1)
less (~> 2.2.0)
libv8 (3.3.10.4)
listen (0.7.3)
lumberjack (1.0.3)
mail (2.5.3)
i18n (>= 0.4.0)
listen (2.7.9)
celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
lumberjack (1.0.9)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
method_source (0.8.1)
mime-types (1.22)
multi_json (1.7.2)
netzke-basepack (0.8.2)
netzke-core (~> 0.8.2)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.0)
multi_json (1.10.1)
netzke-basepack (0.10.1)
netzke-core (~> 0.10.0)
netzke-cancan (0.8.2)
cancan
netzke-core
netzke-core (0.8.3)
netzke-core (0.10.1)
execjs
uglifier
nokogiri (1.5.9)
nokogiri (1.6.3.1)
mini_portile (= 0.6.0)
orm_adapter (0.0.7)
polyglot (0.3.3)
pry (0.9.12)
coderay (~> 1.0.5)
method_source (~> 0.8)
pg (0.17.1)
poltergeist (1.5.1)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyglot (0.3.5)
pry (0.10.0)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
rack-ssl (1.3.3)
rack-ssl (1.3.4)
rack
rack-test (0.6.2)
rack (>= 1.0)
rails (3.2.13)
actionmailer (= 3.2.13)
actionpack (= 3.2.13)
activerecord (= 3.2.13)
activeresource (= 3.2.13)
activesupport (= 3.2.13)
rails (3.2.19)
actionmailer (= 3.2.19)
actionpack (= 3.2.19)
activerecord (= 3.2.19)
activeresource (= 3.2.19)
activesupport (= 3.2.19)
bundler (~> 1.0)
railties (= 3.2.13)
railties (3.2.13)
actionpack (= 3.2.13)
activesupport (= 3.2.13)
railties (= 3.2.19)
railties (3.2.19)
actionpack (= 3.2.19)
activesupport (= 3.2.19)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.0.4)
rake (10.3.2)
rb-fsevent (0.9.4)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
rspec (2.8.0)
rspec-core (~> 2.8.0)
rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.8.0)
rspec-core (2.8.0)
rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.8.0)
rspec-rails (2.8.1)
rspec (3.0.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-core (3.0.3)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0)
rspec-mocks (3.0.3)
rspec-support (~> 3.0.0)
rspec-rails (3.0.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec (~> 2.8.0)
rubyzip (0.9.9)
sass (3.2.7)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-support (~> 3.0.0)
rspec-support (3.0.3)
sass (3.3.10)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
selenium-webdriver (2.31.0)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip
websocket (~> 1.0.4)
shoulda-matchers (1.0.0)
slop (3.4.4)
slop (3.6.0)
spork (0.9.2)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.7)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thor (0.18.1)
tilt (1.3.6)
treetop (1.4.12)
thor (0.19.1)
tilt (1.4.1)
timers (1.1.0)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
turnip (0.3.1)
gherkin (>= 2.5)
rspec (~> 2.0)
twitter-bootstrap-rails (2.0.9)
actionpack (>= 3.1)
less-rails (~> 2.2.2)
railties (>= 3.1)
therubyracer (~> 0.10.1)
tzinfo (0.3.37)
uglifier (1.3.0)
tzinfo (0.3.40)
uglifier (2.5.3)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
json (>= 1.8.0)
warden (1.1.1)
rack (>= 1.0)
websocket (1.0.7)
will_paginate (3.0.4)
xpath (0.1.4)
websocket-driver (0.3.4)
will_paginate (3.0.7)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
@@ -227,30 +233,32 @@ PLATFORMS
DEPENDENCIES
acts_as_loggable!
bootstrap-will_paginate (~> 0.0.6)
bootstrap-sass
bootstrap-will_paginate
cancan
capybara (~> 1.1.2)
coffee-rails (~> 3.2.1)
database_cleaner
capybara (~> 2.2.1)
coffee-rails
database_cleaner (~> 1.2.0)
decent_exposure (~> 1.0.1)
devise (~> 2.0.4)
factory_girl_rails (~> 1.2)
factory_girl_rails
faker
guard-rspec
guard-spork
haml-rails (~> 0.3.4)
jquery-rails (~> 2.0)
launchy
netzke-basepack (~> 0.8.0)
haml-rails
jbuilder
jquery-rails
launchy (~> 2.4.2)
netzke-basepack
netzke-cancan
netzke-core (~> 0.8.0)
pry (~> 0.9.8)
rails (= 3.2.13)
rspec-rails (~> 2.8.1)
sass-rails (~> 3.2.3)
shoulda-matchers (~> 1.0.0)
netzke-core
pg (~> 0.17.1)
poltergeist (~> 1.5.0)
pry
rails (= 3.2.19)
rb-fsevent
rspec-rails
sass-rails
spork
sqlite3 (~> 1.3.5)
turnip (~> 0.3.0)
twitter-bootstrap-rails (~> 2.0.3)
uglifier (>= 1.0.3)
will_paginate (~> 3.0.3)
uglifier
will_paginate
+48 -8
View File
@@ -1,22 +1,62 @@
# 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. You can see those changes on this [branch]( https://github.com/spacemunkay/BikeShed/tree/mobile-ui)
## Deployment
Currently, at the Velocipede collective we have it running on a computer in the shop on the local network. The reason for this, is so that it's only accessible from within the shop. I'm weighing out different methods for ensuring that users can only log in from within the shop (and not from home), such as a shop password that only admins can see and would make visible from within the physical bike shop. I also don't have to worry about security issues while the app is in development. In the future, I'd like to see the app hosted much like http://freehub.bikekitchen.org/.
## Ideas
At Velocipede, with a mobile friendly UI for users, I'm hoping we can get donations of old, unused smart phones, connected via local wifi, that can be used as stations to log a volunteer's work and hours. They can be secured by gluing cables into the earphone jacks to deter theft if necessary. This might be another way that the collective can promote reuse of materials.
# Developer Setup
1. use rvm
1. allow the .rvmrc file
1. `gem install bundler`
1. `bundle`
1. `rake db:create db:migrate db:seed`
1. Download extJS (http://www.sencha.com/products/extjs), unzip, and place where ever you like.
1. Install Postgres (Mac OSX instructions below)
1. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
1. `rake db:create db:migrate`
1. `rake db:seed`
1. Download extJS 4.1 (A version of 4.1 is hosted here: http://my.jasondenney.com/extjs-4.1.1.zip) Latest versions at http://www.sencha.com/products/extjs. Unzip and place where ever you like.
1. Link to your extJS folder path under `public/extjs`: (From app root) `ln -s /MY/PATH/extjs/ public/extjs`
1. `rails s`
# Postgres 9.2 Mac OSX Install
1. Install homebrew `ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"`
1. `brew install postgres`
1. First time db initialization `initdb /usr/local/var/postgres -E utf8`
1. Start Postgres `pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start`
1. Create your PG user `createuser -d -P velocipede`
1. Create your database `createdb -U velocipede --owner=velocipede velocipede`
1. Create your test database `createdb -U velocipede --owner=velocipede velocipede_test`
# Testing
1. Install phantomjs `brew install phantomjs`
1. Run tests with `rspec`
# Optional
Add icons
1. Download icons from http://www.famfamfam.com/lab/icons/silk/
1. Link to the icons under `public/images/icons`: (From app root) `ln -s /MY/PATH/famfamfam_silk_icons/icons public/images/icons`
1. Link to the icons under `public/images/icons`: (From app root) `ln -s /MY/PATH/famfamfam_silk_icons/icons public/images/icons`
# License
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
## 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
<a href="http://madewithloveinbaltimore.org">Made with &hearts; in Baltimore</a>
+13 -9
View File
@@ -12,16 +12,20 @@ require File.expand_path('../config/application', __FILE__)
Velocipede::Application.load_tasks
namespace :spec do
desc 'Run all acceptance specs'
RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
t.pattern = '**/*.feature'
unless Rails.env.production?
namespace :spec do
desc 'Run all acceptance specs'
RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
t.pattern = '**/*.feature'
end
end
end
Rake.application.remove_task 'spec'
Rake.application.remove_task 'spec'
desc 'Run all specs'
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
t.pattern = '**/*{_spec.rb,.feature}'
end
desc 'Run all specs'
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
t.pattern = '**/*{_spec.rb,.feature}'
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+6 -2
View File
@@ -12,5 +12,9 @@
//
//= require jquery
//= require jquery_ujs
//= require twitter/bootstrap
//= require_tree .
// require twitter/bootstrap/bootstrap-button
// require twitter/bootstrap/bootstrap-modal
//= require utils
//= require bootstrap-datepicker
//= require bootstrap-timepicker
//= require jquery-date-format
+31
View File
@@ -0,0 +1,31 @@
$('.btn').button();
$("#add_bike_submit").click(function(){
json_data = { bikes: [{
serial_number: $("#serial_number").val(),
bike_brand_id: parseInt($("#bike_brand_id").val()),
shop_id: parseInt($("#shop_id").val()),
model: $("#model").val(),
bike_style_id: parseInt($('input[name=bike_style]:checked').val()),
seat_tube_height: parseFloat($("#seat_tube_height").val()),
bike_condition_id: parseInt($('input[name=bike_condition]:checked').val()),
bike_purpose_id: 1,
bike_wheel_size_id: parseInt($("#bike_wheel_size_id").val()),
}]};
$.ajax({
url: $("#add_bike_submit").data("url"),
type: "POST",
data: JSON.stringify(json_data),
contentType: 'application/json',
dataType: "json",
success: function(data, status, xhr){
window.location = data.bikes[0].id + "?add_bike=1";
},
error: function(data, status ){
displayFormErrors(data.responseJSON);
}
});
});
+474
View File
@@ -0,0 +1,474 @@
/* =========================================================
* bootstrap-datepicker.js
* http://www.eyecon.ro/bootstrap-datepicker
* =========================================================
* Copyright 2012 Stefan Petre
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
!function( $ ) {
// Picker object
var Datepicker = function(element, options){
this.element = $(element);
this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
this.picker = $(DPGlobal.template)
.appendTo('body')
.on({
click: $.proxy(this.click, this)//,
//mousedown: $.proxy(this.mousedown, this)
});
this.isInput = this.element.is('input');
this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
if (this.isInput) {
this.element.on({
focus: $.proxy(this.show, this),
//blur: $.proxy(this.hide, this),
keyup: $.proxy(this.update, this)
});
} else {
if (this.component){
this.component.on('click', $.proxy(this.show, this));
} else {
this.element.on('click', $.proxy(this.show, this));
}
}
this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
if (typeof this.minViewMode === 'string') {
switch (this.minViewMode) {
case 'months':
this.minViewMode = 1;
break;
case 'years':
this.minViewMode = 2;
break;
default:
this.minViewMode = 0;
break;
}
}
this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
if (typeof this.viewMode === 'string') {
switch (this.viewMode) {
case 'months':
this.viewMode = 1;
break;
case 'years':
this.viewMode = 2;
break;
default:
this.viewMode = 0;
break;
}
}
this.startViewMode = this.viewMode;
this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
this.onRender = options.onRender;
this.fillDow();
this.fillMonths();
this.update();
this.showMode();
};
Datepicker.prototype = {
constructor: Datepicker,
show: function(e) {
this.picker.show();
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
this.place();
$(window).on('resize', $.proxy(this.place, this));
if (e ) {
e.stopPropagation();
e.preventDefault();
}
if (!this.isInput) {
}
var that = this;
$(document).on('mousedown', function(ev){
if ($(ev.target).closest('.datepicker').length == 0) {
that.hide();
}
});
this.element.trigger({
type: 'show',
date: this.date
});
},
hide: function(){
this.picker.hide();
$(window).off('resize', this.place);
this.viewMode = this.startViewMode;
this.showMode();
if (!this.isInput) {
$(document).off('mousedown', this.hide);
}
//this.set();
this.element.trigger({
type: 'hide',
date: this.date
});
},
set: function() {
var formated = DPGlobal.formatDate(this.date, this.format);
if (!this.isInput) {
if (this.component){
this.element.find('input').prop('value', formated);
}
this.element.data('date', formated);
} else {
this.element.prop('value', formated);
}
},
setValue: function(newDate) {
if (typeof newDate === 'string') {
this.date = DPGlobal.parseDate(newDate, this.format);
} else {
this.date = new Date(newDate);
}
this.set();
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
this.fill();
},
place: function(){
var offset = this.component ? this.component.offset() : this.element.offset();
this.picker.css({
top: offset.top + this.height,
left: offset.left
});
},
update: function(newDate){
this.date = DPGlobal.parseDate(
typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
this.format
);
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
this.fill();
},
fillDow: function(){
var dowCnt = this.weekStart;
var html = '<tr>';
while (dowCnt < this.weekStart + 7) {
html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
}
html += '</tr>';
this.picker.find('.datepicker-days thead').append(html);
},
fillMonths: function(){
var html = '';
var i = 0
while (i < 12) {
html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
}
this.picker.find('.datepicker-months td').append(html);
},
fill: function() {
var d = new Date(this.viewDate),
year = d.getFullYear(),
month = d.getMonth(),
currentDate = this.date.valueOf();
this.picker.find('.datepicker-days th:eq(1)')
.text(DPGlobal.dates.months[month]+' '+year);
var prevMonth = new Date(year, month-1, 28,0,0,0,0),
day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
prevMonth.setDate(day);
prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
var nextMonth = new Date(prevMonth);
nextMonth.setDate(nextMonth.getDate() + 42);
nextMonth = nextMonth.valueOf();
var html = [];
var clsName,
prevY,
prevM;
while(prevMonth.valueOf() < nextMonth) {
if (prevMonth.getDay() === this.weekStart) {
html.push('<tr>');
}
clsName = this.onRender(prevMonth);
prevY = prevMonth.getFullYear();
prevM = prevMonth.getMonth();
if ((prevM < month && prevY === year) || prevY < year) {
clsName += ' old';
} else if ((prevM > month && prevY === year) || prevY > year) {
clsName += ' new';
}
if (prevMonth.valueOf() === currentDate) {
clsName += ' active';
}
html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
if (prevMonth.getDay() === this.weekEnd) {
html.push('</tr>');
}
prevMonth.setDate(prevMonth.getDate()+1);
}
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
var currentYear = this.date.getFullYear();
var months = this.picker.find('.datepicker-months')
.find('th:eq(1)')
.text(year)
.end()
.find('span').removeClass('active');
if (currentYear === year) {
months.eq(this.date.getMonth()).addClass('active');
}
html = '';
year = parseInt(year/10, 10) * 10;
var yearCont = this.picker.find('.datepicker-years')
.find('th:eq(1)')
.text(year + '-' + (year + 9))
.end()
.find('td');
year -= 1;
for (var i = -1; i < 11; i++) {
html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
year += 1;
}
yearCont.html(html);
},
click: function(e) {
e.stopPropagation();
e.preventDefault();
var target = $(e.target).closest('span, td, th');
if (target.length === 1) {
switch(target[0].nodeName.toLowerCase()) {
case 'th':
switch(target[0].className) {
case 'switch':
this.showMode(1);
break;
case 'prev':
case 'next':
this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
this.viewDate,
this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
);
this.fill();
this.set();
break;
}
break;
case 'span':
if (target.is('.month')) {
var month = target.parent().find('span').index(target);
this.viewDate.setMonth(month);
} else {
var year = parseInt(target.text(), 10)||0;
this.viewDate.setFullYear(year);
}
if (this.viewMode !== 0) {
this.date = new Date(this.viewDate);
this.element.trigger({
type: 'changeDate',
date: this.date,
viewMode: DPGlobal.modes[this.viewMode].clsName
});
}
this.showMode(-1);
this.fill();
this.set();
break;
case 'td':
if (target.is('.day') && !target.is('.disabled')){
var day = parseInt(target.text(), 10)||1;
var month = this.viewDate.getMonth();
if (target.is('.old')) {
month -= 1;
} else if (target.is('.new')) {
month += 1;
}
var year = this.viewDate.getFullYear();
this.date = new Date(year, month, day,0,0,0,0);
this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
this.fill();
this.set();
this.element.trigger({
type: 'changeDate',
date: this.date,
viewMode: DPGlobal.modes[this.viewMode].clsName
});
}
break;
}
}
},
mousedown: function(e){
e.stopPropagation();
e.preventDefault();
},
showMode: function(dir) {
if (dir) {
this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
}
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
}
};
$.fn.datepicker = function ( option, val ) {
return this.each(function () {
var $this = $(this),
data = $this.data('datepicker'),
options = typeof option === 'object' && option;
if (!data) {
$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
}
if (typeof option === 'string') data[option](val);
});
};
$.fn.datepicker.defaults = {
onRender: function(date) {
return '';
}
};
$.fn.datepicker.Constructor = Datepicker;
var DPGlobal = {
modes: [
{
clsName: 'days',
navFnc: 'Month',
navStep: 1
},
{
clsName: 'months',
navFnc: 'FullYear',
navStep: 1
},
{
clsName: 'years',
navFnc: 'FullYear',
navStep: 10
}],
dates:{
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
},
isLeapYear: function (year) {
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
},
getDaysInMonth: function (year, month) {
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
},
parseFormat: function(format){
var separator = format.match(/[.\/\-\s].*?/),
parts = format.split(/\W+/);
if (!separator || !parts || parts.length === 0){
throw new Error("Invalid date format.");
}
return {separator: separator, parts: parts};
},
parseDate: function(date, format) {
var parts = date.split(format.separator),
date = new Date(),
val;
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
if (parts.length === format.parts.length) {
var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
val = parseInt(parts[i], 10)||1;
switch(format.parts[i]) {
case 'dd':
case 'd':
day = val;
date.setDate(val);
break;
case 'mm':
case 'm':
month = val - 1;
date.setMonth(val - 1);
break;
case 'yy':
year = 2000 + val;
date.setFullYear(2000 + val);
break;
case 'yyyy':
year = val;
date.setFullYear(val);
break;
}
}
date = new Date(year, month, day, 0 ,0 ,0);
}
return date;
},
formatDate: function(date, format){
var val = {
d: date.getDate(),
m: date.getMonth() + 1,
yy: date.getFullYear().toString().substring(2),
yyyy: date.getFullYear()
};
val.dd = (val.d < 10 ? '0' : '') + val.d;
val.mm = (val.m < 10 ? '0' : '') + val.m;
var date = [];
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
date.push(val[format.parts[i]]);
}
return date.join(format.separator);
},
headTemplate: '<thead>'+
'<tr>'+
'<th class="prev">&lsaquo;</th>'+
'<th colspan="5" class="switch"></th>'+
'<th class="next">&rsaquo;</th>'+
'</tr>'+
'</thead>',
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
};
DPGlobal.template = '<div class="datepicker dropdown-menu">'+
'<div class="datepicker-days">'+
'<table class=" table-condensed">'+
DPGlobal.headTemplate+
'<tbody></tbody>'+
'</table>'+
'</div>'+
'<div class="datepicker-months">'+
'<table class="table-condensed">'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
'</table>'+
'</div>'+
'<div class="datepicker-years">'+
'<table class="table-condensed">'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
'</table>'+
'</div>'+
'</div>';
}( window.jQuery );
File diff suppressed because it is too large Load Diff
@@ -1,5 +0,0 @@
//when signed out, or session expires forward to sign in page
Ext.Ajax.on('requestexception', function(conn, response, options) {
if (response.status === 401) { window.location = '/users/sign_in'; }
}, this);
@@ -0,0 +1,51 @@
$(document).ready(function(){
var MIN_LEN = 3;
var MAX_SUBMITS = 3;
var submit_count = 0;
$("input[name=commit]").click( function(e){
console.log("clicked");
submit_count += 1;
//IDs of contact info
var contact_info_ids = [
"user_email",
"user_user_profiles_attributes_0_addrStreet1",
"user_user_profiles_attributes_0_addrCity",
"user_user_profiles_attributes_0_addrState",
"user_user_profiles_attributes_0_addrZip",
"user_user_profiles_attributes_0_phone"
];
var contact_vals = "";
var index = 0;
//see if any contact info exists
for( var index in contact_info_ids){
contact_vals += $("#"+contact_info_ids[index]).val();
}
if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){
if( submit_count > MAX_SUBMITS ){
alert("Fine.");
}
return true;
}else{
switch(submit_count){
case 1:
alert("It appears you have not entered any contact information. " +
"Please do.");
break;
case 2:
alert("It is highly recommended that you enter at least one form of" +
" contact information. It is in your best interest.");
break;
case 3:
alert("If something happens to your bicycle, we will not be able to" +
" notify you. Please enter at least one form of contact.");
break;
default:
alert("Please enter at least one form of contact.");
}
return false;
}
});
});
+46
View File
@@ -0,0 +1,46 @@
$(document).ready(function(){
$("#checkin_menu").show();
$("#checkin").click( function(e){
var username = $("#user_username").val();
var password = $("#user_password").val();
$.ajax({
type: 'POST',
url: '/api/v1/checkin',
dataType: 'json',
contentType: 'application/json',
processData: false,
data: JSON.stringify({"username": username, "password": password }),
complete: function() { },
success: function(data) {
alert("Checked IN!");
$("#user_username").val('');
$("#user_password").val('');
},
error: function(data,textStatus) {
alert( "Error: " + JSON.parse(data.responseText)["error"]);
}
})
});
$("#checkout").click( function(e){
var username = $("#user_username").val();
var password = $("#user_password").val();
$.ajax({
type: 'POST',
url: '/api/v1/checkout',
dataType: 'json',
contentType: 'application/json',
processData: false,
data: JSON.stringify({"username": username, "password": password }),
complete: function() { },
success: function(data) {
alert("Checked OUT!");
$("#user_username").val('');
$("#user_password").val('');
},
error: function(data,textStatus) {
alert( "Error: " + JSON.parse(data.responseText)["error"]);
}
})
});
});
+445
View File
@@ -0,0 +1,445 @@
var DateFormat = {};
(function($) {
var daysInWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var shortMonthsInYear = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var longMonthsInYear = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
var shortMonthsToNumber = { 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12' };
var YYYYMMDD_MATCHER = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;
$.format = (function() {
function numberToLongDay(value) {
// 0 to Sunday
// 1 to Monday
return daysInWeek[parseInt(value, 10)] || value;
}
function numberToShortMonth(value) {
// 1 to Jan
// 2 to Feb
var monthArrayIndex = parseInt(value, 10) - 1;
return shortMonthsInYear[monthArrayIndex] || value;
}
function numberToLongMonth(value) {
// 1 to January
// 2 to February
var monthArrayIndex = parseInt(value, 10) - 1;
return longMonthsInYear[monthArrayIndex] || value;
}
function shortMonthToNumber(value) {
// Jan to 01
// Feb to 02
return shortMonthsToNumber[value] || value;
}
function parseTime(value) {
// 10:54:50.546
// => hour: 10, minute: 54, second: 50, millis: 546
// 10:54:50
// => hour: 10, minute: 54, second: 50, millis: ''
var time = value,
values,
subValues,
hour,
minute,
second,
millis = '',
delimited,
timeArray;
if(time.indexOf('.') !== -1) {
delimited = time.split('.');
// split time and milliseconds
time = delimited[0];
millis = delimited[1];
}
timeArray = time.split(':');
if(timeArray.length === 3) {
hour = timeArray[0];
minute = timeArray[1];
// '20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
// => 20
// '20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
// => 20
second = timeArray[2].replace(/\s.+/, '').replace(/[a-z]/gi, '');
// '01:10:20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
// => 01:10:20
// '01:10:20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
// => 01:10:20
time = time.replace(/\s.+/, '').replace(/[a-z]/gi, '');
return {
time: time,
hour: hour,
minute: minute,
second: second,
millis: millis
};
}
return { time : '', hour : '', minute : '', second : '', millis : '' };
}
function padding(value, length) {
var paddingCount = length - String(value).length;
for(var i = 0; i < paddingCount; i++) {
value = '0' + value;
}
return value;
}
return {
parseDate: function(value) {
var parsedDate = {
date: null,
year: null,
month: null,
dayOfMonth: null,
dayOfWeek: null,
time: null
};
if(typeof value == 'number') {
return this.parseDate(new Date(value));
} else if(typeof value.getFullYear == 'function') {
parsedDate.year = String(value.getFullYear());
// d = new Date(1900, 1, 1) // 1 for Feb instead of Jan.
// => Thu Feb 01 1900 00:00:00
parsedDate.month = String(value.getMonth() + 1);
parsedDate.dayOfMonth = String(value.getDate());
parsedDate.time = parseTime(value.toTimeString());
} else if(value.search(YYYYMMDD_MATCHER) != -1) {
/* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */
values = value.split(/[T\+-]/);
parsedDate.year = values[0];
parsedDate.month = values[1];
parsedDate.dayOfMonth = values[2];
parsedDate.time = parseTime(values[3].split('.')[0]);
} else {
values = value.split(' ');
switch (values.length) {
case 6:
/* Wed Jan 13 10:43:41 CET 2010 */
parsedDate.year = values[5];
parsedDate.month = shortMonthToNumber(values[1]);
parsedDate.dayOfMonth = values[2];
parsedDate.time = parseTime(values[3]);
break;
case 2:
/* 2009-12-18 10:54:50.546 */
subValues = values[0].split('-');
parsedDate.year = subValues[0];
parsedDate.month = subValues[1];
parsedDate.dayOfMonth = subValues[2];
parsedDate.time = parseTime(values[1]);
break;
case 7:
/* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */
case 9:
/* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */
case 10:
/* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */
parsedDate.year = values[3];
parsedDate.month = shortMonthToNumber(values[1]);
parsedDate.dayOfMonth = values[2];
parsedDate.time = parseTime(values[4]);
break;
case 1:
/* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */
subValues = values[0].split('');
parsedDate.year = subValues[0] + subValues[1] + subValues[2] + subValues[3];
parsedDate.month = subValues[5] + subValues[6];
parsedDate.dayOfMonth = subValues[8] + subValues[9];
parsedDate.time = parseTime(subValues[13] + subValues[14] + subValues[15] + subValues[16] + subValues[17] + subValues[18] + subValues[19] + subValues[20]);
break;
default:
return null;
}
}
parsedDate.date = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth);
parsedDate.dayOfWeek = String(parsedDate.date.getDay());
return parsedDate;
},
date : function(value, format) {
try {
var parsedDate = this.parseDate(value);
if(parsedDate === null) {
return value;
}
var date = parsedDate.date,
year = parsedDate.year,
month = parsedDate.month,
dayOfMonth = parsedDate.dayOfMonth,
dayOfWeek = parsedDate.dayOfWeek,
time = parsedDate.time;
var pattern = '',
retValue = '',
unparsedRest = '',
inQuote = false;
/* Issue 1 - variable scope issue in format.date (Thanks jakemonO) */
for(var i = 0; i < format.length; i++) {
var currentPattern = format.charAt(i);
// Look-Ahead Right (LALR)
var nextRight = format.charAt(i + 1);
if (inQuote) {
if (currentPattern == "'") {
retValue += (pattern === '') ? "'" : pattern;
pattern = '';
inQuote = false;
} else {
pattern += currentPattern;
}
continue;
}
pattern += currentPattern;
unparsedRest = '';
switch (pattern) {
case 'ddd':
retValue += numberToLongDay(dayOfWeek);
pattern = '';
break;
case 'dd':
if(nextRight === 'd') {
break;
}
retValue += padding(dayOfMonth, 2);
pattern = '';
break;
case 'd':
if(nextRight === 'd') {
break;
}
retValue += parseInt(dayOfMonth, 10);
pattern = '';
break;
case 'D':
if(dayOfMonth == 1 || dayOfMonth == 21 || dayOfMonth == 31) {
dayOfMonth = parseInt(dayOfMonth, 10) + 'st';
} else if(dayOfMonth == 2 || dayOfMonth == 22) {
dayOfMonth = parseInt(dayOfMonth, 10) + 'nd';
} else if(dayOfMonth == 3 || dayOfMonth == 23) {
dayOfMonth = parseInt(dayOfMonth, 10) + 'rd';
} else {
dayOfMonth = parseInt(dayOfMonth, 10) + 'th';
}
retValue += dayOfMonth;
pattern = '';
break;
case 'MMMM':
retValue += numberToLongMonth(month);
pattern = '';
break;
case 'MMM':
if(nextRight === 'M') {
break;
}
retValue += numberToShortMonth(month);
pattern = '';
break;
case 'MM':
if(nextRight === 'M') {
break;
}
retValue += padding(month, 2);
pattern = '';
break;
case 'M':
if(nextRight === 'M') {
break;
}
retValue += parseInt(month, 10);
pattern = '';
break;
case 'y':
case 'yyy':
if(nextRight === 'y') {
break;
}
retValue += pattern;
pattern = '';
break;
case 'yy':
if(nextRight === 'y') {
break;
}
retValue += String(year).slice(-2);
pattern = '';
break;
case 'yyyy':
retValue += year;
pattern = '';
break;
case 'HH':
retValue += padding(time.hour, 2);
pattern = '';
break;
case 'H':
if(nextRight === 'H') {
break;
}
retValue += parseInt(time.hour, 10);
pattern = '';
break;
case 'hh':
/* time.hour is '00' as string == is used instead of === */
hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
: time.hour - 12);
retValue += padding(hour, 2);
pattern = '';
break;
case 'h':
if(nextRight === 'h') {
break;
}
hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
: time.hour - 12);
retValue += parseInt(hour, 10);
// Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21
// retValue = parseInt(retValue, 10);
pattern = '';
break;
case 'mm':
retValue += padding(time.minute, 2);
pattern = '';
break;
case 'm':
if(nextRight === 'm') {
break;
}
retValue += time.minute;
pattern = '';
break;
case 'ss':
/* ensure only seconds are added to the return string */
retValue += padding(time.second.substring(0, 2), 2);
pattern = '';
break;
case 's':
if(nextRight === 's') {
break;
}
retValue += time.second;
pattern = '';
break;
case 'S':
case 'SS':
if(nextRight === 'S') {
break;
}
retValue += pattern;
pattern = '';
break;
case 'SSS':
retValue += time.millis.substring(0, 3);
pattern = '';
break;
case 'a':
retValue += time.hour >= 12 ? 'PM' : 'AM';
pattern = '';
break;
case 'p':
retValue += time.hour >= 12 ? 'p.m.' : 'a.m.';
pattern = '';
break;
case "'":
pattern = '';
inQuote = true;
break;
default:
retValue += currentPattern;
pattern = '';
break;
}
}
retValue += unparsedRest;
return retValue;
} catch (e) {
if(console && console.log) {
console.log(e);
}
return value;
}
},
/*
* JavaScript Pretty Date
* Copyright (c) 2011 John Resig (ejohn.org)
* Licensed under the MIT and GPL licenses.
*
* Takes an ISO time and returns a string representing how long ago the date
* represents
*
* ('2008-01-28T20:24:17Z') // => '2 hours ago'
* ('2008-01-27T22:24:17Z') // => 'Yesterday'
* ('2008-01-26T22:24:17Z') // => '2 days ago'
* ('2008-01-14T22:24:17Z') // => '2 weeks ago'
* ('2007-12-15T22:24:17Z') // => 'more than 5 weeks ago'
*
*/
prettyDate : function(time) {
var date;
var diff;
var day_diff;
if(typeof time === 'string' || typeof time === 'number') {
date = new Date(time);
}
if(typeof time === 'object') {
date = new Date(time.toString());
}
diff = (((new Date()).getTime() - date.getTime()) / 1000);
day_diff = Math.floor(diff / 86400);
if(isNaN(day_diff) || day_diff < 0) {
return;
}
if(diff < 60) {
return 'just now';
} else if(diff < 120) {
return '1 minute ago';
} else if(diff < 3600) {
return Math.floor(diff / 60) + ' minutes ago';
} else if(diff < 7200) {
return '1 hour ago';
} else if(diff < 86400) {
return Math.floor(diff / 3600) + ' hours ago';
} else if(day_diff === 1) {
return 'Yesterday';
} else if(day_diff < 7) {
return day_diff + ' days ago';
} else if(day_diff < 31) {
return Math.ceil(day_diff / 7) + ' weeks ago';
} else if(day_diff >= 31) {
return 'more than 5 weeks ago';
}
},
toBrowserTimeZone : function(value, format) {
return this.date(new Date(value), format || 'MM/dd/yyyy HH:mm:ss');
}
};
}());
}(DateFormat));
;// require dateFormat.js
// please check `dist/jquery.dateFormat.js` for a complete version
(function($) {
$.format = DateFormat.format;
}(jQuery));
+376
View File
@@ -0,0 +1,376 @@
//when signed out, or session expires forward to sign in page
Ext.Ajax.on('requestexception', function(conn, response, options) {
if (response.status === 401) { window.location = '/users/sign_in'; }
}, this);
//Override default netzke time entry field
Ext.define('Ext.ux.form.field.DateTime', {
extend:'Ext.form.FieldContainer',
mixins:{
field:'Ext.form.field.Field'
},
alias: 'widget.xdatetime',
//configurables
combineErrors: true,
msgTarget: 'under',
layout: 'hbox',
readOnly: false,
dateFormat: 'Y-m-d',
dateSubmitFormat: 'Y-m-d',
timeFormat: 'H:i:s',
timeSubmitFormat: 'H:i:s',
dateConfig:{},
timeConfig:{},
// properties
dateValue: null, // Holds the actual date
dateField: null,
hourField: null,
minuteField: null,
ampmField: null,
initComponent: function(){
var me = this
,i = 0
,key
,tab;
me.items = me.items || [];
me.dateField = Ext.create('Ext.form.field.Date', Ext.apply({
format:me.dateFormat,
flex:1,
isFormField:false, //exclude from field query's
submitValue:false,
submitFormat: me.dateSubmitFormat,
readOnly: me.readOnly
}, me.dateConfig));
me.items.push(me.dateField);
me.hourField = Ext.create('Ext.form.field.Number', {
maxWidth: 25,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Hour cannot be blank.",
allowDecimals: false,
maxValue: 12,
minValue: 1,
maxLength: 2,
enforceMaxLength: 2,
hideTrigger: true,
submitValue:false,
flex:1,
fieldStyle: "text-align:right;",
isFormField:false, //exclude from field query's
});
me.items.push(me.hourField);
me.colon = Ext.create('Ext.draw.Text', {
text: ':',
padding: '3 3 0 3'
});
me.items.push(me.colon);
me.minuteField = Ext.create('Ext.form.field.Text', {
maxWidth: 30,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Minutes cannot be blank.",
regex: /[012345]\d/,
maxLength: 2,
enforceMaxLength: 2,
submitValue:false,
flex:1,
isFormField:false, //exclude from field query's
});
me.items.push(me.minuteField);
me.ampmField = Ext.create('Ext.form.ComboBox', {
maxWidth: 45,
value: 'PM',
store: ['AM', 'PM'],
forceSelection: true,
flex:1,
editable: false
});
me.items.push(me.ampmField);
for (; i < me.items.length; i++) {
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
me.items[i].on('specialkey', function(field, event){
key = event.getKey();
tab = key == event.TAB;
if (tab && me.focussedItem == me.dateField) {
event.stopEvent();
me.timeField.focus();
return;
}
me.fireEvent('specialkey', field, event);
});
}
me.callParent();
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
this.inputEl = {
dom: document.createElement('div'),
swallowEvent:function(){}
};
me.initField();
},
focus:function(){
this.callParent(arguments);
this.dateField.focus();
},
onItemFocus:function(item){
if (this.blurTask){
this.blurTask.cancel();
}
this.focussedItem = item;
},
onItemBlur:function(item, e){
var me = this;
if (item != me.focussedItem){ return; }
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
me.blurTask = new Ext.util.DelayedTask(function(){
me.fireEvent('blur', me, e);
});
me.blurTask.delay(100);
},
getValue: function(){
var value = null
,date = this.dateField.getSubmitValue()
,time = null
,format;
hour = this.hourField.getValue();
minute = this.minuteField.getValue();
ampm = this.ampmField.getValue();
time = Ext.Date.parse(hour + " " + minute + " " + ampm, 'g i A');
time = Ext.Date.format(time, this.timeSubmitFormat);
if (date){
if (time){
format = this.getFormat();
value = Ext.Date.parse(date + ' ' + time, format);
} else {
value = this.dateField.getValue();
}
}
return value;
},
getSubmitValue: function(){
// var value = this.getValue();
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
var me = this
,format = me.getFormat()
,value = me.getValue();
return value ? Ext.Date.format(value, format) : null;
},
setValue: function(value){
if (Ext.isString(value) || Ext.isDate(value)){
if( Ext.isDate(value)){
dt = value;
}else{
dt = new Date(value);
value = Ext.Date.parse(value, this.getFormat()); //this.dateTimeFormat
}
this.dateField.setValue(value);
hour = Ext.Date.format(dt, 'g');
minute = Ext.Date.format(dt, 'i');
ampm = Ext.Date.format(dt, 'A');
this.hourField.setValue(hour);
this.minuteField.setRawValue(minute);
this.ampmField.setValue(ampm);
}
},
getFormat: function(){
value = (this.dateField.submitFormat || this.dateField.format) + " " + (this.timeSubmitFormat || this.timeFormat);
return value;
},
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
getSubmitData: function(){
var me = this
,data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getSubmitValue();
}
return data;
}
});
Ext.define('Ext.ux.form.field.ColorCombo', {
extend:'Ext.form.FieldContainer',
mixins:{
field:'Ext.form.field.Field'
},
alias: 'widget.xcolorcombo',
//configurables
combineErrors: true,
msgTarget: 'under',
layout: 'hbox',
readOnly: false,
// properties
colorValue: null,
/**
* @property dateField
* @type Ext.form.field.Date
*/
colorField: null,
initComponent: function(){
var me = this
,i = 0
,key
,tab;
me.items = me.items || [];
me.colorField = Ext.create('Ext.form.field.Trigger', {
flex:1,
isFormField:false, //exclude from field query's
submitValue:false,
readOnly: me.readOnly,
onTriggerClick: function() {
//show renders, so put first
me.picker.show();
me.picker.alignTo(me.colorField.inputEl);
}
});
me.items.push(me.colorField);
me.picker = Ext.create('Ext.picker.Color', {
renderTo: document.body,
floating: true,
hidden: true,
style: {
backgroundColor: "#fff"
},
listeners: {
scope:this,
select: function(field, value, opts){
me.setValue(value);
me.picker.hide();
}
}
});
me.items.push(me.picker);
for (; i < me.items.length; i++) {
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
me.items[i].on('specialkey', function(field, event){
key = event.getKey();
tab = key == event.TAB;
if (tab && me.focussedItem == me.dateField) {
event.stopEvent();
me.timeField.focus();
return;
}
me.fireEvent('specialkey', field, event);
});
}
me.callParent();
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
this.inputEl = {
dom: document.createElement('div'),
swallowEvent:function(){}
};
me.initField();
},
focus:function(){
this.callParent(arguments);
this.colorField.focus();
var me = this;
},
onItemFocus:function(item){
if (this.blurTask){
this.blurTask.cancel();
}
this.focussedItem = item;
},
onItemBlur:function(item, e){
var me = this;
if (item != me.focussedItem){ return; }
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
me.blurTask = new Ext.util.DelayedTask(function(){
me.picker.hide();
me.fireEvent('blur', me, e);
});
me.blurTask.delay(100);
},
getValue: function(){
var value = null
,color = this.colorField.getSubmitValue();
if (color){
value = this.colorField.getValue();
}
return value;
},
getSubmitValue: function(){
// var value = this.getValue();
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
var me = this
,value = me.getValue();
return value;
},
setValue: function(value){
this.colorField.setValue(value);
},
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
getSubmitData: function(){
var me = this
,data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getSubmitValue();
}
return data;
}
});
+9
View File
@@ -0,0 +1,9 @@
$("#index_logout").click(function(){
$.ajax({
type: "DELETE",
url: $("#index_logout").data("url"),
complete: function(){
window.location.href="/";
}
});
});
+32
View File
@@ -0,0 +1,32 @@
$(".task_list_task").click(function(){
$("#update_tasks_submit").removeClass("disabled");
});
$("#update_tasks_submit").click(function(){
tasks = [];
$(".task_list_task").each(function(){
tasks.push({
id: parseInt($(this).data("id")),
done: $(this).is(":checked")
});
});
json_data = { tasks: tasks };
$.ajax({
url: $("#update_tasks_submit").data("url"),
type: "PUT",
data: JSON.stringify(json_data),
contentType: 'application/json',
dataType: "json",
success: function(data, status, xhr){
//should re-render via JS, but for now reload
location.reload();
},
error: function(data, status ){
alert("An error occured updating tasks");
//displayFormErrors(data.responseJSON);
}
});
});
+81
View File
@@ -0,0 +1,81 @@
$(document).ready(function(){
var currentdate = new Date();
$("#date_id").datepicker().on('changeDate', function(ev){
$("#date_id").datepicker('hide');
});
$("#date_id").datepicker('setValue', currentdate);
$("#start_time_id").timepicker();
$("#end_time_id").timepicker();
$("#add_time_entry_submit").click(function(){
date = $("#date_id").val();
start_date = new Date(date + " " + $("#start_time_id").val());
end_date = new Date(date + " " + $("#end_time_id").val());
forward = $("#add_time_entry_submit").data("forward");
// If a bike is selected, forward to the bike
// checklist.
bike_id = parseInt($("#bike_id").val());
if( bike_id > 0 ){
forward = "/task_lists/" + bike_id + "/edit";
}
//FIXME: Ideally, we'd submit the dates as ISO, but I can't figure out
// how to get Netzke to render UTC dates correctly (it calls to_json
// somewhere and drops off the timezone). For the time being, save dates
// in locale like Netzke.
json_data = { time_entries: [{
start_date: $.format.date(start_date, "dd-MM-yyyy hh:mm a"),
end_date: $.format.date(end_date, "dd-MM-yyyy hh:mm a"),
log_action_id: parseInt($('input[name=action_id]:checked').val()),
bike_id: bike_id,
description: $("#description_id").val(),
}]};
$.ajax({
url: $("#add_time_entry_submit").data("url"),
type: "POST",
data: JSON.stringify(json_data),
contentType: 'application/json',
dataType: "json",
success: function(data, status, xhr){
window.location = forward;
},
error: function(data, status ){
displayFormErrors(data.responseJSON);
}
});
});
$(".work_entry-delete-btn").click(function(){
row = $(this).closest("tr");
entry_id = row.data("id");
start_date = row.data("start_date");
duration = row.data("duration");
description = row.data("description");
$("#work_entry_start_date").html(start_date);
$("#work_entry_duration").html(duration);
$("#work_entry_description").html(description);
$("#confirmation_delete").data("entry_id", entry_id);
});
$("#confirmation_delete").click(function(){
entry_id = $(this).data("entry_id");
url = $("#confirmation_delete").data("url") + entry_id;
$.ajax({
url: url,
type: "delete",
contentType: 'application/json',
success: function(data, status, xhr){
location.reload();
},
error: function(data, status ){
displayFormErrors(data.responseJSON);
}
});
});
});
+8
View File
@@ -0,0 +1,8 @@
function displayFormErrors(data){
if( data.errors != undefined ){
$.each( data.errors, function( field, errorMsg) {
$("#"+field).parents(".control-group").addClass("error");
$("#"+field).siblings(".help-block").html(errorMsg);
});
}
}
+29 -1
View File
@@ -9,5 +9,33 @@
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require_tree .
*= require datepicker
*= require bootstrap
*= require bootstrap-timepicker
*= require bootstrap_and_overrides
*/
.container {
max-width: 940px;
margin: 0 auto;
}
.container img {
display: block;
margin: 0 auto;
}
p {
text-align: center;
padding: 20px 0;
}
.check_box {
width: 30px;
height: 30px;
background-color: white;
}
label {
font-size: x-large;
}
+146
View File
@@ -0,0 +1,146 @@
/*!
* Timepicker Component for Twitter Bootstrap
*
* Copyright 2013 Joris de Wit
*
* Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
.bootstrap-timepicker {
position: relative;
}
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu {
left: auto;
right: 0;
}
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before {
left: auto;
right: 12px;
}
.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after {
left: auto;
right: 13px;
}
.bootstrap-timepicker .add-on {
cursor: pointer;
}
.bootstrap-timepicker .add-on i {
display: inline-block;
width: 16px;
height: 16px;
}
.bootstrap-timepicker-widget.dropdown-menu {
padding: 4px;
}
.bootstrap-timepicker-widget.dropdown-menu.open {
display: inline-block;
}
.bootstrap-timepicker-widget.dropdown-menu:before {
border-bottom: 7px solid rgba(0, 0, 0, 0.2);
border-left: 7px solid transparent;
border-right: 7px solid transparent;
content: "";
display: inline-block;
position: absolute;
}
.bootstrap-timepicker-widget.dropdown-menu:after {
border-bottom: 6px solid #FFFFFF;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
content: "";
display: inline-block;
position: absolute;
}
.bootstrap-timepicker-widget.timepicker-orient-left:before {
left: 6px;
}
.bootstrap-timepicker-widget.timepicker-orient-left:after {
left: 7px;
}
.bootstrap-timepicker-widget.timepicker-orient-right:before {
right: 6px;
}
.bootstrap-timepicker-widget.timepicker-orient-right:after {
right: 7px;
}
.bootstrap-timepicker-widget.timepicker-orient-top:before {
top: -7px;
}
.bootstrap-timepicker-widget.timepicker-orient-top:after {
top: -6px;
}
.bootstrap-timepicker-widget.timepicker-orient-bottom:before {
bottom: -7px;
border-bottom: 0;
border-top: 7px solid #999;
}
.bootstrap-timepicker-widget.timepicker-orient-bottom:after {
bottom: -6px;
border-bottom: 0;
border-top: 6px solid #ffffff;
}
.bootstrap-timepicker-widget a.btn,
.bootstrap-timepicker-widget input {
border-radius: 4px;
}
.bootstrap-timepicker-widget table {
width: 100%;
margin: 0;
}
.bootstrap-timepicker-widget table td {
text-align: center;
height: 30px;
margin: 0;
padding: 2px;
}
.bootstrap-timepicker-widget table td:not(.separator) {
min-width: 30px;
}
.bootstrap-timepicker-widget table td span {
width: 100%;
}
.bootstrap-timepicker-widget table td a {
border: 1px transparent solid;
width: 100%;
display: inline-block;
margin: 0;
padding: 8px 0;
outline: 0;
color: #333;
}
.bootstrap-timepicker-widget table td a:hover {
text-decoration: none;
background-color: #eee;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
border-color: #ddd;
}
.bootstrap-timepicker-widget table td a i {
margin-top: 2px;
font-size: 18px;
}
.bootstrap-timepicker-widget table td input {
width: 25px;
margin: 0;
text-align: center;
}
.bootstrap-timepicker-widget .modal-content {
padding: 4px;
}
@media (min-width: 767px) {
.bootstrap-timepicker-widget.modal {
width: 200px;
margin-left: -100px;
}
}
@media (max-width: 767px) {
.bootstrap-timepicker {
width: 100%;
}
.bootstrap-timepicker .dropdown-menu {
width: 100%;
}
}
-17
View File
@@ -1,17 +0,0 @@
@import "twitter/bootstrap/bootstrap";
@import "twitter/bootstrap/responsive";
// Set the correct sprite paths
@iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
@iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png');
// Your custom LESS stylesheets goes here
//
// Since bootstrap was imported above you have access to its mixins which
// you may use and inherit here
//
// If you'd like to override bootstrap's own variables, you can do so here as well
// See http://twitter.github.com/bootstrap/less.html for their names and documentation
//
// Example:
// @linkColor: #ff0000;
+31
View File
@@ -0,0 +1,31 @@
body {
padding-top: 60px;
padding-left: 20px;
}
// Set the correct sprite paths
$iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
$iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png');
// Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines)
// Note: If you use asset_path() here, your compiled boostrap_and_overrides.css will not
// have the proper paths. So for now we use the absolute path.
$fontAwesomeEotPath: '/assets/fontawesome-webfont.eot';
$fontAwesomeWoffPath: '/assets/fontawesome-webfont.woff';
$fontAwesomeTtfPath: '/assets/fontawesome-webfont.ttf';
$fontAwesomeSvgPath: '/assets/fontawesome-webfont.svg';
[data-toggle="buttons-radio"] > .btn > input[type="radio"],
[data-toggle="buttons"] > .btn > input[type="checkbox"] {
display: none;
}
.inline-block {
display: inline-block;
}
.control-group.error .btn-group > .btn {
color: #b94a48;
border-color: #b94a48;
}
+182
View File
@@ -0,0 +1,182 @@
/*!
* Datepicker for Bootstrap
*
* Copyright 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.datepicker {
top: 0;
left: 0;
padding: 4px;
margin-top: 1px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
/*.dow {
border-top: 1px solid #ddd !important;
}*/
}
.datepicker:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
position: absolute;
top: -7px;
left: 6px;
}
.datepicker:after {
content: '';
display: inline-block;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #ffffff;
position: absolute;
top: -6px;
left: 7px;
}
.datepicker > div {
display: none;
}
.datepicker table {
width: 100%;
margin: 0;
}
.datepicker td,
.datepicker th {
text-align: center;
width: 20px;
height: 20px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.datepicker td.day:hover {
background: #eeeeee;
cursor: pointer;
}
.datepicker td.day.disabled {
color: #eeeeee;
}
.datepicker td.old,
.datepicker td.new {
color: #999999;
}
.datepicker td.active,
.datepicker td.active:hover {
color: #ffffff;
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
*background-color: #0044cc;
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker td.active:hover,
.datepicker td.active:hover:hover,
.datepicker td.active:focus,
.datepicker td.active:hover:focus,
.datepicker td.active:active,
.datepicker td.active:hover:active,
.datepicker td.active.active,
.datepicker td.active:hover.active,
.datepicker td.active.disabled,
.datepicker td.active:hover.disabled,
.datepicker td.active[disabled],
.datepicker td.active:hover[disabled] {
color: #ffffff;
background-color: #0044cc;
*background-color: #003bb3;
}
.datepicker td.active:active,
.datepicker td.active:hover:active,
.datepicker td.active.active,
.datepicker td.active:hover.active {
background-color: #003399 \9;
}
.datepicker td span {
display: block;
width: 47px;
height: 54px;
line-height: 54px;
float: left;
margin: 2px;
cursor: pointer;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.datepicker td span:hover {
background: #eeeeee;
}
.datepicker td span.active {
color: #ffffff;
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
*background-color: #0044cc;
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker td span.active:hover,
.datepicker td span.active:focus,
.datepicker td span.active:active,
.datepicker td span.active.active,
.datepicker td span.active.disabled,
.datepicker td span.active[disabled] {
color: #ffffff;
background-color: #0044cc;
*background-color: #003bb3;
}
.datepicker td span.active:active,
.datepicker td span.active.active {
background-color: #003399 \9;
}
.datepicker td span.old {
color: #999999;
}
.datepicker th.switch {
width: 145px;
}
.datepicker th.next,
.datepicker th.prev {
font-size: 21px;
}
.datepicker thead tr:first-child th {
cursor: pointer;
}
.datepicker thead tr:first-child th:hover {
background: #eeeeee;
}
.input-append.date .add-on i,
.input-prepend.date .add-on i {
display: block;
cursor: pointer;
width: 16px;
height: 16px;
}
+32 -23
View File
@@ -1,45 +1,56 @@
class AppTabPanel < Netzke::Basepack::TabPanel
action :sign_out do |c|
c.icon = :door_out
c.text = "Sign out #{controller.current_user.email}" if controller.current_user
c.text = "Exit"
end
action :check_out do |c|
c.icon = :door_out
c.text = "CHECK OUT" if controller.current_user
end
action :change_account_info do |c|
c.icon = :user_edit
c.text = "Change Email/Password"
end
def configure(c)
#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
if controller.current_user.role?(:user)
# (had to use hash for borders to get the title to display properly)
#for users only
if not controller.current_user.role?(:admin)
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :user_profile_border,
title: "Profile"},
title: "My Profile"},
{ layout: :fit,
wrappedComponent: :user_transactions_border,
title: "Transactions"}
title: "My Transactions"}
]
end
#for admins
if controller.current_user.role?(:admin)
# (had to use hash for borders to get the title to display properly)
if can? :manage, User
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :users_and_profiles_border,
title: "Users/Profiles"},
{ layout: :fit,
title: "Users/Profiles"}]
end
if can? :manage, Transaction
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :transactions_border,
title: "Transactions"},
:logs,
:user_roles]
title: "Users/Transactions"}]
end
if can? :manage, Role
@@app_tab_panel_items.concat [{ layout: :fit,
wrappedComponent: :user_role_joins,
title: "User Roles"},
]
end
if can? :manage, ::ActsAsLoggable::Log.all
@@app_tab_panel_items.concat [:check_ins, :logs]
end
@@app_tab_panel_items.each do |item|
if item.kind_of?(Symbol)
self.class.component item
@@ -47,9 +58,8 @@ class AppTabPanel < Netzke::Basepack::TabPanel
self.class.component item[:wrappedComponent]
end
end
c.prevent_header = true
c.tbar = [:sign_out]
c.tbar = [:sign_out, :check_out, :change_account_info]
c.items = @@app_tab_panel_items
super
end
@@ -58,6 +68,5 @@ class AppTabPanel < Netzke::Basepack::TabPanel
#gets js from app_tab_panel/javascripts/sign_out.js
c.mixin :sign_out
end
end
@@ -5,5 +5,20 @@
url: '/users/sign_out',
method: 'DELETE'
});
},
onCheckOut: function(){
Ext.Ajax.request({
url: '/api/v1/checkout',
method: 'POST',
success: function(response, opts) {
Ext.Ajax.request({
url: '/users/sign_out',
method: 'DELETE'
});
}
});
},
onChangeAccountInfo: function(){
window.location.href="/users/edit";
}
}
+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
+15 -34
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,57 +19,36 @@ 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}"
}
}
]
@bike_logs = ::ActsAsLoggable::Log.where(:loggable_type => "Bike").all
c.prohibit_update = true if cannot? :update, @bike_logs
c.prohibit_create = true if cannot? :create, @bike_logs
c.prohibit_delete = true if cannot? :delete, @bike_logs
=begin
#TODO: fix GUI so it actually respects this
current_bike = Bike.find_by_id(session[:selected_bike_id])
if can? :manage, current_bike
puts "\n\n\nCAN MANAGE!\n\n\n"
c.bbar = default_bbar
else
puts "\n\n\nCANNNOT MANAGE!\n\n\n"
current_bike = Bike.find_by_id(session[:selected_bike_id])
if cannot? :update, current_bike
# if you can't update the bike, you can't do anything to the log
#c.prohibit_update = c.prohibit_create = c.prohibit_delete = true
c.bbar = [ :search ]
c.prohibit_update = c.prohibit_create = c.prohibit_delete = true
end
=end
end
endpoint :set_read_only do |params, this|
current_bike = Bike.find_by_id(session[:selected_bike_id])
puts "\n\n\nREAD!\n\n\n"
if can? :manage, current_bike
puts "\n\n\nCAN MANAGE!\n\n\n"
#calls this.setDisabled()
this.set_disabled(false)
#sets the return value of this function
#this.netzke_set_result(true)
else
puts "\n\n\nCANNNOT MANAGE!\n\n\n"
#sets the return value of this function
#this.netzke_set_result(true)
this.set_disabled(true)
end
end
def default_fields_for_forms
[
{ :name => :start_date},
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :bike_action__action, :field_label => 'Action'}
{ :name => :bike_action__action, :field_label => 'Action', :min_chars => 1 }
]
end
@@ -76,8 +56,9 @@ class BikeLogs < Netzke::Basepack::Grid
#override with nil to remove actions
def default_bbar
bbar = [ :search ]
bbar.concat [ :apply ] #if can? :update, ::ActsAsLoggable::Log
bbar.concat [ :add_in_form ] #if can? :create, ::ActsAsLoggable::Log
bbar.concat [ :apply ] if can? :update, ::ActsAsLoggable::Log
bbar.concat [ :add_in_form ] if can? :create, ::ActsAsLoggable::Log
bbar
end
end
@@ -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 = {
+40 -11
View File
@@ -2,12 +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,22 +19,48 @@ class Bikes < Netzke::Basepack::Grid
end
}
},
:color,
{ :name => :bike_style__style, :text => 'Style' },
:seat_tube_height,
:top_tube_length,
:wheel_size,
#needs to have type :action or else won't work in grid, because... netzke
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block,
:default_value => '000000'},
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style', :default_value => BikeStyle.first.id},
{ :name => :seat_tube_height, :text => 'Seat Tube (in)'},
{ :name => :top_tube_length, :text => 'Top Tube (in)'},
{ :name => :bike_wheel_size__display_string, :text => 'Wheel Size'},
: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}"
}
}
]
end
# Default the sorting to ASC on shop_id
c.data_store.sorters = [{ property: 'shop_id', direction: 'ASC' }]
@bike = Bike.all
c.prohibit_update = true if cannot? :update, @bike
c.prohibit_create = true if cannot? :create, @bike
c.prohibit_delete = true if cannot? :delete, @bike
end
def default_fields_for_forms
# :field_label MUST be defined in order for search to work
[
{ :name => :bike_brand__brand, :field_label => 'Brand', :min_chars => 1 },
{ :name => :model, :field_label => 'Model'},
{ :name => :shop_id, :field_label => 'Shop ID'},
{ :name => :serial_number, :field_label => 'Serial Number'},
{ :name => "color", :xtype => "xcolorcombo"},
{ :name => :bike_style__style, :field_label => 'Style', :min_chars => 1},
{ :name => :seat_tube_height, :field_label => 'Seat Tube (in)'},
{ :name => :top_tube_length, :field_label => 'Top Tube (in)'},
{ :name => :bike_wheel_size__display_string, :field_label => 'Wheel Size'},
{ :name => :value, :field_label => 'Value'},
{ :name => :bike_condition__condition, :field_label => 'Condition', :min_chars => 1},
{ :name => :bike_purpose__purpose, :field_label => 'Purpose', :min_chars => 1}
]
end
#override with nil to remove actions
def default_bbar
[ :apply, :add_in_form, :search ]
@@ -3,11 +3,24 @@
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"bike_brand__brand",
"bike_style__style",
"bike_condition__condition",
"bike_purpose__purpose"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
// setting the 'rowclick' event
var view = this.getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectBikeBrand({bike_brand_id: record.get('bike_brand__brand')});
}, this);
},
colorBlock: function(value){
return Ext.String.format('<div style="display:inline-block">{1}</div><div style="background-color:#{0};width:50px;height:10px;display:inline-block;margin:0 5px 0 5px;border:solid;border-color:gray;border-width:1px;"></div>', value, value);
}
}
+2 -2
View File
@@ -2,7 +2,7 @@ class BikesBorder < Netzke::Base
# Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence
component :bikes
component :bike_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,19 +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();
this.queryById('bike_logs').setReadOnly({}, function(arg, arg1, arg2){
console.log(arg);
console.log(this.getDockedItems());
});
this.queryById('bike_logs').setDisabled(true);
// 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);
}
+32
View File
@@ -0,0 +1,32 @@
class CheckIns < Netzke::Basepack::Grid
def configure(c)
super
c.header = false
c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction).
where(:loggable_type => "User").
where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")).
where("start_date >= ?", Time.zone.now.beginning_of_day);
}
c.columns = [
{ :name => :name, :getter => lambda{ |rec|
user = User.find_by_id(rec.loggable_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
},
{ :name => "Status", :getter => lambda{ |rec| rec.start_date == rec.end_date ? "Checked In" : "Checked Out" } },
:start_date,
:end_date,
]
c.prohibit_update = true
c.prohibit_create = true
c.prohibit_delete = true
end
def default_bbar
[ :search ]
end
end
+1
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);
}
+22
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,
@@ -22,4 +23,25 @@ class Logs < Netzke::Basepack::Grid
{ :name => :hours, :getter => lambda { |rec| (rec.end_date - rec.start_date)/3600 }, :sorting_scope => :sort_by_duration}
]
end
def default_fields_for_forms
[
:loggable_id,
:loggable_type,
:logger_id,
:logger_type,
:context,
:start_date,
:end_date,
:description,
:log_action_id,
:log_action_type,
:created_at,
:updated_at
].map{ |x| { :name => x, :field_label => x.to_s.humanize} }
end
def default_bbar
[ :apply, :add_in_form, :search ]
end
end
+9 -7
View File
@@ -9,6 +9,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,20 +22,21 @@ class Tasks < Netzke::Basepack::Grid
:task_list_id => task_list_id
}
c.columns = [
:done,
:task,
:notes,
:done
:notes
]
end
def default_fields_for_forms
fields = []
fields << { :no_binding => true, :xtype => 'displayfield', :fieldLabel => "No Bike Selected", :value => "Select a Bike First!"}
fields.concat( [
bike = Bike.find_by_id(session[:selected_bike_id])
bike = "Select a Bike First!" if bike.nil?
[
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Bike Selected", :value => "#{bike.to_s}"},
:done,
:task,
:notes,
:done
])
]
end
#override with nil to remove actions
+13 -13
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}"
@@ -29,28 +30,27 @@ class TransactionLogs < Netzke::Basepack::Grid
}
]
c.prohibit_update = true if cannot? :update, ::ActsAsLoggable::Log
c.prohibit_create = true if cannot? :create, ::ActsAsLoggable::Log
c.prohibit_delete = true if cannot? :delete, ::ActsAsLoggable::Log
@transaction_logs = ::ActsAsLoggable::Log.where(:loggable_type => "Transaction").all
c.prohibit_update = true if cannot? :update, @transaction_logs
c.prohibit_create = true if cannot? :create, @transaction_logs
c.prohibit_delete = true if cannot? :delete, @transaction_logs
end
def default_fields_for_forms
customer = nil
item = nil
if session[:selected_transaction_id]
trans = Transaction.find_by_id(session[:selected_transaction_id])
customer = "No Customer Selected"
item = "No Item Selected"
trans = Transaction.find_by_id(session[:selected_transaction_id])
if trans
customer = trans.customer
item = trans.item
end
customer = "No Customer Selected" if customer.nil?
item = "No Item Selected" if item.nil?
[
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment from:", :value => "#{customer.to_s}"},
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"},
{ :name => :description, :xtype => 'numberfield', :field_label => 'Amount'},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :transaction_action__action, :field_label => 'Payment Method'}
{ :name => :transaction_action__action, :field_label => 'Payment Method', :min_chars => 1}
]
end
@@ -58,8 +58,8 @@ class TransactionLogs < Netzke::Basepack::Grid
#override with nil to remove actions
def default_bbar
bbar = [ :search ]
bbar.concat [ :apply ] if can? :update, ::ActsAsLoggable::Log
bbar.concat [:add_in_form ] if can? :create, ::ActsAsLoggable::Log
bbar.concat [ :apply ] if can? :update, @transaction_logs
bbar.concat [:add_in_form ] if can? :create, @transaction_logs
bbar
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
+15 -10
View File
@@ -14,10 +14,16 @@ class UserLogs < Netzke::Basepack::Grid
:copy_action_id => 4
}
if can? :manage, ::ActsAsLoggable::Log
#this seems inefficient
if can? :manage, ::ActsAsLoggable::Log.where(:loggable_type => "User").all
#admins and staff
user_log_scope = lambda { |rel| rel.where(:loggable_type => 'User',:loggable_id => session[:selected_user_id]);}
user_log_strong_default_attrs.merge!( { :loggable_id => session[:selected_user_id] } )
#if selected user nil, then the admin is viewing their own profile
selected_user = User.find_by_id(session[:selected_user_id])
selected_user = controller.current_user if selected_user.nil?
user_log_scope = lambda { |rel| rel.where(:loggable_type => 'User',:loggable_id => selected_user.id );}
user_log_strong_default_attrs.merge!( { :loggable_id => selected_user.id } )
user_log_data_store = {auto_load: true }
else
#just users
@@ -27,19 +33,18 @@ class UserLogs < Netzke::Basepack::Grid
end
c.model = "ActsAsLoggable::Log"
c.title = "User History"
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' },
: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}"
@@ -53,13 +58,13 @@ class UserLogs < Netzke::Basepack::Grid
bike_store = Bike.all.map { |b| [b.id, b.shop_id] }
current_user ||= User.find_by_id(session[:selected_user_id]) || controller.current_user
bike_id = current_user.bike.nil? ? nil : current_user.bike.id
action_id = current_user.user_role.id
[
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Log for:", :value => "#{current_user.to_s}"},
{ :name => :start_date},
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action', :value => action_id},
{ :name => :user_action__action, :field_label => 'Action', :min_chars => 1},
{ :name => :for_bike, :checkboxName => :copy_log, :inputValue => true, :title => "Copy description to a Bike's History?", :xtype => 'fieldset', :checkboxToggle => true, :collapsed => true, :items => [
{: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
+35
View File
@@ -0,0 +1,35 @@
class UserRoleJoins < Netzke::Basepack::Grid
def configure(c)
super
c.model = "UserRoleJoin"
c.force_fit = true
c.header = false
c.title = "User Roles"
c.data_store.sorters = [{ :property => :user__username, :direction => :ASC}]
c.columns = [
{ :name => :user__username, :text => "Username", :read_only => true},
{ :name => :name, :getter => lambda{ |rec|
user = User.find_by_id(rec.user_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
},
},
{ :name => :role__role, :text => "Role", :default_value => Role.exists? ? Role.first.id : nil},
:created_at,
:updated_at,
:ends ]
end
def default_fields_for_forms
[
{ :name => :user__full_name, :field_label => 'Name'},
{ :name => :role__role, :field_label => 'Role' },
{ :name => :ends }
]
end
#override with nil to remove actions
def default_bbar
[ :apply, :add_in_form, :search ]
end
end
-15
View File
@@ -1,15 +0,0 @@
class UserRoles < Netzke::Basepack::Grid
def configure(c)
super
c.model = "UserRole"
c.title = "User Roles"
c.columns = [ :role, :created_at, :updated_at, :ends ]
end
#override with nil to remove actions
def default_bbar
[ :apply, :add_in_form, :search ]
end
end
+19 -4
View File
@@ -2,14 +2,29 @@ class UserStats < Netzke::Base
def body_content(user)
bike = user.bike
%Q(
<div id="user_stats_page">
completed = user.completed_build_bikes
#TODO I think it's time to switch to a template
body = ""
body += %Q(<div id="user_stats_page">)
body += %Q(
<p>Username: #{user.username}</p>
<p>Total Credits Earned: #{user.total_earned_credits}</p>
<p>Credits Available: #{user.total_credits}</p>
<p>Credits Spent: #{user.total_credits_spent}</p>
<p>Total Hours Worked: #{user.total_hours}</p>
<p>Hours worked in #{Time.now.strftime('%B')}: #{user.current_month_hours}</p>
<p>Current bike Shop ID: #{bike.shop_id if bike}</p>
<p>Current bike S/N: #{bike.serial_number if bike}</p>
</div>
)
unless completed.empty?
body += %Q(<p>Previously built bikes (#{completed.count}):</p>)
body += %Q(<ul>)
completed.each do |b|
body += %Q(<li>#{b.shop_id}</li>)
end
body += %Q(</ul>)
end
body += %Q(</div>)
end
js_configure do |c|
@@ -25,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);
}
}
+14 -3
View File
@@ -1,17 +1,28 @@
class Users < Netzke::Basepack::Grid
include Netzke::Basepack::ActionColumn
column :reset do |c|
c.type = :action
c.actions = [{name: :reset_password, icon: :lock_break}]
c.header = ""
c.width = 20
end
def configure(c)
super
c.header = false
c.model = "User"
c.force_fit = true
c.columns = [
{ :name => :username, :read_only => true },
:first_name,
:last_name,
:nickname,
:email,
:user_role__role,
: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
end
#override with nil to remove actions
@@ -2,10 +2,41 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = ["bike__shop_id"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
this.getView().on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
console.log("user: " + record.get('id') );
this.selectCustomer({customer_id: record.get('id'), customer_type: 'User'});
}, this);
},
onResetPassword: function(record){
user = record.data;
Ext.Msg.confirm(
"Reset Password",
"Are you sure you want to reset "+user.first_name+" "+user.last_name+"'s password?",
function(butt_id){
if( butt_id === "yes" ){
$.ajax({
type: 'POST',
url: '/api/v1/reset',
dataType: 'json',
contentType: 'application/json',
processData: false,
data: JSON.stringify({"user_id": user.id}),
complete: function() { },
success: function(data) {
Ext.Msg.alert("Success", "New Password: "+data.password);
},
error: function(data,textStatus) {
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["errors"][0]);
}
});
}
});
}
}
@@ -1,10 +1,10 @@
class 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
+4 -6
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: 300, split: true },
{ netzke_component: :user_profiles, region: :south, height: 150, split: true},
{ netzke_component: :user_logs, region: :east, split: true}
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
{ netzke_component: :user_and_profiles_lower_tabs, region: :south, height: 300, split: true}
]
end
@@ -25,5 +23,5 @@ class UsersAndProfilesBorder < Netzke::Base
# store selected boss id in the session for this component's instance
session[:selected_user_id] = params[:user_id]
end
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);
}
}
+22
View File
@@ -0,0 +1,22 @@
class Api::V1::BaseController < ActionController::Base
respond_to :json
before_filter :authenticate_user
private
def authenticate_user
if params[:username]
user = User.find_for_database_authentication( :username => params[:username] )
@current_user = user if user && user.valid_password?( params[:password] )
if @current_user.nil?
msg = "Username/Password/Token invalid"
render :json => {:error => msg }, :status => 401 and return
end
else
authenticate_user!
@current_user = current_user
end
end
end
@@ -0,0 +1,32 @@
class Api::V1::BikesController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have permission to manage bikes."
EXPECTED_BIKE = "Expected bike in submitted data"
NOT_FOUND = "The bike could not be found."
before_filter :check_bike_permission, except: :show
def create
if params[:bikes] && bike = params[:bikes].first
@bike = Bike.new(bike)
if !@bike.save
render json: { errors: @bike.errors }, status: 422 and return
end
else
render json: { errors: [EXPECTED_BIKE]}, status: 422 and return
end
end
def show
@bike = Bike.find_by_id(params[:id])
if @bike.nil?
render json: { errors: [NOT_FOUND] }, status: 404 and return
end
end
private
def check_bike_permission
if cannot? :manage, Bike
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
end
end
end
+22
View File
@@ -0,0 +1,22 @@
class Api::V1::LogsController < Api::V1::BaseController
def checkin
#must use @current_user since user may not have signed in
if @current_user.checked_in?
render :json => { "error" => "You are already checked in."}, :status => 404 and return
else
@current_user.checkin
render :nothing => true, :status => 204 and return
end
end
def checkout
#must use @current_user since user may not have signed in
if !@current_user.checked_in?
render :json => { "error" => "You were not even checked in."}, :status => 404 and return
else
@current_user.checkout
render :nothing => true, :status => 204 and return
end
end
end
@@ -0,0 +1,24 @@
class Api::V1::TaskListsController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have permission to manage this task list."
NOT_FOUND = "The task list could not be found."
before_filter :get_task_list
before_filter :check_task_list_permission, except: :show
def show
end
private
def get_task_list
@task_list = TaskList.find_by_id(params[:id])
if @task_list.nil?
render json: { errors: [NOT_FOUND] }, status: 404 and return
end
end
def check_task_list_permission
if cannot? :manage, Bike and @task_list.item != current_user.bike
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
end
end
end
@@ -0,0 +1,64 @@
class Api::V1::TasksController < Api::V1::BaseController
EXPECTED_TASKS = "Expected a list of tasks in submitted data."
CANNOT_MANAGE = "You do not have permission to manage this task."
NOT_FOUND = "The task could not be found."
before_filter :validate_params
before_filter :get_tasks
before_filter :check_task_permission, except: :show
def update
errors = []
@tasks.each do |task_hash|
task = task_hash[:record]
attrs = task_hash[:new_attributes]
task.update_attributes(attrs)
if !task.errors.empty?
errors << { id: task.id, errors: task.errors }
end
end
if !errors.empty?
render json: { errors: errors }, status: 422 and return
end
end
private
def validate_params
if params[:tasks].nil? and not params[:tasks].kind_of?(Array)
render json: { errors: [EXPECTED_TASKS]}, status: 422 and return
end
end
def get_tasks
@tasks = []
errors = []
params[:tasks].each do |task|
t = Task.find_by_id(task[:id])
if t.nil?
errors << { id: task[:id], error: NOT_FOUND }
else
@tasks << { record: t, new_attributes: task }
end
end
if !errors.empty?
render json: { errors: errors }, status: 404 and return
end
end
def check_task_permission
errors = []
@tasks.each do |task_hash|
task = task_hash[:record]
if task.task_list.item != current_user.bike
errors << { id: task[:id], error: CANNOT_MANAGE }
end
end
if cannot? :manage, Bike and !errors.empty?
render json: { errors: errors}, status: 403 and return
end
end
end
@@ -0,0 +1,33 @@
class Api::V1::TimeEntriesController < Api::V1::BaseController
EXPECTED_TIME_ENTRY = "Expected time entry in submitted data"
NOT_FOUND = "Time entry not found"
def create
if params[:time_entries] && time_entry = params[:time_entries].first
time_entry.merge!({ loggable_id: current_user.id,
logger_id: current_user.id })
bike_id = time_entry[:bike_id].to_i
@time_entry = TimeEntry.new(time_entry.except(:bike_id))
if bike_id > 0
@time_entry.copy_to_bike_history(bike_id)
end
if !@time_entry.save
render json: { errors: @time_entry.errors }, status: 422 and return
end
else
render json: { errors: [EXPECTED_TIME_ENTRY]}, status: 422 and return
end
end
def delete
if time_entry = TimeEntry.find_by_id(params[:id])
time_entry.delete
render nothing: true, status: 204 and return
else
render json: { errors: [NOT_FOUND]}, status: 404 and return
end
end
end
@@ -0,0 +1,35 @@
require 'securerandom'
class Api::V1::UsersController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have the permission to manager users"
NOT_FOUND = "User not found"
NOT_ALLOWED = "Not allowed to reset your own password in this fashion"
PASS_LENGTH = 8
def password_reset
if can? :manage, User
user = User.find_by_id(params[:user_id])
render :json => { "errors" => [NOT_FOUND]}, :status => 404 and return if user.nil?
render :json => { "errors" => [NOT_ALLOWED]}, :status => 403 and return if user.id == current_user.id
new_pass = SecureRandom.hex[0,PASS_LENGTH]
user.password = new_pass
user.save
render :json => { "password" => new_pass}, :status => 200 and return
else
render :json => { "errors" => [CANNOT_MANAGE]}, :status => 403 and return
end
end
=begin Is this here by accident? Commenting out for now (1/30/14)
def checkout
#must use @current_user since user may not have signed in
if !@current_user.checked_in?
render :json => { "error" => "You were not even checked in."}, :status => 404 and return
else
@current_user.checkout
render :nothing => true, :status => 204 and return
end
end
=end
end
+16
View File
@@ -0,0 +1,16 @@
class BikesController < AuthenticatedController
def new
@brands = BikeBrand.all.map{ |b| [b.brand, b.id] }
@brands.unshift( ["Select a brand", -1] )
@wheel_sizes = BikeWheelSize.all.map{ |w| [w.display_string, w.id] }
@wheel_sizes.unshift( ["Select a wheel size", -1] )
end
def show
@bike = Bike.find_by_id(params[:id])
@task_list_id = @bike.task_list.id
@show_add_bike = true if params[:add_bike]
end
end
+7
View File
@@ -0,0 +1,7 @@
class PanelController < ApplicationController
def index
render :inline => "<%=netzke :app_view, :layout => true %>", :layout => "netzke"
end
end
+1 -2
View File
@@ -1,7 +1,6 @@
class SiteController < ApplicationController
def index
render :inline => "<%= netzke :app_view, :layout => true %>", :layout => "application"
@bike = current_user.bike
end
end
+5
View File
@@ -0,0 +1,5 @@
class TaskListsController < AuthenticatedController
def edit
@task_list = TaskList.find_by_id(params[:id])
end
end
@@ -0,0 +1,16 @@
class TimeEntriesController < AuthenticatedController
def new
@bikes = Bike.all.map{ |b| [b.to_s , b.id] }
if bike = current_user.bike
@bikes.unshift( [bike.to_s, bike.id] )
end
@bikes.unshift( ["Non-bike work", -1] )
end
def index
@user_time_entries = TimeEntry.where(loggable_id: current_user.id)
@credits_available = current_user.total_credits
@hours_worked = current_user.total_hours
end
end
+23
View File
@@ -0,0 +1,23 @@
module DeviseHelper
# A simple way to show error messages for the current devise resource. If you need
# to customize this method, you can either overwrite it in your application helpers or
# copy the views to your application.
#
# This method is intended to stay simple and it is unlikely that we are going to change
# it to add more behavior or options.
def devise_error_messages!
return "" if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:p, msg, :class => "alert") }.join
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count,
:resource => resource.class.model_name.human.downcase)
html = <<-HTML
<p>#{sentence}</p>
#{messages}
HTML
html.html_safe
end
end
+8 -6
View File
@@ -1,14 +1,16 @@
class Ability
include CanCan::Ability
def initialize(current_user)
@current_user = current_user
self.send(current_user.role.to_sym)
current_user.roles.each do |role|
self.send(role.role.to_sym)
end
end
def admin
can :manage, :all
end
end
def staff
can :manage, :all
@@ -21,8 +23,8 @@ class Ability
def user
can :read, :all
can :manage, Bike, :id => @current_user.bike_id unless @current_user.bike.nil?
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "Bike", :loggable_id => @current_user.bike_id }
can :manage, ::ActsAsLoggable::Log, { :loggable_type => "User", :loggable_id => @current_user.id }
can :manage, @current_user.bike unless @current_user.bike.nil?
can :manage, ::ActsAsLoggable::Log, :loggable_type => "Bike", :loggable_id => @current_user.bike_id
can :manage, ::ActsAsLoggable::Log, :loggable_type => "User", :loggable_id => @current_user.id
end
end
+21 -23
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 :bike_brand_id, :presence => true
validates :bike_model_id, :presence => true
validates :color, :presence => true
validates :bike_style_id, :presence => true
validates :seat_tube_height, :presence => true
validates :top_tube_length, :presence => true
validates :wheel_size, :presence => true
#validates :value, :presence => true
validates :bike_condition_id, :presence => true
validates :bike_status_id, :presence => true
validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
validates :serial_number, :length => { :minimum => 3 }
validates :model, :length => { :maximum => 50 }
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" }
#validates :color, :presence => true
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" }
validates :seat_tube_height, :presence => true, :numericality => true
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" }
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" }
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" }
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
+3
View File
@@ -0,0 +1,3 @@
class CreditConversion < ActiveRecord::Base
attr_accessible :conversion
end
+16
View File
@@ -0,0 +1,16 @@
class Role < ActiveRecord::Base
attr_accessible :role
has_many :user_role_joins
has_many :users, through: :user_role_joins
validates_uniqueness_of :role
def to_s
self.role
end
def ==(other)
self.role == other.role
end
end
+1 -1
View File
@@ -1,5 +1,5 @@
class Task < ActiveRecord::Base
attr_accessible :task, :notes, :done
attr_accessible :task, :notes, :done, :task_list_id
belongs_to :task_list
+1 -1
View File
@@ -4,7 +4,7 @@ class TaskList < ActiveRecord::Base
attr_accessible :item_id, :item_type, :name
belongs_to :item, :polymorphic => true
has_many :tasks
has_many :tasks, order: "id ASC"
after_save :create_default_bike_tasks
+27
View File
@@ -0,0 +1,27 @@
class TimeEntry < ActsAsLoggable::Log
default_scope where( loggable_type: "User",
logger_type: "User",
log_action_type: "ActsAsLoggable::UserAction").where("log_action_id != 4").order("start_date DESC")
def copy_to_bike_history(bike_id)
self.assign_attributes({
copy_log: true,
copy_type: 'Bike',
copy_id: bike_id,
copy_action_type: 'ActsAsLoggable::BikeAction',
copy_action_id: 4
})
end
def duration
end_date - start_date
end
def duration_in_hours
(duration / 1.hour).round(2)
end
def type
log_action.try(:action)
end
end
+109 -10
View File
@@ -7,16 +7,21 @@ class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :nickname, :user_role_id, :bike_id,
:user_profiles_attributes
:first_name, :last_name, :bike_id,
:user_profiles_attributes, :username
has_many :transactions
has_many :transactions, as: :customer
has_many :transaction_logs, through: :transactions, source: :logs
has_many :user_profiles
accepts_nested_attributes_for :user_profiles, allow_destroy: false
has_one :user_role
has_many :user_role_joins, :conditions => ["ends IS NULL OR ends > ?", Time.now]
has_many :roles, through: :user_role_joins
belongs_to :bike
default_scope order('username ASC')
validates :first_name, :presence => true
validates :last_name, :presence => true
@@ -24,29 +29,123 @@ class User < ActiveRecord::Base
"#{first_name} #{last_name}"
end
def email_required?
false
end
def full_name
to_s
end
def role
user_role.role
def role?(role)
if role.kind_of?(String) or role.kind_of?(Symbol)
role = Role.find_by_role(role.to_s)
end
roles.include?(role)
end
def role?(role)
user_role.to_s == role.to_s
### TODO methods below probably belong somewhere else
def completed_build_bikes
#default BUILDBIKE/CLASS ID is 5
purpose_id = 5
Bike.find_by_sql("
SELECT *
FROM bikes
INNER JOIN(
SELECT *
FROM transactions
WHERE customer_id = #{self.id}
) AS transactions ON bikes.id = transactions.bike_id
WHERE bike_purpose_id = #{purpose_id}")
end
def total_credits
total_earned_credits - total_credits_spent
end
def total_credits_spent
log_action_id = 1 #TIME
transaction_logs.
where( "log_action_id = ? AND log_action_type = ?",
log_action_id, ::ActsAsLoggable::TransactionAction.to_s).
sum{ |r| r.description.to_i }.round(2)
end
def total_earned_credits
volunteer_id = 1
staff_id = 3
# Find the first credit conversion which has a created_at date before the
# log's created_at date and join it to the log's row so we can calculate
# the credits earned from that log entry (each log could have a different
# conversion rate)
#
# The DISTINCT ON, and ORDER BY are important to getting the
# single conversion rate that applies to the respective log.
::ActsAsLoggable::Log.find_by_sql(["
SELECT DISTINCT ON (logs.created_at) start_date, end_date,
conversion.conversion, conversion.created_at
FROM logs
INNER JOIN(
SELECT conversion, created_at
FROM credit_conversions
) AS conversion ON logs.created_at > conversion.created_at
WHERE logs.loggable_id = :id
AND logs.loggable_type = 'User'
AND (log_action_id IN (:credit_actions) AND log_action_type = :log_action_type)
ORDER BY logs.created_at, conversion.created_at DESC",
{id: self.id,
credit_actions: [volunteer_id, staff_id],
log_action_type: ::ActsAsLoggable::UserAction.to_s}]).
sum{ |l| ((l.end_date - l.start_date)/3600) * l.conversion.to_i}.round(2)
end
def total_hours
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
log_action_id = 4 #CHECKIN
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s).sum { |l| (l.end_date - l.start_date)/3600 }.round(2)
end
def current_month_hours
log_action_id = 4 #CHECKIN
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month
current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month)
ActsAsLoggable::Log.where( :loggable_type => self.class.to_s, :loggable_id => self.id)
logs.where("log_action_id != ? AND log_action_type = ?", log_action_id, ::ActsAsLoggable::UserAction.to_s)
.where( :start_date => current_month_range)
.where( :end_date => current_month_range)
.sum { |l| (l.end_date - l.start_date)/3600 }
.round(2)
end
def checked_in?
#default CHECKIN log action is id, yea yea should be a constant
log_action_id = 4
checked = logs.where( log_action_id: log_action_id).
where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date")
!checked.empty?
end
def checkin
#default CHECKIN log action is id, yea yea should be a constant
log_action_id = 4
time = Time.now
logs.create( logger_id: self.id,
logger_type: self.class.to_s,
start_date: time,
end_date: time,
log_action_id: log_action_id,
log_action_type: ::ActsAsLoggable::UserAction.to_s)
save
end
def checkout
#default CHECKIN log action is id, yea yea should be a constant
log_action_id = 4
checked = logs.where( log_action_id: log_action_id).
where("start_date >= ?", Time.zone.now.beginning_of_day).
where("start_date = end_date").first
checked.end_date = Time.now
checked.save
end
end
+6 -7
View File
@@ -1,16 +1,15 @@
class UserProfile < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :user_id, :addrStreet1, :addrStreet2, :addrCity,
:addrState, :addrZip, :phone
belongs_to :user
belongs_to :bike
#validates :addrStreet1 , :presence => true
#validates :addrCity , :presence => true
#validates :addrState , :presence => true
#validates :addrZip , :presence => true
validates :phone, :presence => true
#validates :addrStreet1 , :presence => true
#validates :addrCity , :presence => true
#validates :addrState , :presence => true
#validates :addrZip , :presence => true
#validates :phone, :presence => true
def to_s
[addrStreet1, addrStreet2, addrCity, addrState, addrZip, phone].join(" - ")
-11
View File
@@ -1,11 +0,0 @@
class UserRole < ActiveRecord::Base
attr_accessible :role
belongs_to :user
self.per_page = 15
def to_s
self.role
end
end
+13
View File
@@ -0,0 +1,13 @@
class UserRoleJoin < ActiveRecord::Base
self.table_name = :user_role_joins
attr_accessible :role_id, :user_id, :ends
belongs_to :user
belongs_to :role
validate :role_id, presence: true, numericality: true
validate :user_id, presence: true, numericality: true
validates_uniqueness_of :user_id, :scope => :role_id
self.per_page = 15
end
@@ -0,0 +1,4 @@
json.bikes [@bike] do |bike|
json.array! bike
json.set! :href, api_bike_path(bike)
end
@@ -0,0 +1,3 @@
json.bikes [@bike] do |bike|
json.array! bike
end
@@ -0,0 +1,15 @@
json.task_lists [@task_list] do |tl|
json.array! tl
json.links do
json.bike do
json.href api_bike_path(tl.item)
json.id tl.item_id
end
json.tasks tl.tasks do |task|
json.id task.id
json.done task.done
json.notes task.notes
json.task task.task
end
end
end
@@ -0,0 +1,3 @@
json.tasks [@tasks.map{|x| x[:record]}] do |task|
json.array! task
end
@@ -0,0 +1,4 @@
json.time_entries [@time_entry] do |time_entry|
json.array! time_entry
#json.set! :href, api_time_entry_path(time_entry)
end
+62
View File
@@ -0,0 +1,62 @@
%a{ class: "btn btn-default btn-lg", href: root_path}
%span{ class:"icon-home"}
%h2 Add Bike
%p
.control-group
.controls
%input{id: "shop_id", placeholder: "Shop ID", type: "number", min:0, class: "input-lg" }
.help-block
.control-group
.controls
= select_tag(:bike_brand_id, options_for_select(@brands))
.help-block
.control-group
.controls
%input{id: "model", placeholder: "Model", type: "text", class: "input-lg" }
.help-block
.control-group
.controls
%input{id: "serial_number", placeholder: "Serial Number", type: "text", class: "input-lg" }
.help-block
.control-group
.controls
.btn-group{ "data-toggle" => "buttons-radio"}
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 3} RD
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 1} MTN
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 2} HYB
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_style", value: 4} OTHER
%input{ id: "bike_style_id", type: "hidden"}
.help-block
.control-group
.controls
= select_tag(:bike_wheel_size_id, options_for_select(@wheel_sizes), id: :bike_wheel_size_id)
.help-block
.control-group
.controls
.btn-group{ "data-toggle" => "buttons-radio"}
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 2} Poor
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 3} Fair
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 4} Good
%label{ class: "btn btn-default"}
%input{ type: "radio", name: "bike_condition", value: 5} Excellent
%input{ id: "bike_condition_id", type: "hidden"}
.help-block
.control-group
.controls
%input{id: "seat_tube_height", placeholder: "Seat Tube (cm)", type: "number", min: 0, max: 100, class: "input-lg" }
.help-block
-# Commenting this out until description is added to Bike
%p
%input{id: "bike_description", placeholder: "Short description", type: "text", class: "input-lg" }
.control-group
.controls
%input{id: "add_bike_submit", value: "Add Bike", type: "button", class: "btn btn-lg btn-block btn-primary", "data-url" => "#{api_create_bike_path}"}
+27
View File
@@ -0,0 +1,27 @@
- if @show_add_bike
%p
%a{class: "btn btn-lg btn-block btn-info", href: new_bike_path} Add Another Bike?
%a{ class: "btn btn-default btn-lg", href: root_path}
%span{ class:"icon-home"}
%h2 #{@bike.shop_id}: #{@bike.bike_brand}
%h2 #{@bike.model}
%dl.dl-horizontal
%dt Type
%dd #{@bike.bike_style}
%dt Wheel Size
%dd #{@bike.bike_wheel_size.display_string}
%dt Condition
%dd #{@bike.bike_condition}
%dt Seat Tube (cm)
%dd #{@bike.seat_tube_height}
%dt Purpose
%dd #{@bike.bike_purpose}
- if !@bike.value.nil?
%dt Value
%dd #{@bike.value}
- if !@bike.color.nil?
%dt Color
%dd{ style: "width: 50px; background-color: ##{@bike.color}; border: black; border-width: 1px; border-style: solid;"} #{@bike.color}
%a{class: "btn btn-lg btn-block btn-primary", href: edit_task_list_path(@task_list_id) } View Checklist
+12 -5
View File
@@ -1,21 +1,28 @@
<%- if controller_name != 'sessions' %>
<%= link_to "Sign in", new_session_path(resource_name) %><br />
<p>
<%= link_to "Sign in", new_session_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<p>
<%= link_to "Sign up", new_registration_path(resource_name), class: "btn btn-block btn-default btn-lg" %><br />
</p>
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<p>
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn btn-block btn-default btn-lg" %><br />
</p>
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<p>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "btn btn-block btn-default" %><br />
</p>
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.omniauthable? %>
+8 -4
View File
@@ -1,12 +1,16 @@
<h2>Forgot your password?</h2>
<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %>
<h2>Forgot your password?</h2>
<br>
<br>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div class="form-group">
<%= f.email_field :email, placeholder: "Email", class: "form-control input-lg" %>
</div>
<div><%= f.submit "Send me reset password instructions" %></div>
<div><%= f.submit "Reset Password", class:"btn btn-lg btn-primary"%></div>
<% end %>
<%= render "links" %>

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