scheduler: allow teachers to cancel student appointments
closes #7560 Test plan: - reserve a time slot with one or more students - as a teacher, click on the reserved time slot (in either scheduler or month view) - you should be able to remove the student from the appointment Change-Id: I2b5faf813ebafe50fbf185ee8f61764667afa941 Reviewed-on: https://gerrit.instructure.com/9594 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Cameron Matheson <cameron@instructure.com>
This commit is contained in:
parent
224774a345
commit
b0d798b76c
app
coffeescripts
stylesheets
views/jst/calendar
public/javascripts
spec/selenium
|
@ -7,11 +7,12 @@ define [
|
|||
'jst/calendar/eventDetails'
|
||||
'jst/calendar/deleteItem'
|
||||
'jst/calendar/reservationOverLimitDialog'
|
||||
'use!underscore'
|
||||
'jquery.ajaxJSON'
|
||||
'jquery.instructure_misc_helpers'
|
||||
'jquery.instructure_misc_plugins'
|
||||
'vendor/jquery.ba-tinypubsub'
|
||||
], ($, I18n, Popover, CommonEvent, EditEventDetailsDialog, eventDetailsTemplate, deleteItemTemplate, reservationOverLimitDialog) ->
|
||||
], ($, I18n, Popover, CommonEvent, EditEventDetailsDialog, eventDetailsTemplate, deleteItemTemplate, reservationOverLimitDialog, _) ->
|
||||
|
||||
class ShowEventDetailsDialog
|
||||
constructor: (event) ->
|
||||
|
@ -86,6 +87,35 @@ define [
|
|||
@deleteEvent(e)
|
||||
return
|
||||
|
||||
cancelAppointment: ($appt) =>
|
||||
url = $appt.data('url')
|
||||
event = _.detect @event.calendarEvent.child_events, (e) -> e.url == url
|
||||
$("<div/>").confirmDelete
|
||||
url: url
|
||||
message: $ deleteItemTemplate(message: I18n.t(
|
||||
'cancel_appointment'
|
||||
'Are you sure you want to cancel your appointment with %{name}?'
|
||||
name: event.user?.short_name or event.group.name)
|
||||
)
|
||||
dialog:
|
||||
title: I18n.t('confirm_removal', "Confirm Removal")
|
||||
width: '400px'
|
||||
resizable: false
|
||||
prepareData: ($dialog) => {cancel_reason: $dialog.find('#cancel_reason').val() }
|
||||
success: =>
|
||||
@event.object.child_events = _(@event.object.child_events).reject (e) ->
|
||||
e.url == $appt.data('url')
|
||||
$appt.remove()
|
||||
|
||||
# this is a little funky, but we want to remove the parent (time
|
||||
# slot) event from the calendar when there are no attendees, *unless*
|
||||
# we are in scheduler view
|
||||
in_scheduler = $('#scheduler').prop('checked')
|
||||
appointments = @event.calendarEvent.child_events
|
||||
if not in_scheduler and appointments.length == 0
|
||||
$.publish "CommonEvent/eventDeleted", @event
|
||||
@popover.hide()
|
||||
|
||||
show: (jsEvent) =>
|
||||
params = $.extend true, {}, @event,
|
||||
can_reserve: @event.object?.reserve_url
|
||||
|
@ -100,14 +130,15 @@ define [
|
|||
params.can_reserve = false
|
||||
|
||||
for e in @event.object.child_events
|
||||
reservation =
|
||||
id: e.user?.id or e.group.id
|
||||
name: e.user?.short_name or e.group.name
|
||||
event_url: e.url
|
||||
(params.reservations ?= []).push reservation
|
||||
if e.user
|
||||
(params.reserved_users ?= []).push
|
||||
id: e.user.id
|
||||
name: e.user.short_name
|
||||
(params.reserved_users ?= []).push reservation
|
||||
if e.group
|
||||
(params.reserved_groups ?= []).push
|
||||
id: e.group.id
|
||||
name: e.group.name
|
||||
(params.reserved_groups ?= []).push reservation
|
||||
|
||||
if @event.object?.available_slots == 0
|
||||
params.can_reserve = false
|
||||
|
@ -133,5 +164,10 @@ define [
|
|||
e.preventDefault()
|
||||
@unreserveEvent()
|
||||
|
||||
@popover.el.find(".cancel_appointment_link").click (e) =>
|
||||
e.preventDefault()
|
||||
$appt = $(e.target).closest('li')
|
||||
@cancelAppointment($appt)
|
||||
|
||||
# @popover.dialog 'option',
|
||||
# width: if @event.description?.length > 2000 then Math.max($(window).width() - 300, 450) else 450
|
||||
|
|
|
@ -62,9 +62,11 @@ define [
|
|||
clearInterval @positionInterval
|
||||
$(window).unbind 'click', @outsideClickHandler
|
||||
|
||||
ignoreOutsideClickSelector: '.ui-dialog'
|
||||
|
||||
# uses a fat arrow so that it has a unique guid per-instance for jquery event unbinding
|
||||
outsideClickHandler: (event) =>
|
||||
unless $(event.target).closest(@el.add(@trigger)).length
|
||||
unless $(event.target).closest(@el.add(@trigger).add(@ignoreOutsideClickSelector)).length
|
||||
@hide()
|
||||
|
||||
position: =>
|
||||
|
|
|
@ -154,6 +154,9 @@
|
|||
&:last-child
|
||||
border-bottom: none
|
||||
|
||||
#attendees li
|
||||
+name_bubbles
|
||||
|
||||
/*replicate button styles to work for fullcalendar */
|
||||
.calendar .fc-button
|
||||
+unselectable
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@import compass
|
||||
@import variables.sass
|
||||
@import mixins/misc.sass
|
||||
@import mixins/misc.sass
|
||||
@import mixins/name_bubbles.sass
|
||||
|
|
|
@ -610,42 +610,7 @@ ul.messages, ul.messages.private, ul.messages.private li:hover
|
|||
margin: 0
|
||||
padding: 0
|
||||
li
|
||||
div
|
||||
background-color: #dee7fa
|
||||
+border-radius(10px)
|
||||
padding: 0 14px 0 11px
|
||||
display: inline-block
|
||||
overflow: hidden
|
||||
span
|
||||
color: #fff
|
||||
font-size: 0.8em
|
||||
vertical-align: top
|
||||
display: inline-block
|
||||
a
|
||||
position: absolute
|
||||
right: 1px
|
||||
top: 1px
|
||||
width: 13px
|
||||
height: 100%
|
||||
color: #c4cbcf
|
||||
vertical-align: top
|
||||
cursor: pointer
|
||||
background: transparent url(/images/messages/token-delete.png) 12px center no-repeat
|
||||
white-space: nowrap
|
||||
float: left
|
||||
margin: 1px 2px 1px 4px
|
||||
color: #000
|
||||
background-color: #85ace0
|
||||
border: 1px solid #a5bcf0
|
||||
+border-radius(10px)
|
||||
cursor: default
|
||||
position: relative
|
||||
li:hover
|
||||
div
|
||||
background-color: #bccef4
|
||||
border-color: #6f94e6
|
||||
a
|
||||
background-position: 1px center
|
||||
+name_bubbles
|
||||
li.selected
|
||||
background-color: #5b89f3
|
||||
border-color: #5b89f3
|
||||
|
@ -948,4 +913,4 @@ form
|
|||
position: absolute
|
||||
left: 50%
|
||||
top: 50%
|
||||
z-index: 3
|
||||
z-index: 3
|
||||
|
|
|
@ -55,4 +55,3 @@
|
|||
-webkit-user-select: none
|
||||
-moz-user-select: none
|
||||
user-select: none
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
=name_bubbles
|
||||
div
|
||||
background-color: #dee7fa
|
||||
+border-radius(10px)
|
||||
padding: 0 14px 0 11px
|
||||
display: inline-block
|
||||
overflow: hidden
|
||||
span
|
||||
color: #fff
|
||||
font-size: 0.8em
|
||||
vertical-align: top
|
||||
display: inline-block
|
||||
a
|
||||
position: absolute
|
||||
right: 1px
|
||||
top: 1px
|
||||
width: 13px
|
||||
height: 100%
|
||||
color: #c4cbcf
|
||||
vertical-align: top
|
||||
cursor: pointer
|
||||
background: transparent url(/images/messages/token-delete.png) 12px center no-repeat
|
||||
white-space: nowrap
|
||||
float: left
|
||||
margin: 1px 2px 1px 4px
|
||||
color: #000
|
||||
background-color: #85ace0
|
||||
border: 1px solid #a5bcf0
|
||||
+border-radius(10px)
|
||||
cursor: default
|
||||
position: relative
|
||||
|
||||
&:hover
|
||||
div
|
||||
background-color: #bccef4
|
||||
border-color: #6f94e6
|
||||
a
|
||||
background-position: 1px center
|
|
@ -30,28 +30,26 @@
|
|||
<td>{{{description}}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{#if reserved_users}}
|
||||
{{#if reservations}}
|
||||
<tr>
|
||||
<th scope="row">{{#t "attendees"}}Attendees{{/t}}</th>
|
||||
<td>
|
||||
<ul>
|
||||
{{#each reserved_users}}
|
||||
<!-- TODO: put avatar images here if enabled -->
|
||||
<li>{{name}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{#if reserved_groups}}
|
||||
<tr>
|
||||
<th scope="row">{{#t "attendees"}}Attendees{{/t}}</th>
|
||||
<td>
|
||||
<ul>
|
||||
{{#each reserved_groups}}
|
||||
<li>{{name}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#if can_edit}}
|
||||
<ul id="attendees">
|
||||
{{#each reservations}}
|
||||
<li data-url="{{event_url}}">
|
||||
<div class="ellipsis">{{name}}</div>
|
||||
<a href="javascript:void(0)" class="cancel_appointment_link"></a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<ul>
|
||||
{{#each reservations}}
|
||||
<li>{{name}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
@ -85,4 +83,4 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -136,6 +136,7 @@ define([
|
|||
buttons: [
|
||||
{
|
||||
text: I18n.t('#buttons.delete', 'Delete'),
|
||||
'class': 'ui-button-primary',
|
||||
click: function() { result = true; $(this).dialog('close'); }
|
||||
}, {
|
||||
text: I18n.t('#buttons.cancel', 'Cancel'),
|
||||
|
|
|
@ -261,6 +261,55 @@ describe "scheduler" do
|
|||
AppointmentGroup.find(ag_id).max_appointments_per_participant.should == 3
|
||||
end
|
||||
|
||||
it "should allow removing individual appointments" do
|
||||
# user appointment group
|
||||
create_appointment_group
|
||||
ag = AppointmentGroup.first
|
||||
2.times do
|
||||
student_in_course(:course => @course, :active_all => true)
|
||||
ag.appointments.first.reserve_for(@user, @user)
|
||||
end
|
||||
|
||||
# group appointment group
|
||||
gc = @course.group_categories.create!(:name => "Blah Groups")
|
||||
title = create_appointment_group :sub_context_code => gc.asset_string,
|
||||
:title => "group ag"
|
||||
ag = AppointmentGroup.find_by_title(title)
|
||||
2.times do |i|
|
||||
student_in_course(:course => @course, :active_all => true)
|
||||
group = Group.create! :group_category => gc,
|
||||
:context => @course,
|
||||
:name => "Group ##{i+1}"
|
||||
group.users << @user
|
||||
group.save!
|
||||
ag.appointments.first.reserve_for(group, @user)
|
||||
end
|
||||
|
||||
get "/calendar2"
|
||||
click_scheduler_link
|
||||
|
||||
2.times do |i|
|
||||
f(".appointment-group-item:nth-child(#{i+1}) .view_calendar_link").click
|
||||
|
||||
f('.fc-event').click
|
||||
ff('#attendees li').size.should eql 2
|
||||
|
||||
# delete the first appointment
|
||||
f('.cancel_appointment_link').click
|
||||
fj('button:visible:contains(Delete)').click
|
||||
wait_for_ajax_requests
|
||||
ff('#attendees li').size.should eql 1
|
||||
|
||||
# make sure the appointment was really deleted
|
||||
f('#refresh_calendar_link').click
|
||||
wait_for_ajax_requests
|
||||
f('.fc-event-time').click
|
||||
ff('#attendees li').size.should eql 1
|
||||
|
||||
f('.single_item_done_button').click
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "as a student" do
|
||||
|
@ -324,6 +373,20 @@ describe "scheduler" do
|
|||
f('.fc-event:nth-child(3)').should include_text "Available"
|
||||
end
|
||||
|
||||
it "should not allow me to cancel reservations from the attendees list" do
|
||||
create_appointment_group
|
||||
ag = AppointmentGroup.first
|
||||
ag.appointments.first.reserve_for(@user, @user)
|
||||
get "/calendar2"
|
||||
wait_for_ajaximations
|
||||
click_scheduler_link
|
||||
wait_for_ajaximations
|
||||
click_appointment_link
|
||||
|
||||
fj('.fc-event:visible').click
|
||||
ff('#reservations').size.should be_zero
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue