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
30 changes: 19 additions & 11 deletions app/helpers/containers_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,25 @@ def contest_description_action_links(container, description)
end

def render_eligibility_rules(description)
eligibility_plain = description.eligibility_rules.to_plain_text
if eligibility_plain.length > 60
content_tag(:div) do
truncate(eligibility_plain, length: 60, omission: '') +
link_to(' ...more', '#',
data: {
action: 'click->modal#open',
url: eligibility_rules_container_contest_description_path(description.container, description),
modal_title: 'Eligibility Rules'
}
)
eligibility_plain = description.eligibility_rules.to_plain_text.to_s.squish
preview_words = eligibility_plain.split

if preview_words.length > 8
preview_text = preview_words.first(8).join(' ')

content_tag(:div, class: 'd-inline-flex align-items-start gap-1 border rounded px-1 py-0 bg-light small') do
safe_join([
content_tag(:span, 'Rules:', class: 'fw-semibold text-muted'),
content_tag(:span, preview_text, class: 'text-muted'),
link_to('...more', '#',
data: {
action: 'click->modal#open',
url: eligibility_rules_container_contest_description_path(description.container, description),
modal_title: 'Eligibility Rules'
},
aria: { label: 'View full eligibility rules' }
)
])
end
else
description.eligibility_rules
Expand Down
16 changes: 12 additions & 4 deletions app/helpers/contest_descriptions_helper.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
module ContestDescriptionsHelper
def contest_description_entries_link(description)
active_instances = description.contest_instances.where(active: true)
return nil if active_instances.empty?

first_active = active_instances.first
entry_count = first_active.entries.where(deleted: false).count
link_to("Active: #{pluralize(entry_count, 'entry')}",
container_contest_description_contest_instance_path(description.container, description, first_active),
class: 'btn btn-sm btn-primary small')
end

def contest_description_summary(description)
total_instances = description.contest_instances.count
active_instances = description.contest_instances.where(active: true)
summary = ''
if active_instances.any?
first_active = active_instances.first
entry_count = first_active.entries.where(deleted: false).count
summary += link_to("Active: #{pluralize(entry_count, 'entry')}",
container_contest_description_contest_instance_path(description.container, description, first_active), class: 'btn btn-sm btn-primary small')
summary += contest_description_entries_link(description).to_s
summary += '<br>'
end
summary += "<small>#{pluralize(total_instances, 'instance')}</small>"
Expand Down
112 changes: 51 additions & 61 deletions app/views/containers/_contest_descriptions_table.html.erb
Original file line number Diff line number Diff line change
@@ -1,63 +1,53 @@
<%# app/views/containers/_contest_descriptions_table.html.erb %>
<div class="table-responsive">
<table class="table table-sm table-striped align-middle w-100">
<thead>
<tr>
<th class="p-1">Name</th>
<th class="p-1">Status</th>
<th class="p-1">Short Name</th>
<th class="p-1">Eligibility</th>
<th class="p-1">Notes</th>
<th class="p-1">Active</th>
<th class="p-1 text-center">Actions</th>
</tr>
</thead>
<tbody>
<% descriptions.each do |description| %>
<tr data-status="<%= description.active %>" data-filter-target="filterRow">
<th scope="row" class="p-1 fw-normal">
<span class="d-block text-truncate" style="max-width: 180px;" title="<%= description.name %>">
<%= description.name %>
</span>
</th>
<td class="p-1">
<%= contest_description_summary(description) %>
</td>
<td class="p-1">
<span class="d-block text-truncate" style="max-width: 100px;" title="<%= description.short_name %>">
<%= description.short_name %>
</span>
</td>
<td class="card-text w-50 p-1 ">

<%# <span class="d-block text-truncate" style="max-width: 200px;" title="<%= strip_tags(description.eligibility_rules.to_plain_text) %>
<%= render_eligibility_rules(description) %>
<%# </span> %>
</td>
<td class="p-1">
<% if description.notes.present? %>
<%= link_to "view", container_contest_description_contest_instances_path(container, description), class: "btn btn-link btn-sm p-0", title: "View notes", aria: { label: "View notes for #{description.name}" } %>
<%# app/views/containers/_contest_descriptions_table.html.erb - Card list for scannability and full names %>
<% descriptions.each do |description| %>
<div class="card mb-2" data-status="<%= description.active %>" data-filter-target="filterRow">
<div class="card-body py-1 px-2">
<div class="d-flex justify-content-between align-items-start gap-3">
<div class="flex-grow-1 min-w-0">
<div class="d-flex align-items-center flex-wrap gap-2 mb-1">
<div class="d-flex align-items-center" style="height: 2rem;">
<%= link_to description.name,
container_contest_description_contest_instances_path(container, description),
class: 'text-decoration-none fw-semibold fs-5 lh-1' %>
</div>
<% if (entries_link = contest_description_entries_link(description)).present? %>
<div class="d-flex align-items-center" style="height: 2rem;">
<%= entries_link %>
</div>
<% end %>
</td>
<td class="p-1 text-success fw-bold">
<%= boolean_to_yes_no(description.active) %>
</td>
<td class="p-1 text-center">
<div class="d-flex flex-row justify-content-center gap-1">
<% contest_description_action_links(container, description).each do |action, config| %>
<%= link_to config[:path],
class: 'btn btn-link btn-sm p-0',
data: config[:data]&.merge('bs-toggle': 'tooltip'),
title: config[:title],
aria: { label: config[:title] } do %>
<i class="bi bi-<%= config[:icon] %> fs-5 text-um-blue" aria-hidden="true"></i>
<span class="visually-hidden"><%= config[:title] %></span>
<% end %>
<% end %>
</div>
<div class="small text-muted d-flex align-items-center gap-2">
<div class="d-flex flex-column flex-shrink-0">
<span class="text-nowrap">Short name: <%= description.short_name %></span>
<span class="text-nowrap"><%= pluralize(description.contest_instances.count, 'instance') %></span>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<div class="flex-grow-1 d-flex justify-content-center min-w-0"><%= render_eligibility_rules(description) %></div>
</div>
</div>
<div class="d-flex flex-column align-items-end flex-shrink-0">
<span class="small mt-1">Active: <%= boolean_to_yes_no(description.active) %></span>
<div class="d-flex align-items-center justify-content-between mt-1" style="min-width: 3.75rem;">
<% contest_description_action_links(container, description).each do |_action, config| %>
<%= link_to config[:path],
class: 'btn btn-link btn-sm p-0 d-inline-flex align-items-center justify-content-center',
style: 'width: 1.5rem; height: 1.5rem;',
data: config[:data]&.merge('bs-toggle': 'tooltip'),
title: config[:title],
aria: { label: config[:title] } do %>
<i class="bi bi-<%= config[:icon] %> fs-5 text-um-blue" aria-hidden="true"></i>
<span class="visually-hidden"><%= config[:title] %></span>
<% end %>
<% end %>
</div>
<% if description.notes.to_plain_text.squish.present? %>
<%= link_to "View notes",
container_contest_description_path(container, description),
class: "btn btn-link btn-sm p-0",
title: "View notes",
aria: { label: "View notes for #{description.name}" } %>
<% end %>
</div>
</div>
</div>
</div>
<% end %>
14 changes: 8 additions & 6 deletions spec/helpers/containers_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
end

describe "#render_eligibility_rules" do
context "when eligibility rules are longer than 60 characters" do
let(:long_text) { "These are very long eligibility rules that will definitely exceed the 60 character limit and therefore trigger the modal functionality." }
context "when eligibility rules have more than 6 words" do
let(:long_text) { "These rules are intentionally long so they trigger modal preview behavior." }
let(:rich_text) { instance_double(ActionText::RichText, to_plain_text: long_text) }

before do
Expand All @@ -61,19 +61,21 @@
result = helper.render_eligibility_rules(description)
parsed_result = Nokogiri::HTML.fragment(result.to_s)
link = parsed_result.at_css('a')
preview_chip = parsed_result.at_css('div')

# Check link attributes
expect(link['data-action']).to eq('click->modal#open')
expect(link['data-modal-title']).to eq('Eligibility Rules')
expect(link['href']).to eq('#')
expect(link.text).to eq(' ...more')
expect(link.text).to eq('...more')

# Check truncated text
expect(parsed_result.text).to include(long_text[0..59])
# Check word-based truncation and visual treatment
expect(parsed_result.text).to match(/Rules:\s*These rules are intentionally long so/)
expect(preview_chip['class']).to include('border')
end
end

context "when eligibility rules are shorter than 100 characters" do
context "when eligibility rules have 6 or fewer words" do
let(:short_text) { "Short rules" }
let(:rich_text) { instance_double(ActionText::RichText, to_plain_text: short_text) }

Expand Down
Loading