Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}
`
+ else
+ detail = `
Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}
`
+
+ summary = `
+ {this.userHeader(this.teacher())}
+
Has accepted your lesson request.
+ {detail}
+ {message}
+
`
decision = ``
`
diff --git a/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee
index a2818fafe..49fcfab49 100644
--- a/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee
@@ -177,7 +177,7 @@
:
- * Time will be local to {window.jstz.determine().name()}
+ * Time will be local to {context.JK.currentTimezone()}
{errorText}
@@ -199,7 +199,7 @@
:
- *Time will be local to {window.jstz.determine().name()}
+ *Time will be local to {context.JK.currentTimezone()}
{errorText}
`
diff --git a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee
index 76fb3f42b..9b60eb7d1 100644
--- a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee
@@ -118,6 +118,7 @@ proficiencyDescriptionMap = {
afterShow: (e) ->
+ UserActions.refresh()
@visible = true
logger.debug("TeacherProfile: afterShow")
@setState({userId: e.id, user: null})
diff --git a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee
index 6ddee4fb8..904b517c0 100644
--- a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee
@@ -37,6 +37,8 @@ ProfileActions = @ProfileActions
@visible = true
afterShow: (e) ->
+ UserActions.refresh()
+
#@setState(TeacherSearchStore.getState())
#if @state.results.length == 0
# don't issue a new search every time someone comes to the screen, to preserve location from previous browsing
diff --git a/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee b/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee
index a94595513..a24cdcd85 100644
--- a/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee
@@ -23,8 +23,18 @@ AttachmentActions = @AttachmentActions
recordingsSelected: (recordings) ->
logger.debug("recording selected", recordings)
+ options = {id: @lessonId}
+ options.recordings = recordings
+ rest.attachRecordingToLesson(options).done((response) => @attachedRecordingsToLesson(response)).fail((jqXHR) => @attachedRecordingsFail(jqXHR))
+
+ attachedRecordingsToLesson: (response) ->
+ context.JK.Banner.showNotice('Recording Attached', 'Your recording has been associated with this lesson, and can be accessed from the Messages window for this lesson.')
+
+ attachedRecordingsFail: (jqXHR) ->
+ @app.ajaxError(jqXHR)
+
onStartAttachRecording: (lessonId) ->
- if @lessonId?
+ if @uploading
logger.warn("rejecting startAttachRecording attempt as currently busy")
return
@lessonId = lessonId
@@ -32,25 +42,25 @@ AttachmentActions = @AttachmentActions
@ui.launchRecordingSelectorDialog([], (recordings) =>
@recordingsSelected(recordings)
)
- @change()
+ @changed()
onStartAttachNotation: (lessonId) ->
- if @lessonId?
+ if @uploading
logger.warn("rejecting onStartAttachNotation attempt as currently busy")
return
@lessonId = lessonId
- logger.debug("notation upload started")
+ logger.debug("notation upload started for lesson: " + lessonId)
@triggerNotation()
- @change()
+ @changed()
onStartAttachAudio: (lessonId) ->
- if @lessonId?
+ if @uploading
logger.warn("rejecting onStartAttachAudio attempt as currently busy")
return
@lessonId = lessonId
- logger.debug("audio upload started")
+ logger.debug("audio upload started for lesson: " + lessonId)
@triggerAudio()
@changed()
@@ -66,7 +76,7 @@ AttachmentActions = @AttachmentActions
onUploadNotations: (notations, doneCallback, failCallback) ->
- logger.debug("beginning upload of notations")
+ logger.debug("beginning upload of notations", notations)
@uploading = true
@changed()
@@ -92,14 +102,16 @@ AttachmentActions = @AttachmentActions
return
- formData.append('client_id', app.clientId)
- formData.append('lesson_session_id', @lessonid);
+ formData.append('lesson_session_id', @lessonId);
+ formData.append('attachment_type', 'notation')
rest.uploadMusicNotations(formData)
- .done((response) => @doneUploadingNotatations(notations, response))
- .fail((jqXHR) => @failUploadingNotations(jqXHR))
+ .done((response) => @doneUploadingNotatations(notations, response, doneCallback, failCallback))
+ .fail((jqXHR) => @failUploadingNotations(jqXHR, failCallback))
- doneUploadingNotatations: (notations, response) ->
+ doneUploadingNotatations: (notations, response, doneCallback, failCallback) ->
+ @uploading = false
+ @changed()
error_files = [];
$.each(response, (i, music_notation) => (
if music_notation.errors
@@ -112,7 +124,9 @@ AttachmentActions = @AttachmentActions
else
doneCallback()
- failUploadingNotations: (jqXHR) ->
+ failUploadingNotations: (jqXHR, failCallback) ->
+ @uploading = false
+ @changed()
if jqXHR.status == 413
# the file is too big. Let the user know.
# This should happen when they select the file, but a misconfiguration on the server could cause this.
diff --git a/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee
index c6f175967..959b6b5e6 100644
--- a/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee
@@ -14,6 +14,7 @@ SessionStore = context.SessionStore
systemMsgId: 0
msgs: {global:[], session:[]}
max_global_msgs: 100
+ channelType: null
init: () ->
# Register with the app store to get @app
@@ -46,11 +47,22 @@ SessionStore = context.SessionStore
@changed()
# called from ChatPanel
- onSessionStarted: () ->
- @msgs['session'] = []
- @channel = 'session'
- @fetchHistory()
- @onEmptyChannel(@channel)
+ onSessionStarted: (sessionId, lessonId) ->
+ logger.debug("ChatStore.sessionStarted sessionId: #{sessionId} lessonId: #{lessonId}")
+ if lessonId?
+ @lessonSessionId = lessonId
+ #@msgs['session'] = []
+ @channel = 'lesson'
+ @channelType = 'lesson'
+ @fetchHistory()
+ @onEmptyChannel(@channel)
+ else
+ @msgs['session'] = []
+ @channel = 'session'
+ @channelType = null
+ @fetchHistory()
+ @onEmptyChannel(@channel)
+
buildQuery: (channel = null) ->
if !channel?
@@ -71,6 +83,7 @@ SessionStore = context.SessionStore
onInitializeLesson: (lessonSessionId) ->
@lessonSessionId = lessonSessionId
+ @channelType = 'lesson'
@fetchHistory('lesson')
@@ -102,6 +115,8 @@ SessionStore = context.SessionStore
convert.created_at = chat.created_at
convert.channel = chat.channel
convert.purpose = chat.purpose
+ convert.music_notation = chat.music_notation
+ convert.claimed_recording = chat.claimed_recording
converted.push(convert)
converted
@@ -146,7 +161,13 @@ SessionStore = context.SessionStore
if msg.channel == 'lesson'
effectiveChannel = msg.lesson_session_id
- console.log("effective channel", effectiveChannel, @msgs)
+ if msg.attachment_type?
+ console.log("attachment type seen")
+ if msg.attachment_type == 'notation' || msg.attachment_type == 'audio'
+ msg.music_notation = {id: msg.attachment_id, file_name: msg.attachment_name, attachment_type: msg.attachment_type}
+ else
+ msg.claimed_recording = {id: msg.attachment_id, name: msg.attachment_name}
+
channelMsgs = @msgs[effectiveChannel]
if !channelMsgs?
@@ -174,7 +195,10 @@ SessionStore = context.SessionStore
payload
onActivateChannel: (channel) ->
+ logger.debug("onActivateChannel: " + channel)
@channel = channel
+ if @channel != 'lesson'
+ @channelType = null
@fetchHistory()
@changed()
@@ -218,7 +242,7 @@ SessionStore = context.SessionStore
window.JK.JamServer.sendChatMessage(channel, msg)
getState: () ->
- return {msgs: @msgs, channel: @channel}
+ return {msgs: @msgs, channel: @channel, channelType: @channelType, lessonSessionId: @lessonSessionId}
changed: () ->
@trigger(@getState())
diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
index 49c7d01b3..bd2447424 100644
--- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
@@ -774,7 +774,8 @@ ConfigureTracksActions = @ConfigureTracksActions
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, @trackChanges);
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, @trackChanges);
- $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document
+ console.log("SESSION STARTED EVENT")
+ $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId, lesson_session: response.lesson_session}}) if document
@handleAutoOpenJamTrack()
diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb
index 3296d9275..5e3ec99e8 100644
--- a/web/app/assets/javascripts/scheduled_session.js.erb
+++ b/web/app/assets/javascripts/scheduled_session.js.erb
@@ -605,6 +605,7 @@
return deferred;
}
+ formData.append('attachment_type', 'notation')
formData.append('client_id', app.clientId);
$btnSelectFiles.text('UPLOADING...').data('uploading', true)
$uploadSpinner.show();
diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js
index cd7edb8ae..d92e1f065 100644
--- a/web/app/assets/javascripts/sessionModel.js
+++ b/web/app/assets/javascripts/sessionModel.js
@@ -274,7 +274,7 @@
server.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges);
server.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges);
- $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: sessionId}});
+ $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: sessionId, lesson_session: session.lesson_session}});
})
.fail(function() {
updateCurrentSession(null);
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index 8dd55d654..9a00e241d 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -1218,6 +1218,23 @@
})
}
+ context.JK.currentTimezone = function() {
+ var tz = window.jstz.determine().name()
+
+ if (tz == 'America/Chicago') {
+ tz = 'US Central Time'
+ }
+ else if(tz == 'America/Los_Angeles' || tz == 'America/Los Angeles') {
+ tz = 'US Pacific Time'
+ }
+ else if(tz == 'America/New_York' || tz == 'America/New York') {
+ tz = 'US Eastern Time'
+ }
+ else if (tz == 'America/Arizona') {
+ tz = 'US Mountain Time'
+ }
+ return tz;
+ }
context.JK.flash = function(msg, options) {
options = options || {}
diff --git a/web/app/assets/stylesheets/client/lessonSessionActions.css.scss b/web/app/assets/stylesheets/client/lessonSessionActions.css.scss
index f8d559669..c1bbc22c8 100644
--- a/web/app/assets/stylesheets/client/lessonSessionActions.css.scss
+++ b/web/app/assets/stylesheets/client/lessonSessionActions.css.scss
@@ -2,6 +2,12 @@
.lesson-action-popup {
+ a{
+ color:#fc0 !important;
+ }
+ li {
+ border-bottom:0 !important;
+ }
&.not-card-ok .bt-content{
height:20px !important;
width:90px !important;
diff --git a/web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss b/web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss
index 4cc84323a..c574014f1 100644
--- a/web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss
+++ b/web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss
@@ -6,6 +6,28 @@ div[data-react-class="ChatWindow"] {
.ChatWindow {
+ .chat-message-purpose {
+ display:inline;
+ color: $ColorTextTypical;
+ margin: 0 10px 0 0;
+ font-size:11px;
+ }
+
+ .chat-message-text {
+ margin-top:8px;
+ display:block;
+ }
+
+ .chat-message-timestamp {
+ display:inline-block;
+ }
+
+ .additional {
+ margin-left:4px;
+ display: block;
+ margin-top:4px;
+ }
+
&.ChatDialog {
.active-tab {
top: 0;
@@ -30,13 +52,7 @@ div[data-react-class="ChatWindow"] {
}
.chat-message {
margin:0 0 15px 0;
- .chat-message-text {
- margin-top:8px;
- display:block;
- }
- .chat-message-timestamp {
- display:inline-block;
- }
+
.chat-message-sender {
&:after {
content: '';
@@ -65,12 +81,6 @@ div[data-react-class="ChatWindow"] {
margin: 8px -4px 0 0;
width:50px;
}
- .chat-message-purpose {
- display:inline;
- color: $ColorTextTypical;
- margin: 0 10px 0 0;
- font-size:11px;
- }
}
height: 100%;
@@ -166,7 +176,6 @@ div[data-react-class="ChatWindow"] {
.chat-message-timestamp {
margin-top: 4px;
color: #AAA;
- display: block;
margin-left:4px;
}
}
@@ -181,4 +190,32 @@ div[data-react-class="ChatWindow"] {
box-sizing: border-box;
}
+ .arrow-down {
+ float:none;
+ margin-left:5px;
+ margin-top:0;
+ margin-right:0;
+ border-top: 4px solid #fc0;
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ display:inline-block;
+ }
+ .arrow-up {
+ float:none;
+ margin-right:0;
+ margin-left:5px;
+ margin-bottom:2px;
+ border-bottom: 4px solid #fc0;
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ display:inline-block;
+ }
+ .lesson-session-actions-btn {
+ margin-bottom:5px;
+ }
+ #attach-files-chat-dialog {
+ font-size:12px;
+ float:left;
+ margin-top:6px;
+ }
}
diff --git a/web/app/controllers/api_lesson_bookings_controller.rb b/web/app/controllers/api_lesson_bookings_controller.rb
index da570b372..ef65f1459 100644
--- a/web/app/controllers/api_lesson_bookings_controller.rb
+++ b/web/app/controllers/api_lesson_bookings_controller.rb
@@ -150,7 +150,7 @@ class ApiLessonBookingsController < ApiController
def counter
if params[:lesson_session_id]
- target_lesson = @lesson_session.find(params[:lesson_session_id])
+ target_lesson = LessonSession.find(params[:lesson_session_id])
else
target_lesson = @lesson_booking.next_lesson
end
diff --git a/web/app/controllers/api_lesson_sessions_controller.rb b/web/app/controllers/api_lesson_sessions_controller.rb
index b2c19bf01..8acc3ff57 100644
--- a/web/app/controllers/api_lesson_sessions_controller.rb
+++ b/web/app/controllers/api_lesson_sessions_controller.rb
@@ -115,6 +115,32 @@ class ApiLessonSessionsController < ApiController
@lesson_payment_charges = current_user.uncollectables
end
+ def attach_recording
+
+ if @lesson_session.student.id == current_user.id
+ me = @lesson_session.student
+ other = @lesson_session.teacher
+ else
+ me = @lesson_session.teacher
+ other = @lesson_session.student
+ end
+
+
+ recordings = params[:recordings]
+ recordings.each do |recording_data|
+ #recording = Recording.find(recording_data[:id])
+ claimed_recording = ClaimedRecording.find_by_id(recording_data[:id])
+ if claimed_recording.nil? || claimed_recording.user != current_user
+ raise JamPermissionError, 'only owner of claimed_recording can associated it with a lesson'
+ end
+
+ msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, @lesson_session, 'JamKazam Recording', nil, claimed_recording)
+ end
+
+ render :json => {}, :status => 200
+
+ end
+
private
def lookup_lesson
diff --git a/web/app/controllers/api_music_notations_controller.rb b/web/app/controllers/api_music_notations_controller.rb
index 90e64435c..c1c6822f1 100644
--- a/web/app/controllers/api_music_notations_controller.rb
+++ b/web/app/controllers/api_music_notations_controller.rb
@@ -6,17 +6,43 @@ class ApiMusicNotationsController < ApiController
respond_to :json
def create
- client_id = params[:client_id]
-
- if client_id.nil?
- raise JamArgumentError, "client_id must be specified"
- end
-
@music_notations = []
+ lesson_session = LessonSession.find_by_id(params[:lesson_session_id])
+
+ if lesson_session
+ session_id = lesson_session.music_session.id
+ else
+ session_id = params[:session_id]
+ end
+
+
params[:files].each do |file|
- music_notation = MusicNotation.create(params[:session_id], file, current_user)
+ music_notation = MusicNotation.create(session_id, params[:attachment_type], file, current_user)
@music_notations.push music_notation
+
+ if lesson_session
+ if lesson_session.student.id == current_user.id
+ me = lesson_session.student
+ other = lesson_session.teacher
+ else
+ me = lesson_session.teacher
+ other = lesson_session.student
+ end
+
+ if !music_notation.errors.any?
+ # if no error and it's a lesson, then make a chat about it
+
+ if params[:attachment_type] == MusicNotation::TYPE_NOTATION
+ purpose = "Notation File"
+ else
+ purpose = "Audio File"
+ end
+
+ msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, lesson_session, purpose, music_notation)
+ end
+ end
+
end if params[:files]
respond_with @music_notations, responder: ApiResponder, :status => 201
diff --git a/web/app/views/api_chats/show.rabl b/web/app/views/api_chats/show.rabl
index 3b6c57e72..ec147a8dc 100644
--- a/web/app/views/api_chats/show.rabl
+++ b/web/app/views/api_chats/show.rabl
@@ -8,4 +8,12 @@ node :user do |c|
user_data[:name] = c.user.name
end
user_data
-end
\ No newline at end of file
+end
+
+child :music_notation do
+ attributes :id, :file_name, :attachment_type
+end
+
+child :claimed_recording do
+ attributes :id, :name
+end
diff --git a/web/app/views/api_music_sessions/show_history.rabl b/web/app/views/api_music_sessions/show_history.rabl
index 19d75ee64..c244ae794 100644
--- a/web/app/views/api_music_sessions/show_history.rabl
+++ b/web/app/views/api_music_sessions/show_history.rabl
@@ -140,6 +140,10 @@ else
}
}
+ child(lesson_session: :lesson_session) {
+ attributes :id, :scheduled_start, :status, :teacher_id, :success, :duration, :student_id
+ }
+
child(:active_music_session => :active_music_session) {
attributes :claimed_recording_initiator_id, :track_changes_counter
diff --git a/web/app/views/clients/_lessonSessionActions.html.slim b/web/app/views/clients/_lessonSessionActions.html.slim
index 26ebca9a7..c42b76c35 100644
--- a/web/app/views/clients/_lessonSessionActions.html.slim
+++ b/web/app/views/clients/_lessonSessionActions.html.slim
@@ -1,10 +1,19 @@
script type='text/template' id='template-lesson-session-actions'
= '{% if (data.cardNotOk) { %}'
+ ul
+ li data-lesson-option="enter-payment"
+ a href='#' Enter Payment
+ = '{% } else if (data.attachments_only) { %}'
+ ul
+ li data-lesson-option="attach-recording"
+ a href='#' Attach Recording
- li data-lesson-option="enter-payment"
- a href='#' Enter Payment
+ li data-lesson-option="attach-notation"
+ a href='#' Attach Notation File
+ li data-lesson-option="attach-audio"
+ a href='#' Attach Audio File
= '{% } else if (data.isRequested) { %}'
ul
li data-lesson-option="status"
diff --git a/web/config/routes.rb b/web/config/routes.rb
index fb67e9e5a..c05812a9c 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -696,6 +696,7 @@ SampleApp::Application.routes.draw do
match '/lesson_sessions/uncollectable' => 'api_lesson_sessions#uncollectable', :via => :get
match '/lesson_sessions/:id' => 'api_lesson_sessions#show', :via => :get
match '/lesson_sessions/:id/update_unread_messages' => 'api_lesson_sessions#update_unread_messages', :via => :post
+ match '/lesson_sessions/:id/attach_recording' => 'api_lesson_sessions#attach_recording', :via => :post
match '/lesson_sessions/:id/start_time' => 'api_lesson_sessions#start_time', :via => :post
match '/lesson_sessions/:id/reschedule_check' => 'api_lesson_sessions#reschedule_check', :via => :post
match '/lesson_sessions/:id/cancel_check' => 'api_lesson_sessions#cancel_check', :via => :post