Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions app/assets/javascripts/invitations.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
$(document).ready(function() {
$(document).on("ajax:success", "#invitations [data-remote]", function(e) {
var xhr = e.detail[2];
var $invitations = $("#invitations");
var $link = $(e.currentTarget);

$invitations.html(xhr.responseText);
if ($link.hasClass('verify_attendance')) {
var $row = $link.closest('.row.attendee');
var rowId = $row.attr('id');
$row.replaceWith(xhr.responseText);
$(".row.attendee[id='" + rowId + "']").find('[data-bs-toggle="tooltip"]').tooltip();
} else {
var $invitations = $("#invitations");
$invitations.html(xhr.responseText);

$invitations.find('select').chosen(function () {
allow_single_deselect: true
no_results_text: 'No results matched'
});
$invitations.find('select').chosen({
allow_single_deselect: true,
no_results_text: 'No results matched'
});
}
});

$(document).on('change','#workshop_invitations ',function() {
Expand Down
15 changes: 11 additions & 4 deletions app/controllers/admin/invitations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ def update
message = update_attendance(attending: params[:attending], attended: params[:attended])

if request.xhr?
set_admin_workshop_data
render partial: 'admin/workshops/invitation_management'
if params[:attended].present?
render partial: 'admin/workshop/attendance_row', locals: { invitation: InvitationPresenter.new(@invitation), workshop: @workshop }
else
set_admin_workshop_data
render partial: 'admin/workshops/invitation_management'
end
else
redirect_back fallback_location: root_path, notice: message
end
Expand All @@ -19,7 +23,8 @@ def update
private

def update_attendance(attending:, attended:)
return update_to_attended if attended
return update_to_attended if attended.to_s == 'true'
return update_to_unattended if attended.to_s == 'false'

result = attending.eql?('true') ? update_to_attending : update_to_not_attending
message, error = result.values_at(:message, :error)
Expand All @@ -34,8 +39,10 @@ def update_attendance(attending:, attended:)

def update_to_attended
@invitation.update(attended: true)
end

"You have verified #{@invitation.member.full_name}’s attendace."
def update_to_unattended
@invitation.update(attended: nil)
end

def update_to_attending
Expand Down
2 changes: 1 addition & 1 deletion app/models/waiting_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class WaitingList < ApplicationRecord

scope :by_workshop, ->(workshop) { joins(:invitation).where('workshop_invitations.workshop_id = ?', workshop.id) }
scope :where_role, ->(role) { where('workshop_invitations.role = ?', role) }
scope :with_notes_and_their_authors, -> { includes(member: { member_notes: :author }) }
scope :with_notes_and_their_authors, -> { includes(member: [{ member_notes: :author }, :attendance_warnings]) }

def self.add(invitation, auto_rsvp = true)
find_or_create_by(invitation: invitation) do |waiting_list|
Expand Down
2 changes: 1 addition & 1 deletion app/models/workshop_invitation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class WorkshopInvitation < ApplicationRecord
scope :last_six_months, -> { joins(:workshop).where(workshops: { date_and_time: 6.months.ago...Time.zone.now }) }
scope :not_reminded, -> { where(reminded_at: nil) }
scope :on_waiting_list, -> { joins(:waiting_list) }
scope :with_notes_and_their_authors, -> { includes(member: { member_notes: :author }) }
scope :with_notes_and_their_authors, -> { includes(member: [{ member_notes: :author }, :attendance_warnings]).includes(:overrider) }

after_save :clear_member_cache, if: :saved_change_to_attending?

Expand Down
63 changes: 63 additions & 0 deletions app/views/admin/workshop/_attendance_row.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
-# locals: (invitation:, workshop:)
.row.attendee.mt-3{ id: "attendee-row-#{invitation.id}" }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Opportunity to use the Rails Strict Locals declaration here, which is sort of a function arg declaration list. Example blog post mentioning the feature - https://masilotti.com/safer-rails-partials-with-strict-locals/#strict-locals

.col-1
- if (workshop.date_and_time - 30.minutes).past?
- if invitation.attended.nil?
= link_to admin_workshop_invitation_path(workshop, invitation, attended: true),
class: 'verify_attendance', title: 'Verify attendance', method: :put, remote: true,
data: { disable_with: "<i class='fa fa-spinner spin'></i>" } do
%i.far.fa-square
- else
= link_to admin_workshop_invitation_path(workshop, invitation, attended: false),
class: 'verify_attendance', title: 'Unverify attendance', method: :put, remote: true,
data: { disable_with: "<i class='fa fa-spinner spin'></i>" } do
%i.far.fa-check-square
- else
= link_to admin_workshop_invitation_path(workshop, invitation, attending: false), class: 'cancel_attendance float-right', title: 'Cancel RSVP', data: { confirm: "Are you sure you want to remove #{invitation.member.full_name} from the list of attendees?" }, method: :put, remote: true do
%i.far.fa-minus-square
.col-5
- if invitation.member.newbie?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'New attendee.' }
%i.fas.fa-paw
- elsif invitation.member.flag_to_organisers?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Multiple no shows and attendance warnings in the last six months' }
%i.fas.fa-exclamation-circle

- if invitation.member.recent_notes.any?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Note recently added'}
%i.fas.fa-comment

= link_to admin_member_path(invitation.member) do
- if invitation.member.full_name.blank?
= invitation.member.email
- else
= invitation.member.full_name
- if invitation.member.dietary_restrictions.present?
%p
- invitation.member.displayed_dietary_restrictions.each do |dr|
%span.badge.bg-secondary.text-wrap.text-break.mb-1.text-start= dr
.col-6
- if invitation.tutorial?
%p.mb-1 Tutorial: #{invitation.tutorial}
- if invitation.note?
%p.mb-1 Note: #{invitation.note}
- if invitation.rsvp_time.present?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Time of RSVP'}
%p.mb-1.small
%i.fas.fa-history
= l(invitation.rsvp_time)
- if invitation.automated_rsvp?
- tooltip_args = { 'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom' }
- if invitation.last_overridden_by_id?
%span{ tooltip_args, title: "Addition by #{invitation.overrider.full_name}" }
%p.mb-1.small
%i.fas.fa-hat-wizard
- else
%span{ tooltip_args, title: 'Waiting list or admin addition' }
%p.mb-1.small
%i.fas.fa-magic
- if invitation.reminded_at.present?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Reminder emailed at'}
%p.mb-1.small
%i.far.fa-clock
= l(invitation.reminded_at)
60 changes: 1 addition & 59 deletions app/views/admin/workshop/_attendances.html.haml
Original file line number Diff line number Diff line change
@@ -1,62 +1,4 @@
.row
.col.attendances
- invitations.each do |invitation|
.row.attendee.mt-3
.col-1
- if (invitation.parent.date_and_time - 30.minutes).past?
- if invitation.attended.nil?
= link_to admin_workshop_invitation_path(@workshop, invitation, attended: true),
class: 'verify_attendance', title: 'Verify attendance', method: :put, remote: true,
data: { disable_with: "<i class='fa fa-spinner spin'></i>" } do
%i.far.fa-square
- else
%i.far.fa-check-square
- else
= link_to admin_workshop_invitation_path(@workshop, invitation, attending: false), class: 'cancel_attendance float-right', title: 'Cancel RSVP', data: { confirm: "Are you sure you want to remove #{invitation.member.full_name} from the list of attendees?" }, method: :put, remote: true do
%i.far.fa-minus-square
.col-5
- if invitation.member.newbie?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'New attendee.' }
%i.fas.fa-paw
- elsif invitation.member.flag_to_organisers?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Multiple no shows and attendance warnings in the last six months' }
%i.fas.fa-exclamation-circle

- if invitation.member.recent_notes.any?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Note recently added'}
%i.fas.fa-comment

= link_to admin_member_path(invitation.member) do
- if invitation.member.full_name.blank?
= invitation.member.email
- else
= invitation.member.full_name
- if invitation.member.dietary_restrictions.present?
%p
- invitation.member.displayed_dietary_restrictions.each do |dr|
%span.badge.bg-secondary.text-wrap.text-break.mb-1.text-start= dr
.col-6
- if invitation.tutorial?
%p.mb-1 Tutorial: #{invitation.tutorial}
- if invitation.note?
%p.mb-1 Note: #{invitation.note}
- if invitation.rsvp_time.present?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Time of RSVP'}
%p.mb-1.small
%i.fas.fa-history
= l(invitation.rsvp_time)
- if invitation.automated_rsvp?
- tooltip_args = { 'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom' }
- if invitation.last_overridden_by_id?
%span{ tooltip_args, title: "Addition by #{invitation.overrider.full_name}" }
%p.mb-1.small
%i.fas.fa-hat-wizard
- else
%span{ tooltip_args, title: 'Waiting list or admin addition' }
%p.mb-1.small
%i.fas.fa-magic
- if invitation.reminded_at.present?
%span{'data-bs-toggle': 'tooltip', 'data-bs-placement': 'bottom', title: 'Reminder emailed at'}
%p.mb-1.small
%i.far.fa-clock
= l(invitation.reminded_at)
= render partial: 'admin/workshop/attendance_row', locals: { invitation: invitation, workshop: @workshop }
30 changes: 30 additions & 0 deletions spec/controllers/admin/invitations_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,34 @@
expect(invitation.last_overridden_by_id).to be admin.id
end
end

describe "PUT #update with attended param" do
let(:workshop) { Fabricate(:workshop, date_and_time: Time.zone.now - 1.day) }
let(:invitation) { Fabricate(:workshop_invitation, workshop: workshop, attending: true) }
let(:admin) { Fabricate(:chapter_organiser) }

before do
admin.add_role(:organiser, workshop.chapter)
login admin
request.env["HTTP_REFERER"] = "/admin/workshop/#{workshop.id}"
end

it "renders the attendance row partial via XHR when verifying" do
put :update, params: { id: invitation.token, workshop_id: workshop.id, attended: true }, xhr: true

expect(response).to have_http_status(:success)
expect(response.media_type).to eq('text/html')
expect(invitation.reload.attended).to be true
end

it "renders the attendance row partial via XHR when unverifying" do
invitation.update!(attended: true)

put :update, params: { id: invitation.token, workshop_id: workshop.id, attended: false }, xhr: true

expect(response).to have_http_status(:success)
expect(response.media_type).to eq('text/html')
expect(invitation.reload.attended).to be_nil
end
end
end
9 changes: 9 additions & 0 deletions spec/controllers/admin/workshops_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
login_as_organiser(admin, workshop.chapter)
end

describe 'GET #show' do
it 'loads the workshop attendance page with attendees' do
Fabricate(:workshop_invitation, workshop: workshop, attending: true)
get :show, params: { id: workshop.id }

expect(response).to have_http_status(:success)
end
end

describe 'DELETE #destroy' do
context 'workshop invitations have been sent' do
before do
Expand Down
21 changes: 21 additions & 0 deletions spec/features/admin/manage_workshop_attendances_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@

expect(page).to have_css('.fa-check-square')
end

scenario 'verifies and unverifies attendance with targeted row replacement', js: true do
second_invitation = Fabricate(:workshop_invitation, workshop: workshop, attending: true)

visit admin_workshop_path(workshop)

# Capture the ID of the second row before clicking
second_row_id = "attendee-row-#{second_invitation.id}"
expect(page).to have_selector("##{second_row_id}")

# Verify first attendee
first('.verify_attendance').click
expect(page).to have_css('.fa-check-square', count: 1, wait: 5)

# Unverify the same attendee
first('.verify_attendance').click
expect(page).to have_css('.fa-check-square', count: 0, wait: 5)

# The unclicked row should still exist with its original ID, proving targeted replacement
expect(page).to have_selector("##{second_row_id}")
end
end

scenario 'can remove a member from the attendee list' do
Expand Down