* add rating dialogs for teacher/students and have them pop at end of lesson, be accessible from teacher rating profile pgae, and also from link in email
This commit is contained in:
parent
92b2e13ee8
commit
141736ad2f
|
|
@ -17,9 +17,42 @@ module JamRuby
|
|||
validates :target, presence: true
|
||||
validates :user_id, presence: true
|
||||
validates :target_id, uniqueness: {scope: :user_id, message: "There is already a review for this User and Target."}
|
||||
validate :requires_lesson
|
||||
|
||||
after_save :reduce
|
||||
|
||||
def requires_lesson
|
||||
if target_type == 'JamRuby::User'
|
||||
|
||||
# you are rating a student
|
||||
lesson = LessonSession.joins(:music_session).where('music_sessions.user_id = ?', target.id).where(teacher_id: user.id).first
|
||||
if lesson.nil?
|
||||
errors.add(:target, "You must have at least scheduled or been in a lesson with this student")
|
||||
end
|
||||
|
||||
elsif target_type == "JamRuby::Teacher"
|
||||
|
||||
# you are rating a teacher
|
||||
lesson = LessonSession.joins(:music_session).where('music_sessions.user_id = ?', user.id).where(teacher_id: target.user.id).first
|
||||
if lesson.nil?
|
||||
errors.add(:target, "You must have at least scheduled or been in a lesson with this teacher")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_or_update(params)
|
||||
review = Review.where(user_id: params[:user].id).where(target_id: params[:target].id).where(target_type: params[:target].class.to_s).first
|
||||
|
||||
if review
|
||||
review.description = params[:description]
|
||||
review.rating = params[:rating]
|
||||
review.save
|
||||
else
|
||||
review = Review.create(params)
|
||||
end
|
||||
|
||||
review
|
||||
end
|
||||
def self.create(params)
|
||||
review = Review.new
|
||||
review.target = params[:target]
|
||||
|
|
@ -81,7 +114,7 @@ module JamRuby
|
|||
|
||||
def reduce
|
||||
ReviewSummary.transaction do
|
||||
ReviewSummary.where(target_type: target_type, target_id: target_id).destroy_all
|
||||
ReviewSummary.where(target_type: target_type, target_id: target_id).delete_all
|
||||
|
||||
Review.select("target_id, target_type AS target_type, AVG(rating) as avg_rating, count(*) as review_count, SUM(CASE WHEN rating>=3.0 THEN 1 ELSE 0 END) AS pos_count")
|
||||
.where("deleted_at IS NULL")
|
||||
|
|
@ -89,15 +122,22 @@ module JamRuby
|
|||
.group("target_type, target_id")
|
||||
.each do |r|
|
||||
wilson_score = Review.ci_lower_bound(r.pos_count, r.review_count)
|
||||
ReviewSummary.create!(
|
||||
|
||||
summary = ReviewSummary.create(
|
||||
target_id: r.target_id,
|
||||
target_type: r.target_type,
|
||||
avg_rating: r.avg_rating,
|
||||
wilson_score: wilson_score,
|
||||
review_count: r.review_count
|
||||
)
|
||||
if summary.errors.any?
|
||||
puts "review summary unable to be created #{summary.errors.inspect}"
|
||||
raise "review summary unable to be created #{summary.errors.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ module JamRuby
|
|||
|
||||
validates :avg_rating, presence:true, numericality: true
|
||||
validates :review_count, presence:true, numericality: {only_integer: true}
|
||||
validates :wilson_score, presence:true, numericality: {greater_than:0, less_than:1}
|
||||
validates :wilson_score, presence:true, numericality: {greater_than_or_equal_to:0, less_than_or_equal_to:1}
|
||||
validates :target_id, presence:true, uniqueness:true
|
||||
|
||||
class << self
|
||||
|
|
|
|||
|
|
@ -2113,11 +2113,11 @@ module JamRuby
|
|||
end
|
||||
|
||||
def has_rated_teacher(teacher)
|
||||
teacher_rating(teacher).count > 0
|
||||
teacher_rating(teacher)
|
||||
end
|
||||
|
||||
def has_rated_student(student)
|
||||
student_rating(student).count > 0
|
||||
Review.where(target_id: student.id).where(target_type: "JamRuby::User").count > 0
|
||||
end
|
||||
|
||||
def teacher_rating(teacher)
|
||||
|
|
@ -2126,9 +2126,11 @@ module JamRuby
|
|||
end
|
||||
Review.where(target_id: teacher.id).where(target_type: teacher.class.to_s)
|
||||
end
|
||||
|
||||
def student_rating(student)
|
||||
Review.where(target_id: student.id).where(target_type: "JamRuby::User")
|
||||
def teacher_rating(teacher)
|
||||
if teacher.is_a?(JamRuby::User)
|
||||
teacher = teacher.teacher
|
||||
end
|
||||
Review.where(target_id: teacher.id).where(target_type: teacher.class.to_s)
|
||||
end
|
||||
|
||||
def has_rated_student(student)
|
||||
|
|
@ -2140,7 +2142,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def ratings_url
|
||||
"#{APP_CONFIG.external_root_url}/client?selected=ratings#/profile/teacher/#{id}"
|
||||
"#{APP_CONFIG.external_root_url}/client?tile=ratings#/profile/teacher/#{id}"
|
||||
end
|
||||
|
||||
def student_ratings_url
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -49,7 +49,6 @@ profileUtils = context.JK.ProfileUtils
|
|||
@root = $(@getDOMNode())
|
||||
@endOfList = @root.find('.end-of-payments-list')
|
||||
@contentBodyScroller = @root
|
||||
@root = $(@getDOMNode())
|
||||
@iCheckify()
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
|
|
|
|||
|
|
@ -2,10 +2,19 @@ context = window
|
|||
|
||||
@RateUserDialog = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(@AppStore, "onAppInit")]
|
||||
mixins: [ICheckMixin,
|
||||
Reflux.listenTo(@AppStore, "onAppInit")]
|
||||
teacher: false
|
||||
|
||||
parseId:(id) ->
|
||||
getInitialState: () ->
|
||||
{
|
||||
id: null,
|
||||
type: null,
|
||||
student: null,
|
||||
teacher: null,
|
||||
rating: null,
|
||||
}
|
||||
parseId: (id) ->
|
||||
if !id?
|
||||
{id: null, type: null}
|
||||
else
|
||||
|
|
@ -17,17 +26,25 @@ context = window
|
|||
|
||||
beforeShow: (args) ->
|
||||
logger.debug("RateUserDialog.beforeShow", args.d1)
|
||||
@firstName = ''
|
||||
@lastName = ''
|
||||
@email = ''
|
||||
parsed = @parseId(args.d1)
|
||||
|
||||
@setState({target: null})
|
||||
@setState({student: null, teacher: null, type: parsed.type, id: parsed.id, rating: null})
|
||||
|
||||
rest.getUserDetail({id: parsed.id}).done((response) => @userLookupDone(response)).fail((jqXHR) => @userLookupFail(jqXHR))
|
||||
|
||||
rest.getUserDetail({id: args.d1}).done((response) => @userLookupDone(response)).fail((jqXHR) => @userLookupFail(jqXHR))
|
||||
afterHide: () ->
|
||||
|
||||
isRatingTeacher: () ->
|
||||
!@isRatingStudent()
|
||||
|
||||
isRatingStudent: () ->
|
||||
@state.type == 'student'
|
||||
|
||||
userLookupDone: (response) ->
|
||||
@setState({target: response})
|
||||
if @isRatingTeacher()
|
||||
@setState({teacher: response})
|
||||
else
|
||||
@setState({student: response})
|
||||
|
||||
userLookupFail: (jqXHR) ->
|
||||
@app.ajaxError(jqXHR, null, null)
|
||||
|
|
@ -38,50 +55,121 @@ context = window
|
|||
'afterHide': @afterHide
|
||||
};
|
||||
|
||||
@app.bindDialog('rate-user', dialogBindings);
|
||||
@app.bindDialog('rate-user-dialog', dialogBindings);
|
||||
|
||||
checkboxChanged: (e) ->
|
||||
$target = $(e.target)
|
||||
@setState({rating: $target.val()})
|
||||
|
||||
componentDidMount: () ->
|
||||
@checkboxes = [{selector: 'input[name="rating"]', stateKey: 'rating'}]
|
||||
|
||||
@root = $(@getDOMNode())
|
||||
|
||||
getInitialState: () ->
|
||||
{inviteErrors: null}
|
||||
@iCheckify()
|
||||
|
||||
componentDidUpdate: () ->
|
||||
@iCheckify()
|
||||
|
||||
descriptionChanged: (e) ->
|
||||
@setState({description: $(e.target).val()})
|
||||
|
||||
doCancel: (e) ->
|
||||
e.preventDefault()
|
||||
@app.layout.closeDialog('rate-user', true);
|
||||
@app.layout.cancelDialog('rate-user-dialog');
|
||||
|
||||
doRating: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
rest.createReview({id: target})
|
||||
if @disabled()
|
||||
return
|
||||
|
||||
createDone:(response) ->
|
||||
context.SchoolActions.addInvitation(@state.teacher, response)
|
||||
context.JK.Banner.showNotice("invitation sent", "Your invitation has been sent!")
|
||||
@app.layout.closeDialog('invite-school-user')
|
||||
if @isRatingTeacher()
|
||||
data =
|
||||
{
|
||||
target_id: @state.id
|
||||
target_type: 'JamRuby::Teacher'
|
||||
}
|
||||
else
|
||||
data =
|
||||
{
|
||||
target_id: @state.id,
|
||||
target_type: 'JamRuby::User',
|
||||
}
|
||||
|
||||
createFail: (jqXHR) ->
|
||||
data.rating = @state.rating
|
||||
data.description = @state.description
|
||||
rest.createReview(data).done((response) => @createReviewDone(response)).fail((jqXHR) => @createReviewFail(jqXHR))
|
||||
|
||||
createReviewDone: (response) ->
|
||||
if @isRatingTeacher()
|
||||
context.JK.Banner.showNotice("teacher rated", "Thank you for taking the time to provide your feedback.")
|
||||
else
|
||||
context.JK.Banner.showNotice("student rated", "Thank you for taking the time to provide your feedback.")
|
||||
|
||||
@app.layout.closeDialog('rate-user-dialog')
|
||||
|
||||
createReviewFail: (jqXHR) ->
|
||||
handled = false
|
||||
|
||||
if jqXHR.status == 422
|
||||
errors = JSON.parse(jqXHR.responseText)
|
||||
@setState({inviteErrors: errors})
|
||||
handled = true
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
if response.errors.target?
|
||||
@app.layout.notify({title: "not allowed", text: "you can not rate someone until you have had a lesson with them"})
|
||||
handled = true
|
||||
|
||||
if !handled
|
||||
@app.ajaxError(jqXHR, null, null)
|
||||
|
||||
render: () ->
|
||||
disabled: () ->
|
||||
!@state.rating? || (!@state.teacher? && !@state.student?)
|
||||
|
||||
if @state.user?.teacher?
|
||||
render: () ->
|
||||
submitClasses = classNames({'button-orange': true, disabled: @disabled()})
|
||||
if @isRatingTeacher()
|
||||
title = 'Rate Teacher'
|
||||
help = `<p>Please rate this teacher based on your experience with them.</p>`
|
||||
help = `<h2>Please rate this teacher based on your experience with them:</h2>`
|
||||
descriptionPrompt = `<h2>Please help other students by explaining what you like or don’t like about this teacher:</h2>`
|
||||
choices =
|
||||
`<div className="choices">
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="5"/><label>Great teacher</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="4"/><label>Good teacher</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="3"/><label>Average teacher</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="2"/><label>Poor teacher</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="1"/><label>Terrible teacher</label>
|
||||
</div>
|
||||
</div>`
|
||||
else
|
||||
title = 'Rate Student'
|
||||
help = `<p>Please rate this student based on your experience with them.</p>`
|
||||
|
||||
help = `<h2>Please rate this student based on your experience with them:</h2>`
|
||||
descriptionPrompt = `<h2>Please help other teachers by explaining what you like or don’t like about this student:</h2>`
|
||||
choices =
|
||||
`<div className="choices">
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="5"/><label>Great student</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="4"/><label>Good student</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="3"/><label>Average student</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="2"/><label>Poor student</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<input type="radio" name="rating" value="1"/><label>Terrible student</label>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
`<div>
|
||||
<div className="content-head">
|
||||
|
|
@ -93,9 +181,17 @@ context = window
|
|||
|
||||
{help}
|
||||
|
||||
{choices}
|
||||
|
||||
<div className="field description">
|
||||
{descriptionPrompt}
|
||||
<textarea name="description" placeholder="Enter a further bit of detail here" value={this.state.description}
|
||||
onChange={this.descriptionChanged}></textarea>
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<a onClick={this.doCancel} className="button-grey">CANCEL</a>
|
||||
<a onClick={this.doRating} className="button-orange">SUBMIT RATING</a>
|
||||
<a onClick={this.doRating} className={submitClasses}>SUBMIT RATING</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
|
|
|||
|
|
@ -4,16 +4,9 @@ context = window
|
|||
|
||||
onLeave: (e) ->
|
||||
e.preventDefault()
|
||||
@rateSession()
|
||||
|
||||
SessionActions.leaveSession.trigger({location: '/client#/home'})
|
||||
|
||||
rateSession: () ->
|
||||
unless @rateSessionDialog?
|
||||
@rateSessionDialog = new context.JK.RateSessionDialog(context.JK.app);
|
||||
@rateSessionDialog.initialize();
|
||||
|
||||
@rateSessionDialog.showDialog();
|
||||
|
||||
render: () ->
|
||||
`<a className="session-leave button-grey right leave" onClick={this.onLeave}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
logger = context.JK.logger
|
||||
EVENTS = context.JK.EVENTS;
|
||||
|
||||
SubjectStore = context.SubjectStore
|
||||
InstrumentStore = context.InstrumentStore
|
||||
|
|
@ -117,8 +118,20 @@ proficiencyDescriptionMap = {
|
|||
@visible = true
|
||||
logger.debug("TeacherProfile: afterShow")
|
||||
@setState({userId: e.id, user: null})
|
||||
@updateProfileInfo(e.id)
|
||||
|
||||
if $.QueryString['tile']?
|
||||
rewrite = true
|
||||
@setState({selected: $.QueryString['tile']})
|
||||
|
||||
if rewrite
|
||||
if window.history.replaceState #ie9 proofing
|
||||
window.history.replaceState({}, "", "/client#/profile/teacher/#{e.id}")
|
||||
|
||||
|
||||
updateProfileInfo: (id) ->
|
||||
rest.getUserDetail({
|
||||
id: e.id,
|
||||
id: id,
|
||||
show_teacher: true,
|
||||
show_profile: true
|
||||
}).done((response) => @userDetailDone(response)).fail(@app.ajaxError)
|
||||
|
|
@ -549,6 +562,12 @@ proficiencyDescriptionMap = {
|
|||
{this.musicSamples(user, teacher)}
|
||||
</div>`
|
||||
|
||||
rateTeacher: (e) ->
|
||||
@app.layout.showDialog('rate-user-dialog', {d1: "teacher_#{@state.user.id}"}).one(EVENTS.DIALOG_CLOSED, (e, data) =>
|
||||
if !data.canceled
|
||||
@updateProfileInfo(@state.userId)
|
||||
)
|
||||
|
||||
ratings: () ->
|
||||
user = @state.user
|
||||
teacher = user.teacher
|
||||
|
|
@ -581,7 +600,8 @@ proficiencyDescriptionMap = {
|
|||
`<div className="ratings-block info-block">
|
||||
<h3>Ratings & Reviews</h3>
|
||||
|
||||
<h4>{user.first_name} Summary Rating: <div data-ratings={summary.avg_rating / 5} className="ratings-box hidden"/> <div className="review-count">({reviewCount})</div></h4>
|
||||
<h4>{user.first_name} Summary Rating: <div data-ratings={summary.avg_rating / 5} className="ratings-box hidden"/> <div className="review-count">({reviewCount})</div> <a onClick={this.rateTeacher} className="button-orange rate-teacher-btn">RATE TEACHER</a></h4>
|
||||
|
||||
|
||||
{reviews}
|
||||
</div>`
|
||||
|
|
|
|||
|
|
@ -21,11 +21,15 @@ teacherActions = window.JK.Actions.Teacher
|
|||
|
||||
$candidate = @root.find(selector)
|
||||
|
||||
|
||||
@iCheckIgnore = true
|
||||
|
||||
if $candidate.attr('type') == 'radio'
|
||||
$found = @root.find(selector + '[value="' + choice + '"]')
|
||||
$found.iCheck('check').attr('checked', true)
|
||||
if choice?
|
||||
$found = @root.find(selector + '[value="' + choice + '"]')
|
||||
$found.iCheck('check').attr('checked', true)
|
||||
else
|
||||
$candidate.iCheck('uncheck').attr('checked', false)
|
||||
else
|
||||
if choice
|
||||
$candidate.iCheck('check').attr('checked', true);
|
||||
|
|
@ -54,5 +58,5 @@ teacherActions = window.JK.Actions.Teacher
|
|||
if @checkboxChanged?
|
||||
@checkboxChanged(e)
|
||||
else
|
||||
logger.warn("no checkbox changed implemented")
|
||||
logger.error("no checkbox changed defined")
|
||||
}
|
||||
|
|
@ -1072,6 +1072,19 @@ ConfigureTracksActions = @ConfigureTracksActions
|
|||
|
||||
@sessionUtils.SessionPageLeave()
|
||||
|
||||
if @currentSession?.lesson_session?
|
||||
if context.JK.currentUserId == @currentSession.lesson_session.teacher_id
|
||||
@app.layout.showDialog('rate-user-dialog', {d1: 'student_' + @currentSession.lesson_session.student_id})
|
||||
else
|
||||
@app.layout.showDialog('rate-user-dialog', {d1: 'teacher_' + @currentSession.lesson_session.student_id})
|
||||
|
||||
else
|
||||
unless @rateSessionDialog?
|
||||
@rateSessionDialog = new context.JK.RateSessionDialog(context.JK.app);
|
||||
@rateSessionDialog.initialize();
|
||||
|
||||
@rateSessionDialog.showDialog();
|
||||
|
||||
leaveSession: () ->
|
||||
|
||||
if !@joinDeferred? || @joinDeferred?.state() == 'resolved'
|
||||
|
|
|
|||
|
|
@ -225,6 +225,12 @@
|
|||
color:$ColorTextTypical;
|
||||
}
|
||||
|
||||
.rate-teacher-btn {
|
||||
margin-left: 36px;
|
||||
top: -2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.review {
|
||||
border-width:1px 0 0 0;
|
||||
border-color:$ColorTextTypical;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
@import "client/common";
|
||||
|
||||
#rate-user-dialog {
|
||||
width: 600px;
|
||||
max-height:600px;
|
||||
|
||||
h2 {
|
||||
color:white;
|
||||
margin-bottom:10px;
|
||||
font-size:16px;
|
||||
}
|
||||
.dialog-inner {
|
||||
width: auto;
|
||||
height:calc(100% - 29px)
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
input {
|
||||
display:inline-block;
|
||||
}
|
||||
label {
|
||||
display:inline-block;
|
||||
}
|
||||
.iradio_minimal {
|
||||
display:inline-block;
|
||||
margin-right: 5px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width:100%;
|
||||
height:80px;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
div[data-react-class="RateUserDialog"] {
|
||||
|
||||
}
|
||||
.field.description {
|
||||
margin-bottom:20px;
|
||||
h2 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
float:right;
|
||||
margin:0 -13px 30px 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,21 @@ class ApiReviewsController < ApiController
|
|||
|
||||
# Create a review:
|
||||
def create
|
||||
@review = Review.create(params)
|
||||
target = User.find(params['target_id'])
|
||||
if params[:target_type] == 'JamRuby::Teacher'
|
||||
target = target.teacher
|
||||
end
|
||||
|
||||
params[:target] = target
|
||||
params[:user] = current_user
|
||||
|
||||
@review = Review.create_or_update(params)
|
||||
|
||||
puts "@review.errors #{@review.errors.inspect}"
|
||||
if @review.errors.any?
|
||||
respond_with_model(@review)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# List reviews matching targets for given review summary:
|
||||
|
|
|
|||
|
|
@ -53,3 +53,4 @@
|
|||
= render 'dialogs/chatDialog'
|
||||
= render 'dialogs/cancelLessonDialog'
|
||||
= render 'dialogs/rescheduleLessonDialog'
|
||||
= render 'dialogs/rateUserDialog'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='rate-user-dialog' id='rate-user-dialog'
|
||||
= react_component 'RateUserDialog', {}
|
||||
|
|
@ -107,4 +107,5 @@ SampleApp::Application.configure do
|
|||
|
||||
config.vst_enabled = true
|
||||
config.verify_email_enabled = true
|
||||
config.jamclass_enabled = true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -115,5 +115,6 @@ SampleApp::Application.configure do
|
|||
:secret_key => 'sk_test_OkjoIF7FmdjunyNsdVqJD02D',
|
||||
:source_customer => 'cus_88Vp44SLnBWMXq'
|
||||
}
|
||||
config.jamclass_enabled = true
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue