slew of fixes for recurring sessions, canceling sessions, and minor UI issues

This commit is contained in:
Seth Call 2016-05-30 16:43:55 -05:00
parent 197313dfc9
commit 55a53325dd
19 changed files with 412 additions and 88 deletions

View File

@ -426,6 +426,9 @@ module JamRuby
self.errors.add(:status, "This lesson is already #{self.status}.")
end
if self.accepter.nil?
self.errors.add(:accepter, "No one has been indicated as accepting the lesson")
end
self.accepting = false
end

View File

@ -173,7 +173,7 @@ module JamRuby
duration = lesson_length * 60 # convert from minutes to seconds
end_time = start_time + duration
if with_timezone
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{tz.pretty_name}"
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} (#{tz.pretty_name})"
else
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
end

View File

@ -741,6 +741,14 @@ describe LessonBooking do
UserMailer.deliveries.clear
Timecop.freeze(7.days.ago)
mailer = mock
mailer.should_receive(:deliver).exactly(2).times
UserMailer.should_receive(:student_lesson_booking_canceled).and_return(mailer)
UserMailer.should_receive(:teacher_lesson_booking_canceled).and_return(mailer)
UserMailer.should_receive(:student_lesson_canceled).exactly(0).times
UserMailer.should_receive(:teacher_lesson_canceled).exactly(0).times
lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: true})
lesson_session.errors.any?.should be_false
lesson_session.reload
@ -748,7 +756,6 @@ describe LessonBooking do
booking.reload
booking.status.should eql LessonSession::STATUS_CANCELED
booking.canceler.should eql user
UserMailer.deliveries.length.should eql 2
end
end
describe "rescheduling" do

View File

@ -83,7 +83,7 @@ def book_lesson(user, teacher, options)
end
if options[:accept]
lesson.accept({message: 'Yeah I got this', slot: slots[0]})
lesson.accept({message: 'Yeah I got this', slot: slots[0], accepter: teacher})
lesson.errors.any?.should be_false
lesson.reload
lesson.slot.should eql slots[0]

View File

@ -1,3 +1,4 @@
context = window
rest = context.JK.Rest()
logger = context.JK.logger

View File

@ -125,6 +125,7 @@ ChatActions = @ChatActions
else
purpose = null
additional = null
if msg.purpose == 'Notation File'
additional = `<a className="additional" onClick={this.notationClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>`
else if msg.purpose == 'Audio File'

View File

@ -129,9 +129,9 @@ LessonTimerActions = context.LessonTimerActions
rest.checkLessonReschedule({id: lesson.id, update_all: recurring})
.done((response) => (
if recurring
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.lesson_booking_id
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.lesson_booking_id + "_rescheduling"
else
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id + "_rescheduling"
))
.fail((jqXHR) => (
if jqXHR.status == 422
@ -179,7 +179,13 @@ LessonTimerActions = context.LessonTimerActions
context.JK.Banner.showAlert('late cancellation warning',
'Cancelling a lesson less than 24 hours before its scheduled to start should be avoided, as its an inconvenience to the student. Repeated violations of this policy will negatively affect your teacher score.')
@refreshLesson(lesson.id)
lessonsFromBooking = []
for check in @lessons()
if check.lesson_booking_id == lesson.lesson_booking_id
lessonsFromBooking.push(check)
for check in lessonsFromBooking
@refreshLesson(check.id)
cancelLessonBookingFail: (jqXHR) ->
@app.ajaxError(jqXHR)
@ -224,12 +230,12 @@ LessonTimerActions = context.LessonTimerActions
if lesson.recurring
buttons = []
buttons.push({
name: 'THIS SESSION',
name: 'THIS LESSON',
buttonStyle: 'button-orange',
click: (() => (@rescheduleSelected(lesson, false)))
})
buttons.push({
name: 'ALL SESSIONS',
name: 'ALL LESSONS',
buttonStyle: 'button-orange',
click: (() => (@rescheduleSelected(lesson, true)))
})
@ -251,13 +257,13 @@ LessonTimerActions = context.LessonTimerActions
verbLower = 'cancel'
if !lesson.isRequested || lesson.recurring
buttons = []
buttons.push({name: 'CANCEL', buttonStyle: 'button-grey'})
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
buttons.push({
name: 'THIS SESSION',
name: 'CANCEL THIS LESSON',
buttonStyle: 'button-orange',
click: (() => (@cancelSelected(lesson, false)))
})
buttons.push({name: 'ALL SESSIONS', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, true)))})
buttons.push({name: 'CANCEL ALL LESSONS', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, true)))})
context.JK.Banner.show({
title: 'Select One',
html: "Do you wish to all #{verbLower} all lessons or just the one selected?",
@ -521,7 +527,10 @@ LessonTimerActions = context.LessonTimerActions
else
unreadMessages = null
timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone
if lessonData.status == 'countered'
timeStmt = lessonData.counter_slot.pretty_scheduled_start_with_timezone
else
timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone
if lessonData.times? && lessonData.displayStatus == 'Scheduled'
if lessonData.times.startingSoon

View File

@ -50,6 +50,44 @@ UserStore = context.UserStore
slot.creatorRoleRelative = "your"
slot.mySlot = @mySlot(slot)
processBooking:(booking) ->
booking.neverAccepted = booking.accepter_id?
booking.isCounter = booking.counter_slot? && booking.status != 'canceled' && booking.status != 'suspended'
booking.studentViewing = booking.user_id == context.JK.currentUserId
booking.teacherViewing = !booking.studentViewing
booking.isRequested = booking.status == 'requested' && !booking.isCounter
booking.isCanceled = booking.status == 'canceled'
booking.isSuspended = booking.status == 'suspended'
if booking.isCounter
if booking.counter_slot['is_teacher_created?']
booking.countererId = booking.teacher_id
else
booking.countererId = booking.user_id
selfLastToAct = false
if booking.isRequested
selfLastToAct = booking.studentViewing
else if booking.isCounter
selfLastToAct = booking.countererId == context.JK.currentUserId
else if booking.isCanceled
selfLastToAct = booking.canceler_id == context.JK.currentUserId
else if booking.isSuspended
selfLastToAct = booking.studentViewing
else
selfLastToAct = false
booking.selfLastToAct = selfLastToAct
multipleOptions = false
if booking.neverAccepted
multipleOptions = !(!booking.isCounter && booking.selfLastToAct)
else if booking.isCounter
multipleOptions = !booking.selfLastToAct
else
multipleOptions = false
onlyOption = !multipleOptions
booking.onlyOption = onlyOption
#nextProps.slot_decision = 'counter'
componentWillUpdate: (nextProps, nextState) ->
if nextState.booking?
booking = nextState.booking
@ -61,6 +99,35 @@ UserStore = context.UserStore
@processSlot(booking.default_slot, booking)
@processSlot(booking.alt_slot, booking)
onlyOption:() ->
#(this.props.initial && !this.props.selfLastToAct )|| !(this.props.counter && !this.props.selfLastToAct)
#@initialRequestSlotsVisible() || !@counteredSlotVisible()
#(this.props.initial && this.props.selfLastToAct ) || !(this.props.counter && !this.props.selfLastToAct)
#(@neverAccepted() && @selfLastToAct()) || !(@isCounter() && !@selfLastToAct())
!@multipleOptions()
multipleOptions: () ->
if @neverAccepted()
!(!@isCounter() && @selfLastToAct())
else if this.props.counter
!@selfLastToAct()
else
false
initialRequestSlotsVisible: () ->
console.log("initialRequestSlotsVisible: " + this.neverAccepted() )
#this.neverAccepted() && this.selfLastToAct()
# is there a counter slot showing
counteredSlotVisible: () ->
console.log("isCounter " + this.isCounter() + ", this.selfLastToAct()" + this.selfLastToAct())
this.isCounter() && !this.selfLastToAct()
counterSlotVisible: () ->
true
getInitialState: () ->
{
user: null,
@ -73,14 +140,28 @@ UserStore = context.UserStore
beforeShow: (e) ->
parseId: (id) ->
result = {purpose: null}
bits = id.split('_')
if bits.length == 1
result.id = id
else if bits.length > 1
result.id =bits[0]
result.purpose = bits[1]
else
result.id = id
result
afterShow: (e) ->
@setState({updating: true, counterErrors: null, cancelErrors: null})
parsed = @parseId(e.id)
@setState({updating: true, counterErrors: null, cancelErrors: null, purpose: parsed.purpose})
rest.getLessonBooking({
id: e.id,
id: parsed.id,
}).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError)
hasFocusedLesson: () ->
this.state.booking.focused_lesson?.id?
@focusedLesson()?
focusedLesson: () ->
this.state?.booking?.focused_lesson
@ -91,10 +172,10 @@ UserStore = context.UserStore
if booking.counter_slot?
startSlotDecision = booking.counter_slot.id
else
if booking.accepter_id?
startSlotDecision = 'counter'
else
startSlotDecision = booking.default_slot.id
if booking.accepter_id?
startSlotDecision = 'counter'
else
startSlotDecision = booking.default_slot.id
update_all = !booking.focused_lesson?.id?
@ -105,6 +186,13 @@ UserStore = context.UserStore
#booking.next_lesson.lesson_booking = booking
@postProcessLesson(booking.next_lesson)
@processBooking(booking)
#if booking.onlyOptions
# you see two options (accept, and propose new) if it's a counter and you are not the last to act
# the only choice possible in most cases is to propose a new time
# nextState.slot_decision = 'counter'
@setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false, update_all: update_all})
getLessonBookingDone: (response) ->
@ -213,7 +301,8 @@ UserStore = context.UserStore
minute = $slot.find('.minute').val()
am_pm = $slot.find('.am_pm').val()
update_all = $slot.find('input.update-all').is(':checked') && @isRecurring()
#update_all = $slot.find('input.update-all').is(':checked') && @isRecurring()
update_all = @state.update_all && @isRecurring()
if hour? and hour != ''
hour = new Number(hour)
@ -227,7 +316,7 @@ UserStore = context.UserStore
else
minute = null
if !@isRecurring()
if !update_all
date = picker.datepicker("getDate")
if date?
date = context.JK.formatDateYYYYMMDD(date)
@ -399,10 +488,10 @@ UserStore = context.UserStore
text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}."
slotTime: (slot, booking = this.state.booking) ->
if @isRecurring(booking)
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
else
if @hasFocusedLesson() || !@isRecurring(booking)
slot.pretty_start_time
else
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
slotTimePhrase: (slot) ->
if @isRecurring()
@ -704,9 +793,10 @@ UserStore = context.UserStore
renderStudentRequested: () ->
`<div className="contents">
<div className="row">
<div className="row request-sent">
{this.userHeader(this.myself())}
Your request has been sent. You will receive an email when {this.teacher().name} responds.
{this.createDetail()}
</div>
<LessonBookingDecision {...this.decisionProps([])} />
</div>`
@ -719,6 +809,22 @@ UserStore = context.UserStore
updateCreditCard: (e) ->
window.location.href="/client#/account/paymentHistory"
createDetail: () ->
if @hasFocusedLesson() || !@isRecurring()
if @onlyOption() && @rescheduling()
detail = `<p className="proposing-new-time">You are proposing to change the date/time of the lesson currently scheduled for {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="generic-time-stmt">Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
else
if @onlyOption() && @rescheduling()
detail = `<p className="proposing-new-time">You are proposing to change the date/time of the lesson currently scheduled for {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="generic-time-stmt">Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
detail
rescheduling: () ->
@state.purpose == 'rescheduling'
renderStudentComplete: () ->
@renderStudentApproved()
@ -746,10 +852,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring()
detail = `<p>Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p>Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
detail = @createDetail()
summary = `<div className="row">
{this.userHeader(this.teacher())}
<p>Has accepted your lesson request.</p>
@ -766,10 +869,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring()
detail = `<p>Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p>Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
detail = @createDetail()
summary = `<div className="row">
{this.userHeader(this.teacher())}
<p>Has accepted your lesson request.</p>
@ -780,10 +880,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring()
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
detail = @createDetail()
summary = `<div className="row">
{this.userHeader(this.teacher())}
@ -847,10 +944,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring()
detail = `<p>Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p>Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
detail = @createDetail()
summary = `<div className="row">
{this.userHeader(this.student())}
<p>Is ready to take the lesson.</p>
@ -869,10 +963,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring()
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
detail = @createDetail()
summary = `<div className="row">
{this.userHeader(this.teacher())}

View File

@ -57,6 +57,11 @@
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}, {selector: 'input.update-all', propsKey: 'update_all'}]
@root = $(@getDOMNode())
@iCheckify()
@slotDate = @root.find('.date-picker')
@slotDate.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
componentDidUpdate: () ->
@iCheckify()
@ -66,6 +71,14 @@
onSelect: ((e) => @toggleDate(e))
})
toggleDate: (e) ->
componentWillReceiveProps: (nextProps) ->
if @onlyOption()
console.log("setting it counter")
# if this isn't a counter situation, then there is no 'Accept their time', so there should only be one radio button, and we'll select that value already
@setState({"slot-decision": "counter"})
checkboxChanged: (e) ->
@ -126,10 +139,21 @@
nullOp: ()->
onlyOption: () ->
# (!this.props.initial && this.props.selfLastToAct ) || !(this.props.counter && !this.props.selfLastToAct)
!@multipleOptions()
multipleOptions: () ->
if this.props.initial
!(!this.props.counter && this.props.selfLastToAct)
else if this.props.counter
!this.props.selfLastToAct
else
false
render: () ->
showUpdateAll = !this.props.initial
#showUpdateAll = !this.props.initial
if (!this.props.initial && !this.props.counter) || this.props.selfLastToAct
userPromptHeader = `<h3>Would you like to change this lesson?</h3>`
@ -150,7 +174,7 @@
else
verb = "CANCEL"
if this.props.update_all && showUpdateAll
if this.props.update_all
actionBtnText = "#{verb} ALL LESSONS"
else
actionBtnText = "#{verb} LESSON"
@ -159,13 +183,13 @@
else
actionBtnText = "ACCEPT & UPDATE LESSON"
counterClasses={field: true, 'slot-decision-field': true, error: this.props.counterErrors?, counterSelected: this.props.slot_decision == 'counter'}
counterClasses={field: true, 'slot-decision-field': true, error: this.props.counterErrors?, counterSelected: this.props.slot_decision == 'counter', onlyOption: @onlyOption()}
if this.props.counterErrors?
errorText = window.JK.reactErrors(this.props.counterErrors, {day_of_week: 'Day' })
if this.props.is_recurring
if this.props.is_recurring && this.props.update_all
slotAltPrompt = `<div className="slot-alt-prompt">
<span className="alt-date-block">
@ -182,17 +206,18 @@
</span>
</div>`
if showUpdateAll
updateAllField =
`<div className="field update-all-field">
<input disabled={this.props.disabled} className="update-all" type="checkbox" name="update-all" readyOnly="true" onChange={this.nullOp} checked={this.props.update_all} /><label>Update all lessons</label>
</div>`
#if @props.update_all
# updateAllField =
# `<div className="field update-all-field">
# <input disabled={this.props.disabled} className="update-all" type="checkbox" name="update-all" readyOnly="true" onChange={this.nullOp} checked={this.props.update_all} /><label>Update all lessons</label>
# </div>`
updateAllField = null
else
slotAltPrompt = `<div className="slot-alt-prompt">
<span className="alt-date-block">
<span className="alt-date">Date:</span>
<input className="date-picker" type="text" data-slot={i}></input>
<input className="date-picker" name="alt-date-input" type="text" data-slot={i}></input>
</span>
<span className="alt-time-block">
<span className="alt-time">Time:</span>
@ -222,7 +247,7 @@
else
slotDetail = `<div className="slot-detail">{slot.slotTime}</div>`
slots.push(`<div key={slot.id} className="field slot-decision-field">
slots.push(`<div key={slot.id} data-slot-id={slot.id} className="field slot-decision-field">
<div className="label-area">
<input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision"
value={slot.id} readyOnly={true} defaultChecked={slot.id == this.props.slot_decision} /><label>{this.slotLabelText(i, slot)}</label>
@ -234,6 +259,12 @@
# if you have issued a counter, you should be able to withdraw it
# TODO
#cancelField = `<div className="field slot-decision-field">
# <input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision" value="decline" readyOnly={true} defaultChecked={this.props.slot_decision == 'decline'} /><label>{declineVerb} lesson
# request</label>
#</div>`
cancelField = null
`<div className="row">
<div className="column column-left">
{userPromptHeader}
@ -247,10 +278,7 @@
</div>
{slotAltPrompt}
</div>
<div className="field slot-decision-field">
<input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision" value="decline" readyOnly={true} defaultChecked={this.props.slot_decision == 'decline'} /><label>{declineVerb} lesson
request</label>
</div>
{cancelField}
{updateAllField}
</div>
</div>

View File

@ -79,6 +79,15 @@
}
}
.onlyOption {
input[type="radio"], .iradio_minimal {
display:none;
}
.slot-alt-prompt {
padding-left:0;
}
}
.slot-detail {
color: $ColorTextTypical;
display: inline-block;
@ -160,6 +169,14 @@
.user-name {
line-height:48px;
vertical-align:middle;
color:white;
}
.request-sent {
color:$ColorTextTypical;
.generic-time-stmt, .proposing-new-time {
margin-top:10px !important;
}
}
.avatar {
position:absolute;

View File

@ -52,7 +52,7 @@ class ApiLessonBookingsController < ApiController
specified_slot.timezone = params[:timezone]
slots << specified_slot
end
@lesson_booking = LessonBooking.book_free(current_user, teacher, slots, params[:description])
if @lesson_booking.errors.any?
@ -129,10 +129,10 @@ class ApiLessonBookingsController < ApiController
def accept
next_lesson = @lesson_booking.next_lesson
result = next_lesson.accept({
message: params[:message],
slot: params[:slot],
accepter: current_user
})
message: params[:message],
slot: params[:slot],
accepter: current_user
})
if result.errors.any?
if result.is_a?(JamRuby::LessonBooking)
@ -159,16 +159,20 @@ class ApiLessonBookingsController < ApiController
slot = LessonBookingSlot.new
if @lesson_booking.recurring
slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING
slot.day_of_week = params[:day_of_week]
if params[:update_all]
slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING
else
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
end
else
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
end
if params[:date].present?
day = params[:date]
day = Date.parse(day) if day && !day.include?('NaN')
slot.preferred_day = day
end
slot.day_of_week = params[:day_of_week]
if params[:date].present?
day = params[:date]
day = Date.parse(day) if day && !day.include?('NaN')
slot.preferred_day = day
end
slot.hour = params[:hour]
slot.minute = params[:minute]
@ -176,10 +180,10 @@ class ApiLessonBookingsController < ApiController
slot.update_all = params[:update_all]
result = target_lesson.counter({
proposer: current_user,
message: params[:message],
slot: slot
})
proposer: current_user,
message: params[:message],
slot: slot
})
if result.errors.any?
if result.is_a?(JamRuby::LessonBooking)
@ -188,7 +192,7 @@ class ApiLessonBookingsController < ApiController
recursive_errors(result, [:lesson_booking_slots])
else
raise "unknown response type in counter #{result.class}"
end
end
return
end
@lesson_booking.reload
@ -203,10 +207,10 @@ class ApiLessonBookingsController < ApiController
@lesson_session = target_lesson
result = target_lesson.cancel({
canceler: current_user,
message: params[:message],
update_all: true
})
canceler: current_user,
message: params[:message],
update_all: params[:update_all]
})
if result.errors.any?
if result.is_a?(JamRuby::LessonBooking)

View File

@ -94,7 +94,7 @@ class ApiLessonSessionsController < ApiController
if params[:update_all]
# check if the next scheduled lesson is doable
if 24.hours.from_now > @lesson_session.booking.next_lesson.music_session.scheduled_start
if 24.hours.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
response = {message: 'time_limit'}
render :json => response, :status => 422
return
@ -135,6 +135,7 @@ class ApiLessonSessionsController < ApiController
end
msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, @lesson_session, 'JamKazam Recording', nil, claimed_recording)
UserMailer.lesson_attachment(me, other, @lesson_session, claimed_recording)
end
render :json => {}, :status => 200

View File

@ -40,6 +40,8 @@ class ApiMusicNotationsController < ApiController
end
msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, lesson_session, purpose, music_notation)
UserMailer.lesson_attachment(me, other, lesson_session, music_notation)
end
end

View File

@ -77,4 +77,8 @@ module MusicSessionHelper
def pretty_scheduled_start(music_session, with_timezone, shorter = false)
music_session.pretty_scheduled_start(with_timezone, shorter)
end
def pretty_scheduled_start_slot(slot, with_timezone)
slot.pretty_scheduled_start(with_timezone)
end
end

View File

@ -3,7 +3,8 @@ object @lesson_session
attributes :id, :lesson_booking_id, :lesson_type, :duration, :price, :teacher_complete, :student_complete,
:status, :student_canceled, :teacher_canceled, :student_canceled_at, :teacher_canceled_at, :student_canceled_reason,
:teacher_canceled_reason, :status, :success, :teacher_unread_messages, :student_unread_messages, :is_active?, :recurring,
:analysed, :school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled
:analysed, :school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled,
:best_display_time
node do |lesson_session|
{
@ -17,6 +18,15 @@ child(:lesson_booking => :lesson_booking) {
}
child(:counter_slot => :counter_slot) {
attributes :id, :preferred_day, :day_of_week, :hour, :minute, :slot_type, :pretty_scheduled_start, :message, :pretty_start_time, :proposer_id, :is_student_created?, :is_teacher_created?, :timezone, :pretty_timezone
node :pretty_scheduled_start_with_timezone do |slot|
pretty_scheduled_start_slot(slot, true)
end
}
child(:music_session => :music_session) {
attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat,
:band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_duration,

View File

@ -118,4 +118,57 @@ describe "JamClassScreen", :js => true, :type => :feature, :capybara_feature =>
find('#jam-class-student-screen td.displayStatusColumn', text: 'Canceled (Teacher)')
end
it "student cancels one of recurring" do
lesson = monthly_lesson(user, teacher_user, {accept: true})
lesson1 = lesson.lesson_booking.lesson_sessions[0]
lesson2 = lesson.lesson_booking.lesson_sessions[1]
fast_signin(user, "/client#/jamclass")
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Scheduled')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
# open up hover
find('tr[data-lesson-session-id="' + lesson1.id + '"] .lesson-session-actions-btn').trigger(:click)
find('li[data-lesson-option="cancel"] a', visible: false, text: 'Cancel Lesson').trigger(:click)
# confirm cancelation -
find('#banner a', text: 'CANCEL THIS LESSON').trigger(:click)
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
switch_user(teacher_user, "/client#/jamclass")
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
it "student cancels all recurring" do
lesson = monthly_lesson(user, teacher_user, {accept: true})
lesson1 = lesson.lesson_booking.lesson_sessions[0]
lesson2 = lesson.lesson_booking.lesson_sessions[1]
lesson1.recurring.should be_true
lesson.lesson_booking.recurring.should be_true
fast_signin(user, "/client#/jamclass")
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Scheduled')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
# open up hover
find('tr[data-lesson-session-id="' + lesson1.id + '"] .lesson-session-actions-btn').trigger(:click)
find('li[data-lesson-option="cancel"] a', visible: false, text: 'Cancel Lesson').trigger(:click)
# confirm cancelation -
find('#banner a', text: 'CANCEL ALL LESSONS').trigger(:click)
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
lesson1.reload
lesson2.reload
lesson1.status.should eql LessonSession::STATUS_CANCELED
lesson2.status.should eql LessonSession::STATUS_CANCELED
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
switch_user(teacher_user, "/client#/jamclass")
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
end

View File

@ -25,6 +25,7 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
find('h2', text: 'your lesson has been requested')
find('p.proposing-new-time')
screenshot
end
@ -32,11 +33,14 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
it "approved" do
lesson = testdrive_lesson(user, teacher, {accept:true, finish:false})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id)
lesson.reload
lesson.status.should
lesson.lesson_booking.accepter_id.should_not be_nil
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id )
find('h2', text: 'this lesson is coming up soon')
find('p.lesson-time', "will take place each")
find('p.generic-time-stmt')
screenshot
end
@ -185,4 +189,90 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
find('#lesson-booking', text: 'US Central Time')
end
it "requested recurring with focused lesson" do
lesson = monthly_lesson(user, teacher, {accept: false})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id)
find('.request-sent', text: 'Your request has been sent.')
page.should_not have_selector('p.proposing-new-time')
# the teacher can either accept or propose a new time, so they see both
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id)
find('p.action', text: 'Has requested')
page.should_not have_selector('p.generic-time-stmt')
find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_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
it "requested recurring with unfocused lesson" do
lesson = monthly_lesson(user, teacher, {accept: false})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id)
find('.request-sent', text: 'Your request has been sent.')
page.should_not have_selector('p.proposing-new-time')
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id)
find('p.action', text: 'Has requested')
page.should_not have_selector('p.generic-time-stmt')
find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_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
it "accepted recurring with focused lesson" do
lesson = monthly_lesson(user, teacher, {accept: true})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id + "_rescheduling")
find('p.proposing-new-time')
# the teacher can either accept or propose a new time, so they see both
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id + "_rescheduling")
find('p.proposing-new-time')
# change the lesson time
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')
switch_user(user, "/client#/jamclass")
end
it "accepted recurring with unfocused lesson" do
lesson = monthly_lesson(user, teacher, {accept: true})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id + "_rescheduling")
find('.request-sent', text: 'Your request has been sent.')
page.should_not have_selector('p.proposing-new-time')
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id + "_rescheduling")
find('p.proposing-new-time')
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(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_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
end

View File

@ -29,7 +29,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
fill_in "email", with: 'student_123@jamkazam.com'
fill_in "password", with: 'jam123'
find('.register-area ins', visible: false) .trigger(:click)
find('.register-area ins', visible: false).trigger(:click)
find('button.cta-button', text: 'SIGN UP').trigger(:click)
# this should show on the /client#/home page (WILL CHANGE)

View File

@ -31,6 +31,9 @@ def teacher_approve(lesson_session)
find('tr[data-lesson-session-id="' + lesson_session.id + '"] .displayStatusColumn', text: 'Scheduled')
end
def date_picker_format(date)
date.strftime('%a %b %d %Y')
end
def fill_out_single_lesson
find('h2', text: 'book testdrive lesson')
@ -159,7 +162,7 @@ def book_lesson(user, teacher, options)
end
if options[:accept]
lesson.accept({message: 'Yeah I got this', slot: slots[0]})
lesson.accept({message: 'Yeah I got this', slot: slots[0], accepter: teacher})
lesson.errors.any?.should be_false unless options[:no_validate]
lesson.reload
lesson.slot.should eql slots[0] unless options[:no_validate]