* decent 1st cut of lesson-booking UI done

This commit is contained in:
Seth Call 2016-04-01 08:12:24 -05:00
parent 0052b054b1
commit 78d46c7e7c
19 changed files with 343 additions and 129 deletions

View File

@ -17,8 +17,8 @@ ActiveAdmin.register JamRuby::LessonSession, :as => 'LessonSessions' do
scope("Completed" ) { |scope| scope.unscoped.completed.order('created_at desc') }
index do
column "User Link" do |lesson_sesson|
lesson_booking = lesson_sesson.lesson_booking
column "User Link" do |lesson_session|
lesson_booking = lesson_session.lesson_booking
span do
link_to "Web URL", "#{Rails.application.config.external_root_url}/client#/jamclass/lesson-booking/#{lesson_booking.id}"
end

View File

@ -135,6 +135,7 @@ CREATE TABLE lesson_booking_slots (
ALTER TABLE lesson_bookings ADD COLUMN default_slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
ALTER TABLE lesson_bookings ADD COLUMN counter_slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
ALTER TABLE lesson_sessions ADD COLUMN counter_slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
ALTER TABLE lesson_sessions ADD COLUMN slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
ALTER TABLE chat_messages ADD COLUMN target_user_id VARCHAR(64) REFERENCES users(id);

View File

@ -695,6 +695,7 @@ message LessonMessage {
optional bool student_directed = 8;
optional string purpose = 9;
optional string sender_name = 10;
optional string lesson_session_id = 11;
}
message ScheduledJamclassInvitation {
@ -705,6 +706,7 @@ message ScheduledJamclassInvitation {
optional string session_date = 5;
optional string notification_id = 6;
optional string created_at = 7;
optional string lesson_session_id = 8;
}

View File

@ -845,7 +845,7 @@ module JamRuby
mail(:to => email, :subject => subject) do |format|
format.text
format.html
format.html { render :layout => "from_user_mailer" }
end
end
@ -870,7 +870,7 @@ module JamRuby
mail(:to => email, :subject => subject) do |format|
format.text
format.html
format.html { render :layout => "from_user_mailer" }
end
end

View File

@ -1,4 +1,4 @@
<% provide(:title, @subject) %>
<% provide(:title, @subject) %>
<% provide(:photo_url, @lesson_session.canceler.resolved_photo_url) %>
<% content_for :note do %>

View File

@ -524,7 +524,7 @@ module JamRuby
)
end
def scheduled_jamclass_invitation(receiver_id, session_id, photo_url, msg, session_name, session_date, notification_id, created_at)
def scheduled_jamclass_invitation(receiver_id, session_id, photo_url, msg, session_name, session_date, notification_id, created_at, lesson_session_id)
scheduled_jamclas_invitation = Jampb::ScheduledJamclassInvitation.new(
:session_id => session_id,
:photo_url => photo_url,
@ -532,7 +532,8 @@ module JamRuby
:session_name => session_name,
:session_date => session_date,
:notification_id => notification_id,
:created_at => created_at
:created_at => created_at,
lesson_session_id: lesson_session_id
)
Jampb::ClientMessage.new(
@ -969,7 +970,7 @@ module JamRuby
end
# creates the general purpose text message
def lesson_message(receiver_id, sender_photo_url, sender_name, sender_id, msg, notification_id, music_session_id, created_at, student_directed, purpose)
def lesson_message(receiver_id, sender_photo_url, sender_name, sender_id, msg, notification_id, music_session_id, created_at, student_directed, purpose, lesson_session_id)
lesson_message = Jampb::LessonMessage.new(
:photo_url => sender_photo_url,
:sender_name => sender_name,
@ -980,7 +981,8 @@ module JamRuby
:music_session_id => music_session_id,
:created_at => created_at,
:student_directed => student_directed,
:purpose => purpose
:purpose => purpose,
:lesson_session_id => lesson_session_id
)
Jampb::ClientMessage.new(

View File

@ -131,7 +131,8 @@ module JamRuby
def next_lesson
if recurring
lesson_sessions.joins(:music_session).where("scheduled_start is not null").where("scheduled_start > ?", Time.now).order(:created_at).first
session = lesson_sessions.joins(:music_session).where("scheduled_start is not null").where("scheduled_start > ?", Time.now).order(:created_at).first
LessonSession.find(session.id) if session
else
lesson_sessions[0]
end
@ -148,10 +149,12 @@ module JamRuby
self.default_slot = slot
self.accepter = accepter
if !self.save
success = self.save
if !success
puts "unable to accept lesson booking #{errors.inspect}"
raise ActiveRecord::Rollback
end
success
end
def counter(lesson_session, proposer, slot)
@ -159,9 +162,7 @@ module JamRuby
self.lesson_booking_slots << slot
self.counter_slot = slot
#self.status = STATUS_COUNTERED
if !self.save
raise ActiveRecord::Rollback
end
self.save
end
def automatically_default_slot
@ -301,7 +302,7 @@ module JamRuby
music_session = MusicSession.create(student, {
name: "#{display_type2} JamClass taught by #{teacher.name}",
description: "This is a #{lesson_length}-minute #{display_type2} with #{teacher.name}.",
description: "This is a #{lesson_length}-minute #{display_type2} lesson with #{teacher.name}.",
musician_access: false,
fan_access: false,
genres: ['other'],
@ -377,7 +378,8 @@ module JamRuby
end
def validate_accepted
if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
# accept is multipe purpose; either accept the initial request, or a counter slot
if self.status_was != STATUS_REQUESTED && counter_slot.nil? # && self.status_was != STATUS_COUNTERED
self.errors.add(:status, "This lesson is already #{self.status}.")
end
@ -387,7 +389,7 @@ module JamRuby
def send_notices
UserMailer.student_lesson_request(self).deliver
UserMailer.teacher_lesson_request(self).deliver
Notification.send_lesson_message('accept', lesson_sessions[0], false) # TODO: this isn't quite an 'accept'
Notification.send_lesson_message('requested', lesson_sessions[0], false) # TODO: this isn't quite an 'accept'
self.sent_notices = true
self.save
end
@ -408,7 +410,7 @@ module JamRuby
elsif is_test_drive?
"TestDrive"
elsif is_normal?
"Purchased Lesson"
"Single"
end
end
@ -481,6 +483,9 @@ module JamRuby
am_pm = 'pm'
else
hour = slot.hour
if hour == 0
hour = 12
end
am_pm = 'am'
end
@ -497,7 +502,8 @@ module JamRuby
self.status = STATUS_CANCELED
self.cancel_message = message
self.canceler = canceler
if save
success = save
if success
if approved_before?
# just tell both people it's cancelled, to act as confirmation
Notification.send_lesson_message('canceled', next_lesson, false)
@ -523,6 +529,8 @@ module JamRuby
msg = ChatMessage.create(canceler, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, other, self)
end
success
end
def card_approved

View File

@ -9,6 +9,7 @@ module JamRuby
belongs_to :proposer, class_name: "JamRuby::User"
has_one :defaulted_booking, class_name: "JamRuby::LessonBooking", foreign_key: :default_slot_id, inverse_of: :default_slot
has_one :countered_booking, class_name: "JamRuby::LessonBooking", foreign_key: :counter_slot_id, inverse_of: :counter_slot
has_one :countered_lesson, class_name: "JamRuby::LessonSession", foreign_key: :counter_slot_id, inverse_of: :counter_slot
SLOT_TYPE_SINGLE = 'single'
SLOT_TYPE_RECURRING = 'recurring'

View File

@ -35,9 +35,11 @@ module JamRuby
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
validates :duration, presence: true, numericality: {only_integer: true}
validates :lesson_booking, presence: true
validates :lesson_type, inclusion: {in: LESSON_TYPES}
@ -59,12 +61,12 @@ module JamRuby
after_save :manage_slot_changes
after_create :create_charge
scope :approved, -> { where(status: STATUS_APPROVED) }
scope :requested, -> { where(status: STATUS_REQUESTED) }
scope :canceled, -> { where(status: STATUS_CANCELED) }
scope :suspended, -> { where(status: STATUS_SUSPENDED) }
scope :completed, -> { where(status: STATUS_COMPLETED) }
scope :missed, -> { where(status: STATUS_MISSED) }
scope :approved, -> { where(status: STATUS_APPROVED) }
scope :requested, -> { where(status: STATUS_REQUESTED) }
scope :canceled, -> { where(status: STATUS_CANCELED) }
scope :suspended, -> { where(status: STATUS_SUSPENDED) }
scope :completed, -> { where(status: STATUS_COMPLETED) }
scope :missed, -> { where(status: STATUS_MISSED) }
def create_charge
if !is_test_drive?
@ -357,8 +359,8 @@ module JamRuby
end
# check 24 hour window
if Time.now.to_i - scheduled_start.to_i > 24 * 60 * 60
self.errors.add(:base, "This session is due to start within 24 hours and can not be canceled")
if scheduled_start.to_i - Time.now.to_i < 24 * 60 * 60
self.errors.add(:base, "This session is due to start within 24 hours and can not be canceled.")
end
self.canceling = false
@ -449,6 +451,7 @@ module JamRuby
# teacher accepts the lesson
def accept(params)
response = self
LessonSession.transaction do
message = params[:message]
@ -469,7 +472,10 @@ module JamRuby
if self.save
# also let the lesson_booking know we got accepted
lesson_booking.accept(self, slot, accepter)
if !lesson_booking.accept(self, slot, accepter)
response = lesson_booking
raise ActiveRecord::Rollback
end
UserMailer.student_lesson_accepted(self, message, slot).deliver
UserMailer.teacher_lesson_accepted(self, message, slot).deliver
chat_message = message ? "Lesson Approved: '#{message}'" : "Lesson Approved"
@ -481,14 +487,19 @@ module JamRuby
else
@@log.error("unable to accept slot #{slot.id} for lesson #{self.id}")
puts("unable to accept slot #{slot.id} for lesson #{self.id}")
response = self
raise ActiveRecord::Rollback
end
else
# this implies a new slot has been countered, and now approved
if self.save
if slot.update_all
lesson_booking.accept(self, slot, accepter)
chat_message = message ? "All Lesson Times Updated: '#{message}'" : "All Lesson Times Updated"
if !lesson_booking.accept(self, slot, accepter)
response = lesson_booking
raise ActiveRecord::Rollback
end
chat_message = message ? "All Lesson Times Updated: '#{message}'" : "All Lesson Times Updated"
msg = ChatMessage.create(slot.proposer, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
Notification.send_lesson_message('accept', self, true) # TODO: this isn't quite an 'accept'
UserMailer.student_lesson_update_all(self, message, slot).deliver
@ -510,39 +521,57 @@ module JamRuby
else
@@log.error("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}")
puts("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}")
response = self
raise ActiveRecord::Rollback
end
end
end
response
end
def counter(params)
proposer = params[:proposer]
slot = params[:slot]
message = params[:message]
response = self
LessonSession.transaction do
proposer = params[:proposer]
slot = params[:slot]
message = params[:message]
self.countering = true
slot.proposer = proposer
slot.lesson_session = self
slot.message = message
self.lesson_booking_slots << slot
self.countered_slot = slot
self.countered_lesson = self
self.status = STATUS_COUNTERED
if self.save
if slot.update_all || lesson_booking.is_requested?
lesson_booking.counter(self, proposer, slot)
update_all = slot.update_all || !lesson_booking.recurring
self.countering = true
slot.proposer = proposer
slot.lesson_session = self
slot.message = message
self.lesson_booking_slots << slot
self.countered_slot = slot
self.countered_lesson = self
self.status = STATUS_COUNTERED
if !update_all
self.counter_slot = slot
end
else
raise ActiveRecord::Rollback
if self.save
if update_all
if !lesson_booking.counter(self, proposer, slot)
response = lesson_booking
raise ActiveRecord::Rollback
end
end
else
response = self
raise ActiveRecord::Rollback
end
msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
end
msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
response
end
# teacher accepts the lesson
def cancel(params)
response = self
LessonSession.transaction do
canceler = params[:canceler]
@ -563,7 +592,10 @@ module JamRuby
if self.save
if update_all
lesson_booking.cancel(canceler, other, message)
if !lesson_booking.cancel(canceler, other, message)
response = lesson_booking
raise ActiveRecord::Rollback
end
else
msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, lesson_booking)
Notification.send_lesson_message('canceled', self, false)
@ -571,8 +603,14 @@ module JamRuby
UserMailer.student_lesson_canceled(self, message).deliver
UserMailer.teacher_lesson_canceled(self, message).deliver
end
else
response = self
raise ActiveRecord::Rollback
end
end
response
end
def description(lesson_booking)

View File

@ -12,6 +12,7 @@ module JamRuby
belongs_to :source_user, :class_name => "JamRuby::User", :foreign_key => "source_user_id"
belongs_to :band, :class_name => "JamRuby::Band", :foreign_key => "band_id"
belongs_to :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => "music_session_id"
belongs_to :lesson_session, :class_name => "JamRuby::LessonSession", :foreign_key => "lesson_session_id"
belongs_to :recording, :class_name => "JamRuby::Recording", :foreign_key => "recording_id"
belongs_to :jam_track_right, :class_name => "JamRuby::JamTrackRight", :foreign_key => "jam_track_right_id"
belongs_to :jam_track_mixdown_package, :class_name => "JamRuby::JamTrackMixdownPackage", :foreign_key => "jam_track_mixdown_package_id"
@ -62,7 +63,7 @@ module JamRuby
end
end
self.class.format_msg(self.description, {:user => source_user, target: target_user :band => band, :session => session, purpose: purpose, student_directed: student_directed})
self.class.format_msg(self.description, {:user => source_user, target: target_user, :band => band, :session => session, purpose: purpose, student_directed: student_directed})
end
# TODO: MAKE ALL METHODS BELOW ASYNC SO THE CLIENT DOESN'T BLOCK ON NOTIFICATION LOGIC
@ -275,7 +276,6 @@ module JamRuby
when NotificationTypes::SCHEDULED_JAMCLASS_INVITATION
if student_directed
"You have been scheduled to take a JamClass with #{user.name}."
else
"You have been scheduled to teach a JamClass to #{user.name}"
@ -420,6 +420,7 @@ module JamRuby
notification.purpose = purpose
notification.session_id = lesson_session.music_session.id
notification.lesson_session_id = lesson_session.id
notification_msg = format_msg(NotificationTypes::LESSON_MESSAGE, {purpose: purpose})
@ -438,7 +439,8 @@ module JamRuby
notification.session_id,
notification.created_date,
notification.student_directed,
notification.purpose
notification.purpose,
notification.lesson_session_id
)
@@mq_router.publish_to_user(notification.target_user.id, message)
@ -730,6 +732,8 @@ module JamRuby
notification.source_user_id = source_user.id
notification.target_user_id = target_user.id
notification.session_id = music_session.id
notification.lesson_session_id = music_session.lesson_session.id
notification.student_directed = false
#notification.message = notification_msg
notification.save
@ -744,7 +748,8 @@ module JamRuby
music_session.name,
music_session.pretty_scheduled_start(false),
notification.id,
notification.created_date
notification.created_date,
notification.lesson_session.id
)
@@mq_router.publish_to_user(target_user.id, msg)
@ -762,13 +767,15 @@ module JamRuby
student = target_user = user
teacher = source_user = music_session.lesson_session.teacher
notification_msg = format_msg(NotificationTypes::SCHEDULED_JAMCLASS_INVITATION, {teacher: teacher, student_directed: true})
notification_msg = format_msg(NotificationTypes::SCHEDULED_JAMCLASS_INVITATION, {user: teacher, student_directed: true})
notification = Notification.new
notification.description = NotificationTypes::SCHEDULED_JAMCLASS_INVITATION
notification.source_user_id = source_user.id
notification.target_user_id = target_user.id
notification.session_id = music_session.id
notification.lesson_session_id = music_session.lesson_session.id
notification.student_directed = true
#notification.message = notification_msg
notification.save
@ -781,7 +788,8 @@ module JamRuby
music_session.name,
music_session.pretty_scheduled_start(false),
notification.id,
notification.created_date
notification.created_date,
notification.lesson_session_id
)
@@mq_router.publish_to_user(target_user.id, msg)

View File

@ -52,6 +52,7 @@
SCHEDULED_SESSION_COMMENT : "SCHEDULED_SESSION_COMMENT",
SCHEDULED_JAMCLASS_INVITATION : "SCHEDULED_JAMCLASS_INVITATION",
LESSON_MESSAGE : "LESSON_MESSAGE",
// recording notifications
MUSICIAN_RECORDING_SAVED : "MUSICIAN_RECORDING_SAVED",

View File

@ -440,8 +440,11 @@
else if (type === context.JK.MessageType.SCHEDULED_SESSION_INVITATION) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.LESSON_MESSAGE) {
linkLessonInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_JAMCLASS_INVITATION) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
linkLessonInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_RSVP) {
var $action_btn = $notification.find($btnNotificationAction);
@ -483,6 +486,14 @@
});
}
function linkLessonInfoNotification(payload, $notification, $btnNotificationAction) {
var $action_btn = $notification.find($btnNotificationAction);
$action_btn.text('LESSON DETAILS');
$action_btn.click(function() {
gotoLessonBookingPage({"lesson_session_id": payload.lesson_session_id});
});
}
function acceptBandInvitation(args) {
rest.updateBandInvitation(
args.band_id,
@ -926,9 +937,9 @@
text: "MORE INFO",
rel: "external",
"class": "button-orange",
callback: openSessionInfoWebPage,
callback: gotoLessonBookingPage,
callback_args: {
"session_id": payload.session_id
"lesson_session_id": payload.lesson_session_id
}
}]
);
@ -1365,6 +1376,10 @@
context.JK.popExternalLink('/sessions/' + args.session_id + '/details');
}
function gotoLessonBookingPage(args) {
window.location.href = "/client#/jamclass/lesson-booking/" + args.lesson_session_id
}
function deleteNotificationHandler(evt) {
evt.stopPropagation();
var notificationId = $(this).attr('notification-id');

View File

@ -46,8 +46,6 @@ UserStore = context.UserStore
slot.creatorRoleRelative = "your"
slot.mySlot = @mySlot(slot)
console.log("SLOT", slot)
componentWillUpdate: (nextProps, nextState) ->
if nextState.booking?
booking = nextState.booking
@ -72,19 +70,24 @@ UserStore = context.UserStore
beforeShow: (e) ->
afterShow: (e) ->
@setState({updating: true})
@setState({updating: true, counterErrors: null, cancelErrors: null})
rest.getLessonBooking({
id: e.id,
}).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError)
getLessonBookingDone: (response) ->
if response.counter_slot?
startSlotDecision = response.counter_slot.id
updateBookingState: (booking) ->
if booking.counter_slot?
startSlotDecision = booking.counter_slot.id
else
startSlotDecision = response.default_slot.id
if booking.accepter_id?
startSlotDecision = 'counter'
else
startSlotDecision = booking.default_slot.id
@setState({booking: response, updating: false, slot_decision: startSlotDecision})
@setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false})
getLessonBookingDone: (response) ->
@updateBookingState(response)
toJamClassMain: (e) ->
e.preventDefault()
@ -96,58 +99,82 @@ UserStore = context.UserStore
onAccept: () ->
@setState({updatingLesson: true})
@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.update_all = true
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.update_all = true
rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response)).fail((response) => @cancelLessonBookingFail(response))
else
else if @state.slot_decision
request = {}
request.message = @getMessage()
request.id = this.state.booking.id
request.slot = this.state.slot_decision
rest.acceptLessonBooking(request).done((response) => @acceptLessonBookingDone(response)).fail((response) => @acceptLessonBookingFail(response))
onCancelLesson: (e) ->
@setState({updatingLesson: true})
request = {}
request.message = @getMessage()
request.id = this.state.booking.id
rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response)).fail(@app.ajaxError)
# {"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")
@setState({booking:response, updatingLesson: false})
@updateBookingState(response)
cancelLessonBookingDone: (response ) ->
logger.debug("cancel lesson booking done")
@setState({booking:response, updatingLesson: false})
@updateBookingState(response)
counterLessonBookingDone: (response ) ->
logger.debug("counter lesson booking done")
@setState({booking:response, updatingLesson: false})
@updateBookingState(response)
counterLessonBookingFail: (response ) ->
counterLessonBookingFail: (jqXHR ) ->
@setState({updatingLesson: false})
logger.debug("counter lesson booking failed", response)
@app.ajaxError(arguments[0], arguments[1], arguments[2])
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)
@app.ajaxError(arguments[0], arguments[1], arguments[2])
cancelLessonBookingFail: (response ) ->
cancelLessonBookingFail: (jqXHR) ->
@setState({updatingLesson: false})
logger.debug("cancel lesson booking failed", response)
@app.ajaxError(arguments[0], arguments[1], arguments[2])
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()
@ -364,13 +391,14 @@ UserStore = context.UserStore
hour = slot.hour - 12
if hour == 0
hour = 12
end
am_pm = 'pm'
else
hour = slot.hour
if hour == 0
hour = 12
am_pm = 'am'
"#{hour + 1}:#{slot.minute}#{am_pm}"
"#{context.JK.padString(hour.toString(), 2)}:#{context.JK.padString(slot.minute.toString(), 2)}#{am_pm} (#{slot.timezone})"
isNow: () ->
startTime = new Date(@state.booking.next_lesson.scheduled_start).getTime()
@ -392,6 +420,20 @@ UserStore = context.UserStore
{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.state.booking.next_lesson.pretty_scheduled_start}</p>`
else if @isRequested()
data = `<p>This lesson is scheduled to start at {this.state.booking.next_lesson.pretty_scheduled_start}</p>`
`<div className="row">
{data}
</div>`
nextLessonSummary: () ->
if @isActive()
if @isNow()
@ -434,6 +476,23 @@ UserStore = context.UserStore
</div>
</div>`
decisionProps: (slots) ->
{
onSlotDecision: this.onSlotDecision,
initial: this.neverAccepted(),
counter: this.isCounter(),
is_recurring: this.isRecurring(),
slot_decision: this.state.slot_decision,
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
}
render: () ->
if @state.updating
@renderLoading()
@ -575,9 +634,7 @@ UserStore = context.UserStore
<p>You can do this manually today; we will soon add a easier way to do so automatically.</p>
{this.nextLessonSummary()}
</div>
<div className="row">
{this.renderCancelLesson()}
</div>
<LessonBookingDecision {...this.decisionProps([])} />
</div>`
renderStudentCanceled: () ->
@ -590,6 +647,7 @@ UserStore = context.UserStore
renderStudentCountered: () ->
@renderCountered()
renderTeacherRequested: () ->
if @isTestDrive()
@ -603,15 +661,13 @@ UserStore = context.UserStore
{action}
{this.slotMessage(this.state.booking.default_slot)}
</div>
<LessonBookingDecision onSlotDecision={this.onSlotDecision} initial={this.neverAccepted()} counter={this.isCounter()} is_recurring={this.isRecurring()} slot_decision={this.state.slot_decision} slots={[this.state.booking.default_slot, this.state.booking.alt_slot]} otherRole={this.otherRole()} onUserDecision={this.onAccept} onUserCancel={this.onCancel} disabled={this.state.updatingLesson} selfLastToAct={this.selfLastToAct()}/>
<LessonBookingDecision {...this.decisionProps([this.state.booking.default_slot, this.state.booking.alt_slot])} />
</div>`
renderTeacherApproved: () ->
`<div className="contents">
{this.nextLessonSummaryWithAvatar()}
<div className="row">
{this.renderCancelLesson()}
</div>
<LessonBookingDecision {...this.decisionProps([])} />
</div>`
renderTeacherCanceled: () ->
@ -664,7 +720,6 @@ UserStore = context.UserStore
renderCountered: () ->
counterer = @counterer()
myself = @myself()
initial = @neverAccepted()
phrase = this.slotTimePhrase(this.counteredSlot())
action = `<p>Has suggested a different time for your lesson.</p>`
@ -677,7 +732,7 @@ UserStore = context.UserStore
{detail}
{this.slotMessage(this.counteredSlot())}
</div>
<LessonBookingDecision onSlotDecision={this.onSlotDecision} initial={initial} counter={this.isCounter()} is_recurring={this.isRecurring()} slot_decision={this.state.slot_decision} slots={[this.state.booking.counter_slot]} otherRole={this.otherRole()} onUserDecision={this.onAccept} onUserCancel={this.onCancel} selfLastToAct={this.selfLastToAct()}/>
<LessonBookingDecision {...this.decisionProps([this.state.booking.counter_slot])} />
</div>`
})

View File

@ -9,6 +9,8 @@
# props.is_recurring
# props.slot_decision
# props.otherRole
# props.cancelErrors
# props.counterErrors
mixins: [
ICheckMixin,
@ -27,14 +29,14 @@
componentWillMount: () ->
@days = []
@days.push(`<option value=''>Choose a day of the week...</option>`)
@days.push(`<option value="0">Sunday</option>`)
@days.push(`<option value="1">Monday</option>`)
@days.push(`<option value="2">Tuesday</option>`)
@days.push(`<option value="3">Wednesday</option>`)
@days.push(`<option value="4">Thursday</option>`)
@days.push(`<option value="5">Friday</option>`)
@days.push(`<option value="6">Saturday</option>`)
@days.push(`<option key='choose' value=''>Choose a day of the week...</option>`)
@days.push(`<option key='0' value="0">Sunday</option>`)
@days.push(`<option key='1' value="1">Monday</option>`)
@days.push(`<option key='2' value="2">Tuesday</option>`)
@days.push(`<option key='3' value="3">Wednesday</option>`)
@days.push(`<option key='4' value="4">Thursday</option>`)
@days.push(`<option key='5' value="5">Friday</option>`)
@days.push(`<option key='6' value="6">Saturday</option>`)
@hours = []
for hour in ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
@ -110,11 +112,14 @@
render: () ->
if this.props.selfLastToAct
userPromptHeader = `<h3>Would you like to do something else?</h3>`
if (!this.props.initial && !this.props.counter) || this.props.selfLastToAct
userPromptHeader = `<h3>Would you like to change this lesson?</h3>`
messagePromptHeader = `<h3>Send message to {this.props.otherRole} with your update.</h3>`
else
userPromptHeader = `<h3>How do you want to handle this request?</h3>`
messagePromptHeader = `<h3>Send message to {this.props.otherRole} with your response.</h3>`
if this.props.slot_decision == 'counter'
actionBtnText = "PROPOSE ALTERNATE TIME"
else if this.props.slot_decision == 'decline'
@ -129,18 +134,27 @@
else
actionBtnText = "ACCEPT & UPDATE LESSON"
counterClasses={field: true, 'slot-decision-field': true, error: this.props.counterErrors?}
if this.props.counterErrors?
errorText = window.JK.reactErrors(this.props.counterErrors, {day_of_week: 'Day' })
if this.props.is_recurring
slotAltPrompt = `<div className="slot-alt-prompt">
<span className="alt-date-block">
<span className="alt-date">Day:</span>
<select disabled={this.props.disabled} className="day_of_week" data-slot={i}>{days}</select>
<select disabled={this.props.disabled} className="day_of_week" data-slot={i}>{this.days}</select>
</span>
<span className="alt-time-block">
<span className="alt-time">Time:</span>
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select className="am_pm">{this.am_pm}</select>
<br/>
<span>* Time will be local to {window.jstz.determine().name()}</span>
{errorText}
</span>
</div>`
else
slotAltPrompt = `<div className="slot-alt-prompt">
@ -152,6 +166,9 @@
<span className="alt-time">Time:</span>
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select disabled={this.props.disabled} className="am_pm">{this.am_pm}</select>
<br/>
<span>*Time will be local to {window.jstz.determine().name()}</span>
{errorText}
</span>
</div>`
@ -191,7 +208,7 @@
<div className="slot-decision slot-1">
{slots}
<div className="field slot-decision-field">
<div className={classNames(counterClasses)}>
<div className="label-area">
<input disabled={this.props.disabled} className="slot-decision" type="radio" readyOnly={true} name="slot-decision" value="counter" defaultChecked={this.props.slot_decision == 'counter'}/>
<label>{proposeAltLabelText}</label>
@ -205,7 +222,7 @@
</div>
</div>
<div className="column column-right">
<h3>Send message to {this.props.otherRole} with your response.</h3>
{messagePromptHeader}
<textarea className="message" name="message" disabled={this.props.disabled}></textarea>
<div className="actions">
<a className={classNames(cancelClasses)} onClick={this.onUserCancel}>CANCEL</a>

View File

@ -93,7 +93,7 @@
white-space: nowrap;
float:left;
display:block;
margin-bottom:10px;
//margin-bottom:10px;
vertical-align: middle;
line-height:31px;
}
@ -139,6 +139,9 @@
width:auto;
}
}
.minute {
margin-right:20px;
}
p {
line-height:125% !important;
font-size:14px !important;
@ -228,4 +231,9 @@
display:block;
}
.error-text {
display: block;
//background-color: #600;
color: #f00;
}
}

View File

@ -128,14 +128,20 @@ class ApiLessonBookingsController < ApiController
def accept
next_lesson = @lesson_booking.next_lesson
next_lesson.accept({
result = next_lesson.accept({
message: params[:message],
slot: params[:slot],
accepter: current_user
})
if next_lesson.errors.any?
respond_with_model next_lesson
if result.errors.any?
if result.is_a?(JamRuby::LessonBooking)
recursive_errors(result, [:lesson_booking_slots])
elsif result.is_a?(JamRuby::LessonSession)
recursive_errors(result, [:lesson_booking_slots])
else
raise "unknown response type in accept #{result.class}"
end
return
end
@lesson_booking.reload
@ -148,7 +154,7 @@ class ApiLessonBookingsController < ApiController
slot = LessonBookingSlot.new
if @lesson_booking.recurring
slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING
slot.day_of_week = slot[:day_of_week]
slot.day_of_week = params[:day_of_week]
else
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
@ -161,15 +167,22 @@ class ApiLessonBookingsController < ApiController
slot.hour = params[:hour]
slot.minute = params[:minute]
slot.timezone = params[:timezone]
slot.update_all = params[:update_all]
next_lesson.counter({
result = next_lesson.counter({
proposer: current_user,
message: params[:message],
slot: slot
})
if next_lesson.errors.any?
respond_with_model next_lesson
if result.errors.any?
if result.is_a?(JamRuby::LessonBooking)
recursive_errors(result, [:lesson_booking_slots])
elsif result.is_a?(JamRuby::LessonSession)
recursive_errors(result, [:lesson_booking_slots])
else
raise "unknown response type in counter #{result.class}"
end
return
end
@lesson_booking.reload
@ -177,14 +190,20 @@ class ApiLessonBookingsController < ApiController
def cancel
next_lesson = @lesson_booking.next_lesson
next_lesson.cancel({
result = next_lesson.cancel({
canceler: current_user,
message: params[:message],
update_all: true
})
if next_lesson.errors.any?
respond_with_model next_lesson
if result.errors.any?
if result.is_a?(JamRuby::LessonBooking)
recursive_errors(result, [:lesson_booking_slots])
elsif result.is_a?(JamRuby::LessonSession)
recursive_errors(result, [:lesson_booking_slots])
else
raise "unknown response type in cancel #{result.class}"
end
return
end
@lesson_booking.reload

View File

@ -1,21 +1,21 @@
object @lesson_booking
attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message
attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message, :booked_price
child(:lesson_booking_slots => :slots) {
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?
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
}
child(:default_slot => :default_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?
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
}
child(:alt_slot => :alt_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?
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
}
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?
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
}

View File

@ -1,6 +1,6 @@
collection @notifications
attributes :description, :source_user_id, :target_user_id, :session_id, :recording_id, :invitation_id, :join_request_id, :friend_request_id, :band_id, :band_invitation_id, :formatted_msg, :message, :created_at
attributes :description, :source_user_id, :target_user_id, :session_id, :recording_id, :invitation_id, :join_request_id, :friend_request_id, :band_id, :band_invitation_id, :formatted_msg, :message, :created_at, :lesson_session_id
node :source_user do |n|
source_user_data = {}

View File

@ -2,11 +2,10 @@ require 'factory_girl'
namespace :lessons do
task book_test_drive: :environment do |task, args|
task book_normal: :environment do |task, args|
user = User.find_by_email(ENV['STUDENT_EMAIL'])
teacher = User.find_by_email(ENV['TEACHER_EMAIL'])
recurring = ENV['RECURRING'] == '1'
slots = []
if recurring
@ -18,6 +17,46 @@ namespace :lessons do
end
if user.stored_credit_card == false
user.stored_credit_card = true
user.save!
end
if recurring
payment_style = LessonBooking::PAYMENT_STYLE_MONTHLY
else
payment_style = LessonBooking::PAYMENT_STYLE_SINGLE
end
booking = LessonBooking.book_normal(user, teacher, slots, "Hey I've heard of you before.", recurring, payment_style, 60)
if booking.errors.any?
puts booking.errors.inspect
raise "booking failed"
end
lesson = booking.lesson_sessions[0]
#lesson.accept({message: 'Yeah I got this', slot: slots[0]})
#lesson.errors.any?.should be_false
#lesson.reload
#lesson.slot.should eql slots[0]
#lesson.status.should eql LessonSession::STATUS_APPROVED
puts "http://localhost:3000/client#/jamclass/lesson-booking/#{booking.id}"
end
task book_test_drive: :environment do |task, args|
user = User.find_by_email(ENV['STUDENT_EMAIL'])
teacher = User.find_by_email(ENV['TEACHER_EMAIL'])
slots = []
slots << FactoryGirl.build(:lesson_booking_slot_single, timezone: 'America/Chicago')
slots << FactoryGirl.build(:lesson_booking_slot_single, timezone: 'America/Chicago')
if user.stored_credit_card == false
user.stored_credit_card = true
user.save!