diff --git a/app/assets/stylesheets/admin/menu.scss b/app/assets/stylesheets/admin/menu.scss
index f80f074cd..a41461485 100644
--- a/app/assets/stylesheets/admin/menu.scss
+++ b/app/assets/stylesheets/admin/menu.scss
@@ -36,7 +36,8 @@
}
&.budgets-link,
- &.investments-link {
+ &.investments-link,
+ &.budget-investments-link {
@include icon(chart-pie, solid);
}
@@ -56,7 +57,8 @@
@include icon(envelope, regular);
}
- &.legislation-link {
+ &.legislation-link,
+ &.legislation-processes-link {
@include icon(file-alt, solid);
}
diff --git a/app/components/sdg_management/menu_component.rb b/app/components/sdg_management/menu_component.rb
index e626b2736..0f719ab2b 100644
--- a/app/components/sdg_management/menu_component.rb
+++ b/app/components/sdg_management/menu_component.rb
@@ -4,14 +4,37 @@ class SDGManagement::MenuComponent < ApplicationComponent
private
def links
- [goals_link]
+ [goals_link, *relatable_links]
end
def goals_link
[t("sdg_management.menu.sdg_content"), sdg_management_goals_path, sdg?, class: "goals-link"]
end
+ def relatable_links
+ SDG::Related::RELATABLE_TYPES.map do |type|
+ [
+ t("sdg_management.menu.#{table_name(type)}"),
+ relatable_type_path(type),
+ controller_name == "relations" && params[:relatable_type] == type.tableize,
+ class: "#{table_name(type).tr("_", "-")}-link"
+ ]
+ end
+ end
+
def sdg?
%w[goals targets local_targets].include?(controller_name)
end
+
+ def relatable_type_path(type)
+ {
+ controller: "sdg_management/relations",
+ action: :index,
+ relatable_type: type.tableize
+ }
+ end
+
+ def table_name(type)
+ type.constantize.table_name
+ end
end
diff --git a/app/components/sdg_management/relations/index_component.html.erb b/app/components/sdg_management/relations/index_component.html.erb
new file mode 100644
index 000000000..cf3592dc9
--- /dev/null
+++ b/app/components/sdg_management/relations/index_component.html.erb
@@ -0,0 +1,23 @@
+<%= header %>
+
+
+
+
+ | <%= model_class.human_attribute_name(:title) %> |
+ <%= SDG::Goal.model_name.human(count: 2).upcase_first %> |
+ <%= SDG::Target.model_name.human(count: 2).upcase_first %> |
+
+
+
+
+ <% @records.each do |record| %>
+
+ | <%= record.title %> |
+ <%= record.sdg_goal_list %> |
+ <%= record.sdg_target_list %> |
+
+ <% end %>
+
+
+
+<%= paginate(@records) %>
diff --git a/app/components/sdg_management/relations/index_component.rb b/app/components/sdg_management/relations/index_component.rb
new file mode 100644
index 000000000..0aaed38d0
--- /dev/null
+++ b/app/components/sdg_management/relations/index_component.rb
@@ -0,0 +1,19 @@
+class SDGManagement::Relations::IndexComponent < ApplicationComponent
+ include Header
+
+ attr_reader :records
+
+ def initialize(records)
+ @records = records
+ end
+
+ private
+
+ def title
+ t("sdg_management.menu.#{model_class.table_name}")
+ end
+
+ def model_class
+ records.model
+ end
+end
diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb
new file mode 100644
index 000000000..f81ad8502
--- /dev/null
+++ b/app/controllers/sdg_management/relations_controller.rb
@@ -0,0 +1,11 @@
+class SDGManagement::RelationsController < SDGManagement::BaseController
+ def index
+ @records = relatable_class.accessible_by(current_ability).order(:id).page(params[:page])
+ end
+
+ private
+
+ def relatable_class
+ params[:relatable_type].classify.constantize
+ end
+end
diff --git a/app/models/concerns/sdg/relatable.rb b/app/models/concerns/sdg/relatable.rb
index 9da20b0d5..64b75f672 100644
--- a/app/models/concerns/sdg/relatable.rb
+++ b/app/models/concerns/sdg/relatable.rb
@@ -15,4 +15,12 @@ module SDG::Relatable
def related_sdgs
sdg_relations.map(&:related_sdg)
end
+
+ def sdg_goal_list
+ sdg_goals.order(:code).map(&:code).join(", ")
+ end
+
+ def sdg_target_list
+ sdg_targets.sort.map(&:code).join(", ")
+ end
end
diff --git a/app/views/sdg_management/relations/index.html.erb b/app/views/sdg_management/relations/index.html.erb
new file mode 100644
index 000000000..55878cd90
--- /dev/null
+++ b/app/views/sdg_management/relations/index.html.erb
@@ -0,0 +1 @@
+<%= render SDGManagement::Relations::IndexComponent.new(@records) %>
diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml
index bc561d093..01c79fc57 100644
--- a/config/locales/en/activerecord.yml
+++ b/config/locales/en/activerecord.yml
@@ -287,6 +287,7 @@ en:
responsible_name: "Person responsible for the group"
poll:
name: "Name"
+ title: "Name"
starts_at: "Start Date"
ends_at: "Closing Date"
geozone_restricted: "Restricted by geozone"
diff --git a/config/locales/en/sdg_management.yml b/config/locales/en/sdg_management.yml
index 1e075fd76..c39a444b1 100644
--- a/config/locales/en/sdg_management.yml
+++ b/config/locales/en/sdg_management.yml
@@ -3,6 +3,11 @@ en:
header:
title: "SDG content"
menu:
+ budget_investments: "Participatory budgets"
+ debates: "Debates"
+ legislation_processes: "Collaborative legislation"
+ polls: "Polls"
+ proposals: "Proposals"
sdg_content: "Goals and Targets"
local_targets:
create:
diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml
index 1f505a6bc..0c736934a 100644
--- a/config/locales/es/activerecord.yml
+++ b/config/locales/es/activerecord.yml
@@ -287,6 +287,7 @@ es:
responsible_name: "Persona responsable del colectivo"
poll:
name: "Nombre"
+ title: "Nombre"
starts_at: "Fecha de apertura"
ends_at: "Fecha de cierre"
geozone_restricted: "Restringida por zonas"
diff --git a/config/locales/es/sdg_management.yml b/config/locales/es/sdg_management.yml
index 4ee4d8893..ce517cf25 100644
--- a/config/locales/es/sdg_management.yml
+++ b/config/locales/es/sdg_management.yml
@@ -3,6 +3,11 @@ es:
header:
title: "Contenido ODS"
menu:
+ budget_investments: "Presupuestos participativos"
+ debates: "Debates"
+ legislation_processes: "Legislación colaborativa"
+ polls: "Votaciones"
+ proposals: "Propuestas"
sdg_content: "Objetivos y Metas"
local_targets:
create:
diff --git a/config/routes/sdg_management.rb b/config/routes/sdg_management.rb
index cad7cbe66..23a73d55d 100644
--- a/config/routes/sdg_management.rb
+++ b/config/routes/sdg_management.rb
@@ -4,4 +4,13 @@ namespace :sdg_management do
resources :goals, only: [:index]
resources :targets, only: [:index]
resources :local_targets, except: [:show]
+
+ types = SDG::Related::RELATABLE_TYPES.map(&:tableize)
+ types_constraint = /#{types.join("|")}/
+
+ get "*relatable_type", to: "relations#index", as: "relations", relatable_type: types_constraint
+
+ types.each do |type|
+ get type, to: "relations#index", as: type
+ end
end
diff --git a/spec/models/sdg/relatable_spec.rb b/spec/models/sdg/relatable_spec.rb
index e6fd1b8d0..595ecad61 100644
--- a/spec/models/sdg/relatable_spec.rb
+++ b/spec/models/sdg/relatable_spec.rb
@@ -28,6 +28,14 @@ describe SDG::Relatable do
end
end
+ describe "#sdg_goal_list" do
+ it "orders goals by code" do
+ relatable.sdg_goals = [SDG::Goal[1], SDG::Goal[3], SDG::Goal[2]]
+
+ expect(relatable.sdg_goal_list).to eq "1, 2, 3"
+ end
+ end
+
describe "#sdg_targets" do
it "can assign targets to a model" do
relatable.sdg_targets = [target, another_target]
@@ -46,6 +54,14 @@ describe SDG::Relatable do
end
end
+ describe "#sdg_target_list" do
+ it "orders targets by code" do
+ relatable.sdg_targets = [SDG::Target[2.2], SDG::Target[1.2], SDG::Target[2.1]]
+
+ expect(relatable.sdg_target_list).to eq "1.2, 2.1, 2.2"
+ end
+ end
+
describe "#sdg_local_targets" do
it "can assign local targets to a model" do
relatable.sdg_local_targets = [local_target, another_local_target]
diff --git a/spec/routing/sdg_management_routes_spec.rb b/spec/routing/sdg_management_routes_spec.rb
new file mode 100644
index 000000000..c04748e47
--- /dev/null
+++ b/spec/routing/sdg_management_routes_spec.rb
@@ -0,0 +1,32 @@
+require "rails_helper"
+
+describe "SDG Management routes" do
+ it "maps routes for relatable classes" do
+ expect(get("/sdg_management/proposals")).to route_to(
+ controller: "sdg_management/relations",
+ action: "index",
+ relatable_type: "proposals"
+ )
+ end
+
+ it "admits named routes" do
+ expect(get(sdg_management_polls_path)).to route_to(
+ controller: "sdg_management/relations",
+ action: "index",
+ relatable_type: "polls"
+ )
+ end
+
+ it "routes relatable types containing a slash" do
+ expect(url_for(
+ controller: "sdg_management/relations",
+ action: "index",
+ relatable_type: "legislation/processes",
+ only_path: true
+ )).to eq "/sdg_management/legislation/processes"
+ end
+
+ it "does not accept non-relatable classes" do
+ expect(get("/sdg_management/tags")).not_to be_routable
+ end
+end
diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb
new file mode 100644
index 000000000..94e479394
--- /dev/null
+++ b/spec/system/sdg_management/relations_spec.rb
@@ -0,0 +1,74 @@
+require "rails_helper"
+
+describe "SDG Relations", :js do
+ before do
+ login_as(create(:administrator).user)
+ Setting["feature.sdg"] = true
+ end
+
+ scenario "navigation" do
+ visit sdg_management_root_path
+
+ within("#side_menu") { click_link "Participatory budgets" }
+
+ expect(page).to have_current_path "/sdg_management/budget/investments"
+ expect(page).to have_css "h2", exact_text: "Participatory budgets"
+
+ within("#side_menu") { click_link "Debates" }
+
+ expect(page).to have_current_path "/sdg_management/debates"
+ expect(page).to have_css "h2", exact_text: "Debates"
+
+ within("#side_menu") { click_link "Collaborative legislation" }
+
+ expect(page).to have_current_path "/sdg_management/legislation/processes"
+ expect(page).to have_css "h2", exact_text: "Collaborative legislation"
+
+ within("#side_menu") { click_link "Polls" }
+
+ expect(page).to have_current_path "/sdg_management/polls"
+ expect(page).to have_css "h2", exact_text: "Polls"
+
+ within("#side_menu") { click_link "Proposals" }
+
+ expect(page).to have_current_path "/sdg_management/proposals"
+ expect(page).to have_css "h2", exact_text: "Proposals"
+ end
+
+ describe "Index" do
+ scenario "list records for the current model" do
+ create(:debate, title: "I'm a debate")
+ create(:proposal, title: "And I'm a proposal")
+
+ visit sdg_management_debates_path
+
+ expect(page).to have_text "I'm a debate"
+ expect(page).not_to have_text "I'm a proposal"
+
+ visit sdg_management_proposals_path
+
+ expect(page).to have_text "I'm a proposal"
+ expect(page).not_to have_text "I'm a debate"
+ end
+
+ scenario "list goals and target for all records" do
+ redistribute = create(:proposal, title: "Resources distribution")
+ redistribute.sdg_goals = [SDG::Goal[1]]
+ redistribute.sdg_targets = [SDG::Target["1.1"]]
+
+ treatment = create(:proposal, title: "Treatment system")
+ treatment.sdg_goals = [SDG::Goal[6]]
+ treatment.sdg_targets = [SDG::Target["6.1"], SDG::Target["6.2"]]
+
+ visit sdg_management_proposals_path
+
+ within("tr", text: "Resources distribution") do
+ expect(page).to have_content "1.1"
+ end
+
+ within("tr", text: "Treatment system") do
+ expect(page).to have_content "6.1, 6.2"
+ end
+ end
+ end
+end