Skip to content

Commit 47e13f3

Browse files
committed
feat: add attendees tab to event show page with visibility for organizers
1 parent d8942e3 commit 47e13f3

File tree

3 files changed

+103
-36
lines changed

3 files changed

+103
-36
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<div class="list-group-item d-flex justify-content-between align-items-center" id="<%= dom_id(attendance) %>">
2+
<div>
3+
<%= attendance.person.name %>
4+
<small class="text-muted ms-2"><%= attendance.status %></small>
5+
</div>
6+
</div>

app/views/better_together/events/show.html.erb

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
</div>
4444

4545
<div class="container mt-4">
46-
<%= render 'better_together/events/invitations_panel' %>
4746
<!-- Membership section -->
4847
<hr aria-hidden="true">
4948

@@ -69,56 +68,90 @@
6968
<% end %>
7069

7170
<!-- Event tabbed section with members -->
72-
<section class="card tabbed-section">
71+
<section class="card tabbed-section" data-controller="better_together--tabs">
7372
<div class="card-header">
7473
<!-- Navigation tabs -->
7574
<%# Initialize the active tab state with accessible attributes %>
7675
<%= content_tag :div, id: 'eventTabs', class: 'nav nav-tabs card-header-tabs', role: 'tablist', aria_label: 'Event Sections' do %>
77-
<%= link_to t('globals.tabs.about'), '#about', class: 'nav-link active', id: 'about-tab', data: { bs_toggle: 'tab', bs_target: '#about', bs_parent: '#eventSections' }, role: 'tab', aria_controls: 'about', aria_selected: 'true', tabindex: '0' %>
76+
<%= link_to t('globals.tabs.about'), '#about', class: 'nav-link active', id: 'about-tab', data: { bs_toggle: 'tab', bs_target: '#about', turbo: false, 'better_together--tabs-target': 'tab' }, role: 'tab', aria: { controls: 'about', selected: 'true' }, tabindex: '0' %>
77+
<%# Show the Attendees tab only to organizers (reuse invitation policy check) %>
78+
<% invitation = BetterTogether::EventInvitation.new(invitable: @event, inviter: current_person) %>
79+
<% if policy(invitation).create? %>
80+
<% attendees_count = BetterTogether::EventAttendance.where(event: @event).count %>
81+
<%= link_to "#{t('globals.tabs.attendees', default: 'Attendees')} (#{attendees_count})", '#attendees', class: 'nav-link', id: 'attendees-tab', data: { bs_toggle: 'tab', bs_target: '#attendees', turbo: false, 'better_together--tabs-target': 'tab' }, role: 'tab', aria: { controls: 'attendees', selected: 'false' }, tabindex: '0' %>
82+
<% end %>
7883
<% end %>
7984
</div>
8085

81-
<%# Accordion content with accessible attributes and flexbox layout %>
86+
<%# Tabbed content using Bootstrap tab panes so nav links activate panes correctly %>
8287
<div class="card-body" id="eventSections" role="tabpanel">
83-
<!-- Person About Section -->
84-
<section id="about" class="row collapse show" aria-labelledby="about-tab" aria-expanded="true" data-bs-parent="#platformTabs">
85-
<div class="col-md-12">
86-
<div class="event-datetime card-text text-muted mt-2">
87-
<i class="fas fa-eye me-2"></i> <%= @event.privacy.humanize %>
88-
</div>
89-
<% if @event.location&.name&.present? %>
90-
<div class="event-location card-text text-muted mt-2">
91-
<i class="fas fa-map-marker-alt me-2"></i> <%= @event.location %>
92-
</div>
93-
<% end %>
94-
<% if @event.starts_at.present? %>
95-
<div class="event-datetime card-text text-muted mt-2">
96-
<i class="fas fa-calendar-alt me-2"></i> <%= l(@event.starts_at, format: :event) %>
97-
</div>
98-
<% end %>
99-
<% if @event.registration_url.present? %>
100-
<div class="event-datetime card-text text-muted mt-2">
101-
<i class="fas fa-ticket me-2"></i> <%= link_to t('better_together.events.register'), @event.registration_url, target: '_blank', class: 'text-decoration-none' %>
102-
</div>
103-
<% end %>
88+
<div class="tab-content">
89+
<!-- Person About Tab Pane -->
90+
<div id="about" class="nav-tab-pane tab-pane fade show active" role="tabpanel" aria-labelledby="about-tab">
91+
<div class="row">
92+
<div class="col-md-12">
93+
<div class="event-datetime card-text text-muted mt-2">
94+
<i class="fas fa-eye me-2"></i> <%= @event.privacy.humanize %>
95+
</div>
96+
<% if @event.location&.name&.present? %>
97+
<div class="event-location card-text text-muted mt-2">
98+
<i class="fas fa-map-marker-alt me-2"></i> <%= @event.location %>
99+
</div>
100+
<% end %>
101+
<% if @event.starts_at.present? %>
102+
<div class="event-datetime card-text text-muted mt-2">
103+
<i class="fas fa-calendar-alt me-2"></i> <%= l(@event.starts_at, format: :event) %>
104+
</div>
105+
<% end %>
106+
<% if @event.registration_url.present? %>
107+
<div class="event-datetime card-text text-muted mt-2">
108+
<i class="fas fa-ticket me-2"></i> <%= link_to t('better_together.events.register'), @event.registration_url, target: '_blank', class: 'text-decoration-none' %>
109+
</div>
110+
<% end %>
111+
112+
<% if @event.categories.any? %>
113+
<div class="event-categories mt-2">
114+
<%= categories_badge(@event) %>
115+
</div>
116+
<% end %>
117+
118+
<!-- Description Section -->
119+
<p class="card-text mt-3 text-muted">
120+
<%= @resource.description.presence || 'No description available.' %>
121+
</p>
104122

105-
<% if @event.categories.any? %>
106-
<div class="event-categories mt-2">
107-
<%= categories_badge(@event) %>
123+
<%= render 'better_together/events/event_hosts', event: @event %>
108124
</div>
109-
<% end %>
125+
</div>
126+
</div>
110127

111-
<!-- Description Section -->
112-
<p class="card-text mt-3 text-muted">
113-
<%= @resource.description.presence || 'No description available.' %>
114-
</p>
128+
<!-- Attendees (organizer-only) Tab Pane -->
129+
<div id="attendees" class="nav-tab-pane tab-pane fade" role="tabpanel" aria-labelledby="attendees-tab">
130+
<div class="row">
131+
<div class="col-md-12">
132+
<% invitation ||= BetterTogether::EventInvitation.new(invitable: @event, inviter: current_person) %>
133+
<% if policy(invitation).create? %>
134+
<%# Render the existing invitations panel inside this attendees pane %>
135+
<%= render 'better_together/events/invitations_panel' %>
115136

116-
<%= render 'better_together/events/event_hosts', event: @event %>
137+
<% attendances = BetterTogether::EventAttendance.includes(:person).where(event: @event) %>
138+
<% if attendances.any? %>
139+
<hr/>
140+
<h6 class="mb-2"><%= t('better_together.events.attendees', default: 'Attendees') %></h6>
141+
<div class="list-group">
142+
<%= render partial: 'better_together/events/attendance_item', collection: attendances, as: :attendance %>
143+
</div>
144+
<% else %>
145+
<p class="text-muted"><%= t('better_together.events.no_attendees', default: 'No attendees yet.') %></p>
146+
<% end %>
147+
<% end %>
148+
</div>
149+
</div>
117150
</div>
118-
</section>
151+
</div>
119152
</div>
120153
</section>
121154
</div>
122-
155+
123156
<%= share_buttons(shareable: @event) if @event.privacy_public? %>
124157
</div>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require 'rails_helper'
2+
3+
RSpec.describe 'Event show attendees tab', type: :view do
4+
let(:manager_user) { BetterTogether::User.find_by(email: '[email protected]') }
5+
let(:event) do
6+
BetterTogether::Event.create!(
7+
name: 'Neighborhood Clean-up',
8+
starts_at: 1.day.from_now,
9+
identifier: SecureRandom.uuid,
10+
privacy: 'public',
11+
creator: manager_user.person
12+
)
13+
end
14+
15+
it 'shows attendees tab to organizers' do
16+
invitation = BetterTogether::EventInvitation.new(invitable: event, inviter: manager_user.person)
17+
allow(view).to receive(:policy).and_call_original
18+
allow(view).to receive(:current_person).and_return(manager_user.person)
19+
allow(view).to receive(:policy).with(invitation).and_return(double(create?: true))
20+
21+
assign(:event, event)
22+
assign(:resource, event)
23+
24+
render template: 'better_together/events/show'
25+
26+
expect(rendered).to include('Attendees')
27+
end
28+
end

0 commit comments

Comments
 (0)