1103 lines
32 KiB
CoffeeScript
1103 lines
32 KiB
CoffeeScript
context = window
|
|
rest = context.JK.Rest()
|
|
logger = context.JK.logger
|
|
|
|
UserStore = context.UserStore
|
|
|
|
@LessonBooking = React.createClass({
|
|
|
|
mixins: [
|
|
@PostProcessorMixin,
|
|
Reflux.listenTo(AppStore, "onAppInit"),
|
|
Reflux.listenTo(UserStore, "onUserChanged")
|
|
]
|
|
|
|
onAppInit: (@app) ->
|
|
@app.bindScreen('jamclass/lesson-booking',
|
|
{beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide, navName: 'Lesson Booking'})
|
|
|
|
onUserChanged: (userState) ->
|
|
@setState({user: userState?.user})
|
|
|
|
onSlotDecision: (slot_decision) ->
|
|
@setState({slot_decision: slot_decision?.slot_decision})
|
|
|
|
onUpdateAllDecision: (update_all) ->
|
|
@setState({update_all: update_all?.update_all})
|
|
|
|
componentWillMount: () ->
|
|
|
|
componentDidMount: () ->
|
|
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}]
|
|
@root = $(@getDOMNode())
|
|
|
|
componentDidUpdate: () ->
|
|
|
|
# add friendly helpers to a slot
|
|
processSlot: (slot, booking) ->
|
|
if !slot?
|
|
return
|
|
|
|
slot.slotTime = @slotTime(slot, booking)
|
|
slot.is_recurring = slot.slot_type == 'recurring'
|
|
if slot['is_teacher_created?']
|
|
slot.creatorRole = 'teacher'
|
|
slot.creatorRoleRelative = 'teacher'
|
|
else
|
|
slot.creatorRole = 'student'
|
|
slot.creatorRoleRelative = 'student'
|
|
if context.JK.currentUserId == slot.proposer_id
|
|
slot.creatorRoleRelative = "your"
|
|
slot.mySlot = @mySlot(slot)
|
|
|
|
# for a test drive packaged delai, while we do create a slot to satisfy certain aspects of the backend, it's not 'real'. So we say 'noSlots' until someone has proposed something
|
|
noSlots: () ->
|
|
@state.booking?.no_slots
|
|
|
|
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
|
|
if !booking.post_processed
|
|
booking.post_processed = true
|
|
for slot in booking.slots
|
|
@processSlot(slot, booking)
|
|
@processSlot(booking.counter_slot, booking)
|
|
@processSlot(booking.default_slot, booking)
|
|
@processSlot(booking.alt_slot, booking)
|
|
if booking.focused_lesson?
|
|
@processSlot(booking.focused_lesson.counter_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,
|
|
booking: null,
|
|
updating: false,
|
|
updatingLesson: false
|
|
}
|
|
|
|
beforeHide: (e) ->
|
|
|
|
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) ->
|
|
parsed = @parseId(e.id)
|
|
@setState({updating: true, counterErrors: null, cancelErrors: null, purpose: parsed.purpose})
|
|
rest.getLessonBooking({
|
|
id: parsed.id,
|
|
}).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError)
|
|
|
|
hasFocusedLesson: (booking = this.state?.booking) ->
|
|
@focusedLesson(booking)?
|
|
|
|
focusedLesson: (booking = this.state?.booking) ->
|
|
booking?.focused_lesson
|
|
|
|
updateBookingState: (booking) ->
|
|
console.log("updating booking state", booking)
|
|
|
|
slot_update_all = false
|
|
counter_slot = @counteredSlot(booking)
|
|
if counter_slot?
|
|
startSlotDecision = counter_slot.id
|
|
slot_update_all = counter_slot['is_recurring?']
|
|
else
|
|
if booking.accepter_id?
|
|
startSlotDecision = 'counter'
|
|
else
|
|
startSlotDecision = booking.default_slot.id
|
|
slot_update_all = booking.default_slot['is_recurring?']
|
|
|
|
update_all = slot_update_all || !booking.focused_lesson?.id?
|
|
|
|
if booking.focused_lesson?
|
|
#booking.focused_lesson.lesson_booking = booking
|
|
@postProcessLesson(booking.focused_lesson)
|
|
if booking.next_lesson?
|
|
#booking.next_lesson.lesson_booking = booking
|
|
@postProcessLesson(booking.next_lesson)
|
|
|
|
@processBooking(booking)
|
|
|
|
|
|
if booking.onlyOption
|
|
# 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'
|
|
startSlotDecision = 'counter'
|
|
@setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false, update_all: update_all})
|
|
|
|
getLessonBookingDone: (response) ->
|
|
@updateBookingState(response)
|
|
|
|
toJamClassMain: (e) ->
|
|
e.preventDefault()
|
|
window.location.href = '/client#/jamclass'
|
|
|
|
onCancel: () ->
|
|
# what to do?
|
|
window.location.href = '/client#/jamclass'
|
|
|
|
onAccept: () ->
|
|
|
|
@setState({updatingLesson: true, counterErrors: null, cancelErrors: null})
|
|
if @state.slot_decision == 'counter'
|
|
request = @getSlotData(0)
|
|
request.id = this.state.booking.id
|
|
request.timezone = window.jstz.determine().name()
|
|
request.message = @getMessage()
|
|
request.lesson_session_id = @focusedLesson()?.id
|
|
rest.counterLessonBooking(request).done((response) => @counterLessonBookingDone(response)).fail((response) => @counterLessonBookingFail(response))
|
|
else if @state.slot_decision == 'decline'
|
|
request = {}
|
|
request.message = @getMessage()
|
|
request.id = this.state.booking.id
|
|
request.lesson_session_id = @focusedLesson()?.id
|
|
rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response)).fail((response) => @cancelLessonBookingFail(response))
|
|
else if @state.slot_decision
|
|
request = {}
|
|
request.message = @getMessage()
|
|
request.id = this.state.booking.id
|
|
request.slot = this.state.slot_decision
|
|
request.lesson_session_id = @focusedLesson()?.id
|
|
rest.acceptLessonBooking(request).done((response) => @acceptLessonBookingDone(response)).fail((response) => @acceptLessonBookingFail(response))
|
|
|
|
# {"errors":{"lesson_booking_slots":["is invalid"]},"_children":{"lesson_booking_slots":[{"errors":{}},{"errors":{}},{"errors":{"day_of_week":["must be specified"]}}]}}
|
|
dayOfWeekMissing: (errors) ->
|
|
console.log("errors", errors)
|
|
childErrors = errors._children
|
|
if childErrors
|
|
for key, errorData of childErrors
|
|
for slotErrors in errorData
|
|
if slotErrors.errors?.day_of_week?
|
|
return true
|
|
|
|
return false
|
|
acceptLessonBookingDone: (response ) ->
|
|
logger.debug("accept lesson booking done")
|
|
@updateBookingState(response)
|
|
window.location.href = '/client#/jamclass'
|
|
|
|
cancelLessonBookingDone: (response ) ->
|
|
context.JK.Banner.showNotice("Lesson Canceled", "Your lesson has been canceled.<br/><br/>We've taken you back to the JamClass home page.")
|
|
logger.debug("cancel lesson booking done")
|
|
@updateBookingState(response)
|
|
window.location.href = '/client#/jamclass'
|
|
|
|
counterLessonBookingDone: (response ) ->
|
|
context.JK.Banner.showNotice("Lesson Change Requested", "Your request for a time has been sent.<br/><br/>We've taken you back to the JamClass home page.")
|
|
logger.debug("counter lesson booking done")
|
|
@updateBookingState(response)
|
|
window.location.href = '/client#/jamclass'
|
|
|
|
counterLessonBookingFail: (jqXHR ) ->
|
|
@setState({updatingLesson: false})
|
|
logger.debug("counter lesson booking failed")
|
|
|
|
handled = false
|
|
if jqXHR.status == 422
|
|
errors = JSON.parse(jqXHR.responseText)
|
|
|
|
if @dayOfWeekMissing(errors)
|
|
handled = true
|
|
@setState({counterErrors: {errors: {day_of_week: ["must be specified"]}}})
|
|
|
|
if !handled
|
|
@app.ajaxError(arguments[0], arguments[1], arguments[2])
|
|
|
|
acceptLessonBookingFail: (response ) ->
|
|
@setState({updatingLesson: false})
|
|
logger.debug("accept lesson booking failed " + response.responseText)
|
|
@app.ajaxError(arguments[0], arguments[1], arguments[2])
|
|
|
|
cancelLessonBookingFail: (jqXHR) ->
|
|
@setState({updatingLesson: false})
|
|
logger.debug("cancel lesson booking failed", jqXHR)
|
|
handled = false
|
|
|
|
if jqXHR.status == 422
|
|
errors = JSON.parse(jqXHR.responseText)
|
|
if errors.errors?.base?
|
|
handled = true
|
|
window.JK.Banner.showAlert("Unable to Cancel Lesson", errors.errors?.base[0])
|
|
if !handled
|
|
@app.ajaxError(arguments[0], arguments[1], arguments[2])
|
|
|
|
getMessage: () ->
|
|
@root.find('textarea.message').val()
|
|
|
|
getSlotData: (position) ->
|
|
$slot = @root.find('.slot-' + (position + 1))
|
|
picker = $slot.find('.date-picker')
|
|
|
|
hour = $slot.find('.hour').val()
|
|
minute = $slot.find('.minute').val()
|
|
am_pm = $slot.find('.am_pm').val()
|
|
|
|
#update_all = $slot.find('input.update-all').is(':checked') && @isRecurring()
|
|
update_all = @state.update_all && @isRecurring()
|
|
|
|
if hour? and hour != ''
|
|
hour = new Number(hour)
|
|
if am_pm == 'PM'
|
|
hour += 12
|
|
else
|
|
hour = null
|
|
|
|
if minute? and minute != ''
|
|
minute = new Number(minute)
|
|
else
|
|
minute = null
|
|
|
|
if !update_all
|
|
date = picker.datepicker("getDate")
|
|
if date?
|
|
date = context.JK.formatDateYYYYMMDD(date)
|
|
else
|
|
day_of_week = $slot.find('.day_of_week').val()
|
|
|
|
|
|
{hour: hour, minute: minute, date: date, day_of_week: day_of_week, update_all: update_all}
|
|
|
|
student: () ->
|
|
@state.booking?.user
|
|
|
|
teacher: () ->
|
|
@state.booking?.teacher
|
|
|
|
otherRole: () ->
|
|
if @teacherViewing()
|
|
'student'
|
|
else
|
|
'teacher'
|
|
|
|
other: () ->
|
|
if @teacherViewing()
|
|
@student()
|
|
else if @studentViewing()
|
|
@teacher()
|
|
else
|
|
null
|
|
|
|
myself: () ->
|
|
if @teacherViewing()
|
|
@teacher()
|
|
else if @studentViewing()
|
|
@student()
|
|
else
|
|
null
|
|
|
|
neverAccepted: () ->
|
|
!this.state.booking?.accepter_id?
|
|
|
|
defaultSlot: () ->
|
|
@state.booking?.default_slot
|
|
|
|
counteredSlot: (booking = @state.booking) ->
|
|
slot = null
|
|
if @hasFocusedLesson(booking)
|
|
focused = @focusedLesson(booking)
|
|
if focused.status == 'countered'
|
|
slot = focused.counter_slot
|
|
if !slot?
|
|
# only consider the booking slot if it's present and if it's marked is_recurring?
|
|
if booking?.counter_slot?['is_recurring?']
|
|
slot = booking?.counter_slot
|
|
|
|
slot
|
|
|
|
canceler: () ->
|
|
if @student().id == this.state.booking?.canceler_id
|
|
@student()
|
|
else
|
|
@teacher()
|
|
|
|
counterer: () ->
|
|
if @counteredSlot()?['is_teacher_created?']
|
|
@teacher()
|
|
else
|
|
@student()
|
|
|
|
otherCountered: () ->
|
|
@myself().id == @counterer().id
|
|
|
|
studentCanceled: () ->
|
|
@canceler().id == @student().id
|
|
|
|
selfCanceled: () ->
|
|
@canceler().id == @myself().id
|
|
|
|
studentMadeDefaultSlot: () ->
|
|
@student()?.id == @defaultSlot()?.proposer_id
|
|
|
|
teacherViewing: () ->
|
|
!@studentViewing()
|
|
|
|
studentViewing: () ->
|
|
@state.booking? && @state.booking.user_id == context.JK.currentUserId
|
|
|
|
isActive: () ->
|
|
@state.booking?.active == true
|
|
|
|
isRequested: () ->
|
|
@state.booking?.status == 'requested' && !@isCounter()
|
|
|
|
isSuccessful: () ->
|
|
@displayableLesson().success
|
|
|
|
isCounter: () ->
|
|
@counteredSlot()? && !@isCanceled() && !@isSuspended()
|
|
|
|
isInitialCounter: () ->
|
|
@isCounter() && !@isActive()
|
|
|
|
isActiveCounter: () ->
|
|
@isCounter() && @isActive()
|
|
|
|
isCompleted: () ->
|
|
if @state.booking?
|
|
@displayableLesson().status == 'completed'
|
|
else
|
|
false
|
|
|
|
isApproved: () ->
|
|
if @hasFocusedLesson()
|
|
@focusedLesson().status == 'approved'
|
|
else
|
|
@state.booking?.status == 'approved'
|
|
|
|
isCanceled: () ->
|
|
|
|
cancelled = @state.booking?.status == 'canceled'
|
|
if cancelled
|
|
# if the booking is canceelled, lessons are done too. No need to check the focused lesson
|
|
return true
|
|
else
|
|
if @hasFocusedLesson()
|
|
return @focusedLesson().status == 'canceled'
|
|
else
|
|
cancelled
|
|
|
|
isSuspended: () ->
|
|
@state.booking?.status == 'suspended'
|
|
|
|
isStudentCountered: () ->
|
|
@counteredSlot()?['is_student_created?']
|
|
|
|
isTeacherCountered: () ->
|
|
@counteredSlot()?['is_teacher_created?']
|
|
|
|
isTestDrive: () ->
|
|
@state.booking?.lesson_type == 'test-drive'
|
|
|
|
isRecurring: (booking = this.state.booking) ->
|
|
booking?.recurring
|
|
|
|
lessonLength: () ->
|
|
@state.booking?.lesson_length
|
|
|
|
lessonDesc: () ->
|
|
if @isRecurring()
|
|
lessonType = "weekly recurring #{this.lessonLength()}-minute lesson"
|
|
else
|
|
lessonType = "single #{this.lessonLength()}-minute lesson"
|
|
|
|
bookedPrice: () ->
|
|
price = this.state.booking?.booked_price
|
|
if price?
|
|
if typeof price == "string"
|
|
price = new Number(price)
|
|
return price.toFixed(2)
|
|
else
|
|
return price
|
|
|
|
lessonPaymentAmt: () ->
|
|
console.log("lessonPaymentAmt")
|
|
if @state.booking?.payment_style == 'elsewhere'
|
|
'$10'
|
|
else if @state.booking?.payment_style == 'single'
|
|
"$#{this.bookedPrice()}"
|
|
else if @state.booking?.payment_style == 'weekly'
|
|
"at $#{this.bookedPrice()} per lesson"
|
|
else if @state.booking?.payment_style == 'monthly'
|
|
"monthly at $#{this.bookedPrice()} per month"
|
|
else
|
|
"$???"
|
|
|
|
selfLastToAct: () ->
|
|
if @isRequested()
|
|
if @isCounter()
|
|
@counterer().id == @myself().id
|
|
else
|
|
@studentViewing()
|
|
else if @isCounter()
|
|
@counterer().id == @myself().id
|
|
else if @isCanceled()
|
|
@canceler().id == @myself().id
|
|
else if @isSuspended()
|
|
@studentViewing()
|
|
else
|
|
false
|
|
|
|
mySlot: (slot) ->
|
|
slot.proposer_id == context.JK.currentUserId
|
|
|
|
slotsDescription: (defaultSlot, altSlot) ->
|
|
text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}."
|
|
|
|
slotTime: (slot, booking = this.state.booking) ->
|
|
if @hasFocusedLesson() || slot.slot_type == 'single' #!@isRecurring(booking)
|
|
slot.pretty_start_time
|
|
else
|
|
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
|
|
|
|
slotTimePhrase: (slot) ->
|
|
console.log("SLOT", slot)
|
|
if slot.slot_type == 'recurring'
|
|
# if @isRecurring()
|
|
"each " + @slotTime(slot)
|
|
else
|
|
@slotTime(slot)
|
|
|
|
slotMessage: (slot, qualifier = '') ->
|
|
@messageBlock(slot.mySlot,slot[qualifier + 'message'] )
|
|
|
|
messageBlock: (selfWroteMessage, message) ->
|
|
|
|
if selfWroteMessage
|
|
whoSaid = "You said:"
|
|
else
|
|
whoSaid = "Message from #{this.other().first_name}:"
|
|
|
|
if message && message.length > 0
|
|
description = `<table>
|
|
<tbody>
|
|
<td className="description">{whoSaid}</td>
|
|
<td className="message">{message}</td>
|
|
</tbody>
|
|
</table>`
|
|
description
|
|
|
|
dayTime: (slot) ->
|
|
if slot.hour > 11
|
|
hour = slot.hour - 12
|
|
if hour == 0
|
|
hour = 12
|
|
am_pm = 'pm'
|
|
else
|
|
hour = slot.hour
|
|
if hour == 0
|
|
hour = 12
|
|
am_pm = 'am'
|
|
|
|
"#{context.JK.padString(hour.toString(), 2)}:#{context.JK.padString(slot.minute.toString(), 2)}#{am_pm} (#{slot.pretty_timezone})"
|
|
|
|
displayableLesson: () ->
|
|
lesson = @focusedLesson()
|
|
|
|
if !lesson?
|
|
lesson = @state.booking.next_lesson
|
|
|
|
lesson
|
|
|
|
isStartingSoonOrNow: () ->
|
|
# 60 minutes before
|
|
|
|
startTime = new Date(@displayableLesson().scheduled_start).getTime()
|
|
endTime = (startTime + @lessonLength() * 60)
|
|
now = new Date().getTime()
|
|
|
|
now > startTime && now < endTime
|
|
|
|
|
|
isNow: () ->
|
|
startTime = new Date(@displayableLesson().scheduled_start).getTime()
|
|
endTime = (startTime + @lessonLength() * 60)
|
|
now = new Date().getTime()
|
|
|
|
now > startTime && now < endTime
|
|
|
|
isPast: () ->
|
|
new Date().getTime() > new Date(@displayableLesson().scheduled_start).getTime()
|
|
|
|
isMissed: () ->
|
|
@displayableLesson().missed
|
|
|
|
sessionLink: () ->
|
|
link = "/client#/session/#{this.displayableLesson().music_session_id}"
|
|
|
|
`<a href={link}>JOIN SESSION</a>`
|
|
|
|
nextLessonSummaryWithAvatar: () ->
|
|
`<div className="row">
|
|
{this.userHeader(this.other())}
|
|
{this.nextLessonSummary()}
|
|
</div>`
|
|
|
|
nextLessonSummaryRow: () ->
|
|
if @isActive()
|
|
if @isNow()
|
|
data =`<p>You should join this session immediately: {this.sessionLink()}</p>`
|
|
else if @isPast()
|
|
data =`<p>This lesson is over.</p>`
|
|
else
|
|
data = `<p>This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}</p>`
|
|
else if @isRequested()
|
|
data = `<p>This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}</p>`
|
|
`<div className="row">
|
|
{data}
|
|
</div>`
|
|
nextLessonSummary: () ->
|
|
if @isActive()
|
|
if @isNow()
|
|
`<p>You should join this session immediately: {this.sessionLink()}</p>`
|
|
else if @isPast()
|
|
`<p>This lesson is over.</p>`
|
|
else
|
|
`<p>This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}</p>`
|
|
else if @isRequested()
|
|
`<p>This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}</p>`
|
|
|
|
renderCancelLesson: () ->
|
|
`<div>
|
|
<h3>Cancel this Lesson</h3>
|
|
<p>If you would like to cancel this lesson, you have to do so 24 hours in advance.</p>
|
|
<a className="button-orange cancel-lesson" onClick={this.onCancelLesson}>CANCEL LESSON</a>
|
|
</div>`
|
|
|
|
dayOfWeek: (slot) ->
|
|
switch slot.day_of_week
|
|
when 0 then "Sunday"
|
|
when 1 then "Monday"
|
|
when 2 then "Tuesday"
|
|
when 3 then "Wednesday"
|
|
when 4 then "Thursday"
|
|
when 5 then "Friday"
|
|
when 6 then "Saturday"
|
|
|
|
userHeader: (user) ->
|
|
photo_url = user?.photo_url
|
|
if !photo_url?
|
|
photo_url = '/assets/shared/avatar_generic.png'
|
|
|
|
`<div className="user-header">
|
|
<div className="avatar">
|
|
<img src={photo_url}/>
|
|
</div>
|
|
<div className="user-name">
|
|
{user.name}
|
|
</div>
|
|
</div>`
|
|
|
|
decisionProps: (slots) ->
|
|
{
|
|
onSlotDecision: this.onSlotDecision,
|
|
onUpdateAllDecision: this.onUpdateAllDecision,
|
|
initial: this.neverAccepted(),
|
|
counter: this.isCounter(),
|
|
is_recurring: this.isRecurring(),
|
|
slot_decision: this.state.slot_decision,
|
|
update_all: this.state.update_all,
|
|
slots: slots,
|
|
otherRole: this.otherRole(),
|
|
onUserDecision: this.onAccept,
|
|
onUserCancel: this.onCancel,
|
|
disabled: this.state.updatingLesson,
|
|
selfLastToAct: this.selfLastToAct(),
|
|
counterErrors: this.state.counterErrors,
|
|
cancelErrors: this.state.cancelErrors,
|
|
focusedLesson: this.focusedLesson(),
|
|
noSlots: this.noSlots()
|
|
}
|
|
|
|
render: () ->
|
|
if @state.updating
|
|
|
|
return @renderLoading()
|
|
|
|
else if @teacherViewing()
|
|
|
|
return @renderTeacher()
|
|
|
|
else if @studentViewing()
|
|
|
|
return @renderStudent()
|
|
|
|
else
|
|
|
|
return @renderLoading()
|
|
|
|
completedHeader: () ->
|
|
if @isNow()
|
|
header = 'the lesson is scheduled for right now!'
|
|
else
|
|
if @isMissed()
|
|
header = "this lesson was #{this.displayableLesson().displayStatus.toLowerCase()}"
|
|
else if @isSuspended()
|
|
header = 'this lesson was suspended due to billing issues'
|
|
else
|
|
header = 'this lesson is over'
|
|
|
|
approvedHeader: () ->
|
|
if @isCompleted()
|
|
if @isMissed()
|
|
header = "this lesson was #{this.displayableLesson().displayStatus.toLowerCase()}"
|
|
else if @isSuspended()
|
|
header = 'this lesson was suspended due to billing issues'
|
|
else
|
|
header = 'this lesson is over'
|
|
else if @isNow()
|
|
header = 'the lesson is scheduled for right now!'
|
|
else if @isPast()
|
|
header = 'this lesson is over'
|
|
else
|
|
header = 'this lesson is coming up soon'
|
|
header
|
|
|
|
renderTeacher: () ->
|
|
if @isRequested()
|
|
header = 'respond to lesson request'
|
|
content = @renderTeacherRequested()
|
|
|
|
else if @isCounter()
|
|
if @isTeacherCountered()
|
|
header = 'your proposed alternate day/time is still pending'
|
|
else
|
|
header = 'student has proposed an alternate day/time'
|
|
content = @renderTeacherCountered()
|
|
|
|
|
|
else if @isApproved()
|
|
header = @approvedHeader()
|
|
content = @renderTeacherApproved()
|
|
|
|
else if @isCompleted()
|
|
header = @completedHeader()
|
|
content = @renderTeacherComplete()
|
|
|
|
else if @isCanceled()
|
|
|
|
header = "this lesson was #{this.displayableLesson()?.displayStatus?.toLowerCase()}"
|
|
content = @renderTeacherCanceled()
|
|
|
|
else if @isSuspended()
|
|
|
|
header = 'This lesson has been suspended'
|
|
content = @renderTeacherSuspended()
|
|
|
|
return `<div className="content-body-scroller">
|
|
<Nav/>
|
|
|
|
<h2>{header}</h2>
|
|
|
|
{content}
|
|
<br className="clearall"/>
|
|
|
|
</div>`
|
|
|
|
|
|
renderStudent: () ->
|
|
if @isRequested()
|
|
header = 'your lesson has been requested'
|
|
content = @renderStudentRequested()
|
|
|
|
else if @isCounter()
|
|
if @isTeacherCountered()
|
|
header = 'teacher has proposed an alternate day/time'
|
|
else
|
|
header = 'your proposed alternate day/time is still pending'
|
|
content = @renderTeacherCountered()
|
|
|
|
else if @isApproved()
|
|
header = @approvedHeader()
|
|
content = @renderStudentApproved()
|
|
|
|
else if @isCompleted()
|
|
header = @completedHeader()
|
|
content = @renderStudentComplete()
|
|
|
|
else if @isCanceled()
|
|
|
|
if @neverAccepted() && @studentViewing() && !@studentCanceled()
|
|
header = "we're sorry, but your lesson request has been declined"
|
|
else
|
|
header = "this lesson was #{this.displayableLesson()?.displayStatus?.toLowerCase()}"
|
|
|
|
content = @renderStudentCanceled()
|
|
|
|
else if @isSuspended()
|
|
|
|
header = 'this lesson has been suspended'
|
|
content = @renderStudentSuspended()
|
|
|
|
|
|
`<div className="content-body-scroller">
|
|
<Nav/>
|
|
|
|
<h2>{header}</h2>
|
|
|
|
{content}
|
|
<br className="clearall"/>
|
|
|
|
</div>`
|
|
|
|
renderLoading: () ->
|
|
header = 'Loading ...'
|
|
`<div className="content-body-scroller">
|
|
<Nav/>
|
|
|
|
<h2>{header}</h2>
|
|
|
|
<br className="clearall"/>
|
|
|
|
</div>`
|
|
|
|
|
|
renderStudentRequested: () ->
|
|
`<div className="contents">
|
|
<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>`
|
|
|
|
joinSessionNow: (e) ->
|
|
e.preventDefault()
|
|
|
|
SessionActions.enterSession(@displayableLesson().music_session.id)
|
|
|
|
updateCreditCard: (e) ->
|
|
window.location.href="/client#/account/paymentHistory"
|
|
|
|
createDetail: () ->
|
|
if @hasFocusedLesson() || !@isRecurring()
|
|
if @onlyOption() && @rescheduling()
|
|
if @noSlots()
|
|
detail = `<p className="proposing-new-time no-slot">You are proposing a date/time for this lesson.</p>`
|
|
else
|
|
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
|
|
if @noSlots()
|
|
detail = `<p className="generic-time-stmt no-slot">Your lesson has no scheduled time yet.</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()
|
|
|
|
renderTeacherComplete: () ->
|
|
@renderTeacherApproved()
|
|
|
|
renderStudentApproved: () ->
|
|
|
|
if @isCompleted()
|
|
if @isMissed()
|
|
whatNow = null
|
|
summary = `<div className="row">
|
|
{this.userHeader(this.displayableLesson().missedUser)}
|
|
<p>This lesson was missed by {this.displayableLesson().missedRole}.</p>
|
|
</div>`
|
|
else if @isSuspended()
|
|
whatNow = `<div className="row">
|
|
<h3>What Now?</h3>
|
|
<p>You should update your credit card info. <a onClick={this.updateCreditCard}>update credit card</a></p>
|
|
</div>`
|
|
else
|
|
summary = null
|
|
else if @isNow()
|
|
|
|
if @studentMadeDefaultSlot()
|
|
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
|
|
|
detail = @createDetail()
|
|
summary = `<div className="row">
|
|
{this.userHeader(this.teacher())}
|
|
<p>Has accepted your lesson request.</p>
|
|
{detail}
|
|
{message}
|
|
</div>`
|
|
|
|
whatNow = `<div className="row">
|
|
<h3>What Now?</h3>
|
|
<p>You should join the lesson session as soon as possible. <a onClick={this.joinSessionNow}>join session now</a></p>
|
|
{this.nextLessonSummary()}
|
|
</div>`
|
|
else if @isPast()
|
|
if @studentMadeDefaultSlot()
|
|
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
|
|
|
detail = @createDetail()
|
|
summary = `<div className="row">
|
|
{this.userHeader(this.teacher())}
|
|
<p>Has accepted your lesson request.</p>
|
|
{detail}
|
|
{message}
|
|
</div>`
|
|
else
|
|
if @studentMadeDefaultSlot()
|
|
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
|
|
|
detail = @createDetail()
|
|
|
|
summary = `<div className="row">
|
|
{this.userHeader(this.teacher())}
|
|
<p>Has accepted your lesson request.</p>
|
|
{detail}
|
|
{message}
|
|
</div>`
|
|
decision = `<LessonBookingDecision {...this.decisionProps([])} />`
|
|
|
|
`<div className="contents">
|
|
{summary}
|
|
{whatNow}
|
|
{decision}
|
|
</div>`
|
|
|
|
|
|
renderStudentCanceled: () ->
|
|
@renderCanceled()
|
|
|
|
renderStudentSuspended: () ->
|
|
|
|
# <p className="description">Message from {this.other().first_name}:</p>
|
|
# <div className="message"></div>
|
|
renderStudentCountered: () ->
|
|
@renderCountered()
|
|
|
|
|
|
renderTeacherRequested: () ->
|
|
|
|
if @isTestDrive()
|
|
action = `<p className="action">Has requested a TestDrive {this.lessonLength()}-minute lesson, for which you will be paid $10.</p>`
|
|
else
|
|
action = `<p className="action">Has requested a {this.lessonDesc()} lesson, for which you will be paid {this.lessonPaymentAmt()}.</p>`
|
|
|
|
|
|
if @noSlots()
|
|
slots = []
|
|
else
|
|
slots = [this.state.booking.default_slot]
|
|
if this.state.booking.alt_slot?
|
|
slots.push(this.state.booking.alt_slot)
|
|
`<div className="contents">
|
|
<div className="row">
|
|
{this.userHeader(this.other())}
|
|
{action}
|
|
{this.slotMessage(this.state.booking.default_slot)}
|
|
</div>
|
|
<LessonBookingDecision {...this.decisionProps(slots)} />
|
|
</div>`
|
|
|
|
renderTeacherApproved: () ->
|
|
if @isCompleted()
|
|
if @isMissed()
|
|
whatNow = null
|
|
summary = `<div className="row">
|
|
{this.userHeader(this.displayableLesson().missedUser)}
|
|
<p>This lesson was missed by {this.displayableLesson().missedRole}.</p>
|
|
</div>`
|
|
else if @isSuspended()
|
|
whatNow = `<div className="row">
|
|
<h3>What Now?</h3>
|
|
<p>You should update your credit card info. <a onClick={this.updateCreditCard}>update credit card</a></p>
|
|
</div>`
|
|
else
|
|
summary = null
|
|
else if @isNow()
|
|
|
|
if @studentMadeDefaultSlot()
|
|
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
|
|
|
detail = @createDetail()
|
|
summary = `<div className="row">
|
|
{this.userHeader(this.student())}
|
|
<p>Is ready to take the lesson.</p>
|
|
{detail}
|
|
{message}
|
|
</div>`
|
|
|
|
whatNow = `<div className="row">
|
|
<h3>What Now?</h3>
|
|
<p>You should join the lesson session as soon as possible. <a onClick={this.joinSessionNow}>join session now</a></p>
|
|
{this.nextLessonSummary()}
|
|
</div>`
|
|
else if @isPast()
|
|
|
|
else
|
|
if @studentMadeDefaultSlot()
|
|
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
|
|
|
detail = @createDetail()
|
|
|
|
summary = `<div className="row">
|
|
{this.userHeader(this.teacher())}
|
|
<p>Has accepted your lesson request.</p>
|
|
{detail}
|
|
{message}
|
|
</div>`
|
|
decision = `<LessonBookingDecision {...this.decisionProps([])} />`
|
|
|
|
`<div className="contents">
|
|
{summary}
|
|
{whatNow}
|
|
{decision}
|
|
</div>`
|
|
|
|
|
|
renderTeacherCanceled: () ->
|
|
@renderCanceled()
|
|
|
|
renderTeacherSuspended: () ->
|
|
|
|
renderTeacherCountered: () ->
|
|
@renderCountered()
|
|
|
|
renderCanceled: () ->
|
|
canceler = @canceler()
|
|
myself = @myself()
|
|
initial = @neverAccepted()
|
|
|
|
if initial
|
|
if @studentViewing()
|
|
if @studentCanceled()
|
|
action = `<p>You canceled this lesson request.</p>`
|
|
else
|
|
action = `<p>Has declined your lesson request.</p>`
|
|
else
|
|
if @studentCanceled()
|
|
action = `<p>Has canceled this lesson request.</p>`
|
|
else
|
|
action = `<p>You declined this lesson request.</p>`
|
|
|
|
if @studentViewing()
|
|
|
|
if @studentCanceled()
|
|
blurb = `<p>We're sorry this scheduling attempt did not working out for you. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request</p>`
|
|
else
|
|
blurb = `<p>We're sorry this instructor has declined your request. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request</p>`
|
|
|
|
whatNow =
|
|
`<div className="row">
|
|
<h3>What Now?</h3>
|
|
{blurb}
|
|
<a href="/client#/jamclass/searchOptions" className="search-for-more-teachers">SEARCH INSTRUCTORS NOW</a>
|
|
</div>`
|
|
`<div className="contents">
|
|
<div className="row">
|
|
{this.userHeader(canceler)}
|
|
{action}
|
|
{this.messageBlock(this.selfCanceled(), this.state.booking.cancel_message)}
|
|
</div>
|
|
{whatNow}
|
|
</div>`
|
|
|
|
renderCountered: () ->
|
|
counterer = @counterer()
|
|
myself = @myself()
|
|
|
|
phrase = this.slotTimePhrase(this.counteredSlot())
|
|
action = `<p>Has suggested a different time for your lesson.</p>`
|
|
detail = `<p className="counter-stmt">Proposed alternate day/time is {phrase}</p>`
|
|
|
|
`<div className="contents">
|
|
<div className="row">
|
|
{this.userHeader(counterer)}
|
|
{action}
|
|
{detail}
|
|
{this.slotMessage(this.counteredSlot())}
|
|
</div>
|
|
<LessonBookingDecision {...this.decisionProps([this.counteredSlot()])} />
|
|
</div>`
|
|
|
|
}) |