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
27 changes: 26 additions & 1 deletion app/controllers/stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ def index
:created_by, :bookmarks, :primary_asset,
:story_idea))
filtered = base_scope.search_by_params(params)
.order(created_at: :desc)
sortable = %w[title updated_at created_at windows_type workshop author organization]
@sort = sortable.include?(params[:sort]) ? params[:sort] : "created_at"
@sort_direction = params[:direction] == "asc" ? "asc" : "desc"
filtered = apply_sort(filtered, @sort, @sort_direction)
@stories = filtered.paginate(page: params[:page], per_page: per_page).decorate
@count_display = filtered.count == base_scope.count ? base_scope.count : "#{filtered.count}/#{base_scope.count}"

Expand Down Expand Up @@ -170,6 +173,28 @@ def set_story
@story = Story.find(params[:id])
end

def apply_sort(scope, column, direction)
dir = direction.to_sym
case column
when "title", "updated_at", "created_at"
scope.reorder(column => dir)
when "windows_type"
scope.left_joins(:windows_type)
.reorder(WindowsType.arel_table[:short_name].public_send(dir))
when "workshop"
scope.left_joins(:workshop)
.reorder(Workshop.arel_table[:title].public_send(dir))
when "author"
scope.left_joins(created_by: :person)
.reorder(Person.arel_table[:first_name].public_send(dir))
when "organization"
scope.left_joins(:organization)
.reorder(Organization.arel_table[:name].public_send(dir))
else
scope.reorder(created_at: :desc)
end
end

# Strong parameters
def story_params
params.require(:story).permit(
Expand Down
30 changes: 24 additions & 6 deletions app/views/stories/_story_results.html.erb
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
<%= turbo_stream.replace("story_count", partial: "story_count") %>
<% if @stories.any? %>
<%
sort_base = params.permit(:title, :query, :published, :number_of_items_per_page).to_h.symbolize_keys
sort_icon = ->(column) {
if @sort == column
@sort_direction == "asc" ? "fa-arrow-up" : "fa-arrow-down"
else
"fa-sort"
end
}
sort_link = ->(column, label) {
link_to stories_path(sort_base.merge(sort: column, direction: (@sort == column && @sort_direction == "desc") ? "asc" : "desc", page: nil)),
data: { turbo_frame: "story_results" },
class: "inline-flex items-center gap-1 text-gray-700 hover:text-gray-900" do
concat label
concat content_tag(:i, "", class: "fa-solid #{sort_icon.call(column)} text-xs opacity-70")
end
}
%>
<div class="overflow-x-auto bg-white border border-gray-200 rounded-xl shadow-sm animate-fade blur-on-submit">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Title</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Windows Type</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Workshop</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Author</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Organization</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Updated At</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700"><%= sort_link.call("title", "Title") %></th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700"><%= sort_link.call("windows_type", "Windows Type") %></th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700"><%= sort_link.call("workshop", "Workshop") %></th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700"><%= sort_link.call("author", "Author") %></th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700"><%= sort_link.call("organization", "Organization") %></th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700"><%= sort_link.call("updated_at", "Updated At") %></th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Actions</th>
</tr>
</thead>
Expand Down
96 changes: 96 additions & 0 deletions spec/requests/stories_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,102 @@
expect(response.body).to include(public_story.title)
expect(response.body).to include(private_story.title)
end

describe "sorting" do
let(:turbo_headers) { { "Turbo-Frame" => "story_results" } }

let(:org_alpha) { create(:organization, name: "Alpha Org") }
let(:org_zulu) { create(:organization, name: "Zulu Org") }
let(:wt_adult) { create(:windows_type, short_name: "ADULT") }
let(:wt_children) { create(:windows_type, short_name: "CHILDREN") }
let(:ws_art) { create(:workshop, title: "Art Workshop") }
let(:ws_music) { create(:workshop, title: "Music Workshop") }

let(:author_alice) do
person = create(:person, first_name: "Alice", last_name: "Smith")
person.user
end
let(:author_zara) do
person = create(:person, first_name: "Zara", last_name: "Jones")
person.user
end

let!(:story_a) do
create(:story, :published,
title: "Alpha Story",
windows_type: wt_adult,
workshop: ws_art,
organization: org_alpha,
created_by: author_alice).tap do |s|
s.update_columns(created_at: 3.days.ago, updated_at: 1.day.ago)
end
end

let!(:story_z) do
create(:story, :published,
title: "Zulu Story",
windows_type: wt_children,
workshop: ws_music,
organization: org_zulu,
created_by: author_zara).tap do |s|
s.update_columns(created_at: 1.day.ago, updated_at: 3.days.ago)
end
end

def titles_in_response
response.body.scan(/(?:Alpha|Zulu) Story/)
end

it "defaults to created_at desc when no sort param" do
get stories_url, params: {}, headers: turbo_headers
expect(titles_in_response).to eq([ "Zulu Story", "Alpha Story" ])
end

it "sorts by title asc" do
get stories_url, params: { sort: "title", direction: "asc" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Alpha Story", "Zulu Story" ])
end

it "sorts by title desc" do
get stories_url, params: { sort: "title", direction: "desc" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Zulu Story", "Alpha Story" ])
end

it "sorts by updated_at asc" do
get stories_url, params: { sort: "updated_at", direction: "asc" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Zulu Story", "Alpha Story" ])
end

it "sorts by windows_type asc" do
get stories_url, params: { sort: "windows_type", direction: "asc" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Alpha Story", "Zulu Story" ])
end

it "sorts by workshop asc" do
get stories_url, params: { sort: "workshop", direction: "asc" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Alpha Story", "Zulu Story" ])
end

it "sorts by author asc" do
get stories_url, params: { sort: "author", direction: "asc" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Alpha Story", "Zulu Story" ])
end

it "sorts by organization asc" do
get stories_url, params: { sort: "organization", direction: "asc" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Alpha Story", "Zulu Story" ])
end

it "falls back to created_at desc for invalid sort column" do
get stories_url, params: { sort: "bogus" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Zulu Story", "Alpha Story" ])
end

it "combines sort with title filter" do
get stories_url, params: { sort: "title", direction: "desc", title: "Story" }, headers: turbo_headers
expect(titles_in_response).to eq([ "Zulu Story", "Alpha Story" ])
end
end
end

describe "GET /show" do
Expand Down