slew of fixes for recurring sessions, canceling sessions, and minor UI issues
This commit is contained in:
parent
197313dfc9
commit
55a53325dd
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
context = window
|
||||
rest = context.JK.Rest()
|
||||
logger = context.JK.logger
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 it’s scheduled to start should be avoided, as it’s 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
|
||||
|
|
|
|||
|
|
@ -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())}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue