slew of jamclass fixes

This commit is contained in:
Seth Call 2016-05-31 19:20:03 -05:00
parent e449139b63
commit 920d648a2b
33 changed files with 520 additions and 218 deletions

View File

@ -7,6 +7,8 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
config.clear_action_items!
config.filters = false
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
index do
column "Email" do |user|
user.email

View File

@ -295,7 +295,10 @@ require "jam_ruby/models/affiliate_distribution"
require "jam_ruby/models/teacher_intent"
require "jam_ruby/models/school"
require "jam_ruby/models/school_invitation"
require "jam_ruby/models/teacher_instrument"
require "jam_ruby/models/teacher_subject"
require "jam_ruby/models/teacher_language"
require "jam_ruby/models/teacher_genre"
include Jampb
module JamRuby

View File

@ -16,7 +16,8 @@ module JamRuby
has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres"
# teachers
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_genres"
has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_genres
has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre"
# jam tracks
has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "genre_id"

View File

@ -44,7 +44,8 @@ module JamRuby
has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::ActiveMusicSession", :join_table => "genres_music_sessions"
# teachers
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_instruments"
has_many :teachers, :class_name => "JamRuby::Teacher", through: :teachers_instruments
has_many :teachers_instruments, class_name: "JamRuby::TeacherInstrument"
def self.standard_list
return Instrument.where('instruments.popularity > 0').order('instruments.description ASC')

View File

@ -2,7 +2,8 @@ module JamRuby
class Language < ActiveRecord::Base
include HtmlSanitize
html_sanitize strict: [:name, :description]
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_languages"
has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_languages
has_many :teachers_languages, class_name: "JamRuby::TeacherLanguage"
def self.english_sort
languages = Language.order(:description)

View File

@ -33,6 +33,9 @@ module JamRuby
validate :validate_proposer
before_validation :before_validation
def is_recurring?
slot_type == SLOT_TYPE_RECURRING
end
def before_validation
if proposer.nil?
self.proposer = container.student

View File

@ -5,7 +5,7 @@ module JamRuby
include HtmlSanitize
html_sanitize strict: [:cancel_message]
attr_accessor :accepting, :creating, :countering, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student
attr_accessor :accepting, :creating, :countering, :countering_flag, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student
@@log = Logging.logger[LessonSession]
@ -61,6 +61,7 @@ module JamRuby
validates :post_processed, inclusion: {in: [true, false]}
validate :validate_creating, :if => :creating
validate :validate_countering, :if => :countering_flag
validate :validate_accepted, :if => :accepting
validate :validate_canceled, :if => :canceling
validate :validate_autocancel, :if => :autocanceling
@ -505,6 +506,22 @@ module JamRuby
end
end
def validate_countering
if counter_slot.nil?
errors.add(:counter_slot, "must be specified")
elsif !approved_before? && (status == STATUS_REQUESTED || status == STATUS_COUNTERED)
if recurring && !counter_slot.update_all
errors.add(:counter_slot, "Only 'update all' counter-proposals are allowed for un-approved, recurring lessons")
end
if recurring && !counter_slot.is_recurring?
errors.add(:counter_slot, "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons")
end
end
self.countering_flag = false
end
def validate_accepted
if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
self.errors.add(:status, "This session is already #{self.status_was}.")
@ -791,6 +808,7 @@ module JamRuby
update_all = slot.update_all || !lesson_booking.recurring
self.countering = true
self.countering_flag = true
slot.proposer = proposer
slot.lesson_session = self
slot.message = message

View File

@ -2,6 +2,7 @@ module JamRuby
class Subject < ActiveRecord::Base
include HtmlSanitize
html_sanitize strict: [:name, :description]
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_subjects"
has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_subjects
has_many :teachers_subjects, class_name: "JamRuby::TeacherSubject"
end
end

View File

@ -4,10 +4,14 @@ module JamRuby
html_sanitize strict: [:biography, :website]
attr_accessor :validate_introduction, :validate_basics, :validate_pricing
attr_accessible :genres, :teacher_experiences, :experiences_teaching, :experiences_education, :experiences_award
has_and_belongs_to_many :genres, :class_name => "JamRuby::Genre", :join_table => "teachers_genres", :order => "description"
has_and_belongs_to_many :instruments, :class_name => "JamRuby::Instrument", :join_table => "teachers_instruments", :order => "description"
has_and_belongs_to_many :subjects, :class_name => "JamRuby::Subject", :join_table => "teachers_subjects", :order => "description"
has_and_belongs_to_many :languages, :class_name => "JamRuby::Language", :join_table => "teachers_languages", :order => "description"
has_many :genres, :class_name => "JamRuby::Genre", :through => :teachers_genres # , :order => "description"
has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre"
has_many :instruments, :class_name => "JamRuby::Instrument", through: :teachers_instruments # , :order => "description"
has_many :teachers_instruments, class_name: "JamRuby::TeacherInstrument"
has_many :subjects, :class_name => "JamRuby::Subject", :through => :teachers_subjects # , :order => "description"
has_many :teachers_subjects, class_name: "JamRuby::TeacherSubject"
has_many :languages, :class_name => "JamRuby::Language", :through => :teachers_languages # , :order => "description"
has_many :teachers_languages, class_name: "JamRuby::TeacherLanguage"
has_many :teacher_experiences, :class_name => "JamRuby::TeacherExperience"
has_many :experiences_teaching, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'teaching'}
has_many :experiences_education, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'education'}
@ -36,7 +40,7 @@ module JamRuby
validate :offer_duration, :if => :validate_pricing
validate :teaches_ages, :if => :validate_basics
default_scope { includes(:genres).order('created_at desc') }
#default_scope { includes(:genres).order('created_at desc') }
after_save :update_profile_pct
@ -53,7 +57,7 @@ module JamRuby
limit ||= 20
limit = limit.to_i
query = User.joins(:teacher)
query = User.unscoped.joins(:teacher)
# only show teachers with ready for session set to true
query = query.where('teachers.ready_for_session_at IS NOT NULL')

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherGenre < ActiveRecord::Base
self.table_name = "teachers_genres"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :genre, class_name: "JamRuby::Genre"
validates :teacher, presence:true
validates :genre, presence: true
end
end

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherInstrument < ActiveRecord::Base
self.table_name = "teachers_instruments"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :instrument, class_name: "JamRuby::Instrument"
validates :teacher, presence:true
validates :instrument, presence: true
end
end

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherLanguage < ActiveRecord::Base
self.table_name = "teachers_languages"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :language, class_name: "JamRuby::Language"
validates :teacher, presence:true
validates :language, presence: true
end
end

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherSubject < ActiveRecord::Base
self.table_name = "teachers_subjects"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :subject, class_name: "JamRuby::Subject"
validates :teacher, presence:true
validates :subject, presence: true
end
end

View File

@ -51,7 +51,7 @@ module JamRuby
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
has_many :reviews, :class_name => "JamRuby::Review"
has_one :review_summary, :class_name => "JamRuby::ReviewSummary"
has_one :review_summary, :class_name => "JamRuby::ReviewSummary", as: :target
# calendars (for scheduling NOT in music_session)
has_many :calendars, :class_name => "JamRuby::Calendar"

View File

@ -12,8 +12,57 @@ describe LessonSession do
describe "counter" do
describe "recurring" do
it "" do
it "counter madness" do
lesson = monthly_lesson(user, teacher, {accept:false})
# start with the student
invalid = FactoryGirl.build(:lesson_booking_slot_single, update_all: false)
lesson.counter({proposer: user, message: "crumble and bumble", slot: invalid})
lesson.errors.any?.should be_true
lesson.errors[:counter_slot].should eql ["Only 'update all' counter-proposals are allowed for un-approved, recurring lessons", "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons"]
lesson.reload
lesson.counter_slot.should be_nil
counter1 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true)
lesson.counter({proposer: user, message: "crumble and bumble take 2", slot: counter1})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter1.id
lesson.lesson_booking.counter_slot.id.should eql counter1.id
counter2 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true)
lesson.counter({proposer: teacher, message: "crumble and bumble take 3", slot: counter2})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter2.id
lesson.lesson_booking.counter_slot.id.should eql counter2.id
lesson.accept({accepter: user, message: "burp", slot: counter2})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_APPROVED
counter3 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: false)
lesson.counter({proposer: user, message: "crumble and bumble take 4", slot: counter3})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter3.id
lesson.lesson_booking.counter_slot.id.should eql counter3.id
counter4 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true)
lesson.counter({proposer: teacher, message: "crumble and bumble take 5", slot: counter4})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter4.id
lesson.lesson_booking.counter_slot.id.should eql counter4.id
end
end
end

View File

@ -45,18 +45,20 @@ describe Teacher do
it "instruments" do
teacher = FactoryGirl.create(:teacher, ready_for_session_at: Time.now)
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
teachers.length.should eq 0
#teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
#teachers.length.should eq 0
teacher.instruments << Instrument.find('acoustic guitar')
teacher.save!
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
teachers.length.should eq 1
teachers[0].should eq(teacher.user)
#teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
#teachers.length.should eq 1
#teachers[0].should eq(teacher.user)
teacher.instruments << Instrument.find('electric guitar')
teacher.save!
#teacher.instruments << Instrument.find('electric guitar')
#teacher.save!
puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!---"
teachers.length.should eq 1
teachers[0].should eq(teacher.user)
end

View File

@ -9,6 +9,7 @@ describe GoogleAnalyticsEvent do
end
describe "track band analytics" do
pending "job is commented out"
it 'reports first recording' do
ResqueSpec.reset!
user = FactoryGirl.create(:user)
@ -26,6 +27,7 @@ describe GoogleAnalyticsEvent do
end
it 'reports first real session' do
pending "job is commented out"
ResqueSpec.reset!
JamRuby::GoogleAnalyticsEvent::BandSessionTracker.should have_schedule_size_of(0)
user = FactoryGirl.create(:user)
@ -72,6 +74,7 @@ describe GoogleAnalyticsEvent do
ResqueSpec.reset!
end
it 'reports size increment' do
pending "job is commented out"
user = FactoryGirl.create(:user)
music_session = FactoryGirl.create(:active_music_session,
:creator => user,
@ -86,6 +89,7 @@ describe GoogleAnalyticsEvent do
end
it 'reports duration' do
pending "job is commented out"
user = FactoryGirl.create(:user)
JamRuby::GoogleAnalyticsEvent::SessionDurationTracker.should have_schedule_size_of(0)
music_session = FactoryGirl.create(:active_music_session,

View File

@ -38,7 +38,7 @@ require 'timecop'
require 'resque_spec/scheduler'
# uncomment this to see active record logs
# ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base)
ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base)
include JamRuby

View File

@ -82,7 +82,7 @@
}
function subtlePulse($element) {
$element.find('.bt-content').pulse({'background-color' : '#868686'}, {pulses: 3}, function() { $element.css('background-color', '#980006')})
$element.find('.bt-content').pulse({'background-color' : '#868686'}, {pulses: 2, duration: 1000, interval:300}, function() { $element.css('background-color', '#980006')})
}
helpBubble.rotateJamTrackLandingBubbles = function($preview, $video, $ctaButton, $alternativeCta) {
@ -203,7 +203,7 @@
helpBubble.showBuyTestDrive = function($element, $offsetParent, user, callback) {
return context.JK.onceBubble($element, 'side-buy-test-drive', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) {
subtlePulse(container)
var $bookNow = container('a.book-now')
var $bookNow = container.find('a.book-now')
$bookNow.off('click').on('click', function(e) {
e.preventDefault()
callback()

View File

@ -0,0 +1,135 @@
@JamClassSearchHeader = React.createClass({
mixins: [Reflux.listenTo(@UserStore, "onUserChanged"), Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchStore")]
onTeacherSearchStore: ()->
getInitialState: () ->
{user: null}
onUserChanged: (@user) ->
@setState({user: @user?.user})
createSearchDescription: () ->
searchOptions = TeacherSearchStore.getState()
summary = ''
if searchOptions.onlyMySchool && @state.user?.school_id?
summary += "From My School Only"
instruments = searchOptions.instruments
if instruments? && instruments.length > 0
if instruments.length == 1
bit = "Instrument = #{InstrumentStore.display(instruments[0])}"
else
instruments.length > 1
bit = "Instruments = #{InstrumentStore.display(instruments[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
subjects = searchOptions.subjects
if subjects? && subjects.length > 0
if subjects.length == 1
bit = "Subject = #{SubjectStore.display(subjects[0])}"
else
subjects.length > 1
bit = "Subjects = #{SubjectStore.display(subjects[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
genres = searchOptions.genres
if genres? && genres.length > 0
if genres.length == 1
bit = "Genre = #{GenreStore.display(genres[0])}"
else
genres.length > 1
bit = "Genres = #{GenreStore.display(genres[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
languages = searchOptions.languages
if languages? && languages.length > 0
if languages.length == 1
bit = "Language = #{LanguageStore.display(languages[0])}"
else
languages.length > 1
bit = "Languages = #{LanguageStore.display(languages[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
if searchOptions.teaches_beginner || searchOptions.teaches_intermediate || searchOptions.teaches_advanced
bit = "Teaches "
qualifier = ''
if searchOptions.teaches_beginner
qualifier += "Beginner"
if searchOptions.teaches_intermediate
if qualifier.length > 0
qualifier += ", "
qualifier += "Intermediate"
if searchOptions.teaches_advanced
if qualifier.length > 0
qualifier += ", "
qualifier += "Advanced"
if summary.length > 0
summary += ', '
summary += " #{bit}#{qualifier}"
if searchOptions.student_age?
if summary.length > 0
summary += ', '
summary += "Student Age = #{searchOptions.student_age}"
if searchOptions.years_teaching?
if summary.length > 0
summary += ', '
summary += "Years Teaching = #{searchOptions.years_teaching}"
if searchOptions.location?.country?
if summary.length > 0
summary += ', '
summary += "Country = #{searchOptions.location.country}"
if searchOptions.location?.region?
if summary.length > 0
summary += ', '
summary += "Region = #{searchOptions.location.region}"
if summary.length == 0
summary = 'all teachers'
summary
render: () ->
searchDesc = @createSearchDescription()
if @props.teacher
complete = `<span className="search-results-options">
<a href="/client#/teachers/search" className="results-text link">Search Results</a>&nbsp;:&nbsp;
<span className="teacher-name">{this.props.teacher.name}</span>
</span>`
else
complete =
`<span className="search-results-options">
<span className="search-description">
<span className="results-text">Search Results / </span>
<span className="search-summary">{searchDesc}</span>
</span>
</span>`
`<div className="jamclass-search-header">
<a href="/client#/home">JamKazam Home</a>&nbsp;:&nbsp;
<a href="/client#/jamclass">JamClass Home</a>&nbsp;:&nbsp;
<a className="teacher-search-options" href="/client#/jamclass/searchOptions">Teachers Search</a><span
className="teacher-quote"> : </span>
{complete}
</div>`
})

View File

@ -704,14 +704,21 @@ proficiencyDescriptionMap = {
render: () ->
if @state.user?
avatar = context.JK.resolveAvatarUrl(@state.user.photo_url);
if @state.user?.teacher?
mainContent = @mainContent()
profileLeft = @profileLeft()
else
if context.JK.currentUserId? && @state.user?.id == context.JK.currentUserId
noTeacherProfile = `<div className="no-teacher-profile">You have no teacher profile defined yet. <a onClick={this.editTeacherProfile}>Start making one now.</a></div>`
if @state.user?
if @state.user.teacher?
mainContent = @mainContent()
profileLeft = @profileLeft()
else
noTeacherProfile = `<div className="no-teacher-profile">This user has no teacher profile.</div>`
if context.JK.currentUserId? && @state.user?.id == context.JK.currentUserId
noTeacherProfile = `<div className="no-teacher-profile">You have no teacher profile defined yet. <a onClick={this.editTeacherProfile}>Start making one now.</a></div>`
else
noTeacherProfile = `<div className="no-teacher-profile">This user has no teacher profile.</div>`
mainContent = `<div className="">
{noTeacherProfile}
</div>`
else
noTeacherProfile = `<div><div className="loading-profile">Loading profile...</div><div className="spinner spinner-large"></div></div>`
mainContent = `<div className="">
{noTeacherProfile}
</div>`
@ -730,10 +737,7 @@ proficiencyDescriptionMap = {
</div>`
`<div className="content-body-scroller">
<div className="profile-header profile-head">
<div className="user-header">
<h2 id="username"></h2>
</div>
<JamClassSearchHeader teacher={this.state.user}/>
{actionButtons}
<br clear="all"/><br />

View File

@ -28,7 +28,7 @@ ProfileActions = @ProfileActions
refreshing: false
getInitialState: () ->
{searchOptions: {}, results: [], user: null}
{searchOptions: {}, results: [], user: null, searching: false}
onAppInit: (@app) ->
@app.bindScreen('teachers/search', {beforeShow: @beforeShow, afterShow: @afterShow, afterHide: @afterHide})
@ -58,7 +58,7 @@ ProfileActions = @ProfileActions
@needToSearch = true
onTeacherSearchResultsStore: (results) ->
results.searching = false
#results.searching = false
@refreshing = false
@contentBodyScroller.find('.infinite-scroll-loader-2').remove()
@setState(results)
@ -89,7 +89,7 @@ ProfileActions = @ProfileActions
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
$scroller.append('<div class="infinite-scroll-loader-2">... Loading more Teachers ...</div>')
@refreshing = true
@setState({searching: true})
#@setState({searching: true})
logger.debug("refreshing more teachers for infinite scroll")
TeacherSearchResultsActions.nextPage()
)
@ -149,7 +149,9 @@ ProfileActions = @ProfileActions
moreAboutTeacher: (user, e) ->
e.preventDefault()
ProfileActions.viewTeacherProfile(user, '/client#/teachers/search', 'BACK TO TEACHER SEARCH')
context.location = "/client#/profile/teacher/#{user.id}"
#ProfileActions.viewTeacherProfile(user, '/client#/teachers/search', 'BACK TO TEACHER SEARCH')
bookTestDrive: (user, e) ->
e.preventDefault()
@ -210,162 +212,60 @@ ProfileActions = @ProfileActions
target.trigger( 'destroy.dot' );
teacherBio.css('height', 'auto')
createSearchDescription: () ->
searchOptions = TeacherSearchStore.getState()
summary = ''
if searchOptions.onlyMySchool && @state.user?.school_id?
summary += "From My School Only"
instruments = searchOptions.instruments
if instruments? && instruments.length > 0
if instruments.length == 1
bit = "Instrument = #{InstrumentStore.display(instruments[0])}"
else
instruments.length > 1
bit = "Instruments = #{InstrumentStore.display(instruments[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
subjects = searchOptions.subjects
if subjects? && subjects.length > 0
if subjects.length == 1
bit = "Subject = #{SubjectStore.display(subjects[0])}"
else
subjects.length > 1
bit = "Subjects = #{SubjectStore.display(subjects[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
genres = searchOptions.genres
if genres? && genres.length > 0
if genres.length == 1
bit = "Genre = #{GenreStore.display(genres[0])}"
else
genres.length > 1
bit = "Genres = #{GenreStore.display(genres[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
languages = searchOptions.languages
if languages? && languages.length > 0
if languages.length == 1
bit = "Language = #{LanguageStore.display(languages[0])}"
else
languages.length > 1
bit = "Languages = #{LanguageStore.display(languages[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
if searchOptions.teaches_beginner || searchOptions.teaches_intermediate || searchOptions.teaches_advanced
bit = "Teaches "
qualifier = ''
if searchOptions.teaches_beginner
qualifier += "Beginner"
if searchOptions.teaches_intermediate
if qualifier.length > 0
qualifier += ", "
qualifier += "Intermediate"
if searchOptions.teaches_advanced
if qualifier.length > 0
qualifier += ", "
qualifier += "Advanced"
if summary.length > 0
summary += ', '
summary += " #{bit}#{qualifier}"
if searchOptions.student_age?
if summary.length > 0
summary += ', '
summary += "Student Age = #{searchOptions.student_age}"
if searchOptions.years_teaching?
if summary.length > 0
summary += ', '
summary += "Years Teaching = #{searchOptions.years_teaching}"
if searchOptions.location?.country?
if summary.length > 0
summary += ', '
summary += "Country = #{searchOptions.location.country}"
if searchOptions.location?.region?
if summary.length > 0
summary += ', '
summary += "Region = #{searchOptions.location.region}"
if summary.length == 0
summary = 'all teachers'
summary
render: () ->
searchDesc = @createSearchDescription()
resultsJsx = []
for user in @state.results
if @state.currentPage == 1 && @state.searching
resultsJsx = `<div className="spinner spinner-large"></div>`
else
for user in @state.results
photo_url = user.photo_url
if !photo_url?
photo_url = '/assets/shared/avatar_generic.png'
photo_url = user.photo_url
if !photo_url?
photo_url = '/assets/shared/avatar_generic.png'
bio = user.teacher.biography
if !bio?
bio = 'No bio'
bio = user.teacher.biography
if !bio?
bio = 'No bio'
school_on_school = user.teacher.school_id? && @state.user?.school_id? && user.teacher.school_id == @state.user.school_id
school_on_school = user.teacher.school_id? && @state.user?.school_id? && user.teacher.school_id == @state.user.school_id
bookSingleBtn = null
bookTestDriveBtn = null
bookSingleBtn = null
bookTestDriveBtn = null
if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?'])
bookTestDriveBtn = `<a className="button-orange try-test-drive" onClick={this.bookTestDrive.bind(this, user)}>BOOK TESTDRIVE LESSON</a>`
else
bookSingleBtn = `<a className="button-orange try-normal" onClick={this.bookNormalLesson.bind(this, user)}>BOOK LESSON</a>`
resultsJsx.push(`<div key={user.id} className="teacher-search-result" data-teacher-id={user.id}>
<div className="user-avatar">
<div className="avatar small">
<img src={photo_url} />
if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?'])
bookTestDriveBtn = `<a className="button-orange try-test-drive" onClick={this.bookTestDrive.bind(this, user)}>BOOK TESTDRIVE LESSON</a>`
else
bookSingleBtn = `<a className="button-orange try-normal" onClick={this.bookNormalLesson.bind(this, user)}>BOOK LESSON</a>`
resultsJsx.push(`<div key={user.id} className="teacher-search-result" data-teacher-id={user.id}>
<div className="user-avatar">
<div className="avatar small">
<img src={photo_url} />
</div>
<div className="user-name">
{user.name}
</div>
</div>
<div className="user-name">
{user.name}
<div className="user-info">
<div className="teacher-bio">
{bio}
<a className="readmore" onClick={this.readMore}>more</a>
</div>
<div className="teacher-actions">
<a className="button-orange more-about-teacher" onClick={this.moreAboutTeacher.bind(this, user)}>MORE ABOUT THIS TEACHER</a>
{bookTestDriveBtn}
{bookSingleBtn}
</div>
</div>
</div>
<div className="user-info">
<div className="teacher-bio">
{bio}
<a className="readmore" onClick={this.readMore}>more</a>
</div>
<div className="teacher-actions">
<a className="button-orange more-about-teacher" onClick={this.moreAboutTeacher.bind(this, user)}>MORE ABOUT THIS TEACHER</a>
{bookTestDriveBtn}
{bookSingleBtn}
</div>
</div>
<br className="clearall" />
</div>`)
<br className="clearall" />
</div>`)
`<div className="content-body-scroller">
<div className="screen-content">
<div className="header">
<a href="/client#/home">JamKazam Home</a>&nbsp;:&nbsp;
<a href="/client#/jamclass">JamClass Home</a>&nbsp;:&nbsp;
<a className="teacher-search-options" href="/client#/jamclass/searchOptions" >Teachers Search</a><span className="teacher-quote"> :</span>
<span className="search-results-options">
<span className="search-description">
<span className="results-text">Search Results / </span>
<span className="search-summary">{searchDesc}</span>
</span>
</span>
<JamClassSearchHeader/>
</div>
<div className="results">
{resultsJsx}

View File

@ -45,8 +45,8 @@ rest = context.JK.Rest()
</li>
</ol>
<p>While you're getting this done, if you want to learn more about all the nifty features you can access in
JamClass and in JamKazam in general, you can check out our online <a href="" target="_blank"
onClick={alert.bind('not yet')}>JamClass
JamClass and in JamKazam in general, you can check out our online <a target="_blank"
href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles">JamClass
User Guide</a>.</p>
</div>`
`<div className="top-container">

View File

@ -11,6 +11,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
results: []
page: 1
limit: 20
searching: false
init: ->
# Register with the app store to get @app
@ -22,6 +23,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
onReset: () ->
@results = []
@page = 1
@searching = true
@changed()
query = @createQuery()
@ -29,11 +31,13 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
rest.searchTeachers(query)
.done((response) =>
@next = response.next
@searching = false
@results.push.apply(@results, response.entries)
@changed()
)
.fail((jqXHR, textStatus, errorMessage) =>
@searching = false
@changed()
@app.ajaxError(jqXHR, textStatus, errorMessage)
)
@ -42,18 +46,22 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
query = @createQuery()
@searching = true
rest.searchTeachers(query)
.done((response) =>
@next = response.next
@results.push.apply(@results, response.entries)
@searching = false
@changed()
)
.fail((jqXHR, textStatus, errorMessage) =>
@searching = false
@app.ajaxError(jqXHR, textStatus, errorMessage)
@changed()
)
getState: () ->
({results: @results, next: @next, currentPage: @page})
({results: @results, next: @next, currentPage: @page, searching: @searching})
changed:() ->
@trigger(@getState())

View File

@ -190,7 +190,7 @@
logger.debug("marking all unassigned inputs length=(" + $allInputs.length + ")")
var maxTries = 20;
var maxTries = 12; // we only allow up to 6 tracks, 12 inputs
for(var i = 0; i < maxTries; i++) {
$unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)');
@ -1148,12 +1148,12 @@
}
var $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)')
$allInputs.eq(0).iCheck('check').attr('checked', 'checked')
var firstInputDomNode = $allInputs.get(0)
var maxTries = 20;
var maxTries = 36;
for(var i = 0; i < maxTries; i++) {
var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked')
var $allInputs = $inputChannels.find('input[type="checkbox"]')
var firstInputDomNode = $allInputs.get(0)
if ($assignedInputs.length == 1) {
break;
}
@ -1170,9 +1170,7 @@
}
})
}
}
}
function onFocus() {

View File

@ -194,7 +194,22 @@
.years {float:right}
}
.profileNavActions {
margin-right: -3px;
right:20px;
top:10px;
position:absolute;
}
.spinner-large {
width:200px;
height:200px;
position:relative;
margin:0 auto;
}
.loading-profile {
text-align:center;
color:white;
}
.ratings-block {

View File

@ -1,5 +1,41 @@
@import "client/common";
.jamclass-search-header {
a {
font-size:16px;
text-decoration:underline;
margin-bottom:5px;
}
.search-results-options {
font-size:16px;
color:$ColorTextTypical;
}
.search-summary {
font-size:11px;
}
.search-description {
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align:middle;
}
.search-summary {
line-height:16px;
vertical-align: middle;
}
.results-text {
&.link {
}
}
}
#teacherSearch {
div[data-react-class="TeacherSearchScreen"] {
height:100%;
@ -9,29 +45,21 @@
padding:20px;
}
.header {
.jamclass-search-header {
margin-bottom:10px;
}
a {
font-size:16px;
text-decoration:underline;
margin-bottom:5px;
}
.search-results-options {
font-size:16px;
color:$ColorTextTypical;
}
.spinner-large {
width:200px;
height:200px;
position:relative;
margin:0 auto;
}
a.readmore {
display:none;
}
.search-summary {
font-size:11px;
}
.teacher-search-result {
@include border_box_sizing;
clear:both;
@ -108,17 +136,7 @@
padding-right: 31px;
margin-bottom: 20px;
}
.search-description {
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align:middle;
}
.search-summary {
line-height:16px;
vertical-align: middle;
}
.result-text {
line-height:16px;
}

View File

@ -955,10 +955,13 @@ class ApiUsersController < ApiController
end
def lookup_user
User.includes([{musician_instruments: :instrument},
{band_musicians: :user},
{genre_players: :genre},
:bands, :instruments, :genres, :jam_track_rights, :affiliate_partner])
:bands, :instruments, :genres, :jam_track_rights,
:affiliate_partner, :reviews, :review_summary, :recordings,
:teacher => [:subjects, :instruments, :languages, :genres, :teachers_languages, :experiences_teaching, :experiences_award, :experiences_education, :reviews, :review_summary]])
.find(params[:id])
end

View File

@ -3,7 +3,7 @@ node :next do |page|
end
node :entries do |page|
partial "api_users/show", object: @users
partial "api_users/show_teacher_index", object: @users
end
node :total_entries do |page|

View File

@ -0,0 +1,8 @@
object @user
attributes :id, :first_name, :last_name, :name, :photo_url
child :teacher do |teacher|
attributes :id, :biography, :school_id
end

View File

@ -23,6 +23,21 @@ describe ApiTeachersController do
Teacher.destroy_all
end
describe "index" do
it "simple" do
@teacher = Teacher.save_teacher(
user,
years_teaching: 21,
biography: BIO,
genres: [genre1, genre2],
instruments: [instrument1, instrument2],
languages: [language1, language2],
subjects: [subject1, subject2]
)
get :index
end
end
describe "creates" do
it "simple" do
post :create, biography: BIO, format: 'json'

View File

@ -171,4 +171,42 @@ describe "JamClassScreen", :js => true, :type => :feature, :capybara_feature =>
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
end
describe "counter" do
it "test drive" do
lesson = testdrive_lesson(user, teacher_user, {accept: false})
fast_signin(teacher_user, "/client#/jamclass")
validate_status(lesson, 'Requested')
jamclass_hover_option('reschedule', 'Reschedule Lesson')
# no popup should show in this case, because it's not yet scheduled
# we should be at lesson status page
find('h2', text: 'respond to lesson request')
screenshot
counter_day
# switch to student
switch_user(user, "/client#/jamclass")
validate_status(lesson, 'Requested')
jamclass_hover_option('status', 'View Status')
find('h2', text: 'this lesson is coming up soon')
screenshot
approve_lesson(lesson)
jamclass_hover_option('reschedule', 'Reschedule Lesson')
find('#banner h1', text: 'Lesson Change Requested')
find('#banner .close-btn').trigger(:click)
counter_day
end
end
end

View File

@ -60,6 +60,33 @@ def fill_out_single_lesson
end
def validate_status(lesson, expectedStatus)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: expectedStatus)
end
def jamclass_hover_option(lesson, option, text)
# open up hover
find('tr[data-lesson-session-id="' + lesson.id + '"] .lesson-session-actions-btn').trigger(:click)
find('li[data-lesson-option="' + option + '"] a', visible: false, text: text).trigger(:click)
end
def counter_day
fill_in "alt-date-input", with: date_picker_format(Date.new(Date.today.year, Date.today.month + 1, 17))
find('td a', text: '17').trigger(:click)
sleep 3
find('.schedule.button-orange').trigger(:click)
find('#banner h1', text: 'Lesson Change Requested')
find('#banner .close-btn').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Requested')
end
def approve_lesson(lesson, slot = lesson.lesson_booking.default_slot)
find(".slot-decision-field[data-slot-id=\"#{slot.id}\"] ins", visible: false).trigger(:click)
find('.schedule.button-orange').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
def fill_out_payment(expected = nil)
fill_in 'card-number', with: '4111111111111111'