diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb
index 0a0b9e722..6e2e8ca12 100644
--- a/app/controllers/stories_controller.rb
+++ b/app/controllers/stories_controller.rb
@@ -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}"
@@ -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(
diff --git a/app/views/stories/_story_results.html.erb b/app/views/stories/_story_results.html.erb
index 3b6bcefe3..96c9fad09 100644
--- a/app/views/stories/_story_results.html.erb
+++ b/app/views/stories/_story_results.html.erb
@@ -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
+ }
+ %>
- | Title |
- Windows Type |
- Workshop |
- Author |
- Organization |
- Updated At |
+ <%= sort_link.call("title", "Title") %> |
+ <%= sort_link.call("windows_type", "Windows Type") %> |
+ <%= sort_link.call("workshop", "Workshop") %> |
+ <%= sort_link.call("author", "Author") %> |
+ <%= sort_link.call("organization", "Organization") %> |
+ <%= sort_link.call("updated_at", "Updated At") %> |
Actions |
diff --git a/spec/requests/stories_spec.rb b/spec/requests/stories_spec.rb
index fea2ba38c..a825f06c1 100644
--- a/spec/requests/stories_spec.rb
+++ b/spec/requests/stories_spec.rb
@@ -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