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) %>
-
-
- <% show_links = show_links_to_budget_investments(current_budget) %>
- <% if show_links %>
- -
- <%= link_to budget_path(current_budget) do %>
- <%= t("budgets.index.investment_proyects") %>
- <% end %>
-
- <% end %>
- -
- <%= link_to budget_path(current_budget, filter: "unfeasible") do %>
- <%= t("budgets.index.unfeasible_investment_proyects") %>
- <% end %>
-
- <% if show_links %>
- -
- <%= link_to budget_path(current_budget, filter: "unselected") do %>
- <%= t("budgets.index.not_selected_investment_proyects") %>
- <% end %>
-
- <% end %>
-
<% 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}")