231 Commits
Author SHA1 Message Date
Jonathan Rosenbaum 50732ce07b Previous doc change not necessary, was running two instances on the same port. 2017-07-06 09:35:16 +00:00
Jonathan Rosenbaum 9bad2bf73d Improved documentation to include restart for db. 2017-07-06 09:03:56 +00:00
Jonathan Rosenbaum c9af3d4897 Enhance Dockerfile / Fix missing extjs file bug
1. Speed-up nokogiri install significantly by turning off lib compilation
2. File at http://my.jasondenney.com/extjs-4.1.1.zip can't be found, but
it has been used for a long time.  Now just including it in the
repository.
3. Added helping documentation in Dockerfile
2017-07-04 04:50:59 +00:00
Jason Denney 4a30bc5efd Merge pull request #122 from ilya-konanykhin/csv-import
CSV import
2017-03-27 20:53:28 -10:00
Ilya Konanykhin 0f1dab1ad1 BikeCsvImporter: add import of brands as a separate step 2017-03-27 10:59:07 +06:00
Ilya Konanykhin b7729ae29a BikeCsvImporter: add comments 2017-03-20 22:49:45 +06:00
Ilya Konanykhin a6a616cf6d BikeCsvImporter: add status logs 2017-03-20 22:38:14 +06:00
Ilya Konanykhin ae79a34652 BikeCsvImporter: colorize importer output to highlight individual row statuses 2017-03-20 21:44:27 +06:00
Ilya Konanykhin 072f303c65 BikeCsvImporter: refactor importer to output log messages to stdoud 2017-03-20 21:41:07 +06:00
Ilya Konanykhin c1ca501204 BikeCsvImporter: fix incorrect method signatures 2017-03-20 21:25:24 +06:00
Ilya Konanykhin 0a338a2485 BikeCsvImporter: refactor BikeAttrs for methods to receive a single value, not the whole hash 2017-03-20 00:55:26 +06:00
Ilya Konanykhin b5e8aa554f BikeCsvImporter: refactor by splitting into parts 2017-03-20 00:48:37 +06:00
Ilya Konanykhin cfe81d6e65 BikeCsvImporter: add dummy field values for "bike_condition_id", "bike_wheel_size_id", "bike_style_id", "serial_number", "seat_tube_height" 2017-03-20 00:38:24 +06:00
Ilya Konanykhin 802db2ba34 BikeCsvImporter: print successfull matches & fix "unknown" model 2017-03-20 00:26:47 +06:00
Ilya Konanykhin 0083dd9bca BikeCsvImporter: add dry run 2017-03-20 00:18:32 +06:00
Ilya Konanykhin 95dbe162f7 BikeCsvImporter: make Bike#bike_model_id field accessible and specify it as a foreign key explicitly 2017-03-20 00:09:01 +06:00
Ilya Konanykhin c171598354 BikeCsvImporter: add "bike_brand_id", "bike_model_id", "model" 2017-03-20 00:08:16 +06:00
Ilya Konanykhin ae8b5cccd0 BikeCsvImporter: add "value" 2017-03-17 15:22:37 +06:00
Ilya Konanykhin c79ec57684 BikeCsvImporter: add "purpose_id", temporary remove "gone" 2017-03-17 15:15:23 +06:00
Ilya Konanykhin 5564a1931f BikeCsvImporter: import bikes, first step (main code, shop_id) 2017-03-16 15:17:14 +06:00
Ilya Konanykhin 904b824c8b Create rake task (import:bikes:csv, import:bikes:analyze_csv) & importing class 2017-03-15 15:17:40 +06:00
Jason Denney 21fba7251f Merge pull request #121 from ilya-konanykhin/issue-120
Issue #120: user email is nullable now. Closes #121.
2017-02-02 11:15:09 -08:00
Ilya Konanykhin 2e29fb7ed1 Updated specs to reflect new user email & username behavior 2017-02-01 00:02:41 +06:00
Ilya Konanykhin 87e4124f48 Update Poltergeist: the version 1.5 requires PhantomJS 1.8 which isn't even available in Homebrew now 2017-01-31 23:47:44 +06:00
Ilya Konanykhin 45e14a1070 Fix: user email should be null instead of empty string 2017-01-31 23:46:10 +06:00
Jason Denney b8ba64ab93 Merge pull request #118 from ilya-konanykhin/issue-105-106
Issue #105, #106: photo upload for User & Bike
2017-01-26 08:22:19 -08:00
Ilya Konanykhin 66fee66db0 Fix: validate username presence & uniqueness 2017-01-26 15:56:28 +06:00
Ilya Konanykhin ed2f3f6e61 Fix: alert "Fine" only once 2017-01-26 15:50:47 +06:00
Ilya Konanykhin ca72edb7e0 Client side validation on sign up page 2017-01-26 15:50:24 +06:00
Jason Denney 9df4aa38d9 Merge pull request #116 from ilya-konanykhin/issue-115
Issue #115: New top menu for desktop & mobile.
2017-01-22 16:35:02 -08:00
Jason Denney 6786bf3eff Merge pull request #117 from ilya-konanykhin/issue-114
Issue #114: Can't delete time entries in mobile UI
2017-01-22 08:43:38 -08:00
Jason Denney 1b72fdcc0b Merge pull request #119 from ilya-konanykhin/issue-109
Issue #109: db:seed failing
2017-01-21 23:20:23 -08:00
Ilya Konanykhin e9b5a6ab7c Fix: file storage to .gitignore 2017-01-19 17:35:02 +06:00
Ilya Konanykhin 02a6442299 Fix: db:setup instead of :create/:migrate/:seed 2017-01-19 17:10:49 +06:00
Ilya Konanykhin f7b1d8fd5e Fix: move database seeding from migrations to db:seed 2017-01-19 15:40:19 +06:00
Ilya Konanykhin 6794fc899c Show bike photo in a task list 2017-01-18 18:40:22 +06:00
Ilya Konanykhin 5f7eb43f63 Bike photo upload 2017-01-18 18:38:29 +06:00
Ilya Konanykhin a68e7eca56 User avatar upload at sign up 2017-01-18 17:18:32 +06:00
Ilya Konanykhin 4dcce60fb8 Fix: HTML injection 2017-01-18 14:59:48 +06:00
Ilya Konanykhin 76980a8903 Fix: more explicit (and correct) time entries delete URL 2017-01-18 14:58:14 +06:00
Ilya Konanykhin 2c6a58206a Fix: incorrect variable scoping in task_lists.js & time_entries.js 2017-01-18 14:54:48 +06:00
Ilya Konanykhin 56ca135e80 New top menu for desktop & mobile: correct color for logo 2017-01-18 00:33:42 +06:00
Ilya Konanykhin 327ec462d1 New top menu for desktop & mobile. 2017-01-18 00:27:16 +06:00
Jason Denney 8a8c82f5b7 Merge pull request #113 from ilya-konanykhin/master
Bootstrap 2 to 3
2016-12-27 12:45:30 -05:00
Ilya Konanykhin 551676a5b3 Cleanup: removed now obsolete jquery-date-format 2016-12-27 10:18:41 +06:00
Ilya Konanykhin 3713e0a450 BS3: tests are up-to-date now 2016-12-27 10:05:35 +06:00
Ilya Konanykhin b503ec6fbd BS3: cleanup 2016-12-27 09:53:47 +06:00
Ilya Konanykhin 9bb5190c4d Fix: dashboard buttons span the whole screen width 2016-12-27 09:42:47 +06:00
Ilya Konanykhin be97729eef Fix: do not hide link to Desktop View on mobiles 2016-12-27 09:28:58 +06:00
Ilya Konanykhin 8a6a2b6cd1 Cleanup: removed unused views and controller. 2016-12-25 01:08:57 +06:00
Ilya Konanykhin 9faa92a8e6 Fix: stick to the .ruby-version will ya? 2016-12-25 01:00:35 +06:00
Ilya Konanykhin 1880f91c15 BS3: removed bootstrap-timepicker in favor of universal bootstrap-datetimepicker. 2016-12-25 00:37:36 +06:00
Ilya Konanykhin 3fc812bd7a Fix: '', '' -> nil, nil in input generation. 2016-12-23 00:20:03 +06:00
Ilya Konanykhin 58bc6b1a83 BS3: add bike. 2016-12-22 23:58:48 +06:00
Ilya Konanykhin 1106e08098 BS3: show bike. 2016-12-22 23:47:11 +06:00
Ilya Konanykhin e8506f6fbc BS3: top menu. 2016-12-22 23:35:26 +06:00
Ilya Konanykhin 1774a6ad34 BS3: body padding. 2016-12-22 23:33:14 +06:00
Ilya Konanykhin 3c521eba15 BS3: js validation errors. 2016-12-22 23:23:48 +06:00
Ilya Konanykhin c58c039e3c BS3: edit task list. 2016-12-22 23:21:41 +06:00
Ilya Konanykhin 462cf8b5d5 Fix: timepicker error messages vertical align. 2016-12-22 23:08:13 +06:00
Ilya Konanykhin 37acc35921 BS3: timesheet. 2016-12-22 23:06:26 +06:00
Ilya Konanykhin 1b3d115161 BS3: replaced old datepicker with modern datetimepicker. 2016-12-22 22:17:15 +06:00
Ilya Konanykhin e775c97fd1 BS3: new time entry. 2016-12-22 21:45:21 +06:00
Ilya Konanykhin 2dd48c5b65 BS3: dashboard. 2016-12-22 20:57:42 +06:00
Ilya Konanykhin 7fbb40e9f9 Fix: any user sign in dev env is possible again 2016-12-22 20:02:42 +06:00
Ilya Konanykhin ec6b58680b BS3: devise & flash messaging. 2016-12-21 17:17:54 +06:00
Ilya Konanykhin 508a1d8d06 Bootstrap 3 setup. 2016-12-20 17:09:56 +06:00
Ilya Konanykhin 40a22e8b78 .powrc for Pow + RVM. 2016-12-19 18:47:34 +06:00
Ilya Konanykhin 10870d0961 Get rid of twitter-bootstrap-rails gem, update others accordingly. 2016-12-19 18:42:38 +06:00
Jason Denney 1191f4448f Add demo and alternate dockerfile branch links 2016-03-06 08:31:05 -08:00
Jason Denney ff3ec4f443 Update README.md 2016-02-26 18:52:59 -08:00
Jason Denney 98a51f51d2 Merge pull request #108 from spacemunkay/denney-docker
dockerize!
2016-02-26 18:09:56 -08:00
Jason Denney bc6802e5fb Adding famfamfam icons for docker w/ symlink 2016-02-26 18:00:48 -08:00
Jason Denney e48924a228 Fixed bug where rando 401 causes signin redirect 2016-02-26 17:55:24 -08:00
Jason Denney 1887840507 Fix port in dev guide 2016-02-26 16:19:29 -08:00
Jason Denney 0629972dca Update docs again 2016-02-26 16:11:22 -08:00
Jason Denney 6daff093eb Remove comment 2016-02-26 16:10:59 -08:00
Jason Denney 43faa79f60 Update the readme & docs 2016-02-26 16:10:47 -08:00
Jason Denney 9e282eda7c Need symlink for Docker to link extjs 2016-02-25 13:54:10 -08:00
Jason Denney 02fba62499 Fix bug in seed_bike factory 2016-02-25 13:36:18 -08:00
Jason Denney 4fe8865aca Dockerize! Startup with docker-compose up
Make sure to docker-compose build first
2016-02-25 13:36:01 -08: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
167 changed files with 4873 additions and 1270 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ tmp/
.powrc
.rbenv-version
.DS_Store
public/extjs
public/assets
public/images/icons
public/system
config/database.yml
.idea/
+15
View File
@@ -0,0 +1,15 @@
if [ -f "$rvm_path/scripts/rvm" ]; then
source "$rvm_path/scripts/rvm"
if [ -f ".rvmrc" ]; then
source ".rvmrc"
fi
if [ -f ".ruby-version" ]; then
rvm use `cat .ruby-version`
fi
if [ -f ".ruby-gemset" ]; then
rvm gemset use --create `cat .ruby-gemset`
fi
fi
-1
View File
@@ -1,2 +1 @@
--colour
-rturnip
+1
View File
@@ -0,0 +1 @@
velocipede
+1
View File
@@ -0,0 +1 @@
ruby-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
+31
View File
@@ -0,0 +1,31 @@
FROM ruby:2.1
# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y nodejs --no-install-recommends && \
apt-get install -y mysql-client postgresql-client sqlite3 --no-install-recommends && \
apt-get install -y zip unzip --no-install-recommends && \
rm -rf /var/lib/apt/lists/*
COPY Gemfile /usr/src/app/
COPY Gemfile.lock /usr/src/app/
RUN env NOKOGIRI_USE_SYSTEM_LIBRARIES=true bundle install
#COPY install_extjs.rb /usr/src/app/
#RUN /usr/src/app/install_extjs.rb
COPY extjs-4.1.1.zip /usr/lib/
RUN unzip -d /usr/lib /usr/lib/extjs-4.1.1.zip; \
mv /usr/lib/ext-4.1.1a /usr/lib/extjs
COPY install_famfamfam.rb /usr/src/app/
RUN /usr/src/app/install_famfamfam.rb
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
+22 -20
View File
@@ -1,6 +1,7 @@
source 'https://rubygems.org'
gem 'rails', '3.2.13'
gem 'rake', '< 11.0'
gem 'netzke-cancan'
gem 'netzke-core', '~>0.8.0'
@@ -12,38 +13,39 @@ gem 'bootstrap-will_paginate', '~> 0.0.6'
gem 'cancan'
gem 'decent_exposure', '~> 1.0.1'
gem 'devise', '~> 2.0.4'
gem 'haml-rails', '~> 0.3.4'
gem 'haml-rails'
gem 'jquery-rails', '~> 2.0'
gem 'pg'
gem 'pg', '~> 0.17.1'
gem 'will_paginate', '~> 3.0.3'
gem 'jbuilder', '~> 2.0.3'
gem 'paperclip', '~> 4.3'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'coffee-rails', '~> 3.2.1'
gem "twitter-bootstrap-rails", "~> 2.0.3"
# Assets
gem 'sass-rails', '~> 3.0'
gem 'coffee-rails', '~> 3.2.1'
gem 'bootstrap-sass', '~> 3.1.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer'
gem 'momentjs-rails', '>= 2.9.0'
gem 'bootstrap3-datetimepicker-rails', '~> 4.17.43'
gem 'uglifier', '>= 1.0.3'
end
gem 'uglifier', '>= 1.0.3'
group :development, :test do
gem 'rspec-rails', '~> 2.8.1'
gem 'rspec-rails', '~> 2.14.0'
gem 'factory_girl_rails', '~> 1.2'
gem 'pry', '~> 0.9.8'
gem 'faker', '~> 1.2.0'
gem 'colorize'
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 'spork'
gem 'capybara', '~> 2.2.1'
gem 'poltergeist', '~> 1.10.0'
gem 'database_cleaner', '~> 1.2.0'
gem 'launchy', '~> 2.4.2'
gem 'spork', '~> 0.9.2'
#guard dependency for Mac OS 10
gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
gem 'guard-spork'
gem 'guard-rspec'
gem 'guard-spork', '~> 1.5.1'
gem 'guard-rspec', '~> 4.2.6'
end
+145 -111
View File
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/spacemunkay/acts_as_loggable.git
revision: 49e264849ed9018445fb999cacfc55e1e28d7faa
revision: 8484dc1f7a58699705c5ffb593ebdf700a9f374a
specs:
acts_as_loggable (0.0.7)
activerecord (>= 3.0)
@@ -36,93 +36,109 @@ GEM
activesupport (3.2.13)
i18n (= 0.6.1)
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.4.0)
arel (3.0.3)
bcrypt (3.1.10)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
bootstrap-sass (3.1.1.1)
sass (~> 3.2)
bootstrap-will_paginate (0.0.10)
will_paginate
bootstrap3-datetimepicker-rails (4.17.43)
momentjs-rails (>= 2.8.1)
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)
childprocess (0.5.9)
ffi (~> 1.0, >= 1.0.11)
coderay (1.0.9)
climate_control (0.1.0)
cliver (0.3.2)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
coderay (1.1.1)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.6.2)
commonjs (0.2.6)
database_cleaner (0.9.1)
coffee-script-source (1.10.0)
colorize (0.8.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)
execjs (2.6.0)
factory_girl (2.6.4)
activesupport (>= 2.3.9)
factory_girl_rails (1.7.0)
factory_girl (~> 2.6.0)
railties (>= 3.0.0)
ffi (1.6.0)
formatador (0.2.4)
gherkin (2.11.6)
json (>= 1.7.6)
guard (1.7.0)
faker (1.2.0)
i18n (~> 0.5)
ffi (1.9.10)
formatador (0.2.5)
guard (2.13.0)
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, <= 4.0)
lumberjack (~> 1.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-rspec (4.2.10)
guard (~> 2.1)
rspec (>= 2.14, < 4.0)
guard-spork (1.5.1)
childprocess (>= 0.2.3)
guard (>= 1.1)
spork (>= 0.8.4)
haml (3.1.8)
haml-rails (0.3.5)
haml (4.0.7)
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)
hike (1.2.3)
i18n (0.6.1)
jbuilder (2.0.8)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
journey (1.0.4)
jquery-rails (2.2.1)
jquery-rails (2.3.0)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.7.7)
launchy (2.2.0)
json (1.8.3)
launchy (2.4.3)
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 (3.0.6)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9.7)
lumberjack (1.0.10)
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.9)
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
mini_portile2 (2.0.0)
momentjs-rails (2.15.1)
railties (>= 3.1)
multi_json (1.12.1)
nenv (0.3.0)
netzke-basepack (0.8.4)
netzke-core (~> 0.8.2)
netzke-cancan (0.8.2)
@@ -131,20 +147,34 @@ GEM
netzke-core (0.8.4)
execjs
uglifier
nokogiri (1.5.9)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
notiffany (0.0.8)
nenv (~> 0.1)
shellany (~> 0.0)
orm_adapter (0.0.7)
pg (0.15.1)
polyglot (0.3.3)
pry (0.9.12)
coderay (~> 1.0.5)
paperclip (4.3.7)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
cocaine (~> 0.5.5)
mime-types
mimemagic (= 0.3.0)
pg (0.17.1)
poltergeist (1.10.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
websocket-driver (>= 0.2.0)
polyglot (0.3.5)
pry (0.9.12.6)
coderay (~> 1.0)
method_source (~> 0.8)
slop (~> 3.4)
rack (1.4.5)
rack-cache (1.2)
rack (1.4.7)
rack-cache (1.6.1)
rack (>= 0.4)
rack-ssl (1.3.3)
rack-ssl (1.3.4)
rack
rack-test (0.6.2)
rack-test (0.6.3)
rack (>= 1.0)
rails (3.2.13)
actionmailer (= 3.2.13)
@@ -161,61 +191,58 @@ GEM
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.0.4)
rb-fsevent (0.9.3)
rake (10.5.0)
rb-fsevent (0.9.7)
rb-inotify (0.9.7)
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 (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.8)
rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6)
rspec-rails (2.14.2)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec (~> 2.8.0)
rubyzip (0.9.9)
selenium-webdriver (2.31.0)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip
websocket (~> 1.0.4)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
sass (3.4.22)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
shellany (0.0.1)
shoulda-matchers (1.0.0)
slop (3.4.4)
slop (3.6.0)
spork (0.9.2)
sprockets (2.2.2)
sprockets (2.2.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thor (0.18.1)
tilt (1.3.6)
treetop (1.4.12)
thor (0.19.4)
tilt (1.4.1)
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 (2.1.2)
tzinfo (0.3.46)
uglifier (2.7.2)
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.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
will_paginate (3.0.7)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
@@ -223,30 +250,37 @@ PLATFORMS
DEPENDENCIES
acts_as_loggable!
bootstrap-sass (~> 3.1.1)
bootstrap-will_paginate (~> 0.0.6)
bootstrap3-datetimepicker-rails (~> 4.17.43)
cancan
capybara (~> 1.1.2)
capybara (~> 2.2.1)
coffee-rails (~> 3.2.1)
database_cleaner
colorize
database_cleaner (~> 1.2.0)
decent_exposure (~> 1.0.1)
devise (~> 2.0.4)
factory_girl_rails (~> 1.2)
guard-rspec
guard-spork
haml-rails (~> 0.3.4)
faker (~> 1.2.0)
guard-rspec (~> 4.2.6)
guard-spork (~> 1.5.1)
haml-rails
jbuilder (~> 2.0.3)
jquery-rails (~> 2.0)
launchy
launchy (~> 2.4.2)
momentjs-rails (>= 2.9.0)
netzke-basepack (~> 0.8.0)
netzke-cancan
netzke-core (~> 0.8.0)
pg
paperclip (~> 4.3)
pg (~> 0.17.1)
poltergeist (~> 1.10.0)
pry (~> 0.9.8)
rails (= 3.2.13)
rb-fsevent
rspec-rails (~> 2.8.1)
rake (< 11.0)
rspec-rails (~> 2.14.0)
sass-rails (~> 3.0)
shoulda-matchers (~> 1.0.0)
spork
turnip (~> 0.3.0)
twitter-bootstrap-rails (~> 2.0.3)
spork (~> 0.9.2)
uglifier (>= 1.0.3)
will_paginate (~> 3.0.3)
+39 -23
View File
@@ -1,32 +1,48 @@
# 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 a live demo here: <http://bikeshed.wvcompletestreets.org/> Thanks @fspc for setting that up!
See an overview video of what the desktop view looks like and how it works here: https://www.youtube.com/watch?v=0-JjM6d9nK4.
## Overview/Instructions/Guides
See [guides](doc/guides.md)
# Developer Setup
1. use rvm
1. allow the .rvmrc file
1. `gem install bundler`
1. `bundle`
1. Install Postgres (Mac OSX instructions below)
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`
## Running with Docker (recommended)
1. These instructions haven't been tested, please provide corrections!
1. Install Docker Toolbox <https://www.docker.com/toolbox>
1. Make sure you have a machine running: `docker-machine start default && eval "$(docker-machine env default)"`
1. Execute `docker-compose build`
1. Execute `docker-compose run web rake db:setup`
1. Execute `docker-compose up`
1. If using Docker Toolbox, use `docker-machine ip default` to get the IP where the server is running.
1. Test the Rails server is running with by visiting `<INSERT IP>:8080` in your browser.
# 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. Copy over DB config: `cp config/database.yml.example config/database.yml`
1. Update config with your database (velocipede), user (velocipede), and password.
### Alternative Dockerfile
# Optional
Add icons
See <https://github.com/fspc/bikeshed> for an alternative docker setup.
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`
### Developer Workflow
The project directory should already be mounted inside the container, so you should be able to make live changes. However, since the project is running in the 'web' container, you need to prepend commands with `docker-compose run web`.
You'll likely want to add the following aliases:
```
alias dm='docker-machine'
alias dc='docker-compose'
alias dcrw='docker-compose run web'
```
That way your commands can be shortened to:
```
dcrw rake routes
dcrw rails console
dcrw rspec
```
If there's a better way, I'm all ears. Alternatively you could ssh into the machine with `dcrw bash`.
# License
Velocipede is released under the MIT license (http://opensource.org/licenses/MIT)
+6 -1
View File
@@ -12,4 +12,9 @@
//
//= require jquery
//= require jquery_ujs
//= require custom_netzke_helpers
//= require bootstrap
//= require utils
//= require moment
//= require bootstrap-datetimepicker
//= require jquery.form
+11
View File
@@ -0,0 +1,11 @@
$('.btn').button();
$("#add_bike_form").ajaxForm({
dataType: "json",
success: function(data){
window.location = data.bikes[0].id + "?add_bike=1";
},
error: function(data){
displayFormErrors(data.responseJSON);
}
})
@@ -1,150 +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);
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;
}
});
+69 -40
View File
@@ -2,50 +2,79 @@ $(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.");
var checkContacts = function() {
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 = "";
//see if any contact info exists
for( var index in contact_info_ids){
contact_vals += $("#"+contact_info_ids[index]).val();
}
return true;
if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){
}else{
if( submit_count == MAX_SUBMITS + 1 ){
alert("Fine.");
}
return true;
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.");
}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;
}
return false;
}
};
var checkValid = function() {
var errors = {};
var hasErrors = false;
["username", "first_name", "last_name"].forEach(function(requiredField) {
if(!$("#user_" + requiredField).val().trim()) {
errors["user_" + requiredField] = ["can't be blank"];
hasErrors = true;
}
});
if($("#user_password").val().length < 6) {
errors["user_password"] = ["is too short (minimum is 6 characters)"];
hasErrors = true;
}
if($("#user_password").val() != $("#user_password_confirmation").val()) {
errors["user_password_confirmation"] = ["confirmation doesn't match password"];
hasErrors = true;
}
displayFormErrors({errors: errors}, "#new_user");
return !hasErrors;
};
$("input[name=commit]").click(function(e){
return checkContacts() && checkValid();
});
});
+5 -4
View File
@@ -1,7 +1,7 @@
$(document).ready(function(){
$("#checkin_menu").show();
$("#checkin").click( function(e){
var username = $("#user_email").val();
var username = $("#user_username").val();
var password = $("#user_password").val();
$.ajax({
type: 'POST',
@@ -13,7 +13,7 @@ $(document).ready(function(){
complete: function() { },
success: function(data) {
alert("Checked IN!");
$("#user_email").val('');
$("#user_username").val('');
$("#user_password").val('');
},
error: function(data,textStatus) {
@@ -22,7 +22,7 @@ $(document).ready(function(){
})
});
$("#checkout").click( function(e){
var username = $("#user_email").val();
var username = $("#user_username").val();
var password = $("#user_password").val();
$.ajax({
type: 'POST',
@@ -34,7 +34,8 @@ $(document).ready(function(){
complete: function() { },
success: function(data) {
alert("Checked OUT!");
$("#user_email").val('');
$("#user_username").val('');
$("#user_password").val('');
},
error: function(data,textStatus) {
+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 && response.statusText === "Unauthorized") { window.location = '/users/sign_in'; }
}, this);
//Override default netzke time entry field
Ext.define('Ext.ux.form.field.DateTime', {
extend:'Ext.form.FieldContainer',
mixins:{
field:'Ext.form.field.Field'
},
alias: 'widget.xdatetime',
//configurables
combineErrors: true,
msgTarget: 'under',
layout: 'hbox',
readOnly: false,
dateFormat: 'Y-m-d',
dateSubmitFormat: 'Y-m-d',
timeFormat: 'H:i:s',
timeSubmitFormat: 'H:i:s',
dateConfig:{},
timeConfig:{},
// properties
dateValue: null, // Holds the actual date
dateField: null,
hourField: null,
minuteField: null,
ampmField: null,
initComponent: function(){
var me = this
,i = 0
,key
,tab;
me.items = me.items || [];
me.dateField = Ext.create('Ext.form.field.Date', Ext.apply({
format:me.dateFormat,
flex:1,
isFormField:false, //exclude from field query's
submitValue:false,
submitFormat: me.dateSubmitFormat,
readOnly: me.readOnly
}, me.dateConfig));
me.items.push(me.dateField);
me.hourField = Ext.create('Ext.form.field.Number', {
maxWidth: 25,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Hour cannot be blank.",
allowDecimals: false,
maxValue: 12,
minValue: 1,
maxLength: 2,
enforceMaxLength: 2,
hideTrigger: true,
submitValue:false,
flex:1,
fieldStyle: "text-align:right;",
isFormField:false, //exclude from field query's
});
me.items.push(me.hourField);
me.colon = Ext.create('Ext.draw.Text', {
text: ':',
padding: '3 3 0 3'
});
me.items.push(me.colon);
me.minuteField = Ext.create('Ext.form.field.Text', {
maxWidth: 30,
allowBlank: false,
allowOnlyWhitespace: false,
blankText: "Minutes cannot be blank.",
regex: /[012345]\d/,
maxLength: 2,
enforceMaxLength: 2,
submitValue:false,
flex:1,
isFormField:false, //exclude from field query's
});
me.items.push(me.minuteField);
me.ampmField = Ext.create('Ext.form.ComboBox', {
maxWidth: 45,
value: 'PM',
store: ['AM', 'PM'],
forceSelection: true,
flex:1,
editable: false
});
me.items.push(me.ampmField);
for (; i < me.items.length; i++) {
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
me.items[i].on('specialkey', function(field, event){
key = event.getKey();
tab = key == event.TAB;
if (tab && me.focussedItem == me.dateField) {
event.stopEvent();
me.timeField.focus();
return;
}
me.fireEvent('specialkey', field, event);
});
}
me.callParent();
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
this.inputEl = {
dom: document.createElement('div'),
swallowEvent:function(){}
};
me.initField();
},
focus:function(){
this.callParent(arguments);
this.dateField.focus();
},
onItemFocus:function(item){
if (this.blurTask){
this.blurTask.cancel();
}
this.focussedItem = item;
},
onItemBlur:function(item, e){
var me = this;
if (item != me.focussedItem){ return; }
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
me.blurTask = new Ext.util.DelayedTask(function(){
me.fireEvent('blur', me, e);
});
me.blurTask.delay(100);
},
getValue: function(){
var value = null
,date = this.dateField.getSubmitValue()
,time = null
,format;
hour = this.hourField.getValue();
minute = this.minuteField.getValue();
ampm = this.ampmField.getValue();
time = Ext.Date.parse(hour + " " + minute + " " + ampm, 'g i A');
time = Ext.Date.format(time, this.timeSubmitFormat);
if (date){
if (time){
format = this.getFormat();
value = Ext.Date.parse(date + ' ' + time, format);
} else {
value = this.dateField.getValue();
}
}
return value;
},
getSubmitValue: function(){
// var value = this.getValue();
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
var me = this
,format = me.getFormat()
,value = me.getValue();
return value ? Ext.Date.format(value, format) : null;
},
setValue: function(value){
if (Ext.isString(value) || Ext.isDate(value)){
if( Ext.isDate(value)){
dt = value;
}else{
dt = new Date(value);
value = Ext.Date.parse(value, this.getFormat()); //this.dateTimeFormat
}
this.dateField.setValue(value);
hour = Ext.Date.format(dt, 'g');
minute = Ext.Date.format(dt, 'i');
ampm = Ext.Date.format(dt, 'A');
this.hourField.setValue(hour);
this.minuteField.setRawValue(minute);
this.ampmField.setValue(ampm);
}
},
getFormat: function(){
value = (this.dateField.submitFormat || this.dateField.format) + " " + (this.timeSubmitFormat || this.timeFormat);
return value;
},
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
getSubmitData: function(){
var me = this
,data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getSubmitValue();
}
return data;
}
});
Ext.define('Ext.ux.form.field.ColorCombo', {
extend:'Ext.form.FieldContainer',
mixins:{
field:'Ext.form.field.Field'
},
alias: 'widget.xcolorcombo',
//configurables
combineErrors: true,
msgTarget: 'under',
layout: 'hbox',
readOnly: false,
// properties
colorValue: null,
/**
* @property dateField
* @type Ext.form.field.Date
*/
colorField: null,
initComponent: function(){
var me = this
,i = 0
,key
,tab;
me.items = me.items || [];
me.colorField = Ext.create('Ext.form.field.Trigger', {
flex:1,
isFormField:false, //exclude from field query's
submitValue:false,
readOnly: me.readOnly,
onTriggerClick: function() {
//show renders, so put first
me.picker.show();
me.picker.alignTo(me.colorField.inputEl);
}
});
me.items.push(me.colorField);
me.picker = Ext.create('Ext.picker.Color', {
renderTo: document.body,
floating: true,
hidden: true,
style: {
backgroundColor: "#fff"
},
listeners: {
scope:this,
select: function(field, value, opts){
me.setValue(value);
me.picker.hide();
}
}
});
me.items.push(me.picker);
for (; i < me.items.length; i++) {
me.items[i].on('focus', Ext.bind(me.onItemFocus, me));
me.items[i].on('blur', Ext.bind(me.onItemBlur, me));
me.items[i].on('specialkey', function(field, event){
key = event.getKey();
tab = key == event.TAB;
if (tab && me.focussedItem == me.dateField) {
event.stopEvent();
me.timeField.focus();
return;
}
me.fireEvent('specialkey', field, event);
});
}
me.callParent();
// this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not
this.inputEl = {
dom: document.createElement('div'),
swallowEvent:function(){}
};
me.initField();
},
focus:function(){
this.callParent(arguments);
this.colorField.focus();
var me = this;
},
onItemFocus:function(item){
if (this.blurTask){
this.blurTask.cancel();
}
this.focussedItem = item;
},
onItemBlur:function(item, e){
var me = this;
if (item != me.focussedItem){ return; }
// 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field
me.blurTask = new Ext.util.DelayedTask(function(){
me.picker.hide();
me.fireEvent('blur', me, e);
});
me.blurTask.delay(100);
},
getValue: function(){
var value = null
,color = this.colorField.getSubmitValue();
if (color){
value = this.colorField.getValue();
}
return value;
},
getSubmitValue: function(){
// var value = this.getValue();
// return value ? Ext.Date.format(value, this.dateTimeFormat) : null;
var me = this
,value = me.getValue();
return value;
},
setValue: function(value){
this.colorField.setValue(value);
},
// Bug? A field-mixin submits the data from getValue, not getSubmitValue
getSubmitData: function(){
var me = this
,data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getSubmitValue();
}
return data;
}
});
+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(){
var tasks = [];
$(".task_list_task").each(function(){
tasks.push({
id: parseInt($(this).data("id")),
done: $(this).is(":checked")
});
});
var 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);
}
});
});
+83
View File
@@ -0,0 +1,83 @@
$(document).ready(function () {
var $date_input = $("#date_id");
$date_input.datetimepicker({format: $date_input.data("format")});
var $start_time_input = $("#start_time_id");
var $end_time_input = $("#end_time_id");
[$start_time_input, $end_time_input].forEach(function($x) {
$x.datetimepicker({format: $x.data("format"), stepping: 15});
})
$("#add_time_entry_submit").click(function () {
var date = $date_input.val();
var start_date = new Date(date + " " + $start_time_input.val());
var end_date = new Date(date + " " + $end_time_input.val());
var forward = $("#add_time_entry_submit").data("forward");
// If a bike is selected, forward to the bike
// checklist.
var 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.
var json_data = {
time_entries: [{
start_date: moment(start_date).format("DD-MM-YYYY h:mm A"),
end_date: moment(end_date).format("DD-MM-YYYY h: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 () {
var row = $(this).closest("tr");
var entry_id = row.data("id");
var start_date = row.data("start_date");
var duration = row.data("duration");
var description = row.data("description");
$("#work_entry_start_date").text(start_date);
$("#work_entry_duration").text(duration);
$("#work_entry_description").text(description);
$("#confirmation_delete").data("entry_id", entry_id);
});
$("#confirmation_delete").click(function () {
var entry_id = $(this).data("entry_id");
var url = $("#confirmation_delete").data("url-template").replace(/__ID__/, entry_id);
$.ajax({
url: url,
type: "delete",
contentType: 'application/json',
success: function (data, status, xhr) {
location.reload();
},
error: function (data, status) {
displayFormErrors(data.responseJSON);
}
});
});
});
+10
View File
@@ -0,0 +1,10 @@
function displayFormErrors(data, form){
if(form){
$(form).find(".form-group.has-error").removeClass("has-error").find(".help-block").html("");
}
if(data.errors != undefined ){
$.each(data.errors, function(field, errorMsg) {
$("#"+field).closest(".form-group").addClass("has-error").find(".help-block").html(errorMsg.join(", "));
});
}
}
-13
View File
@@ -1,13 +0,0 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require_tree .
*/
+3
View File
@@ -0,0 +1,3 @@
@import "bootstrap";
@import "bootstrap-datetimepicker";
@import "frontend";
-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;
+69
View File
@@ -0,0 +1,69 @@
body {
padding-top: $grid-gutter-width / 2;
}
.x-boundlist-item {
white-space: nowrap;
}
fieldset, .fieldset {
margin-top: $line-height-computed;
margin-bottom: $line-height-computed;
}
.timepickers {
.form-group {
vertical-align: top;
&.dash {
line-height: 2.4em;
}
}
.bootstrap-datetimepicker-widget.dropdown-menu {
width: auto;
}
.bootstrap-datetimepicker-widget .timepicker-hour,
.bootstrap-datetimepicker-widget .timepicker-minute,
.bootstrap-datetimepicker-widget .timepicker-second {
width: 40px;
}
.bootstrap-datetimepicker-widget table td {
height: 40px;
line-height: 40px;
width: 40px;
padding: 0 !important;
}
.bootstrap-datetimepicker-widget table td.separator {
width: 10px;
}
.bootstrap-datetimepicker-widget table td span {
width: 40px;
height: 40px;
line-height: 40px;
margin: 0;
}
.bootstrap-datetimepicker-widget a[data-action] {
padding: 0;
color: #000;
}
}
.bike-color {
display: inline-block;
padding-left: 6px;
padding-right: 6px;
}
.navbar-default {
.navbar-text {
width: 100%;
font-size: 150%;
margin-top: 10px;
margin-bottom: 10px;
}
}
+1 -5
View File
@@ -19,11 +19,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel
#all users
# (had to use hash for borders to get the title to display properly)
@@app_tab_panel_items = [ :bikes_border,
{ layout: :fit,
wrappedComponent: :brands_and_models_border,
title: "Brands/Models"}
]
@@app_tab_panel_items = [ :bikes_border ]
#for users only
if not controller.current_user.role?(:admin)
@@ -19,6 +19,6 @@
});
},
onChangeAccountInfo: function(){
window.location.href="users/edit";
window.location.href="/users/edit";
}
}
+1
View File
@@ -3,6 +3,7 @@ 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
+4 -3
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,9 +19,9 @@ class BikeLogs < Netzke::Basepack::Grid
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :end_date, :hidden => true, :default_value => Time.now.to_formatted_s(:db) },
{ :name => :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 => :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}"
@@ -47,7 +48,7 @@ class BikeLogs < Netzke::Basepack::Grid
{ :name => :start_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :bike_action__action, :field_label => 'Action'}
{ :name => :bike_action__action, :field_label => 'Action', :min_chars => 1 }
]
end
@@ -1,10 +0,0 @@
class BikeLogsAndTasksAccordian < Netzke::Basepack::Accordion
component :bike_logs
component :tasks
def configure(c)
c.prevent_header = true
c.items = [ :bike_logs, :tasks ]
super
end
end
+12
View File
@@ -0,0 +1,12 @@
class BikeLowerTabs < Netzke::Basepack::TabPanel
component :bike_logs
component :tasks
component :brands_and_models_border
def configure(c)
c.prevent_header = true
c.items = [ :bike_logs, :tasks,
{netzke_component: :brands_and_models_border, title: "Brands and Models"} ]
super
end
end
+1
View File
@@ -4,6 +4,7 @@ class BikeModels < Netzke::Basepack::Grid
c.model = "BikeModel"
c.title = "Models"
c.force_fit = true
c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:bike_brand_id => session[:selected_bike_brand_id]);}
c.strong_default_attrs = {
+17 -11
View File
@@ -2,11 +2,12 @@ class Bikes < Netzke::Basepack::Grid
def configure(c)
super
c.model = "Bike"
c.force_fit = true
# columns with :id set, have :min_chars set in init_component
# See: http://stackoverflow.com/questions/17738962/netzke-grid-filtering
c.columns = [
{ :name => :shop_id, :text => 'Shop ID'},
{ :name => :shop_id, :text => 'Shop ID', :default_value => Bike.last.id.to_i + 1},
:serial_number,
{ :id => :bike_brand__brand, :name => :bike_brand__brand, :text => 'Brand'},
{ :name => :model, :text => 'Model',
@@ -19,20 +20,25 @@ class Bikes < Netzke::Basepack::Grid
}
},
#needs to have type :action or else won't work in grid, because... netzke
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block},
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style' },
{ :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block,
:default_value => '000000'},
{ :id => :bike_style__style, :name => :bike_style__style, :text => 'Style', :default_value => BikeStyle.first.id},
{ :name => :seat_tube_height, :text => 'Seat Tube (in)'},
{ :name => :top_tube_length, :text => 'Top Tube (in)'},
{ :name => :wheel_size, :text => 'Wheel Size (in)'},
{ :name => :bike_wheel_size__display_string, :text => 'Wheel Size'},
:value,
{ :id => :bike_condition__condition, :name => :bike_condition__condition, :text => 'Condition'},
{ :id => :bike_purpose__purpose, :name => :bike_purpose__purpose, :text => 'Purpose'},
{ :id => :bike_condition__condition, :name => :bike_condition__condition, :text => 'Condition', :default_value => BikeCondition.first.id},
{ :id => :bike_purpose__purpose, :name => :bike_purpose__purpose, :text => 'Purpose', :default_value => BikePurpose.first.id},
{ :name => :owner, :getter => lambda { |rec|
user = rec.owner
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
}
]
# Default the sorting to ASC on shop_id
c.data_store.sorters = [{ property: 'shop_id', direction: 'ASC' }]
@bike = Bike.all
c.prohibit_update = true if cannot? :update, @bike
c.prohibit_create = true if cannot? :create, @bike
@@ -41,18 +47,18 @@ class Bikes < Netzke::Basepack::Grid
def default_fields_for_forms
# :field_label MUST be defined in order for search to work
[
{ :name => :bike_brand__brand, :field_label => 'Brand' },
{ :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' },
{ :name => :bike_style__style, :field_label => 'Style', :min_chars => 1},
{ :name => :seat_tube_height, :field_label => 'Seat Tube (in)'},
{ :name => :top_tube_length, :field_label => 'Top Tube (in)'},
{ :name => :wheel_size, :field_label => 'Wheel Size (in)'},
{ :name => :bike_wheel_size__display_string, :field_label => 'Wheel Size'},
{ :name => :value, :field_label => 'Value'},
{ :name => :bike_condition__condition, :field_label => 'Condition'},
{ :name => :bike_purpose__purpose, :field_label => 'Purpose'}
{ :name => :bike_condition__condition, :field_label => 'Condition', :min_chars => 1},
{ :name => :bike_purpose__purpose, :field_label => 'Purpose', :min_chars => 1}
]
end
#override with nil to remove actions
@@ -3,7 +3,6 @@
// calling superclass's initComponent
this.callParent();
// something...
//due to Netzke bug, :min_chars attribute doesn't work
var min_char_columns = [
"bike_brand__brand",
+2 -2
View File
@@ -2,7 +2,7 @@ class BikesBorder < Netzke::Base
# Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence
component :bikes
component :bike_logs_and_tasks_accordian
component :bike_lower_tabs
def configure(c)
super
@@ -10,7 +10,7 @@ class BikesBorder < Netzke::Base
c.title = "Bikes"
c.items = [
{ netzke_component: :bikes, region: :center, split: true },
{ netzke_component: :bike_logs_and_tasks_accordian, region: :south, height: 300, split: true}
{ netzke_component: :bike_lower_tabs, region: :south, height: 300, split: true}
]
end
@@ -5,14 +5,30 @@
// setting the 'rowclick' event
var view = this.getComponent('bikes').getView();
//disable until a bike is clicked
var bike_logs_comp = this.queryById('bike_logs');
if( bike_logs_comp){
bike_logs_comp.disable();
}
var bike_tasks_comp = this.queryById('tasks');
if( bike_tasks_comp ){
bike_tasks_comp.disable();
}
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectBike({bike_id: record.get('id')});
if( this.queryById('bike_logs') ){
this.queryById('bike_logs').getStore().load();
// query for these components again, can change if not visible because of accordian
var bike_logs_comp = this.queryById('bike_logs');
var bike_tasks_comp = this.queryById('tasks');
if( bike_logs_comp ){
bike_logs_comp.getStore().load();
bike_logs_comp.enable();
}
if( this.queryById('tasks') ){
this.queryById('tasks').getStore().load();
if( bike_tasks_comp ){
bike_tasks_comp.getStore().load();
bike_tasks_comp.enable();
}
}, this);
}
+1
View File
@@ -4,6 +4,7 @@ class CheckIns < Netzke::Basepack::Grid
super
c.header = false
c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction).
where(:loggable_type => "User").
where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")).
+1
View File
@@ -1,6 +1,7 @@
class Customers < Netzke::Basepack::Grid
def configure(c)
c.model = "Customer"
c.force_fit = true
end
#override with nil to remove actions
@@ -4,7 +4,6 @@
this.callParent();
this.getView().on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
console.log("user: " + record.get('id') );
this.selectCustomer({customer_id: record.get('id'), customer_type: 'Customer'});
}, this);
}
+1
View File
@@ -4,6 +4,7 @@ class Logs < Netzke::Basepack::Grid
super
c.header = false
c.model = "ActsAsLoggable::Log"
c.force_fit = true
c.columns = [
:loggable_id,
:loggable_type,
+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
+3 -2
View File
@@ -5,6 +5,7 @@ class TransactionLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log"
c.title = "Transaction Payments"
c.force_fit = true
c.data_store = {auto_load: false}
c.scope = lambda { |rel| rel.where(:loggable_type => 'Transaction',:loggable_id => session[:selected_transaction_id]);}
c.strong_default_attrs = {
@@ -20,7 +21,7 @@ class TransactionLogs < Netzke::Basepack::Grid
c.columns = [
{ :name => :start_date, :format => "g:ia - D, M j - Y", :width => 165, :default_value => Time.now.to_formatted_s(:db), :text => 'Date' },
{ :name => :description, :text => "Amount"} ,
{ :name => :transaction_action__action, :text => 'Method'},
{ :name => :transaction_action__action, :text => 'Method', :default_value => ::ActsAsLoggable::TransactionAction.first.id},
{ :name => :logged_by, :getter => lambda{ |rec|
user = User.find_by_id(rec.logger_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
@@ -49,7 +50,7 @@ class TransactionLogs < Netzke::Basepack::Grid
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Payment for:", :value => "#{item.to_s}"},
{ :name => :description, :xtype => 'numberfield', :field_label => 'Amount'},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :transaction_action__action, :field_label => 'Payment Method'}
{ :name => :transaction_action__action, :field_label => 'Payment Method', :min_chars => 1}
]
end
+1
View File
@@ -2,6 +2,7 @@ class Transactions < Netzke::Basepack::Grid
def configure(c)
super
c.model = "Transaction"
c.force_fit = true
c.strong_default_attrs = {
:vendor_id => controller.current_user.id,
:customer_id => session[:selected_customer_id],
+2 -2
View File
@@ -4,7 +4,7 @@ class TransactionsBorder < Netzke::Base
component :transactions
component :transaction_logs
#users and customers components are required for the transactions form
component :users_and_customers_accordian
component :users_and_customers_lower_tabs
def configure(c)
super
@@ -13,7 +13,7 @@ class TransactionsBorder < Netzke::Base
c.items = [
{ netzke_component: :transactions, region: :center, height: 300, split: true },
{ netzke_component: :transaction_logs, region: :east, width: 300, split: true },
{ netzke_component: :users_and_customers_accordian, region: :south, height: 300, split: true }
{ netzke_component: :users_and_customers_lower_tabs, region: :south, height: 300, split: true }
]
end
@@ -5,10 +5,19 @@
// setting the 'rowclick' event
var view = this.getComponent('transactions').getView();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').disable();
}
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectTransaction({transaction_id: record.get('id')});
this.getComponent('transaction_logs').getStore().load();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').enable();
}
}, this);
}
}
@@ -0,0 +1,13 @@
class UserAndProfilesLowerTabs < Netzke::Basepack::TabPanel
component :user_profiles
component :user_logs
component :user_stats
def configure(c)
c.prevent_header = true
c.items = [ :user_logs,
{ netzke_component: :user_profiles, title: "User Profiles" },
{ netzke_component: :user_stats, title: "User Stats" }]
super
end
end
+19 -8
View File
@@ -1,4 +1,5 @@
class UserLogs < Netzke::Basepack::Grid
def configure(c)
super
@@ -33,6 +34,7 @@ class UserLogs < Netzke::Basepack::Grid
c.model = "ActsAsLoggable::Log"
c.title = "User Timesheet"
c.force_fit = true
c.data_store = user_log_data_store
c.scope = user_log_scope
c.strong_default_attrs = user_log_strong_default_attrs
@@ -51,10 +53,23 @@ class UserLogs < Netzke::Basepack::Grid
]
end
# Overriding the defautl add in form
def preconfigure_record_window(c)
super
c.form_config.klass = UserLogsAddItem
def default_fields_for_forms
#figure out a better way to do this
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
[
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Log for:", :value => "#{current_user.to_s}"},
{ :name => :start_date},
{ :name => :end_date},
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action', :min_chars => 1},
{ :name => :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}
]
}
]
end
#override with nil to remove actions
@@ -62,8 +77,4 @@ class UserLogs < Netzke::Basepack::Grid
[ :apply, :add_in_form, :search ]
end
js_configure do |c|
c.mixin :init_component
end
end
@@ -2,48 +2,12 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
// TESTTING
//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;
// });
var view = this.getView();
view.on('itemclick', function(view, record){
console.log('clicking ');
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
// console.log('Old record', record);
}, this);
view.on('itemupdate', function(record, index, node){
console.log('Updating item');
// if('end_date' in record.data.modified){
// console.log('start', record.data.start_date);
// console.log('end', record.data.end_date);
// }else if('start_date' in record.data.modified){
// }
}, this);
// var hoursInput = Ext.getCmp('user_logs_add_form_hours-inputEl');
// console.log(hoursInput);
// hoursInput.on('change', function(field, newValue, oldValue){
// console.log('Hours changing...', newValue);
// });
},
addNewItem: function(e){
e.fireEvent('click', {});
console.log(e);
console.log('Adding new item');
"user_action__purpose"]
Ext.each(min_char_columns, function(column, index) {
Ext.ComponentManager.get(column).editor.minChars = 1;
});
}
}
-31
View File
@@ -1,31 +0,0 @@
# The User Log Add Item Form
class UserLogsAddItem < Netzke::Basepack::Form
def configure(c)
super
c.model = 'ActsAsLoggable::Log'
#figure out a better way to do this
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
c.items = [
{ :no_binding => true, :xtype => 'displayfield', :fieldLabel => "Log for:", :value => "#{current_user.to_s}"},
{ :id => :user_logs_add_form_start, :name => :start_date},
{ :id => :user_logs_add_form_time, :no_binding => true, :name => :time, :xtype => 'field', :fieldLabel => "Time:", :value => 0 },
{ :id => :user_logs_add_form_units, :xtype => 'combo', :no_binding => true, :name => :units, :fieldLabel => 'Units', :store => ['Mins', 'Hrs'], :value => 'Mins' },
{ :id => :user_logs_add_form_end, :name => :end_date, :hidden => true },
{ :name => :description},
#had to hack acts_as_loggable/log.rb to get this to work
{ :name => :user_action__action, :field_label => 'Action'},
{ :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}
]
}
]
end
js_configure do |c|
c.mixin :init_component
end
end
@@ -1,76 +0,0 @@
{
initComponent: function(){
// calling superclass's initComponent
this.callParent();
var panel = this;
var theForm = this.getForm();
var startInput = Ext.getCmp('user_logs_add_form_start');
var timeInput = Ext.getCmp('user_logs_add_form_time');
var unitsInput = Ext.getCmp('user_logs_add_form_units');
var endInput = Ext.getCmp('user_logs_add_form_end');
var startdate = startInput.items.items[0];
var starttime = startInput.items.items[1];
var enddate = endInput.items.items[0];
var endtime = endInput.items.items[1];
startdate.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
starttime.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
timeInput.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
unitsInput.on('change', function(e){
var hours = timeInput.getValue();
var startTime = starttime.getValue();
if('Mins' === unitsInput.getValue()){
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.MINUTE, parseInt(hours));
}else{
var calculatedEndTime = Ext.Date.add(new Date(startTime), Ext.Date.HOUR, parseInt(hours));
}
endtime.setValue(calculatedEndTime);
enddate.setValue(calculatedEndTime);
});
}
}
+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
+3 -2
View File
@@ -2,6 +2,7 @@ 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}]
@@ -10,9 +11,9 @@ class UserRoleJoins < Netzke::Basepack::Grid
{ :name => :name, :getter => lambda{ |rec|
user = User.find_by_id(rec.user_id)
user.nil? ? "" : "#{user.first_name} #{user.last_name}"
}
},
},
{ :name => :role__role, :text => "Role"},
{ :name => :role__role, :text => "Role", :default_value => Role.exists? ? Role.first.id : nil},
:created_at,
:updated_at,
:ends ]
+1 -1
View File
@@ -40,7 +40,7 @@ class UserStats < Netzke::Base
private
def user
controller.current_user
User.find_by_id(session[:selected_user_id]) || controller.current_user
end
end
+1
View File
@@ -5,6 +5,7 @@ class UserTransactions < Netzke::Basepack::Grid
c.model = "Transaction"
c.title = "Transactions"
c.force_fit = true
c.scope = lambda { |rel| rel.where(:customer_id => controller.current_user.id, :customer_type => 'User');}
c.data_store = { auto_load: true }
c.columns = [
@@ -5,10 +5,18 @@
// setting the 'rowclick' event
var view = this.getComponent('user_transactions').getView();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').disable();
}
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectTransaction({transaction_id: record.get('id')});
this.getComponent('transaction_logs').getStore().load();
if( this.queryById('transaction_logs')){
this.queryById('transaction_logs').enable();
}
}, this);
}
}
+2 -1
View File
@@ -12,13 +12,14 @@ class Users < Netzke::Basepack::Grid
super
c.header = false
c.model = "User"
c.force_fit = true
c.columns = [
{ :name => :username, :read_only => true },
:first_name,
:last_name,
:email,
{ :id => :bike__shop_id, :name => :bike__shop_id}
{ :text => "Bike - Shop ID", :id => :bike__shop_id, :name => :bike__shop_id, :default_value => Bike.exists? ? Bike.first.id : nil }
]
c.columns << :reset if can? :manage, User
@@ -33,7 +33,7 @@
Ext.Msg.alert("Success", "New Password: "+data.password);
},
error: function(data,textStatus) {
Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]);
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
+2 -4
View File
@@ -2,16 +2,14 @@ class UsersAndProfilesBorder < Netzke::Base
# Remember regions collapse state and size
include Netzke::Basepack::ItemPersistence
component :users
component :user_profiles
component :user_logs
component :user_and_profiles_lower_tabs
def configure(c)
super
c.header = false
c.items = [
{ netzke_component: :users, header: "Users", region: :center, width: 350, split: true },
{ netzke_component: :user_profiles, region: :south, height: 150, split: true},
{ netzke_component: :user_logs, region: :east, split: true}
{ netzke_component: :user_and_profiles_lower_tabs, region: :south, height: 300, split: true}
]
end
@@ -2,18 +2,48 @@
initComponent: function(){
// calling superclass's initComponent
this.callParent();
var stats = this.getComponent('user_stats');
var stats = this.queryById('user_stats');
if (stats != undefined){
stats.updateStats();
}
if( this.queryById('user_profiles')){
this.queryById('user_profiles').disable();
}
if( this.queryById('user_logs')){
this.queryById('user_logs').disable();
//update user stats
var store = this.queryById('user_logs').getStore()
store.on('load', function (store, records, operation, success){
if( this.queryById('user_stats') ){
this.queryById('user_stats').updateStats();
}
}, this);
}
if( this.queryById('user_stats') ){
this.queryById('user_stats').disable();
}
// setting the 'rowclick' event
var view = this.getComponent('users').getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectUser({user_id: record.get('id')});
this.getComponent('user_profiles').getStore().load();
this.getComponent('user_logs').getStore().load();
if( this.queryById('user_profiles')){
this.queryById('user_profiles').getStore().load();
this.queryById('user_profiles').enable();
}
if( this.queryById('user_logs')){
this.queryById('user_logs').getStore().load();
this.queryById('user_logs').enable();
}
if( this.queryById('user_stats') ){
this.queryById('user_stats').updateStats();
this.queryById('user_stats').enable();
}
}, this);
}
}
+2 -2
View File
@@ -6,12 +6,12 @@ class Api::V1::BaseController < ActionController::Base
private
def authenticate_user
if params[:username]
user = User.find_for_database_authentication( :email => 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 => 403 and return
render :json => {:error => msg }, :status => 401 and return
end
else
authenticate_user!
@@ -0,0 +1,32 @@
class Api::V1::BikesController < Api::V1::BaseController
CANNOT_MANAGE = "You do not have permission to manage bikes."
EXPECTED_BIKE = "Expected bike in submitted data"
NOT_FOUND = "The bike could not be found."
before_filter :check_bike_permission, except: :show
def create
if params[:bikes] && bike = params[:bikes].first
@bike = Bike.new(bike)
if !@bike.save
render json: { errors: @bike.errors }, status: 422 and return
end
else
render json: { errors: [EXPECTED_BIKE]}, status: 422 and return
end
end
def show
@bike = Bike.find_by_id(params[:id])
if @bike.nil?
render json: { errors: [NOT_FOUND] }, status: 404 and return
end
end
private
def check_bike_permission
if cannot? :manage, Bike
render json: { errors: [CANNOT_MANAGE]}, status: 403 and return
end
end
end
@@ -0,0 +1,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
+11 -4
View File
@@ -1,21 +1,27 @@
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 => { "error" => "User not found"}, :status => 404 and return if user.nil?
render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_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,8]
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 => { "error" => "You do not have the permission"}, :status => 403 and return
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?
@@ -25,4 +31,5 @@ class Api::V1::UsersController < Api::V1::BaseController
render :nothing => true, :status => 204 and return
end
end
=end
end
+17
View File
@@ -0,0 +1,17 @@
class BikesController < AuthenticatedController
def new
@bike = Bike.new bike_purpose_id: 1
@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 = @bike.task_list
@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
-34
View File
@@ -1,34 +0,0 @@
class UsersController < AuthenticatedController
expose(:user)
expose(:users) { User.order('id').paginate(:page => params[:page]) }
def index
end
def show
end
def new
end
def create
if user.save
redirect_to user
else
render :new
end
end
def update
if user.save
redirect_to user
else
render :edit
end
end
def destroy
user.destroy
redirect_to bikes_url
end
end
+24
View File
@@ -7,4 +7,28 @@ module ApplicationHelper
end
link_to(name, '#', id: "add_#{association.to_s.singularize}" , class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
def top_menu(right = nil)
content_tag :nav, class: 'navbar navbar-default' do
content_tag :div, class: 'container-fluid' do
content_tag :div, class: 'row' do
[
content_tag(:div, link_to_dashboard, class: 'col-xs-3'),
content_tag(:div, header_logo, class: 'col-xs-6'),
content_tag(:div, class: 'col-xs-3') { content_tag :div, right, class: 'pull-right' },
].join.html_safe
end
end
end
end
def link_to_dashboard
link_to root_path, class: 'navbar-brand', title: 'Back to dashboard' do
content_tag :i, '', class: "glyphicon glyphicon-home"
end
end
def header_logo
content_tag :div, 'Velocipede', class: 'navbar-text text-center'
end
end
+7 -10
View File
@@ -1,21 +1,18 @@
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?
return '' if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:p, msg, :class => "alert") }.join
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count,
:resource => resource.class.model_name.human.downcase)
html = <<-HTML
<p>#{sentence}</p>
#{messages}
<div class="alert alert-danger">#{sentence}
<ul>
#{messages.join}
</ul>
</div>
HTML
html.html_safe
+1 -1
View File
@@ -23,7 +23,7 @@ class Ability
def user
can :read, :all
can :manage, Bike, :id => @current_user.bike_id unless @current_user.bike.nil?
can :manage, @current_user.bike unless @current_user.bike.nil?
can :manage, ::ActsAsLoggable::Log, :loggable_type => "Bike", :loggable_id => @current_user.bike_id
can :manage, ::ActsAsLoggable::Log, :loggable_type => "User", :loggable_id => @current_user.id
end
+20 -13
View File
@@ -1,31 +1,34 @@
class Bike < ActiveRecord::Base
acts_as_loggable
attr_accessible :shop_id, :serial_number, :bike_brand_id, :model, :color, :bike_style_id, :seat_tube_height,
:top_tube_length, :wheel_size, :value, :bike_condition_id, :bike_purpose_id
attr_accessible :shop_id, :serial_number, :bike_brand_id, :bike_model_id, :model, :color, :bike_style_id,
:seat_tube_height, :top_tube_length, :bike_wheel_size_id, :value, :bike_condition_id, :bike_purpose_id, :photo
has_many :transactions
has_one :owner, :class_name => 'User'
has_one :task_list, :as => :item, :dependent => :destroy
belongs_to :bike_brand
belongs_to :bike_model
belongs_to :bike_style
belongs_to :bike_condition
belongs_to :bike_purpose
belongs_to :bike_wheel_size
default_scope order('shop_id ASC')
has_attached_file :photo, :styles => {:thumb => '100x100>'}
validates :shop_id, :presence => true, :uniqueness => true, :length => { :minimum => 3 }
validates :shop_id, :presence => true, :uniqueness => true, :numericality => { :only_integer => true }
validates :serial_number, :length => { :minimum => 3 }
validates :model, :length => { :maximum => 50 }
validates :bike_brand_id, :presence => true
validates :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_purpose_id, :presence => true
validates :bike_brand_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid brand" }
#validates :color, :presence => true
validates :bike_style_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid style" }
validates :seat_tube_height, :presence => true, :numericality => true
validates :bike_wheel_size_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid wheel size" }
validates :bike_condition_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid condition" }
validates :bike_purpose_id, :presence => true, :numericality => { greater_than: 0, message: "is not a valid purpose" }
validates_attachment :photo, :content_type => {:content_type => %w{ image/jpeg image/gif image/png }},
:file_name => {:matches => [/png\Z/, /jpe?g\Z/, /gif\Z/]}
self.per_page = 15
@@ -47,6 +50,10 @@ class Bike < ActiveRecord::Base
self.bike_purpose
end
def wheel_size
self.bike_wheel_size
end
def to_s
"#{brand} - #{model} - #{style}"
end
+160
View File
@@ -0,0 +1,160 @@
require 'csv'
# Imports data from CSV file into the bikes database.
class BikeCsvImporter
include BikeCsvImporter::Cache
include BikeCsvImporter::Cleaner
include BikeCsvImporter::BikeAttrs
include BikeCsvImporter::Logs
attr_reader :file
# Default constructor
#
# @param [String] file Path to the CSV file
def initialize(file)
@file = file
end
# Runs the import. Will print out progress to stdout
#
# @param [Boolean] dry_run If true, does not save data, only shows the progress of validation
def run(dry_run)
imported_count, skipped_count = 0, 0
puts "Performing a #{dry_run ? 'DRY RUN' : 'LIVE RUN'} of import"
fetch do |bike_hash|
bike = new_bike bike_hash
check_method = dry_run ? :valid? : :save
if bike.try check_method
puts "Imported #{bike.shop_id}: #{bike}".green
logs = new_logs_entries bike, bike_hash
logs.each do |log|
if log.send check_method
puts "\tLog entry created: #{log.inspect}".green
else
puts "\tLog entry creation failed: #{log.errors.full_messages.join '; '}".red
end
end
imported_count += 1
else
puts "Skipped #{bike.try(:shop_id) || bike_hash.values.first}: #{bike.try(:errors).try(:full_messages).try :join, '; '}".red
skipped_count += 1
end
end
puts "#{imported_count} bikes imported, #{skipped_count} bikes skipped, total of #{imported_count + skipped_count} rows in the CSV"
end
# Analyzes and prints out the input CSV file values
#
# @param [Array<Strong>] fields If passed, analyze only the given fields (names are down cased)
def analyze(fields = [])
puts "Analyzing CSV values frequency for #{fields.any? ? fields.join(', ') + ' field' : 'all fields'}"
fields = fields.map &:downcase
grouped = {}
fetch do |bike_hash|
bike_hash.each do |key, value|
next if fields.any? && !fields.include?(key)
grouped[key] ||= {}
grouped[key][value] ||= 0
grouped[key][value] += 1
end
end
grouped.each do |field, values|
puts "#{field}:"
values.each do |value, count|
puts "\t#{value.inspect}: #{count}"
end
puts "\tTotal of #{values.count} distinct values"
end
end
# Imports new brands from CSV file (field 'make'). Will print out progress to stdout
#
# @param [Boolean] dry_run If true, does not save data, only shows the progress of validation
def brands(dry_run)
created_count, skipped_count = 0, 0
puts "Performing a #{dry_run ? 'DRY RUN' : 'LIVE RUN'} of brands import"
fetch do |bike_hash|
make = clean_value bike_hash['make']
brand = bike_attr_bike_brand make, true
check_method = dry_run ? :valid? : :save
if brand.try :persisted?
puts "Skipped already existing brand #{brand.brand}"
skipped_count +=1
elsif brand.try check_method
puts "Created brand #{brand.brand}".green
created_count += 1
else
puts "Skipped #{brand.try(:brand) || make}: #{brand.try(:errors).try(:full_messages).try(:join, '; ') || 'object not created'}".red
skipped_count += 1
end
end
puts "#{created_count} brand created, #{skipped_count} brand skipped, total of #{created_count + skipped_count} rows in the CSV"
end
private
# Parses the CSV header & rows, yielding a block for each row (except the header)
# Header is down cased!
#
# @param [Proc] &block The block to yield to
def fetch
CSV.foreach(file).each_with_index do |row, i|
if i.zero?
parse_header row
else
yield parse_bike(row)
end
end
end
# Parses & stores the input header, down casing by the way
#
# @param [Array<String>] row
def parse_header(row)
@header = row.map(&:downcase)
end
# Parses the input row into a hash with keys from the header, @see #parse_header
#
# @param [Array<String>] row
#
# @return [Hash]
def parse_bike(row)
@header.zip(row).to_h
end
# Constructs a new Bike instance from the given hash from a CSV row
#
# @param [Hash] bike_hash
#
# @return [Bike]
def new_bike(bike_hash)
Bike.new bike_attrs(bike_hash)
end
# Constructs new Bike Log Entries instances from the given hash from a CSV row
#
# @param [Bike] bike The Bike instance to construct log entries for
# @param [Hash] bike_hash The input hash from a CSV row
#
# @return [Array<ActsAsLoggable::Log>]
def new_logs_entries(bike, bike_hash)
%i{ acquired comment gone }.map { |x| send :"log_entry_#{x}", bike, bike_hash }.compact
end
end
@@ -0,0 +1,93 @@
# Helper module to create various Bike instanct fields from a CSV row hash
class BikeCsvImporter
module BikeAttrs
def bike_attr_fields
{
shop_id: 'velocipede number',
bike_purpose_id: 'program',
#gone: 'gone',
value: 'price',
bike_brand_id: 'make',
bike_model_id: 'model',
model: 'model',
bike_style_id: nil,
bike_condition_id: nil,
seat_tube_height: nil,
bike_wheel_size_id: nil,
serial_number: nil,
}
end
def bike_attrs(bike_hash)
bike_attr_fields.each_with_object({}) do |(model_field, csv_field), memo|
memo[model_field] = send :"bike_attr_#{model_field}", clean_value(bike_hash[csv_field])
end
end
def bike_attr_shop_id(value)
value.to_i
end
def bike_attr_bike_purpose_id(value)
map = {
'SALE' => /shop|as(-|\s+)is|safety\s*check/,
'BUILDBIKE' => /build|bikes.*world/,
'STORAGE' => nil,
'PARTS' => /part|frame/,
'SCRAP' => /scrap|strip/,
}
default = 'UNDETERMINED'
test_value = value.try :downcase
value = map.find { |_, regexp| regexp.try :match, test_value }.try :first
cached_bike_purpose(value || default).id
end
def bike_attr_gone(value)
%w{ yes yeah y }.include? value.try :downcase
end
def bike_attr_value(value)
value.try(:gsub, /[$]/, '').try :to_i
end
def bike_attr_bike_brand(value, new_if_empty = false)
value = 'Unknown' if !value || value =~ /\Aunknown/i
cached_bike_brand value, new_if_empty
end
def bike_attr_bike_brand_id(value)
bike_attr_bike_brand(value, false).try :id
end
def bike_attr_bike_model_id(value)
return unless value
cached_bike_model(value).try :id
end
def bike_attr_model(value)
value if value && value !~ /unknown/i
end
def bike_attr_bike_style_id(_)
@bike_style_other_cache ||= BikeStyle.find_by_style('OTHER').id
end
def bike_attr_bike_condition_id(_)
@bike_condition_undertermined_cache ||= BikeCondition.find_by_condition('UNDETERMINED').id
end
def bike_attr_seat_tube_height(_)
0
end
def bike_attr_bike_wheel_size_id(_)
@bike_condition_wheel_size_undertermined_cache ||= BikeWheelSize.find_by_description('UNDETERMINED').id
end
def bike_attr_serial_number(_)
'UNDETERMINED'
end
end
end
+35
View File
@@ -0,0 +1,35 @@
# Helper module to create various cached instances for bike CSV imports
class BikeCsvImporter
module Cache
def cached_bike_purpose(purpose)
@bike_purpose_cache ||= {}
@bike_purpose_cache[purpose] ||= BikePurpose.find_by_purpose purpose
end
def cached_bike_brand(brand, new_if_empty = false)
@bike_brand_cache ||= {}
if @bike_brand_cache.has_key? brand
@bike_brand_cache[brand]
else
bike_brand = BikeBrand.where('lower(brand) = ?', brand.downcase).first
bike_brand ||= BikeBrand.new(brand: brand) if new_if_empty
@bike_brand_cache[brand] = bike_brand
end
end
def cached_bike_model(model)
@bike_model_cache ||= {}
if @bike_model_cache.has_key? model
@bike_model_cache[model]
else
@bike_model_cache[model] = BikeModel.where('lower(model) = ?', model.downcase).first
end
end
def cached_log_bike_action(action)
@log_bike_action_id_cache ||= {}
@log_bike_action_id_cache[action] ||= ActsAsLoggable::BikeAction.find_by_action(action)
end
end
end
+16
View File
@@ -0,0 +1,16 @@
# Helper module to clean the incoming data from CSV fields
class BikeCsvImporter
module Cleaner
def clean_value(value)
value_or_nil strip_value(value)
end
def strip_value(value)
value.try(:strip).try(:gsub, /\n|\r/, '')
end
def value_or_nil(value)
return value unless ['?', 'n/a', 'missing', 'unknown', ''].include? value.try(:downcase)
end
end
end
+42
View File
@@ -0,0 +1,42 @@
# Helper module to create ActsAsLoggable log entries for a Bike instance from a CSV row hash
class BikeCsvImporter
module Logs
def log_entry_gone(bike, bike_hash)
if clean_value(bike_hash['gone']).to_s =~ /y/i
log_entry bike, log_entry_date(clean_value(bike_hash['date out'])), 'COMPLETED', 'Gone'
end
end
def log_entry_acquired(bike, bike_hash)
if clean_value(bike_hash['date in'])
log_entry bike, log_entry_date(clean_value(bike_hash['date in'])), 'ACQUIRED'
end
end
def log_entry_comment(bike, bike_hash)
if clean_value(bike_hash['comment']).present?
log_entry bike, nil, 'NOTE', clean_value(bike_hash['comment'])
end
end
def log_entry_date(value)
return unless value
Date.strptime value, '%m/%d/%y' rescue nil
end
def log_entry(bike, date, type, description = nil)
date ||= DateTime.now
bike_action = cached_log_bike_action(type)
ActsAsLoggable::Log.new(
loggable_type: bike.class.to_s,
loggable_id: bike.id || bike.shop_id.to_i, # for dry run
log_action_type: bike_action.class.to_s,
log_action_id: bike_action.id,
start_date: date,
end_date: date,
description: description,
)
end
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
+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
+43 -20
View File
@@ -1,4 +1,6 @@
class User < ActiveRecord::Base
MAX_AVATAR_SIZE_KB = 1024
acts_as_loggable
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
@@ -8,7 +10,7 @@ class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :bike_id,
:user_profiles_attributes, :username
:user_profiles_attributes, :username, :avatar
has_many :transactions, as: :customer
has_many :transaction_logs, through: :transactions, source: :logs
@@ -20,11 +22,19 @@ class User < ActiveRecord::Base
belongs_to :bike
has_attached_file :avatar, :styles => {:thumb => '100x100>'}
default_scope order('username ASC')
validates :username, :presence => true, uniqueness: true
validates :first_name, :presence => true
validates :last_name, :presence => true
validates_attachment :avatar, :content_type => {:content_type => %w{ image/jpeg image/gif image/png }},
:file_name => {:matches => [/png\Z/, /jpe?g\Z/, /gif\Z/]},
:size => {:in => 0..MAX_AVATAR_SIZE_KB.kilobytes}
def to_s
"#{first_name} #{last_name}"
end
@@ -44,10 +54,16 @@ class User < ActiveRecord::Base
roles.include?(role)
end
# try keeping the email field in DB clear and consistent, without empty strings (NULLs instead)
def email=(other)
super(other.blank? ? nil : other)
end
### TODO methods below probably belong somewhere else
def completed_build_bikes
purpose_id = BikePurpose.find_by_purpose("BUILDBIKE").id
#default BUILDBIKE/CLASS ID is 5
purpose_id = 5
Bike.find_by_sql("
SELECT *
FROM bikes
@@ -64,15 +80,16 @@ class User < ActiveRecord::Base
end
def total_credits_spent
log_action = ::ActsAsLoggable::TransactionAction.find_by_action("TIME")
log_action_id = 1 #TIME
transaction_logs.
where( "log_action_id = ? AND log_action_type = ?",
log_action.id, log_action.class.to_s).
log_action_id, ::ActsAsLoggable::TransactionAction.to_s).
sum{ |r| r.description.to_i }.round(2)
end
def total_earned_credits
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
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
@@ -81,7 +98,7 @@ class User < ActiveRecord::Base
#
# 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("
::ActsAsLoggable::Log.find_by_sql(["
SELECT DISTINCT ON (logs.created_at) start_date, end_date,
conversion.conversion, conversion.created_at
FROM logs
@@ -89,23 +106,26 @@ class User < ActiveRecord::Base
SELECT conversion, created_at
FROM credit_conversions
) AS conversion ON logs.created_at > conversion.created_at
WHERE logs.loggable_id = #{self.id}
WHERE logs.loggable_id = :id
AND logs.loggable_type = 'User'
AND (log_action_id != #{log_action.id} AND log_action_type = '#{log_action.class.to_s}')
ORDER BY logs.created_at, conversion.created_at DESC").
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
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s).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 = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
log_action_id = 4 #CHECKIN
#TODO need to prevent users from saving logs across months, force to create a new log if crossing month
current_month_range = (Time.now.beginning_of_month..Time.now.end_of_month)
logs.where("log_action_id != ? AND log_action_type = ?", log_action.id, log_action.class.to_s)
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 }
@@ -113,28 +133,31 @@ class User < ActiveRecord::Base
end
def checked_in?
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
checked = logs.where( log_action_id: log_action.id).
#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
log_action = ::ActsAsLoggable::UserAction.find_by_action("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: log_action.class.to_s)
log_action_id: log_action_id,
log_action_type: ::ActsAsLoggable::UserAction.to_s)
save
end
def checkout
log_action = ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")
checked = logs.where( log_action_id: log_action.id).
#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
@@ -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
+80
View File
@@ -0,0 +1,80 @@
= top_menu
%h1 Add Bike
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for @bike, as: 'bikes', index: '', url: api_create_bike_path, method: :post, enctype: 'multipart/form-data', html: {id: 'add_bike_form'} do |f|
= f.hidden_field 'bike_purpose_id'
%fieldset
.form-group
= f.number_field 'shop_id', placeholder: 'Shop ID', min: 0, class: 'form-control', id: 'shop_id'
.help-block
.form-group
= f.select 'bike_brand_id', options_for_select(@brands), {}, class: 'form-control', id: 'bike_brand_id'
.help-block
.form-group
= f.text_field 'model', placeholder: 'Model', class: 'form-control', id: 'model'
.help-block
.form-group
= f.text_field 'serial_number', placeholder: 'Serial Number', class: 'form-control', id: 'serial_number'
.help-block
.form-group
.btn-group(data-toggle="buttons")
%label.btn.btn-default
= f.radio_button 'bike_style_id', 3
RD
%label.btn.btn-default
= f.radio_button 'bike_style_id', 1
MTN
%label.btn.btn-default
= f.radio_button 'bike_style_id', 2
HYB
%label.btn.btn-default
= f.radio_button 'bike_style_id', 4
OTHER
= hidden_field_tag nil, nil, id: 'bike_style_id'
.help-block
.form-group
= f.select 'bike_wheel_size_id', options_for_select(@wheel_sizes), {}, class: 'form-control', id: 'bike_wheel_size_id'
.help-block
.form-group
.btn-group(data-toggle="buttons")
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 2
Poor
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 3
Fair
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 4
Good
%label.btn.btn-default
= f.radio_button 'bike_condition_id', 5
Excellent
= hidden_field_tag nil, nil, id: 'bike_condition_id'
.help-block
.form-group
.input-group
= f.number_field 'seat_tube_height', placeholder: 'Seat Tube', min: 0, max: 100, class: 'form-control', id: 'seat_tube_height'
.input-group-addon cm
.help-block
.form-group
%label Bike photo (optional)
= f.file_field 'photo', id: 'photo'
.help-block
-# Commenting this out until description is added to Bike
/.form-group
%input{id: "bike_description", placeholder: "Short description", type: "text", class: "input-lg" }
.form-group
= button_tag 'Add Bike', class: 'btn btn-primary'
+57
View File
@@ -0,0 +1,57 @@
- btn = if @show_add_bike
- capture do
= link_to 'Add Another Bike?', new_bike_path, class: 'btn btn-default navbar-btn hidden-xs'
= link_to new_bike_path, class: 'navbar-brand visible-xs', title: 'Add Another Bike?' do
%i.glyphicon.glyphicon-plus
= top_menu btn
%h1 #{@bike.shop_id}: #{@bike.bike_brand}
%h4= @bike.model
.form-horizontal
- if @bike.photo?
.form-group
%label.col-sm-2.control-label Photo
.col-sm-10
= link_to @bike.photo.url, target: '_blank' do
%img{src: @bike.photo.url(:thumb), class: 'img-thumbnail'}
.form-group
%label.col-sm-2.control-label Type
.col-sm-10.form-control-static= @bike.bike_style
.form-group
%label.col-sm-2.control-label Wheel Size
.col-sm-10.form-control-static= @bike.bike_wheel_size.display_string
.form-group
%label.col-sm-2.control-label Condition
.col-sm-10.form-control-static= @bike.bike_condition
.form-group
%label.col-sm-2.control-label Seat Tube (cm)
.col-sm-10.form-control-static= @bike.seat_tube_height
.form-group
%label.col-sm-2.control-label Purpose
.col-sm-10.form-control-static= @bike.bike_purpose
- unless @bike.value.nil?
.form-group
%label.col-sm-2.control-label Value
.col-sm-10.form-control-static= @bike.value
- unless @bike.color.nil?
.form-group
%label.col-sm-2.control-label Color
.col-sm-10.form-control-static
.bike-color(style="background-color: ##{@bike.color}")= @bike.color
- if @task_list
- tasks = @task_list.tasks.to_a
.form-group
%label.col-sm-2.control-label Task list
.col-sm-10.form-control-static
= link_to edit_task_list_path(@task_list.id) do
#{tasks.select(&:done).count}/#{tasks.count}
-25
View File
@@ -1,25 +0,0 @@
<%- if controller_name != 'sessions' %>
<%= link_to "Sign in", new_session_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %><br />
<% end -%>
<% end -%>
+20
View File
@@ -0,0 +1,20 @@
- links = []
- if controller_name != 'sessions'
- links << ['Sign in', new_session_path(resource_name)]
- if devise_mapping.registerable? && controller_name != 'registrations'
- links << ['Sign up', new_registration_path(resource_name)]
- if devise_mapping.recoverable? && controller_name != 'passwords'
- links << ['Forgot your password?', new_password_path(resource_name)]
- if devise_mapping.confirmable? && controller_name != 'confirmations'
- links << ['Didn\'t receive confirmation instructions?', new_confirmation_path(resource_name)]
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
- links << ["Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider)]
- if links.any?
%p= links.map { |title, url| link_to title, url }.join(' &middot; ').html_safe
@@ -1,12 +0,0 @@
<h2>Resend confirmation instructions</h2>
<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.submit "Resend confirmation instructions" %></div>
<% end %>
<%= render "links" %>
-16
View File
@@ -1,16 +0,0 @@
<h2>Change your password</h2>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<%= f.hidden_field :reset_password_token %>
<div><%= f.label :password, "New password" %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.submit "Change my password" %></div>
<% end %>
<%= render "links" %>
+19
View File
@@ -0,0 +1,19 @@
%h1 Change your password
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for resource, as: resource_name, url: password_path(resource_name), html: {method: :put} do |f|
= devise_error_messages!
= f.hidden_field :reset_password_token
%fieldset
.form-group
= f.password_field :password, placeholder: 'New password', class: 'form-control'
.form-group
= f.password_field :password_confirmation, placeholder: 'Confirm new password', class: 'form-control'
.form-group
= f.submit 'Change my password', class: 'btn btn-primary'
= render 'links'
-12
View File
@@ -1,12 +0,0 @@
<h2>Forgot your password?</h2>
<%= 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><%= f.submit "Send me reset password instructions" %></div>
<% end %>
<%= render "links" %>
+15
View File
@@ -0,0 +1,15 @@
%h1 Forgot your password?
.row
.col-xs-12.col-sm-6.col-lg-4
= form_for resource, as: resource_name, url: password_path(resource_name), html: {method: :post} do |f|
= devise_error_messages!
%fieldset
.form-group
= f.email_field :email, placeholder: 'Email', class: 'form-control'
.form-group
= f.submit 'Reset Password', class: 'btn btn-primary'
= render 'links'
@@ -1,25 +1,17 @@
%fieldset
.control-group
= f.label :addrStreet1, :class => "control-label"
.controls
= f.text_field :addrStreet1, :class => "control-label"
.control-group
= f.label :addrStreet2, :class => "control-label"
.controls
= f.text_field :addrStreet2, :class => "control-label"
.control-group
= f.label :addrCity, :class => "control-label"
.controls
= f.text_field :addrCity, :class => "control-label"
.control-group
= f.label :addrState, :class => "control-label"
.controls
= f.text_field :addrState, :class => "control-label"
.control-group
= f.label :addrZip, :class => "control-label"
.controls
= f.text_field :addrZip, :class => "control-label"
.control-group
= f.label :phone, :class => "control-label"
.controls
= f.text_field :phone, :class => "control-label"
.form-group
= f.text_field :addrStreet1, placeholder: 'Street Address Line 1', class: 'form-control'
.form-group
= f.text_field :addrStreet2, placeholder: 'Street Address Line 2',class: 'form-control'
.form-group
= f.text_field :addrCity, placeholder: 'City', class: 'form-control'
.form-group
= f.text_field :addrState, placeholder: 'State Abbreviation', class: 'form-control'
.form-group
= f.text_field :addrZip, placeholder: 'Zip Code', class: 'form-control'
.form-group
= f.text_field :phone, placeholder: 'Phone', class: 'form-control'

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