From b67062fb327aaf972064b8ce2de686dd8849e0c6 Mon Sep 17 00:00:00 2001 From: "John N. Milner" Date: Thu, 23 May 2013 23:56:18 -0400 Subject: [PATCH 01/17] New view to show the current day's check ins --- app/components/app_tab_panel.rb | 3 ++- app/components/check_ins.rb | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 app/components/check_ins.rb diff --git a/app/components/app_tab_panel.rb b/app/components/app_tab_panel.rb index d888c04..af1262a 100644 --- a/app/components/app_tab_panel.rb +++ b/app/components/app_tab_panel.rb @@ -44,7 +44,8 @@ class AppTabPanel < Netzke::Basepack::TabPanel :logs, { layout: :fit, wrappedComponent: :user_role_joins, - title: "User Roles"} + title: "User Roles"}, + :check_ins ] end diff --git a/app/components/check_ins.rb b/app/components/check_ins.rb new file mode 100644 index 0000000..2320b77 --- /dev/null +++ b/app/components/check_ins.rb @@ -0,0 +1,21 @@ +class CheckIns < Netzke::Basepack::Grid + + def configure(c) + super + c.header = false + c.model = "ActsAsLoggable::Log" + c.scope = lambda { |rel| rel.where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")). + where("start_date >= ?", Time.zone.now.beginning_of_day); + } + c.columns = [ + { :name => :logged_by, :getter => lambda{ |rec| + user = User.find_by_id(rec.logger_id) + user.nil? ? "" : "#{user.first_name} #{user.last_name}" + } + }, + { :name => "Status", :getter => lambda{ |rec| rec.start_date == rec.end_date ? "Checked In" : "Checked Out" } }, + :start_date, + :end_date, + ] + end +end From 2e08f655eaa0571f3d8589b22b769fd32a6222dd Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 09:00:12 -0400 Subject: [PATCH 02/17] Registration form now remembers field values for nested form errors --- app/models/user_profile.rb | 13 ++++++------- app/views/devise/registrations/new.html.haml | 3 ++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb index a9a569c..7c1cf32 100644 --- a/app/models/user_profile.rb +++ b/app/models/user_profile.rb @@ -1,16 +1,15 @@ class UserProfile < ActiveRecord::Base - # Setup accessible (or protected) attributes for your model attr_accessible :user_id, :addrStreet1, :addrStreet2, :addrCity, :addrState, :addrZip, :phone - + belongs_to :user belongs_to :bike - #validates :addrStreet1 , :presence => true - #validates :addrCity , :presence => true - #validates :addrState , :presence => true - #validates :addrZip , :presence => true - validates :phone, :presence => true + #validates :addrStreet1 , :presence => true + #validates :addrCity , :presence => true + #validates :addrState , :presence => true + #validates :addrZip , :presence => true + #validates :phone, :presence => true def to_s [addrStreet1, addrStreet2, addrCity, addrState, addrZip, phone].join(" - ") diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index f5b9336..ac01d8b 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -18,7 +18,8 @@ %br/ = f.email_field :email %div - = f.fields_for :user_profiles, UserProfile.new do |builder| + - profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles + = f.fields_for :user_profiles, profile_builder do |builder| = render 'user_profile_fields', f: builder %div = f.label :password From 8c5132814f0d7821c29321c7744ada91c0cc8aef Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 09:02:26 -0400 Subject: [PATCH 03/17] New User Sign up now annoys user to input contact info --- app/assets/javascripts/new_user.js | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 app/assets/javascripts/new_user.js diff --git a/app/assets/javascripts/new_user.js b/app/assets/javascripts/new_user.js new file mode 100644 index 0000000..89e7d76 --- /dev/null +++ b/app/assets/javascripts/new_user.js @@ -0,0 +1,51 @@ +$(document).ready(function(){ + var MIN_LEN = 3; + var MAX_SUBMITS = 3; + var submit_count = 0; + $("input[name=commit]").click( function(e){ + console.log("clicked"); + submit_count += 1; + //IDs of contact info + var contact_info_ids = [ + "user_email", + "user_user_profiles_attributes_0_addrStreet1", + "user_user_profiles_attributes_0_addrCity", + "user_user_profiles_attributes_0_addrState", + "user_user_profiles_attributes_0_addrZip", + "user_user_profiles_attributes_0_phone" + ]; + var contact_vals = ""; + var index = 0; + //see if any contact info exists + for( var index in contact_info_ids){ + contact_vals += $("#"+contact_info_ids[index]).val(); + } + if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){ + + if( submit_count > MAX_SUBMITS ){ + alert("Fine."); + } + return true; + + }else{ + + switch(submit_count){ + case 1: + alert("It appears you have not entered any contact information. " + + "Please do."); + break; + case 2: + alert("It is highly recommended that you enter at least one form of" + + " contact information. It is in your best interest."); + break; + case 3: + alert("If something happens to your bicycle, we will not be able to" + + " notify you. Please enter at least one form of contact."); + break; + default: + alert("Please enter at least one form of contact."); + } + return false; + } + }); +}); From 73bb35de889c8f31ff90a575162f9cd19cc10e30 Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 09:58:43 -0400 Subject: [PATCH 04/17] Moving bootstrap.js --- app/assets/javascripts/{ => twitter}/bootstrap.js.coffee | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/assets/javascripts/{ => twitter}/bootstrap.js.coffee (100%) diff --git a/app/assets/javascripts/bootstrap.js.coffee b/app/assets/javascripts/twitter/bootstrap.js.coffee similarity index 100% rename from app/assets/javascripts/bootstrap.js.coffee rename to app/assets/javascripts/twitter/bootstrap.js.coffee From 75734a075c48763128b1d3a9dcda55451d5d08b4 Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 09:59:49 -0400 Subject: [PATCH 05/17] Moving new_user.js to devise/registrations.s --- app/assets/javascripts/{new_user.js => devise/registrations.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/assets/javascripts/{new_user.js => devise/registrations.js} (100%) diff --git a/app/assets/javascripts/new_user.js b/app/assets/javascripts/devise/registrations.js similarity index 100% rename from app/assets/javascripts/new_user.js rename to app/assets/javascripts/devise/registrations.js From db6b7ff7da0230c8c580f4eaa6fcc85891e214b0 Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 10:12:03 -0400 Subject: [PATCH 06/17] Adjusting how javascript is loaded Now js is loaded per controller --- app/assets/javascripts/application.js | 3 +-- app/assets/javascripts/devise/sessions.js | 0 app/assets/javascripts/site.js | 0 app/views/layouts/application.html.haml | 1 + 4 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/devise/sessions.js create mode 100644 app/assets/javascripts/site.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 5733810..7cec09e 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,5 +12,4 @@ // //= require jquery //= require jquery_ujs -//= require twitter/bootstrap -//= require_tree . +//= require custom_netzke_helpers diff --git a/app/assets/javascripts/devise/sessions.js b/app/assets/javascripts/devise/sessions.js new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/site.js b/app/assets/javascripts/site.js new file mode 100644 index 0000000..e69de29 diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 70660f1..6323b38 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -28,3 +28,4 @@ %p © Velocipede 2013 = javascript_include_tag "application" + = javascript_include_tag params[:controller] From a07d6345e8e2c2c921dee945deba076531ce33b5 Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 10:25:12 -0400 Subject: [PATCH 07/17] Mildly better looking signup page w/ errors. --- app/helpers/devise_helper.rb | 23 ++++++++++++++++++++ app/views/devise/registrations/new.html.haml | 1 + 2 files changed, 24 insertions(+) create mode 100644 app/helpers/devise_helper.rb diff --git a/app/helpers/devise_helper.rb b/app/helpers/devise_helper.rb new file mode 100644 index 0000000..e672d5b --- /dev/null +++ b/app/helpers/devise_helper.rb @@ -0,0 +1,23 @@ +module DeviseHelper + # A simple way to show error messages for the current devise resource. If you need + # to customize this method, you can either overwrite it in your application helpers or + # copy the views to your application. + # + # This method is intended to stay simple and it is unlikely that we are going to change + # it to add more behavior or options. + def devise_error_messages! + return "" if resource.errors.empty? + + messages = resource.errors.full_messages.map { |msg| content_tag(:p, msg, :class => "alert") }.join + sentence = I18n.t("errors.messages.not_saved", + :count => resource.errors.count, + :resource => resource.class.model_name.human.downcase) + + html = <<-HTML +

#{sentence}

+ #{messages} + HTML + + html.html_safe + end +end diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index ac01d8b..49aeb22 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,3 +1,4 @@ += stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %h2 Sign up = form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| = devise_error_messages! From 9021294c4b6b64a06bcb67222af5bfa498726cec Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 11:08:58 -0400 Subject: [PATCH 08/17] Auth by username instead of email Added username to User table/model --- app/components/app_tab_panel.rb | 2 +- app/components/user_stats.rb | 1 + app/components/users.rb | 1 + app/views/devise/registrations/new.html.haml | 4 ++++ app/views/devise/sessions/new.html.erb | 8 ++++---- config/initializers/devise.rb | 2 +- db/migrate/20130525143240_add_username_to_user.rb | 6 ++++++ db/schema.rb | 4 +++- spec/factories/users.rb | 4 ++++ 9 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20130525143240_add_username_to_user.rb diff --git a/app/components/app_tab_panel.rb b/app/components/app_tab_panel.rb index faf4bc8..58013a3 100644 --- a/app/components/app_tab_panel.rb +++ b/app/components/app_tab_panel.rb @@ -3,7 +3,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel action :sign_out do |c| c.icon = :door_out - c.text = "Sign out #{controller.current_user.email}" if controller.current_user + c.text = "Exit" if controller.current_user end def configure(c) diff --git a/app/components/user_stats.rb b/app/components/user_stats.rb index cc52fa2..beec72c 100644 --- a/app/components/user_stats.rb +++ b/app/components/user_stats.rb @@ -4,6 +4,7 @@ class UserStats < Netzke::Base bike = user.bike %Q(
+

Username: #{user.username}

Total Hours Worked: #{user.total_hours}

Hours worked in #{Time.now.strftime('%B')}: #{user.current_month_hours}

Current bike Shop ID: #{bike.shop_id if bike}

diff --git a/app/components/users.rb b/app/components/users.rb index b844e4a..45ef596 100644 --- a/app/components/users.rb +++ b/app/components/users.rb @@ -5,6 +5,7 @@ class Users < Netzke::Basepack::Grid c.model = "User" c.columns = [ + :username, :first_name, :last_name, :nickname, diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 49aeb22..fb3f7f3 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -2,6 +2,10 @@ %h2 Sign up = form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| = devise_error_messages! + %div + = f.label :username + %br/ + = f.text_field :username %div = f.label :first_name %br/ diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 6acf013..34db7fc 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -3,8 +3,8 @@

Sign in

<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> -
<%= f.label :email %>
- <%= f.email_field :email %>
+
<%= f.label :username%>
+ <%= f.text_field :username%>
<%= f.label :password %>
<%= f.password_field :password %>
@@ -21,9 +21,9 @@ <% if Rails.env.development? %> <% User.all.each do |user| %> <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> - <%= f.hidden_field :email, :value => user.email %>
+ <%= f.hidden_field :username, :value => user.username%> <%= f.hidden_field :password, :value => 'password' %> -
<%= f.submit "Sign in as #{user.email}" %>
+
<%= f.submit "Sign in as #{user.username}" %>
<% end %> <% end %> <% end %> diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index e0d6120..e923b33 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -26,7 +26,7 @@ Devise.setup do |config| # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. - # config.authentication_keys = [ :email ] + config.authentication_keys = [ :username ] # Configure parameters from the request object used for authentication. Each entry # given should be a request method and it will automatically be passed to the diff --git a/db/migrate/20130525143240_add_username_to_user.rb b/db/migrate/20130525143240_add_username_to_user.rb new file mode 100644 index 0000000..9911140 --- /dev/null +++ b/db/migrate/20130525143240_add_username_to_user.rb @@ -0,0 +1,6 @@ +class AddUsernameToUser < ActiveRecord::Migration + def change + add_column :users, :username, :string + add_index :users, :username, :unique => true + end +end diff --git a/db/schema.rb b/db/schema.rb index fcb1f7c..c68d03e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130419010051) do +ActiveRecord::Schema.define(:version => 20130525143240) do create_table "bike_actions", :force => true do |t| t.string "action", :limit => 128, :null => false @@ -177,10 +177,12 @@ ActiveRecord::Schema.define(:version => 20130419010051) do t.string "first_name", :default => "", :null => false t.string "last_name", :default => "", :null => false t.string "nickname" + t.string "username" end add_index "users", ["bike_id"], :name => "index_users_on_bike_id", :unique => true add_index "users", ["email"], :name => "index_users_on_email", :unique => true add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + add_index "users", ["username"], :name => "index_users_on_username", :unique => true end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 53c85a8..6d01799 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -1,5 +1,6 @@ FactoryGirl.define do factory :user do + sequence(:username) { |n| "user_#{n}" } sequence(:email) { |n| "user_#{n}@example.com" } password 'password' password_confirmation { password } @@ -9,16 +10,19 @@ FactoryGirl.define do association :user_role, factory: :role_user factory :staff do + username "staff" first_name 'Staff' association :user_role, factory: :role_staff end factory :admin do + username "admin" first_name 'Admin' association :user_role, factory: :role_admin end factory :bike_admin do + username "bike_admin" first_name 'BikeAdmin' association :user_role, factory: :role_bike_admin end From 6d2e7686d6bb51080b78a399c02973530f96642d Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sat, 25 May 2013 11:13:52 -0400 Subject: [PATCH 09/17] Remove unneeded if --- app/components/app_tab_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/app_tab_panel.rb b/app/components/app_tab_panel.rb index 58013a3..1398d2a 100644 --- a/app/components/app_tab_panel.rb +++ b/app/components/app_tab_panel.rb @@ -3,7 +3,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel action :sign_out do |c| c.icon = :door_out - c.text = "Exit" if controller.current_user + c.text = "Exit" end def configure(c) From c7974c52e83d4c73f82aee046575dd611586e62c Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sun, 26 May 2013 13:31:52 -0400 Subject: [PATCH 10/17] Adding color picker for bikes view/forms --- .../javascripts/custom_netzke_helpers.js | 341 ++++++++++++++++++ app/components/bikes.rb | 21 +- 2 files changed, 361 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/custom_netzke_helpers.js b/app/assets/javascripts/custom_netzke_helpers.js index b46baa7..38b5850 100644 --- a/app/assets/javascripts/custom_netzke_helpers.js +++ b/app/assets/javascripts/custom_netzke_helpers.js @@ -3,3 +3,344 @@ 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() { + //needs to be called twice because? + me.picker.alignTo(me.colorField.inputEl); + me.picker.show(); + me.picker.alignTo(me.colorField.inputEl); + me.picker.show(); + } + }); + 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; + } +}); + +/** +Ext.define('Ext.ux.ColorPickerCombo', { + extend: 'Ext.form.field.Trigger', + alias: 'widget.colorcbo', + initComponent: function(){ + console.log("INIT"); + this.my_value = "pee"; + console.log(this.getValue); + console.log(this.rawToValue(this.processRawValue(this.getRawValue()))); + console.log("hmmm"); + }, + getSubmitValue: function(){ + console.log("GETTING SUBMIT VALUE"); + }, + getSubmitData: function(){ + console.log("GETTING SUBMIT DATA VALUE"); + } +}); +*/ +/** +Ext.define('Ext.ux.ColorPickerCombo', { + extend: 'Ext.form.field.Trigger', + mixins: { + field:'Ext.form.field.Field' + }, + alias: 'widget.colorcbo', + triggerTip: 'Please select a color.', + picker: null, + initComponent: function(){ + console.log("INIT"); + var me = this; + console.log(me); + me.picker = Ext.create('Ext.picker.Color', { + renderTo: document.body, + floating: true, + hidden: false, + style: { + backgroundColor: "#fff" + } , + listeners: { + scope:this, + select: function(field, value, opts){ + console.log(me.originalValue); + console.log(value); + me.setValue(value); + me.setRawValue(value); + me.setFieldStyle("background-color:#"+value+";"); + me.picker.hide(); + console.log("input el"); + console.log(me.inputEl); + //$("#"+me.getInputId()).val(value); + console.log("owner"); + console.log(me.picker.ownerCt); + console.log(me.getValue()); + me.my_value = value; + console.log("my_value"); + console.log(this.my_value); + console.log(this.getValue); + }, + show: function(field,opts){ + console.log("show"); + //field.getEl().monitorMouseLeave(500, field.hide, field); + } + } + }); + //me.picker.hide(); + console.log(me.picker.ownerCt); + }, + onTriggerClick: function() { + var me = this; + me.picker.alignTo(me.inputEl, 'tl-bl?'); + //me.picker.show(me.inputEl); + }, + getValue: function(){ + console.log("GETTING VALUE"); + var me = this, + val = me.rawToValue(me.processRawValue(me.getRawValue())); + me.value = val; + return val; + } +});*/ + + +/** +Ext.define('Ext.ux.ColorPickerCombo', { + extend: 'Ext.form.field.Trigger', + alias: 'widget.colorcbo', + triggerTip: 'Please select a color.', + myvalue: null, + initComponent: function(){ + console.log("INIT"); + console.log(this); + }, + onTriggerClick: function() { + var me = this; + picker = Ext.create('Ext.picker.Color', { + pickerField: this, + ownerCt: this, + renderTo: document.body, + floating: true, + hidden: true, + focusOnShow: true, + style: { + backgroundColor: "#fff" + } , + listeners: { + scope:this, + select: function(field, value, opts){ + me.setRawValue(value); + me.resetOriginalValue(value); + me.myvalue = value; + me.inputEl.setStyle({backgroundColor:value}); + picker.hide(); + me.focus(true); + }, + show: function(field,opts){ + field.getEl().monitorMouseLeave(500, field.hide, field); + } + } + }); + picker.alignTo(me.inputEl, 'tl-bl?'); + picker.show(me.inputEl); + picker.on('on', function(){ + var me = this; + me.fireEvent('blur', me, e); + }, me); + }, + getValue: function(){ + console.log("GETTING VALUE"); + console.log(this.myvalue); + console.log(this.inputEl.getValue()); + var me = this, + val = this.myvalue || me.rawToValue(me.processRawValue(me.getRawValue())); + me.value = val; + return val; + } +});*/ +/** +Ext.define('Ext.ux.ColorPickerCombo', { + extend: 'Ext.form.FieldContainer', + mixins:{ + field:'Ext.form.field.Field' + }, + alias: 'widget.colorcbo', + + //configurables + combineErrors: true, + msgTarget: 'under', + layout: 'hbox', + readOnly: false, + + initComponent: function() { + var me = this; + console.log(me); + me.picker = Ext.create('Ext.picker.Color', { + pickerField: this, + ownerCt: this, + renderTo: document.body, + floating: true, + hidden: true, + focusOnShow: true, + style: { + backgroundColor: "#fff" + }, + listeners: { + scope:this, + select: function(field, value, opts){ + me.setValue(value); + me.inputEl.setStyle({backgroundColor:value}); + me.picker.hide(); + }, + show: function(field,opts){ + field.getEl().monitorMouseLeave(500, field.hide, field); + } + } + }); + me.callParent(); + //picker.alignTo(me.inputEl, 'tl-bl?'); + //picker.show(me.inputEl); + // 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.picker.focus(); + } +});*/ diff --git a/app/components/bikes.rb b/app/components/bikes.rb index 9897742..64169fd 100644 --- a/app/components/bikes.rb +++ b/app/components/bikes.rb @@ -16,7 +16,9 @@ class Bikes < Netzke::Basepack::Grid end } }, - :color, + #needs to have type :action or else won't work in grid, because... netzke + { :name => "color", :type => :action, :editor => { :xtype => "xcolorcombo"}}, + #{ :name => :color }, { :name => :bike_style__style, :text => 'Style' }, { :name => :seat_tube_height, :text => 'Seat Tube (in)'}, { :name => :top_tube_length, :text => 'Top Tube (in)'}, @@ -32,6 +34,23 @@ class Bikes < Netzke::Basepack::Grid ] end + def default_fields_for_forms + [ + { :name => :shop_id, :field_label => 'Shop ID'}, + :serial_number, + { :name => :bike_brand__brand, :field_label => 'Brand' }, + { :name => :bike_model__model, :field_label => 'Model'}, + { :name => "color", :xtype => "xcolorcombo"}, + { :name => :bike_style__style, :field_label => 'Style' }, + { :name => :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)'}, + :value, + { :name => :bike_condition__condition, :field_label => 'Condition'}, + { :name => :bike_status__status, :field_label => 'Status'} + ] + end + #override with nil to remove actions def default_bbar [ :apply, :add_in_form, :search ] From 7f029f2a83eb86e23d70a842844fac5e42df4e76 Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sun, 26 May 2013 13:33:35 -0400 Subject: [PATCH 11/17] Removing commented js --- .../javascripts/custom_netzke_helpers.js | 194 ------------------ 1 file changed, 194 deletions(-) diff --git a/app/assets/javascripts/custom_netzke_helpers.js b/app/assets/javascripts/custom_netzke_helpers.js index 38b5850..443ea9e 100644 --- a/app/assets/javascripts/custom_netzke_helpers.js +++ b/app/assets/javascripts/custom_netzke_helpers.js @@ -150,197 +150,3 @@ Ext.define('Ext.ux.form.field.ColorCombo', { return data; } }); - -/** -Ext.define('Ext.ux.ColorPickerCombo', { - extend: 'Ext.form.field.Trigger', - alias: 'widget.colorcbo', - initComponent: function(){ - console.log("INIT"); - this.my_value = "pee"; - console.log(this.getValue); - console.log(this.rawToValue(this.processRawValue(this.getRawValue()))); - console.log("hmmm"); - }, - getSubmitValue: function(){ - console.log("GETTING SUBMIT VALUE"); - }, - getSubmitData: function(){ - console.log("GETTING SUBMIT DATA VALUE"); - } -}); -*/ -/** -Ext.define('Ext.ux.ColorPickerCombo', { - extend: 'Ext.form.field.Trigger', - mixins: { - field:'Ext.form.field.Field' - }, - alias: 'widget.colorcbo', - triggerTip: 'Please select a color.', - picker: null, - initComponent: function(){ - console.log("INIT"); - var me = this; - console.log(me); - me.picker = Ext.create('Ext.picker.Color', { - renderTo: document.body, - floating: true, - hidden: false, - style: { - backgroundColor: "#fff" - } , - listeners: { - scope:this, - select: function(field, value, opts){ - console.log(me.originalValue); - console.log(value); - me.setValue(value); - me.setRawValue(value); - me.setFieldStyle("background-color:#"+value+";"); - me.picker.hide(); - console.log("input el"); - console.log(me.inputEl); - //$("#"+me.getInputId()).val(value); - console.log("owner"); - console.log(me.picker.ownerCt); - console.log(me.getValue()); - me.my_value = value; - console.log("my_value"); - console.log(this.my_value); - console.log(this.getValue); - }, - show: function(field,opts){ - console.log("show"); - //field.getEl().monitorMouseLeave(500, field.hide, field); - } - } - }); - //me.picker.hide(); - console.log(me.picker.ownerCt); - }, - onTriggerClick: function() { - var me = this; - me.picker.alignTo(me.inputEl, 'tl-bl?'); - //me.picker.show(me.inputEl); - }, - getValue: function(){ - console.log("GETTING VALUE"); - var me = this, - val = me.rawToValue(me.processRawValue(me.getRawValue())); - me.value = val; - return val; - } -});*/ - - -/** -Ext.define('Ext.ux.ColorPickerCombo', { - extend: 'Ext.form.field.Trigger', - alias: 'widget.colorcbo', - triggerTip: 'Please select a color.', - myvalue: null, - initComponent: function(){ - console.log("INIT"); - console.log(this); - }, - onTriggerClick: function() { - var me = this; - picker = Ext.create('Ext.picker.Color', { - pickerField: this, - ownerCt: this, - renderTo: document.body, - floating: true, - hidden: true, - focusOnShow: true, - style: { - backgroundColor: "#fff" - } , - listeners: { - scope:this, - select: function(field, value, opts){ - me.setRawValue(value); - me.resetOriginalValue(value); - me.myvalue = value; - me.inputEl.setStyle({backgroundColor:value}); - picker.hide(); - me.focus(true); - }, - show: function(field,opts){ - field.getEl().monitorMouseLeave(500, field.hide, field); - } - } - }); - picker.alignTo(me.inputEl, 'tl-bl?'); - picker.show(me.inputEl); - picker.on('on', function(){ - var me = this; - me.fireEvent('blur', me, e); - }, me); - }, - getValue: function(){ - console.log("GETTING VALUE"); - console.log(this.myvalue); - console.log(this.inputEl.getValue()); - var me = this, - val = this.myvalue || me.rawToValue(me.processRawValue(me.getRawValue())); - me.value = val; - return val; - } -});*/ -/** -Ext.define('Ext.ux.ColorPickerCombo', { - extend: 'Ext.form.FieldContainer', - mixins:{ - field:'Ext.form.field.Field' - }, - alias: 'widget.colorcbo', - - //configurables - combineErrors: true, - msgTarget: 'under', - layout: 'hbox', - readOnly: false, - - initComponent: function() { - var me = this; - console.log(me); - me.picker = Ext.create('Ext.picker.Color', { - pickerField: this, - ownerCt: this, - renderTo: document.body, - floating: true, - hidden: true, - focusOnShow: true, - style: { - backgroundColor: "#fff" - }, - listeners: { - scope:this, - select: function(field, value, opts){ - me.setValue(value); - me.inputEl.setStyle({backgroundColor:value}); - me.picker.hide(); - }, - show: function(field,opts){ - field.getEl().monitorMouseLeave(500, field.hide, field); - } - } - }); - me.callParent(); - //picker.alignTo(me.inputEl, 'tl-bl?'); - //picker.show(me.inputEl); - // 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.picker.focus(); - } -});*/ From 687cb3397469a15c0066d33cae00d27efcf77ac6 Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sun, 26 May 2013 19:52:40 -0400 Subject: [PATCH 12/17] Added color blocks to grid view --- app/components/bikes.rb | 3 +-- app/components/bikes/javascripts/init_component.js | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/components/bikes.rb b/app/components/bikes.rb index 64169fd..225c70d 100644 --- a/app/components/bikes.rb +++ b/app/components/bikes.rb @@ -17,8 +17,7 @@ class Bikes < Netzke::Basepack::Grid } }, #needs to have type :action or else won't work in grid, because... netzke - { :name => "color", :type => :action, :editor => { :xtype => "xcolorcombo"}}, - #{ :name => :color }, + { :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block}, { :name => :bike_style__style, :text => 'Style' }, { :name => :seat_tube_height, :text => 'Seat Tube (in)'}, { :name => :top_tube_length, :text => 'Top Tube (in)'}, diff --git a/app/components/bikes/javascripts/init_component.js b/app/components/bikes/javascripts/init_component.js index 402ef15..e152db1 100644 --- a/app/components/bikes/javascripts/init_component.js +++ b/app/components/bikes/javascripts/init_component.js @@ -9,5 +9,8 @@ // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! this.selectBikeBrand({bike_brand_id: record.get('bike_brand__brand')}); }, this); + }, + colorBlock: function(value){ + return Ext.String.format('
{1}
', value, value); } } From 761ab81b5698597a878194e1b1bc7c7c2d64f0ab Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Tue, 28 May 2013 00:25:23 -0400 Subject: [PATCH 13/17] Added reset password api method --- app/controllers/api/v1/users_controller.rb | 28 ++++++++++++++++++++++ config/routes.rb | 1 + 2 files changed, 29 insertions(+) create mode 100644 app/controllers/api/v1/users_controller.rb diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb new file mode 100644 index 0000000..35176bd --- /dev/null +++ b/app/controllers/api/v1/users_controller.rb @@ -0,0 +1,28 @@ +require 'securerandom' +class Api::V1::UsersController < Api::V1::BaseController + + 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 + + new_pass = SecureRandom.hex[0,8] + 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 + end + end + + def checkout + #must use @current_user since user may not have signed in + if !@current_user.checked_in? + render :json => { "error" => "You were not even checked in."}, :status => 404 and return + else + @current_user.checkout + render :nothing => true, :status => 204 and return + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 59ff145..1e005fb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,7 @@ Velocipede::Application.routes.draw do scope 'v1', :module => :v1 do post 'checkin' => "logs#checkin", :as => "api_checkin" post 'checkout' => "logs#checkout", :as => "api_checkout" + post 'reset' => "users#password_reset", :as => "api_password_reset" end end From 0ef4e814746fd224aa505bd5cdbd51938f751ec4 Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Tue, 28 May 2013 00:27:03 -0400 Subject: [PATCH 14/17] Added reset password action column to users comp. --- app/components/users.rb | 11 ++++++++ .../users/javascripts/init_component.js | 26 ++++++++++++++++++- app/components/users_and_profiles_border.rb | 4 +-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/components/users.rb b/app/components/users.rb index 846b5fa..37c8f33 100644 --- a/app/components/users.rb +++ b/app/components/users.rb @@ -1,4 +1,13 @@ class Users < Netzke::Basepack::Grid + include Netzke::Basepack::ActionColumn + + column :reset do |c| + c.type = :action + c.actions = [{name: :reset_password, icon: :lock_break}] + c.header = "" + c.width = 20 + end + def configure(c) super c.header = false @@ -11,6 +20,8 @@ class Users < Netzke::Basepack::Grid :email, :bike__shop_id ] + + c.columns << :reset if can? :manage, User end #override with nil to remove actions diff --git a/app/components/users/javascripts/init_component.js b/app/components/users/javascripts/init_component.js index d330667..de240a6 100644 --- a/app/components/users/javascripts/init_component.js +++ b/app/components/users/javascripts/init_component.js @@ -4,8 +4,32 @@ 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: 'User'}); }, this); + }, + onResetPassword: function(record){ + user = record.data; + Ext.Msg.confirm( + "Reset Password", + "Are you sure you want to reset "+user.first_name+" "+user.last_name+"'s password?", + function(butt_id){ + if( butt_id === "yes" ){ + $.ajax({ + type: 'POST', + url: '/api/v1/reset', + dataType: 'json', + contentType: 'application/json', + processData: false, + data: JSON.stringify({"user_id": user.id}), + complete: function() { }, + success: function(data) { + Ext.Msg.alert("Success", "New Password: "+data.password); + }, + error: function(data,textStatus) { + Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]); + } + }); + } + }); } } diff --git a/app/components/users_and_profiles_border.rb b/app/components/users_and_profiles_border.rb index e91cb5a..afb31b4 100644 --- a/app/components/users_and_profiles_border.rb +++ b/app/components/users_and_profiles_border.rb @@ -9,7 +9,7 @@ class UsersAndProfilesBorder < Netzke::Base super c.header = false c.items = [ - { netzke_component: :users, header: "Users", region: :center, width: 300, split: true }, + { netzke_component: :users, header: "Users", region: :center, width: 350, split: true }, { netzke_component: :user_profiles, region: :south, height: 150, split: true}, { netzke_component: :user_logs, region: :east, split: true} ] @@ -25,5 +25,5 @@ class UsersAndProfilesBorder < Netzke::Base # store selected boss id in the session for this component's instance session[:selected_user_id] = params[:user_id] end - + end From d5662284ab084f68ddb7b294b26cfe587df1dfeb Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Tue, 28 May 2013 00:27:34 -0400 Subject: [PATCH 15/17] Added link for users to update email/password --- app/components/app_tab_panel.rb | 7 ++++++- app/components/app_tab_panel/javascripts/sign_out.js | 3 +++ app/views/devise/registrations/edit.html.erb | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/components/app_tab_panel.rb b/app/components/app_tab_panel.rb index d888c04..9ab5243 100644 --- a/app/components/app_tab_panel.rb +++ b/app/components/app_tab_panel.rb @@ -11,6 +11,11 @@ class AppTabPanel < Netzke::Basepack::TabPanel c.text = "CHECK OUT" if controller.current_user end + action :change_account_info do |c| + c.icon = :user_edit + c.text = "Change Email/Password" + end + def configure(c) #all users @@ -56,7 +61,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel end end c.prevent_header = true - c.tbar = [:sign_out, :check_out] + c.tbar = [:sign_out, :check_out, :change_account_info] c.items = @@app_tab_panel_items super end diff --git a/app/components/app_tab_panel/javascripts/sign_out.js b/app/components/app_tab_panel/javascripts/sign_out.js index 8672024..870f79e 100644 --- a/app/components/app_tab_panel/javascripts/sign_out.js +++ b/app/components/app_tab_panel/javascripts/sign_out.js @@ -17,5 +17,8 @@ }); } }); + }, + onChangeAccountInfo: function(){ + window.location.href="users/edit"; } } diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index bb66fbf..e2e50f8 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,3 +1,5 @@ +<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %> +

Edit <%= resource_name.to_s.humanize %>

<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> From adeca0e08a16eb00af05c0fdb5478fc131b00a06 Mon Sep 17 00:00:00 2001 From: "John N. Milner" Date: Thu, 30 May 2013 19:30:40 -0400 Subject: [PATCH 16/17] Corrected Check Ins view to show the checked-in user, not the user who logged the check in --- app/components/check_ins.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/check_ins.rb b/app/components/check_ins.rb index 2320b77..4763c7e 100644 --- a/app/components/check_ins.rb +++ b/app/components/check_ins.rb @@ -8,8 +8,8 @@ class CheckIns < Netzke::Basepack::Grid where("start_date >= ?", Time.zone.now.beginning_of_day); } c.columns = [ - { :name => :logged_by, :getter => lambda{ |rec| - user = User.find_by_id(rec.logger_id) + { :name => :name, :getter => lambda{ |rec| + user = User.find_by_id(rec.loggable_id) user.nil? ? "" : "#{user.first_name} #{user.last_name}" } }, From b25a4520f923df76b0430688ef4e80793d056c82 Mon Sep 17 00:00:00 2001 From: "John N. Milner" Date: Thu, 30 May 2013 19:52:57 -0400 Subject: [PATCH 17/17] Correct log criteria for Check Ins view --- app/components/check_ins.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/check_ins.rb b/app/components/check_ins.rb index 4763c7e..22b8653 100644 --- a/app/components/check_ins.rb +++ b/app/components/check_ins.rb @@ -4,7 +4,9 @@ class CheckIns < Netzke::Basepack::Grid super c.header = false c.model = "ActsAsLoggable::Log" - c.scope = lambda { |rel| rel.where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")). + c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction). + where(:loggable_type => "User"). + where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")). where("start_date >= ?", Time.zone.now.beginning_of_day); } c.columns = [