diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index f66510049..774ea5c78 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -70,6 +70,7 @@ //= require advanced_search //= require registration_form //= require suggest +//= require filter_selector //= require forms //= require valuation_budget_investment_form //= require embed_video @@ -133,6 +134,7 @@ var initialize_modules = function() { App.RegistrationForm.initialize(); App.Suggest.initialize(); App.Forms.initialize(); + App.FilterSelector.initialize(); App.ValuationBudgetInvestmentForm.initialize(); App.EmbedVideo.initialize(); App.FixedBar.initialize(); diff --git a/app/assets/javascripts/filter_selector.js b/app/assets/javascripts/filter_selector.js new file mode 100644 index 000000000..5f35db45c --- /dev/null +++ b/app/assets/javascripts/filter_selector.js @@ -0,0 +1,8 @@ +(function() { + "use strict"; + App.FilterSelector = { + initialize: function() { + App.Forms.submitOnChange(".filter-selector select"); + } + }; +}).call(this); diff --git a/app/assets/javascripts/forms.js b/app/assets/javascripts/forms.js index 822b2391b..0a2dfe107 100644 --- a/app/assets/javascripts/forms.js +++ b/app/assets/javascripts/forms.js @@ -8,8 +8,8 @@ } }); }, - submitOnChange: function() { - $("body").on("change", ".js-submit-on-change", function() { + submitOnChange: function(selector) { + $("body").on("change", selector, function() { $(this).closest("form").submit(); return false; }); @@ -58,7 +58,7 @@ }, initialize: function() { App.Forms.disableEnter(); - App.Forms.submitOnChange(); + App.Forms.submitOnChange(".js-submit-on-change"); App.Forms.toggleLink(); App.Forms.synchronizeInputs(); App.Forms.hideOrShowFieldsAfterSelection(); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 6ce3e2ede..e71bfaf26 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -12,6 +12,7 @@ @import "milestones"; @import "pages"; @import "dashboard"; +@import "filter_selector"; @import "legislation"; @import "legislation_process"; @import "legislation_process_form"; diff --git a/app/assets/stylesheets/filter_selector.scss b/app/assets/stylesheets/filter_selector.scss new file mode 100644 index 000000000..10de18b25 --- /dev/null +++ b/app/assets/stylesheets/filter_selector.scss @@ -0,0 +1,14 @@ +.filter-selector { + text-align: right; + + label { + display: inline-block; + font-size: $small-font-size; + margin-right: $line-height / 2; + padding-top: $line-height / 2; + } + + select { + width: auto; + } +} diff --git a/app/components/shared/filter_selector_component.html.erb b/app/components/shared/filter_selector_component.html.erb new file mode 100644 index 000000000..f8cdd6dee --- /dev/null +++ b/app/components/shared/filter_selector_component.html.erb @@ -0,0 +1,5 @@ +<%= form_tag({}, method: :get, enforce_utf8: false, class: "filter-selector") do %> + <%= query_parameters_tags %> + <%= label_tag "filter_selector_filter", t("#{i18n_namespace}.filter") %> + <%= select_tag "filter", options_for_select(filter_options, current_filter), id: "filter_selector_filter" %> +<% end %> diff --git a/app/components/shared/filter_selector_component.rb b/app/components/shared/filter_selector_component.rb new file mode 100644 index 000000000..6ea316a62 --- /dev/null +++ b/app/components/shared/filter_selector_component.rb @@ -0,0 +1,22 @@ +class Shared::FilterSelectorComponent < ApplicationComponent + delegate :valid_filters, :current_filter, to: :helpers + attr_reader :i18n_namespace + + def initialize(i18n_namespace:) + @i18n_namespace = i18n_namespace + end + + private + + def query_parameters_tags + safe_join(request.query_parameters.reject do |name, _| + ["page", "filter"].include?(name) + end.map do |name, value| + hidden_field_tag name, value, id: "filter_selector_#{name}" + end) + end + + def filter_options + valid_filters.map { |filter| [t("#{i18n_namespace}.filters.#{filter}"), filter] } + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5b74ae2e7..71089a97a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -111,14 +111,6 @@ class ApplicationController < ActionController::Base end end - def set_default_budget_filter - if @budget&.balloting? || @budget&.publishing_prices? - params[:filter] ||= "selected" - elsif @budget&.finished? - params[:filter] ||= "winners" - end - end - def current_budget Budget.current end diff --git a/app/controllers/budgets/groups_controller.rb b/app/controllers/budgets/groups_controller.rb index b0124135f..5ad4a4392 100644 --- a/app/controllers/budgets/groups_controller.rb +++ b/app/controllers/budgets/groups_controller.rb @@ -1,12 +1,14 @@ module Budgets class GroupsController < ApplicationController + include InvestmentFilters + before_action :load_budget before_action :load_group authorize_resource :budget authorize_resource :group, class: "Budget::Group" - before_action :set_default_budget_filter, only: :show - has_filters %w[not_unfeasible feasible unfeasible unselected selected winners], only: [:show] + before_action :set_default_investment_filter, only: :show + has_filters investment_filters, only: [:show] def show end diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index 19e7655db..e4ef6ae62 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -6,6 +6,7 @@ module Budgets include RandomSeed include ImageAttributes include Translatable + include InvestmentFilters PER_PAGE = 10 @@ -20,7 +21,7 @@ module Budgets before_action :load_heading, only: [:index, :show] before_action :set_random_seed, only: :index before_action :load_categories, only: [:index, :new, :create, :edit, :update] - before_action :set_default_budget_filter, only: :index + before_action :set_default_investment_filter, only: :index before_action :set_view, only: :index before_action :load_content_blocks, only: :index @@ -31,8 +32,7 @@ module Budgets has_orders %w[most_voted newest oldest], only: :show has_orders ->(c) { c.instance_variable_get(:@budget).investments_orders }, only: :index - valid_filters = %w[not_unfeasible feasible unfeasible unselected selected winners] - has_filters valid_filters, only: [:index, :show, :suggest] + has_filters investment_filters, only: [:index, :show, :suggest] invisible_captcha only: [:create, :update], honeypot: :subtitle, scope: :budget_investment diff --git a/app/controllers/budgets_controller.rb b/app/controllers/budgets_controller.rb index ec7e93f57..b7ee1da8a 100644 --- a/app/controllers/budgets_controller.rb +++ b/app/controllers/budgets_controller.rb @@ -1,12 +1,13 @@ class BudgetsController < ApplicationController include FeatureFlags include BudgetsHelper + include InvestmentFilters feature_flag :budgets before_action :load_budget, only: :show load_and_authorize_resource - before_action :set_default_budget_filter, only: :show - has_filters %w[not_unfeasible feasible unfeasible unselected selected winners], only: :show + before_action :set_default_investment_filter, only: :show + has_filters investment_filters, only: :show respond_to :html, :js diff --git a/app/controllers/concerns/has_filters.rb b/app/controllers/concerns/has_filters.rb index d12058fc4..2072df3be 100644 --- a/app/controllers/concerns/has_filters.rb +++ b/app/controllers/concerns/has_filters.rb @@ -9,7 +9,7 @@ module HasFilters class_methods do def has_filters(valid_filters, *args) before_action(*args) do - @valid_filters = valid_filters + @valid_filters = valid_filters.respond_to?(:call) ? valid_filters.call(self) : valid_filters @current_filter = @valid_filters.include?(params[:filter]) ? params[:filter] : @valid_filters.first end end diff --git a/app/controllers/concerns/investment_filters.rb b/app/controllers/concerns/investment_filters.rb new file mode 100644 index 000000000..045dbba0d --- /dev/null +++ b/app/controllers/concerns/investment_filters.rb @@ -0,0 +1,27 @@ +module InvestmentFilters + extend ActiveSupport::Concern + + class_methods do + def investment_filters + ->(controller) { controller.investment_filters } + end + end + + def set_default_investment_filter + if @budget&.finished? + params[:filter] ||= "winners" + elsif @budget&.publishing_prices_or_later? + params[:filter] ||= "selected" + end + end + + def investment_filters + [ + "not_unfeasible", + "unfeasible", + ("unselected" if @budget.publishing_prices_or_later?), + ("selected" if @budget.publishing_prices_or_later?), + ("winners" if @budget.finished?) + ].compact + end +end diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb index d27fec415..fcf937fa0 100644 --- a/app/helpers/budgets_helper.rb +++ b/app/helpers/budgets_helper.rb @@ -1,8 +1,4 @@ module BudgetsHelper - def show_links_to_budget_investments(budget) - ["balloting", "reviewing_ballots", "finished"].include? budget.phase - end - def budget_voting_styles_select_options Budget::VOTING_STYLES.map do |style| [Budget.human_attribute_name("voting_style_#{style}"), style] diff --git a/app/views/budgets/groups/show.html.erb b/app/views/budgets/groups/show.html.erb index b7565a265..d8741f6eb 100644 --- a/app/views/budgets/groups/show.html.erb +++ b/app/views/budgets/groups/show.html.erb @@ -55,7 +55,7 @@
<%= link_to t("budgets.groups.show.unfeasible"), - budget_path(@budget, filter: "unfeasible") %> + budget_group_path(@budget, @group, filter: "unfeasible") %>
@@ -66,7 +66,7 @@
<%= link_to t("budgets.groups.show.unselected"), - budget_path(@budget, filter: "unselected") %> + budget_group_path(@budget, @group, filter: "unselected") %>
diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb index 73f53bbd9..bdd3d3504 100644 --- a/app/views/budgets/index.html.erb +++ b/app/views/budgets/index.html.erb @@ -55,29 +55,6 @@

<%= t("budgets.index.map") %>

<%= render_map(nil, "budgets", false, nil, @budgets_coordinates) %> - - <% end %> diff --git a/app/views/budgets/investments/_feasibility_link.html.erb b/app/views/budgets/investments/_feasibility_link.html.erb deleted file mode 100644 index 080efcd18..000000000 --- a/app/views/budgets/investments/_feasibility_link.html.erb +++ /dev/null @@ -1,11 +0,0 @@ - - -
- -<% if params[:unfeasible].present? %> - <%= link_to t("budgets.investments.index.sidebar.feasible"), - budget_investments_path(@budget, heading_id: @heading, unfeasible: nil) %> -<% else %> - <%= link_to t("budgets.investments.index.sidebar.unfeasible"), - budget_investments_path(@budget, heading_id: @heading, unfeasible: 1) %> -<% end %> diff --git a/app/views/budgets/investments/index.html.erb b/app/views/budgets/investments/index.html.erb index c1cca7353..1879a8923 100644 --- a/app/views/budgets/investments/index.html.erb +++ b/app/views/budgets/investments/index.html.erb @@ -42,9 +42,9 @@ <% if @current_filter == "unfeasible" %>
-

<%= t("budgets.investments.index.unfeasible") %>: <%= @heading.name %>

+

<%= t("budgets.investments.index.unfeasible") %>

- <%= t("budgets.investments.index.unfeasible_text") %> + <%= t("budgets.investments.index.unfeasible_text") %>
<% elsif @heading.present? %> @@ -70,6 +70,8 @@ <%= render("shared/order_links", i18n_namespace: "budgets.investments.index") %> <% end %> + <%= render Shared::FilterSelectorComponent.new(i18n_namespace: "budgets.investments.index") %> + <% if investments_default_view? %> <% @investments.each do |investment| %> diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index dd76cacec..71933acf9 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -127,6 +127,7 @@ ignore_missing: ## Consider these keys used: ignore_unused: - "budgets.phase.*" + - "budgets.investments.index.filter*" - "budgets.investments.index.orders.*" - "budgets.index.section_header.*" - "budgets.ballots.show.amount_available.*" diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml index e653cfdd7..7931cc773 100644 --- a/config/locales/en/budgets.yml +++ b/config/locales/en/budgets.yml @@ -69,9 +69,6 @@ en: prev_phase: Previous phase current_phase: Current phase map: Budget investments' proposals located geographically - investment_proyects: List of all investment projects - unfeasible_investment_proyects: List of all unfeasible investment projects - not_selected_investment_proyects: List of all investment projects not selected for balloting finished_budgets: Finished participatory budgets see_results: See results section_footer: @@ -120,9 +117,13 @@ en: verified_only: "To create a new budget investment %{verify}." create: "Create a budget investment" not_logged_in: "To create a new budget investment you must %{sign_in} or %{sign_up}." - by_feasibility: By feasibility - feasible: Feasible projects - unfeasible: Unfeasible projects + filter: "Filtering projects by" + filters: + not_unfeasible: "Not unfeasible" + selected: "Selected" + unfeasible: "Unfeasible" + unselected: "Unselected" + winners: "Winners" orders: random: random confidence_score: highest rated diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml index 34f06c306..98eda05f9 100644 --- a/config/locales/es/budgets.yml +++ b/config/locales/es/budgets.yml @@ -69,9 +69,6 @@ es: prev_phase: Fase anterior current_phase: Fase actual map: Proyectos localizables geográficamente - investment_proyects: Ver lista completa de proyectos de gasto - unfeasible_investment_proyects: Ver lista de proyectos de gasto inviables - not_selected_investment_proyects: Ver lista de proyectos de gasto no seleccionados para la votación final finished_budgets: Presupuestos participativos terminados see_results: Ver resultados section_footer: @@ -120,9 +117,13 @@ es: verified_only: "Para crear un nuevo proyecto de gasto %{verify}." create: "Crear proyecto de gasto" not_logged_in: "Para crear un nuevo proyecto de gasto debes %{sign_in} o %{sign_up}." - by_feasibility: Por viabilidad - feasible: Ver los proyectos viables - unfeasible: Ver los proyectos inviables + filter: "Filtrando proyectos" + filters: + not_unfeasible: "No inviables" + selected: "Seleccionados" + unfeasible: "Inviables" + unselected: "No seleccionados" + winners: "Ganadores" orders: random: Aleatorios confidence_score: Mejor valorados diff --git a/spec/components/shared/filter_selector_component_spec.rb b/spec/components/shared/filter_selector_component_spec.rb new file mode 100644 index 000000000..6943a1a9e --- /dev/null +++ b/spec/components/shared/filter_selector_component_spec.rb @@ -0,0 +1,15 @@ +require "rails_helper" + +describe Shared::FilterSelectorComponent, type: :component do + it "renders a form with a select" do + component = Shared::FilterSelectorComponent.new(i18n_namespace: "budgets.investments.index") + allow(component).to receive(:url_for).and_return("/") + allow(component).to receive(:valid_filters).and_return(["unfeasible", "winners"]) + allow(component).to receive(:current_filter).and_return(nil) + + render_inline component + + expect(page).to have_select "Filtering projects by" + expect(page).to have_selector "form[method='get'].filter-selector select" + end +end diff --git a/spec/system/budgets/budgets_spec.rb b/spec/system/budgets/budgets_spec.rb index f3e0ba3a2..022ad0693 100644 --- a/spec/system/budgets/budgets_spec.rb +++ b/spec/system/budgets/budgets_spec.rb @@ -139,53 +139,10 @@ describe "Budgets" do expect(page).not_to have_link "#{heading.name} €1,000,000" expect(page).to have_content "#{heading.name} €1,000,000" - expect(page).to have_link "List of all investment projects", - href: budget_path(budget) - - expect(page).to have_link "List of all unfeasible investment projects", - href: budget_path(budget, filter: "unfeasible") - - expect(page).to have_link "List of all investment projects not selected for balloting", - href: budget_path(budget, filter: "unselected") - expect(page).to have_css("div.map") end end - scenario "Show investment links only on balloting or later" do - budget = create(:budget) - create(:budget_heading, budget: budget) - - allowed_phase_list.each do |phase| - budget.update!(phase: phase) - - visit budgets_path - - expect(page).to have_content(I18n.t("budgets.index.investment_proyects")) - expect(page).to have_content(I18n.t("budgets.index.unfeasible_investment_proyects")) - expect(page).to have_content(I18n.t("budgets.index.not_selected_investment_proyects")) - end - end - - scenario "Not show investment links earlier of balloting " do - budget = create(:budget) - create(:budget_heading, budget: budget) - phases_without_links = ["informing"] - not_allowed_phase_list = Budget::Phase::PHASE_KINDS - - phases_without_links - - allowed_phase_list - - not_allowed_phase_list.each do |phase| - budget.update!(phase: phase) - - visit budgets_path - - expect(page).not_to have_content(I18n.t("budgets.index.investment_proyects")) - expect(page).to have_content(I18n.t("budgets.index.unfeasible_investment_proyects")) - expect(page).not_to have_content(I18n.t("budgets.index.not_selected_investment_proyects")) - end - end - scenario "No budgets" do Budget.destroy_all diff --git a/spec/system/budgets/groups_spec.rb b/spec/system/budgets/groups_spec.rb index c8f68b0d8..df2eb6c8e 100644 --- a/spec/system/budgets/groups_spec.rb +++ b/spec/system/budgets/groups_spec.rb @@ -49,5 +49,24 @@ describe "Budget Groups" do expect(first_heading.name).to appear_before(last_heading.name) end + + scenario "Links to investment filters", :js do + create(:budget_heading, group: group, name: "Southwest") + budget.update!(phase: "finished") + + visit budget_group_path(budget, group) + + click_link "See unfeasible investments" + + expect(page).to have_css "h3", exact_text: "Unfeasible investments" + expect(page).to have_link "Southwest" + expect(page).not_to have_link "See unfeasible investments" + + click_link "See investments not selected for balloting phase" + + expect(page).to have_css "h3", exact_text: "Investments not selected for balloting phase" + expect(page).to have_link "Southwest" + expect(page).not_to have_link "See investments not selected for balloting phase unfeasible investments" + end end end diff --git a/spec/system/budgets/investments_spec.rb b/spec/system/budgets/investments_spec.rb index 11a7707fd..5640def42 100644 --- a/spec/system/budgets/investments_spec.rb +++ b/spec/system/budgets/investments_spec.rb @@ -151,6 +151,51 @@ describe "Budget Investments" do end end + scenario "Index filter by status", :js do + budget.update!(phase: "finished") + + create(:budget_investment, :feasible, heading: heading, title: "Feasible investment") + create(:budget_investment, :unfeasible, heading: heading, title: "Unfeasible investment") + create(:budget_investment, :unselected, heading: heading, title: "Unselected investment") + create(:budget_investment, :selected, heading: heading, title: "Selected investment") + create(:budget_investment, :winner, heading: heading, title: "Winner investment") + + visit budget_investments_path(budget, heading_id: heading.id) + + expect(page).to have_select "Filtering projects by", + options: ["Not unfeasible", "Unfeasible", "Unselected", "Selected", "Winners"] + + select "Unfeasible", from: "Filtering projects by" + + expect(page).to have_css ".budget-investment", count: 1 + expect(page).to have_content "Unfeasible investment" + + select "Unselected", from: "Filtering projects by" + + expect(page).to have_css ".budget-investment", count: 2 + expect(page).to have_content "Unselected investment" + expect(page).to have_content "Feasible investment" + + select "Selected", from: "Filtering projects by" + + expect(page).to have_css ".budget-investment", count: 2 + expect(page).to have_content "Selected investment" + expect(page).to have_content "Winner investment" + + select "Winners", from: "Filtering projects by" + + expect(page).to have_css ".budget-investment", count: 1 + expect(page).to have_content "Winner investment" + + select "Not unfeasible", from: "Filtering projects by" + + expect(page).to have_css ".budget-investment", count: 4 + expect(page).to have_content "Selected investment" + expect(page).to have_content "Unselected investment" + expect(page).to have_content "Feasible investment" + expect(page).to have_content "Winner investment" + end + context("Search") do scenario "Search by text" do investment1 = create(:budget_investment, heading: heading, title: "Get Schwifty") @@ -503,7 +548,9 @@ describe "Budget Investments" do expect(page).not_to have_content "by price" expect(page).not_to have_content "highest rated" end + end + Budget::Phase.kind_or_later("publishing_prices").each do |phase| visit budget_investments_path(budget, heading_id: heading.id, filter: "unselected") within(".submenu") do @@ -1325,10 +1372,7 @@ describe "Budget Investments" do expect(page).to have_css("#budget_heading_#{heading_1.id}.is-active") expect(page).to have_css("#budget_heading_#{heading_2.id}") - visit budget_group_path(budget, group) - click_link "See unfeasible investments" - click_link "Health" within("#headings") do expect(page).to have_css("#budget_heading_#{heading_1.id}")