Skip to content
61 changes: 33 additions & 28 deletions app/controllers/resources_controller.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
class ResourcesController < ApplicationController

def index
per_page = params[:number_of_items_per_page].presence || 25
unpaginated = Resource.where(kind: Resource::PUBLISHED_KINDS) #TODO - #FIXME brittle
.includes(:main_image, :gallery_images, :attachments)
filtered = unpaginated.search_by_params(params)
.by_created
@resources = filtered.paginate(page: params[:page], per_page: per_page).decorate

@count_display = if filtered.count == unpaginated.count
unpaginated.count
else
"#{filtered.count}/#{unpaginated.count}"
end
@sortable_fields = Resource::PUBLISHED_KINDS
if turbo_frame_request?
per_page = params[:number_of_items_per_page].presence || 25
unfiltered = Resource.where(kind: Resource::PUBLISHED_KINDS) # TODO - #FIXME brittle
Copy link
Collaborator

@maebeale maebeale Dec 27, 2025

Choose a reason for hiding this comment

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

FYI 1: i started changing these to unfiltered instead of unpaginated to be more clear

FYI 2: total_entries can't be used anymore now that we're decorating the paginated collection

.includes(:main_image, :gallery_images, :attachments)
filtered = unfiltered.search_by_params(params)
.by_created
@resources = filtered.paginate(page: params[:page], per_page: per_page)

total_count = unfiltered.count
filtered_count = filtered.count
@count_display = if filtered_count == total_count
total_count
else
"#{filtered_count}/#{total_count}"
end

render :resource_results
else
render :index
end
end

def stories
Expand Down Expand Up @@ -58,11 +64,11 @@ def update
@resource = Resource.find(params[:id])
@resource.user ||= current_user
if @resource.update(resource_params)
flash[:notice] = 'Resource updated.'
flash[:notice] = "Resource updated."
redirect_to resources_path
else
set_form_variables
flash[:alert] = 'Failed to update Resource.'
flash[:alert] = "Failed to update Resource."
render :edit
end
end
Expand All @@ -73,32 +79,31 @@ def destroy
redirect_to resources_path, notice: "Resource was successfully destroyed."
end


def search
process_search
@sortable_fields = Resource::PUBLISHED_KINDS
render :index
end

def download
if params[:attachment_id].to_i > 0
attachment = Attachment.where(owner_type: "Resource", id: params[:attachment_id]).last
attachment = if params[:attachment_id].to_i > 0
Attachment.where(owner_type: "Resource", id: params[:attachment_id]).last
else
attachment = Resource.find(params[:resource_id]).download_attachment
Resource.find(params[:resource_id]).download_attachment
end

if attachment&.file&.blob.present?
redirect_to rails_blob_url(attachment.file, disposition: "attachment")
else
if params[:from] == "resources_index"
path = resources_path
elsif params[:from] == "dashboard_index"
path = authenticated_root_path
else
resource_path(params[:resource_id])
end
elsif params[:from] == "dashboard_index"
path = authenticated_root_path
else
resource_path(params[:resource_id])
end
redirect_to path,
alert: "File not found or not attached."
alert: "File not found or not attached."
end
end

Expand All @@ -110,8 +115,8 @@ def set_form_variables

@windows_types = WindowsType.all
@authors = User.active.or(User.where(id: @resource.user_id))
.order(:first_name, :last_name)
.map{|u| [u.full_name, u.id] }
.order(:first_name, :last_name)
.map { |u| [u.full_name, u.id] }
end

def process_search
Expand Down
33 changes: 29 additions & 4 deletions app/frontend/javascript/controllers/collection_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@ import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="collection"
export default class extends Controller {

static classes = [ "unselected", "selected" ]
connect() {
this.element.addEventListener("change", (event) => {
if (event.target.type === "checkbox" || event.target.type === "radio") {
this.submitForm()
const { type } = event.target;

if (type === "checkbox") {
this.toggleClass(event.target);
}
})

if (
type === "checkbox" ||
type === "radio" ||
type === "select-one" ||
type === "select-multiple"
) {
this.submitForm();
}
});
this.element.addEventListener("input", (event) => {
if (event.target.type === "text") {
this.debouncedSubmit()
Expand All @@ -27,4 +37,19 @@ export default class extends Controller {
this.submitForm()
}, 400)
}

toggleClass(el) {
const button = el.closest("label")
if (!button || !this.selectedClasses) return

// Toggle selected classes
this.selectedClasses.forEach(cls => {
button.classList.toggle(cls)
})

// Toggle unselected classes
this.unselectedClasses.forEach(cls => {
button.classList.toggle(cls)
})
}
}
9 changes: 6 additions & 3 deletions app/models/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ class Resource < ApplicationRecord
scope :category_names, ->(names) { tag_names(:categories, names) }
scope :sector_names, ->(names) { tag_names(:sectors, names) }
scope :featured, -> (featured=nil) { featured.present? ? where(featured: featured) : where(featured: true) }
scope :kind, -> (kind) { where("kind like ?", kind ) }
scope :leader_spotlights, -> { kind("LeaderSpotlight") }
scope :kinds, ->(kinds) {
kinds = Array(kinds).flatten.map(&:to_s)
where(kind: kinds)
}
scope :leader_spotlights, -> { kinds("LeaderSpotlight") }
scope :published_kinds, -> { where(kind: PUBLISHED_KINDS) }
scope :published, ->(published=nil) {
if ["true", "false"].include?(published)
Expand All @@ -86,7 +89,7 @@ def self.search_by_params(params)
resources = resources.category_names(params[:category_names]) if params[:category_names].present?
resources = resources.windows_type_name(params[:windows_type_name]) if params[:windows_type_name].present?
resources = resources.title(params[:title]) if params[:title].present?
resources = resources.kind(params[:kind]) if params[:kind].present?
resources = resources.kinds(params[:kinds]) if params[:kinds].present?
resources = resources.published_search(params[:published_search]) if params[:published_search].present?
resources = resources.featured(params[:featured]) if params[:featured].present?
resources
Expand Down
3 changes: 2 additions & 1 deletion app/views/resources/_resource_card.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
<%= link_to(
content_tag(:span, "", class: "far fa-edit"),
edit_resource_path(resource.object),
class: "admin-only bg-blue-100 btn btn-secondary-outline"
class: "admin-only bg-blue-100 btn btn-secondary-outline",
data: {turbo: false}
) %>
<% end %>

Expand Down
7 changes: 7 additions & 0 deletions app/views/resources/_resource_count.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div id="resource_count">
<hr class="border-gray-300 my-6">
<div class="filters-applied w-full flex gap-3 items-center">
<h3 class="text-sm font-semibold uppercase text-gray-500 tracking-wide">Resources ( <%= @count_display %>)</h3>
<%= render "shared/copy_url" %>
</div>
</div>
31 changes: 19 additions & 12 deletions app/views/resources/_search_boxes.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
<%= form_tag resources_path, method: :get, class: "space-y-2" do %>
<% default_btn = "text-gray-600 bg-white border border-gray-300
hover:bg-gray-100 hover:text-gray-800 shadow-sm" %>

<% selected_btn = "text-white #{ DomainTheme.bg_class_for(:resources, intensity: 600) } border-indigo-600
#{ DomainTheme.bg_class_for(:resources, intensity: 700, hover: true) } shadow-md ring-2 ring-indigo-300" %>

<%= form_tag resources_path, method: :get,
data: {controller: "collection share-url",
collection_unselected_class: default_btn,
collection_selected_class: selected_btn,
turbo_frame: "resource_results" },
autocomplete: "off",
class: "space-y-2" do %>
<!-- Search Fields -->
<div class="flex flex-wrap md:flex-nowrap items-end gap-6">

<!-- KEYWORDS -->
<div class="flex-1 min-w-[200px] pb-2">
<label for="query" class="block text-xs font-semibold uppercase text-gray-500 tracking-wide mb-1">
Expand All @@ -11,38 +22,34 @@
<%= text_field_tag :query, params[:query],
class: "w-full bg-white border border-gray-300 rounded-lg px-3 py-2 pr-10
focus:ring-blue-500 focus:border-blue-500" %>
<button type="submit"
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700">
<button type="submit" class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700">
<i class="fa fa-search"></i>
</button>
</div>
</div>

<!-- PUBLISHED -->
<div class="min-w-[150px] admin-only bg-blue-100 p-2 rounded-md">
<label for="published"
class="block text-xs font-semibold uppercase text-gray-600 tracking-wide mb-1">
<label for="published" class="block text-xs font-semibold uppercase text-gray-600 tracking-wide mb-1">
Published
</label>
<%= select_tag :published_search,
options_for_select(
[["All", ""], ["Published", "true"], ["Hidden", "false"]],
params[:published_search]
),
class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full",
onchange: "this.form.submit();" %>
class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full" %>
</div>

<!-- KINDS -->
<div class="min-w-[150px]">
<%= render "search_boxes_kinds", sortable_fields: Resource::PUBLISHED_KINDS %>
</div>
<div class="min-w-[150px]"><%= render "search_boxes_kinds", default_btn: default_btn %></div>

<!-- CLEAR FILTERS -->
<div class="min-w-[120px] pb-3">
<label class="block text-xs font-semibold uppercase text-gray-500 tracking-wide mb-1">&nbsp;</label>
<%= link_to "Clear filters", resources_path, class: "btn btn-utility" %>
</div>

</div>

<%= render "resource_count" %>
<% end %>
39 changes: 10 additions & 29 deletions app/views/resources/_search_boxes_kinds.html.erb
Original file line number Diff line number Diff line change
@@ -1,45 +1,26 @@
<% default_btn = "text-gray-600 bg-white border border-gray-300
hover:bg-gray-100 hover:text-gray-800 shadow-sm" %>

<% selected_btn = "text-white #{ DomainTheme.bg_class_for(:resources, intensity: 600) } border-indigo-600
#{ DomainTheme.bg_class_for(:resources, intensity: 700, hover: true) } shadow-md ring-2 ring-indigo-300" %>

<div class="mb-3">
<h3 class="text-xs font-semibold text-gray-500 tracking-wide mb-1 uppercase">
Kinds (click to filter)
</h3>

<div class="flex flex-wrap gap-2">
<% sortable_fields.each do |sortable_field| %>
<% Resource::PUBLISHED_KINDS.each do |sortable_field| %>
Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah. this is much clearer.

<% name = sortable_field.is_a?(String) ? sortable_field : sortable_field.name %>

<label for="kind_<%= name %>"
class="
<label
for="kind_<%= name %>"
class="
inline-flex items-center cursor-pointer
px-4 py-2 rounded-lg font-medium text-sm
transition-all duration-150
<%= name.to_s == params[:kind] ? selected_btn : default_btn %>
">
<%= check_box_tag(:kind, name, name.to_s == params[:kind],
id: "kind_#{name}",
class: "hidden") %>
<%= default_btn %>
"
>
<%= check_box_tag("kinds[]", name, Array(params[:kinds]).include?(name),
id: "kind_#{name}",
class: "hidden") %>
<%= name.titleize.pluralize %>
</label>

<script>
document.addEventListener("turbo:load", function () {
const el = document.getElementById("kind_<%= name %>")
if (el) {
el.addEventListener("change", function () {
const url = new URL(window.location.href)
url.searchParams.set("kind", "<%= name %>")
url.searchParams.delete("show_all")
window.location.href = url.toString()
})
}
})
</script>

<% end %>
</div>
</div>
Loading