diff --git a/decidim-admin/app/controllers/concerns/decidim/admin/filterable.rb b/decidim-admin/app/controllers/concerns/decidim/admin/filterable.rb index f34a2f0a21a0..d3412478782e 100644 --- a/decidim-admin/app/controllers/concerns/decidim/admin/filterable.rb +++ b/decidim-admin/app/controllers/concerns/decidim/admin/filterable.rb @@ -20,12 +20,15 @@ module Filterable :filters, :filters_with_values, :find_dynamic_translation, + :filter_prefix_key, :query, :query_params, :query_params_with, :query_params_without, + :blank_query_params, :ransack_params, - :search_field_predicate + :search_field_predicate, + :adjacent_items delegate :categories, to: :current_component delegate :scopes, to: :current_organization @@ -36,10 +39,70 @@ def query private + def check_admin_session_filters + if (current_filters = ransack_params).present? + admin_session_filters = session["admin_filters"] || {} + return if admin_session_filters[filter_prefix_key] == current_filters + + current_filters = {} if current_filters[:reset_filters] == "true" + + admin_session_filters[filter_prefix_key] = current_filters + session["admin_filters"] = admin_session_filters + + redirect_to url_for(query_params.merge(q: {})) if current_filters.blank? + else + @session_filter_params = {} unless session_filter_params.is_a?(Hash) + redirect_to url_for(query_params_with(session_filter_params)) if session_filter_params.present? + end + end + def filtered_collection paginate(query.result) end + def session_filtered_collection + @session_filtered_collection ||= begin + query = base_query.ransack(session_filter_params, search_context: :admin, auth_object: current_user).result + # The limit reorders as pagination does + query.limit(query.count) + end + end + + # This method takes the query used by filter and selects the id of + # each item of the filtered collection (this extra select id avoids + # some errors where the SQL of the filtered collection query uses + # aliases and the id is not available in the result) and uses the lag + # and lead window functions which returns the previous and next ids in + # the query + def adjacent_items(item) + query = + <<-SQL.squish + WITH + collection AS (#{session_filtered_collection.select(:id).to_sql}), + successors AS ( + SELECT + id, + Lag(id, 1) OVER () prev_item, + Lead(id, 1) OVER () next_item + FROM + collection + ) + SELECT + prev_item, + next_item + FROM + successors + WHERE + successors.id = #{item.id} + SQL + + (ActiveRecord::Base.connection.exec_query(query).first || {}).compact_blank.transform_values { |id| collection.find_by(id:) } + end + + def filter_prefix_key + @filter_prefix_key ||= controller_name.to_sym + end + def base_query raise NotImplementedError, "A base query is needed to filter admin resources" end @@ -63,14 +126,26 @@ def ransack_params query_params[:q] || {} end + def session_filter_params + @session_filter_params ||= (session["admin_filters"] || {}).with_indifferent_access.fetch(filter_prefix_key, {}) + end + # For injecting ransack params while keeping query params in links. def query_params_with(hash) query_params.merge(q: ransack_params.merge(hash)) end # For rejecting ransack params while keeping query params in links. - def query_params_without(*filters) - query_params.merge(q: ransack_params.except(*filters)) + def query_params_without(*) + q = ransack_params.except(*) + + return blank_query_params if q.blank? + + query_params.merge(q:) + end + + def blank_query_params + query_params.merge(q: { reset_filters: true }) end # Ransack predicate to use in the search_form_for. diff --git a/decidim-admin/app/helpers/decidim/admin/filterable_helper.rb b/decidim-admin/app/helpers/decidim/admin/filterable_helper.rb index 35eeb8e63d26..21e53401f53f 100644 --- a/decidim-admin/app/helpers/decidim/admin/filterable_helper.rb +++ b/decidim-admin/app/helpers/decidim/admin/filterable_helper.rb @@ -98,9 +98,14 @@ def applied_filters_hidden_field_tags end def applied_filters_tags(i18n_ctx) - ransack_params.slice(*filters).map do |filter, value| + tags = ransack_params.slice(*filters).map do |filter, value| applied_filter_tag(filter, value, filterable_i18n_scope_from_ctx(i18n_ctx)) - end.join.html_safe + end + return if tags.blank? + + tags << remove_all_filters_tag if tags.count > 1 + + tags.join.html_safe end def applied_filter_tag(filter, value, i18n_scope) @@ -111,6 +116,13 @@ def applied_filter_tag(filter, value, i18n_scope) end end + def remove_all_filters_tag + link_to(url_for(blank_query_params), class: "label bg-transparent") do + concat t("decidim.admin.filters.remove_all") + concat icon("delete-bin-line", aria_label: t("decidim.admin.filters.remove_all"), role: "img") + end + end + def remove_filter_icon_link(filter) icon_link_to( "delete-bin-line", @@ -125,6 +137,10 @@ def filterable_i18n_scope_from_ctx(i18n_ctx) i18n_scope += ".#{i18n_ctx}" if i18n_ctx i18n_scope end + + def filtered_adjacent_paths(item, path_method) + adjacent_items(item).transform_values(&method(path_method)) + end end end end diff --git a/decidim-admin/app/packs/stylesheets/decidim/admin/_cards.scss b/decidim-admin/app/packs/stylesheets/decidim/admin/_cards.scss index 16066695ae4f..eab273bf743d 100644 --- a/decidim-admin/app/packs/stylesheets/decidim/admin/_cards.scss +++ b/decidim-admin/app/packs/stylesheets/decidim/admin/_cards.scss @@ -107,7 +107,7 @@ } .fcell .label { - @apply p-2 mb-2; + @apply p-2; } .card-section-draggable-list { diff --git a/decidim-admin/app/views/decidim/admin/shared/_adjacent_navigation.html.erb b/decidim-admin/app/views/decidim/admin/shared/_adjacent_navigation.html.erb new file mode 100644 index 000000000000..d915726e5611 --- /dev/null +++ b/decidim-admin/app/views/decidim/admin/shared/_adjacent_navigation.html.erb @@ -0,0 +1,30 @@ +<% adjacent_paths ||= {} %> +