From 00eec532a6eb14636ea77f52be5608fff81d1236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 24 Feb 2016 18:50:39 +0100 Subject: [PATCH 1/6] removes resolution status from spending proposals --- .../admin/spending_proposals_controller.rb | 4 +- app/models/spending_proposal.rb | 42 +------ .../admin/spending_proposals/index.html.erb | 18 --- .../admin/spending_proposals/show.html.erb | 19 --- app/views/spending_proposals/show.html.erb | 5 - config/locales/admin.en.yml | 6 - config/locales/admin.es.yml | 6 - .../features/admin/spending_proposals_spec.rb | 114 ------------------ spec/models/spending_proposal_spec.rb | 112 ----------------- 9 files changed, 3 insertions(+), 323 deletions(-) diff --git a/app/controllers/admin/spending_proposals_controller.rb b/app/controllers/admin/spending_proposals_controller.rb index 559a7afda..3e0be1bf5 100644 --- a/app/controllers/admin/spending_proposals_controller.rb +++ b/app/controllers/admin/spending_proposals_controller.rb @@ -1,14 +1,12 @@ class Admin::SpendingProposalsController < Admin::BaseController include FeatureFlags - has_filters %w{unresolved accepted rejected}, only: :index - load_and_authorize_resource feature_flag :spending_proposals def index - @spending_proposals = @spending_proposals.includes([:geozone]).send(@current_filter).order(created_at: :desc).page(params[:page]) + @spending_proposals = @spending_proposals.includes([:geozone], [administrator: :user]).order(created_at: :desc).page(params[:page]) end def show diff --git a/app/models/spending_proposal.rb b/app/models/spending_proposal.rb index c023f83f4..f51ef9fd5 100644 --- a/app/models/spending_proposal.rb +++ b/app/models/spending_proposal.rb @@ -4,8 +4,6 @@ class SpendingProposal < ActiveRecord::Base apply_simple_captcha - RESOLUTIONS = ["accepted", "rejected"] - belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :geozone @@ -15,42 +13,10 @@ class SpendingProposal < ActiveRecord::Base validates :title, length: { in: 4..SpendingProposal.title_max_length } validates :description, length: { maximum: SpendingProposal.description_max_length } - validates :resolution, inclusion: { in: RESOLUTIONS, allow_nil: true } validates :terms_of_service, acceptance: { allow_nil: false }, on: :create - scope :accepted, -> { where(resolution: "accepted") } - scope :rejected, -> { where(resolution: "rejected") } - scope :unresolved, -> { where(resolution: nil) } - - def accept - update_attribute(:resolution, "accepted") - end - - def reject - update_attribute(:resolution, "rejected") - end - - def accepted? - resolution == "accepted" - end - - def rejected? - resolution == "rejected" - end - - def unresolved? - resolution.blank? - end - - def legality - case legal - when true - "legal" - when false - "not_legal" - else - "undefined" - end + def description + super.try :html_safe end def feasibility @@ -64,8 +30,4 @@ class SpendingProposal < ActiveRecord::Base end end - def description - super.try :html_safe - end - end diff --git a/app/views/admin/spending_proposals/index.html.erb b/app/views/admin/spending_proposals/index.html.erb index 2b8cb639e..a0ed583cc 100644 --- a/app/views/admin/spending_proposals/index.html.erb +++ b/app/views/admin/spending_proposals/index.html.erb @@ -1,7 +1,5 @@

<%= t("admin.spending_proposals.index.title") %>

-<%= render 'shared/filter_subnav', i18n_namespace: "admin.spending_proposals.index" %> -

<%= page_entries_info @spending_proposals %>

@@ -14,22 +12,6 @@ - <% end %>
<%= geozone_name(spending_proposal) %> - <% unless spending_proposal.accepted? %> - <%= link_to t("admin.spending_proposals.actions.accept"), - accept_admin_spending_proposal_path(spending_proposal, request.query_parameters), - method: :put, - data: { confirm: t("admin.actions.confirm") }, - class: "button radius tiny success no-margin" %> - <% end %> - <% unless spending_proposal.rejected? %> - <%= link_to t("admin.spending_proposals.actions.reject"), - reject_admin_spending_proposal_path(spending_proposal, request.query_parameters), - method: :put, - data: { confirm: t("admin.actions.confirm") }, - class: "button radius tiny warning right" %> - <% end %> -
diff --git a/app/views/admin/spending_proposals/show.html.erb b/app/views/admin/spending_proposals/show.html.erb index 4d3b57935..016c5e630 100644 --- a/app/views/admin/spending_proposals/show.html.erb +++ b/app/views/admin/spending_proposals/show.html.erb @@ -15,30 +15,11 @@

<%= t("admin.spending_proposals.show.geozone") %>: <%= geozone_name(@spending_proposal) %>

-

<%= l @spending_proposal.created_at, format: :datetime %>

- -

- <% unless @spending_proposal.accepted? %> - <%= link_to t("admin.spending_proposals.actions.accept"), - accept_admin_spending_proposal_path(@spending_proposal), - method: :put, - data: { confirm: t("admin.actions.confirm") }, - class: "button radius tiny success no-margin" %> - <% end %> - <% unless @spending_proposal.rejected? %> - <%= link_to t("admin.spending_proposals.actions.reject"), - reject_admin_spending_proposal_path(@spending_proposal), - method: :put, - data: { confirm: t("admin.actions.confirm") }, - class: "button radius tiny warning" %> - <% end %>

<%= t("admin.spending_proposals.show.dossier") %>:

<%= t("admin.spending_proposals.show.price") %>: <%= @spending_proposal.price %>

-

<%= t("admin.spending_proposals.show.legality") %>: <%= t("admin.spending_proposals.show.#{@spending_proposal.legality}") %>

<%= t("admin.spending_proposals.show.feasibility") %>: <%= t("admin.spending_proposals.show.#{@spending_proposal.feasibility}") %>

-<%= safe_html_with_links(@spending_proposal.explanation.html_safe) if @spending_proposal.explanation %> diff --git a/app/views/spending_proposals/show.html.erb b/app/views/spending_proposals/show.html.erb index 95432fba9..987118a6a 100644 --- a/app/views/spending_proposals/show.html.erb +++ b/app/views/spending_proposals/show.html.erb @@ -23,11 +23,6 @@ <% end %> - <% if @spending_proposal.resolution.present? %> -
- <%= @spending_proposal.resolution %> -
- <% end %> diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 1711c9221..6f90446d7 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -137,9 +137,6 @@ en: button: Search placeholder: Search user by name or email' spending_proposals: - actions: - accept: Accept - reject: Reject index: filter: Filter filters: @@ -153,9 +150,6 @@ en: geozone: Scope dossier: Dossier price: Price - legality: Legality - legal: Legal - not_legal: Not legal feasibility: Feasibility feasible: Feasible not_feasible: Not feasible diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 4ccdba39e..5a1ecafab 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -137,9 +137,6 @@ es: button: Buscar placeholder: Buscar usuario por nombre o email spending_proposals: - actions: - accept: Aceptar - reject: Rechazar index: filter: Filtro filters: @@ -153,9 +150,6 @@ es: geozone: Ámbito dossier: Informe price: Coste - legality: Legalidad - legal: Legal - not_legal: No legal feasibility: Viabilidad feasible: Viable not_feasible: No viable diff --git a/spec/features/admin/spending_proposals_spec.rb b/spec/features/admin/spending_proposals_spec.rb index 3b619dc3e..5f2527fe4 100644 --- a/spec/features/admin/spending_proposals_spec.rb +++ b/spec/features/admin/spending_proposals_spec.rb @@ -19,97 +19,12 @@ feature 'Admin spending proposals' do expect(page).to have_content(spending_proposal.title) end - scenario 'Accept from index' do - spending_proposal = create(:spending_proposal) - visit admin_spending_proposals_path - - click_link 'Accept' - - expect(page).to_not have_content(spending_proposal.title) - - click_link 'Accepted' - expect(page).to have_content(spending_proposal.title) - - expect(spending_proposal.reload).to be_accepted - end - - scenario 'Reject from index' do - spending_proposal = create(:spending_proposal) - visit admin_spending_proposals_path - - click_link 'Reject' - - expect(page).to_not have_content(spending_proposal.title) - - click_link('Rejected') - expect(page).to have_content(spending_proposal.title) - - expect(spending_proposal.reload).to be_rejected - end - - scenario "Current filter is properly highlighted" do - visit admin_spending_proposals_path - expect(page).to_not have_link('Unresolved') - expect(page).to have_link('Accepted') - expect(page).to have_link('Rejected') - - visit admin_spending_proposals_path(filter: 'unresolved') - expect(page).to_not have_link('Unresolved') - expect(page).to have_link('Accepted') - expect(page).to have_link('Rejected') - - visit admin_spending_proposals_path(filter: 'accepted') - expect(page).to have_link('Unresolved') - expect(page).to_not have_link('Accepted') - expect(page).to have_link('Rejected') - - visit admin_spending_proposals_path(filter: 'rejected') - expect(page).to have_link('Accepted') - expect(page).to have_link('Unresolved') - expect(page).to_not have_link('Rejected') - end - - scenario "Filtering proposals" do - create(:spending_proposal, title: "Recent spending proposal") - create(:spending_proposal, title: "Good spending proposal", resolution: "accepted") - create(:spending_proposal, title: "Bad spending proposal", resolution: "rejected") - - visit admin_spending_proposals_path(filter: 'unresolved') - expect(page).to have_content('Recent spending proposal') - expect(page).to_not have_content('Good spending proposal') - expect(page).to_not have_content('Bad spending proposal') - - visit admin_spending_proposals_path(filter: 'accepted') - expect(page).to have_content('Good spending proposal') - expect(page).to_not have_content('Recent spending proposal') - expect(page).to_not have_content('Bad spending proposal') - - visit admin_spending_proposals_path(filter: 'rejected') - expect(page).to have_content('Bad spending proposal') - expect(page).to_not have_content('Good spending proposal') - expect(page).to_not have_content('Recent spending proposal') - end - - scenario "Action links remember the pagination setting and the filter" do - per_page = Kaminari.config.default_per_page - (per_page + 2).times { create(:spending_proposal, resolution: "accepted") } - - visit admin_spending_proposals_path(filter: 'accepted', page: 2) - - click_on('Reject', match: :first, exact: true) - - expect(current_url).to include('filter=accepted') - expect(current_url).to include('page=2') - end - scenario 'Show' do spending_proposal = create(:spending_proposal, geozone: create(:geozone), association_name: 'People of the neighbourhood', price: 1234.56, - legal: true, feasible: false, - explanation: "It's impossible") visit admin_spending_proposals_path click_link spending_proposal.title @@ -120,37 +35,8 @@ feature 'Admin spending proposals' do expect(page).to have_content(spending_proposal.association_name) expect(page).to have_content(spending_proposal.geozone.name) expect(page).to have_content("1234.56") - expect(page).to have_content("Legal") expect(page).to have_content("Not feasible") expect(page).to have_content("It's impossible") end - scenario 'Accept from show' do - spending_proposal = create(:spending_proposal) - visit admin_spending_proposal_path(spending_proposal) - - click_link 'Accept' - - expect(page).to_not have_content(spending_proposal.title) - - click_link 'Accepted' - expect(page).to have_content(spending_proposal.title) - - expect(spending_proposal.reload).to be_accepted - end - - scenario 'Reject from show' do - spending_proposal = create(:spending_proposal) - visit admin_spending_proposal_path(spending_proposal) - - click_link 'Reject' - - expect(page).to_not have_content(spending_proposal.title) - - click_link('Rejected') - expect(page).to have_content(spending_proposal.title) - - expect(spending_proposal.reload).to be_rejected - end - end diff --git a/spec/models/spending_proposal_spec.rb b/spec/models/spending_proposal_spec.rb index 3d12b9489..d0b6973a4 100644 --- a/spec/models/spending_proposal_spec.rb +++ b/spec/models/spending_proposal_spec.rb @@ -43,23 +43,6 @@ describe SpendingProposal do end describe "dossier info" do - describe "#legality" do - it "can be legal" do - spending_proposal.legal = true - expect(spending_proposal.legality).to eq "legal" - end - - it "can be not-legal" do - spending_proposal.legal = false - expect(spending_proposal.legality).to eq "not_legal" - end - - it "can be undefined" do - spending_proposal.legal = nil - expect(spending_proposal.legality).to eq "undefined" - end - end - describe "#feasibility" do it "can be feasible" do spending_proposal.feasible = true @@ -78,99 +61,4 @@ describe SpendingProposal do end end - describe "resolution status" do - it "should be valid" do - spending_proposal.resolution = "accepted" - expect(spending_proposal).to be_valid - spending_proposal.resolution = "rejected" - expect(spending_proposal).to be_valid - spending_proposal.resolution = "wrong" - expect(spending_proposal).to_not be_valid - end - - it "can be accepted" do - spending_proposal.accept - expect(spending_proposal.reload.resolution).to eq("accepted") - end - - it "can be rejected" do - spending_proposal.reject - expect(spending_proposal.reload.resolution).to eq("rejected") - end - - describe "#accepted?" do - it "should be true if resolution equals 'accepted'" do - spending_proposal.resolution = "accepted" - expect(spending_proposal.accepted?).to eq true - end - - it "should be false otherwise" do - spending_proposal.resolution = "rejected" - expect(spending_proposal.accepted?).to eq false - spending_proposal.resolution = nil - expect(spending_proposal.accepted?).to eq false - end - end - - describe "#rejected?" do - it "should be true if resolution equals 'rejected'" do - spending_proposal.resolution = "rejected" - expect(spending_proposal.rejected?).to eq true - end - - it "should be false otherwise" do - spending_proposal.resolution = "accepted" - expect(spending_proposal.rejected?).to eq false - spending_proposal.resolution = nil - expect(spending_proposal.rejected?).to eq false - end - end - - describe "#unresolved?" do - it "should be true if resolution is blank" do - spending_proposal.resolution = nil - expect(spending_proposal.unresolved?).to eq true - end - - it "should be false otherwise" do - spending_proposal.resolution = "accepted" - expect(spending_proposal.unresolved?).to eq false - spending_proposal.resolution = "rejected" - expect(spending_proposal.unresolved?).to eq false - end - end - end - - describe "scopes" do - before(:each) do - 2.times { create(:spending_proposal, resolution: "accepted") } - 2.times { create(:spending_proposal, resolution: "rejected") } - 2.times { create(:spending_proposal, resolution: nil) } - end - - describe "unresolved" do - it "should return all spending proposals without resolution" do - unresolved = SpendingProposal.all.unresolved - expect(unresolved.size).to eq(2) - unresolved.each {|u| expect(u.resolution).to be_nil} - end - end - - describe "accepted" do - it "should return all accepted spending proposals" do - accepted = SpendingProposal.all.accepted - expect(accepted.size).to eq(2) - accepted.each {|a| expect(a.resolution).to eq("accepted")} - end - end - - describe "rejected" do - it "should return all rejected spending proposals" do - rejected = SpendingProposal.all.rejected - expect(rejected.size).to eq(2) - rejected.each {|r| expect(r.resolution).to eq("rejected")} - end - end - end - end From 34239d84787f0d545513e3f374486e2e6f12093a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 24 Feb 2016 18:52:15 +0100 Subject: [PATCH 2/6] creates new fields and assignments for spending_ps --- app/models/spending_proposal.rb | 3 ++ app/models/valuation_assignment.rb | 4 +++ app/models/valuator.rb | 3 ++ ...101038_change_spending_proposals_fields.rb | 16 +++++++++ ...0224123110_create_valuation_assignments.rb | 9 +++++ db/schema.rb | 33 ++++++++++++------- spec/factories.rb | 1 + .../features/admin/spending_proposals_spec.rb | 1 + 8 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 app/models/valuation_assignment.rb create mode 100644 db/migrate/20160224101038_change_spending_proposals_fields.rb create mode 100644 db/migrate/20160224123110_create_valuation_assignments.rb diff --git a/app/models/spending_proposal.rb b/app/models/spending_proposal.rb index f51ef9fd5..6339fd545 100644 --- a/app/models/spending_proposal.rb +++ b/app/models/spending_proposal.rb @@ -6,6 +6,9 @@ class SpendingProposal < ActiveRecord::Base belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :geozone + belongs_to :administrator + has_many :valuation_assignments, dependent: :destroy + has_many :valuators, through: :valuation_assignments validates :title, presence: true validates :author, presence: true diff --git a/app/models/valuation_assignment.rb b/app/models/valuation_assignment.rb new file mode 100644 index 000000000..c26b7087f --- /dev/null +++ b/app/models/valuation_assignment.rb @@ -0,0 +1,4 @@ +class ValuationAssignment < ActiveRecord::Base + belongs_to :valuator + belongs_to :spending_proposal +end diff --git a/app/models/valuator.rb b/app/models/valuator.rb index c513e997e..9e18d2cca 100644 --- a/app/models/valuator.rb +++ b/app/models/valuator.rb @@ -2,5 +2,8 @@ class Valuator < ActiveRecord::Base belongs_to :user, touch: true delegate :name, :email, to: :user + has_many :valuation_assignments, dependent: :destroy + has_many :spending_proposals, through: :valuation_assignments + validates :user_id, presence: true, uniqueness: true end diff --git a/db/migrate/20160224101038_change_spending_proposals_fields.rb b/db/migrate/20160224101038_change_spending_proposals_fields.rb new file mode 100644 index 000000000..22a7270cb --- /dev/null +++ b/db/migrate/20160224101038_change_spending_proposals_fields.rb @@ -0,0 +1,16 @@ +class ChangeSpendingProposalsFields < ActiveRecord::Migration + def change + remove_index :spending_proposals, column: :resolution + + remove_column :spending_proposals, :legal, :boolean + remove_column :spending_proposals, :resolution, :string + remove_column :spending_proposals, :explanation, :text + + add_column :spending_proposals, :price_explanation, :text + add_column :spending_proposals, :feasible_explanation, :text + add_column :spending_proposals, :internal_comments, :text + add_column :spending_proposals, :valuation_finished, :boolean, default: false + add_column :spending_proposals, :explanations_log, :text + add_column :spending_proposals, :administrator_id, :integer + end +end diff --git a/db/migrate/20160224123110_create_valuation_assignments.rb b/db/migrate/20160224123110_create_valuation_assignments.rb new file mode 100644 index 000000000..7ec9a9df4 --- /dev/null +++ b/db/migrate/20160224123110_create_valuation_assignments.rb @@ -0,0 +1,9 @@ +class CreateValuationAssignments < ActiveRecord::Migration + def change + create_table :valuation_assignments do |t| + t.belongs_to :valuator + t.belongs_to :spending_proposal + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2c948febc..fd1e0e484 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160222145100) do +ActiveRecord::Schema.define(version: 20160224123110) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -108,10 +108,10 @@ ActiveRecord::Schema.define(version: 20160222145100) do t.string "visit_id" t.datetime "hidden_at" t.integer "flags_count", default: 0 - t.datetime "ignored_flag_at" t.integer "cached_votes_total", default: 0 t.integer "cached_votes_up", default: 0 t.integer "cached_votes_down", default: 0 + t.datetime "ignored_flag_at" t.integer "comments_count", default: 0 t.datetime "confirmed_hide_at" t.integer "cached_anonymous_votes_total", default: 0 @@ -129,6 +129,7 @@ ActiveRecord::Schema.define(version: 20160222145100) do add_index "debates", ["cached_votes_total"], name: "index_debates_on_cached_votes_total", using: :btree add_index "debates", ["cached_votes_up"], name: "index_debates_on_cached_votes_up", using: :btree add_index "debates", ["confidence_score"], name: "index_debates_on_confidence_score", using: :btree + add_index "debates", ["description"], name: "index_debates_on_description", using: :btree add_index "debates", ["geozone_id"], name: "index_debates_on_geozone_id", using: :btree add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree add_index "debates", ["hot_score"], name: "index_debates_on_hot_score", using: :btree @@ -205,7 +206,7 @@ ActiveRecord::Schema.define(version: 20160222145100) do create_table "locks", force: :cascade do |t| t.integer "user_id" t.integer "tries", default: 0 - t.datetime "locked_until", default: '2000-01-01 07:01:01', null: false + t.datetime "locked_until", default: '2000-01-01 00:01:01', null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -265,6 +266,7 @@ ActiveRecord::Schema.define(version: 20160222145100) do add_index "proposals", ["author_id"], name: "index_proposals_on_author_id", using: :btree add_index "proposals", ["cached_votes_up"], name: "index_proposals_on_cached_votes_up", using: :btree add_index "proposals", ["confidence_score"], name: "index_proposals_on_confidence_score", using: :btree + add_index "proposals", ["description"], name: "index_proposals_on_description", using: :btree add_index "proposals", ["geozone_id"], name: "index_proposals_on_geozone_id", using: :btree add_index "proposals", ["hidden_at"], name: "index_proposals_on_hidden_at", using: :btree add_index "proposals", ["hot_score"], name: "index_proposals_on_hot_score", using: :btree @@ -294,20 +296,22 @@ ActiveRecord::Schema.define(version: 20160222145100) do t.text "description" t.integer "author_id" t.string "external_url" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "geozone_id" - t.string "resolution" t.float "price" - t.boolean "legal" t.boolean "feasible" - t.text "explanation" t.string "association_name" + t.text "price_explanation" + t.text "feasible_explanation" + t.text "internal_comments" + t.boolean "valuation_finished", default: false + t.text "explanations_log" + t.integer "administrator_id" end add_index "spending_proposals", ["author_id"], name: "index_spending_proposals_on_author_id", using: :btree add_index "spending_proposals", ["geozone_id"], name: "index_spending_proposals_on_geozone_id", using: :btree - add_index "spending_proposals", ["resolution"], name: "index_spending_proposals_on_resolution", using: :btree create_table "taggings", force: :cascade do |t| t.integer "tag_id" @@ -328,8 +332,8 @@ ActiveRecord::Schema.define(version: 20160222145100) do t.boolean "featured", default: false t.integer "debates_count", default: 0 t.integer "proposals_count", default: 0 - t.string "kind" t.integer "spending_proposals_count", default: 0 + t.string "kind" end add_index "tags", ["debates_count"], name: "index_tags_on_debates_count", using: :btree @@ -405,8 +409,8 @@ ActiveRecord::Schema.define(version: 20160222145100) do t.boolean "public_activity", default: true t.boolean "newsletter", default: false t.integer "notifications_count", default: 0 - t.string "locale" t.boolean "registering_with_oauth", default: false + t.string "locale" t.string "oauth_email" t.integer "geozone_id" t.string "redeemable_code" @@ -418,6 +422,13 @@ ActiveRecord::Schema.define(version: 20160222145100) do add_index "users", ["hidden_at"], name: "index_users_on_hidden_at", using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + create_table "valuation_assignments", force: :cascade do |t| + t.integer "valuator_id" + t.integer "spending_proposal_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "valuators", force: :cascade do |t| t.integer "user_id" end diff --git a/spec/factories.rb b/spec/factories.rb index 0e5831556..6cfe2779b 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,4 +1,5 @@ FactoryGirl.define do + sequence(:document_number) { |n| "#{n.to_s.rjust(8, '0')}X" } factory :user do diff --git a/spec/features/admin/spending_proposals_spec.rb b/spec/features/admin/spending_proposals_spec.rb index 5f2527fe4..de1906a76 100644 --- a/spec/features/admin/spending_proposals_spec.rb +++ b/spec/features/admin/spending_proposals_spec.rb @@ -25,6 +25,7 @@ feature 'Admin spending proposals' do association_name: 'People of the neighbourhood', price: 1234.56, feasible: false, + feasible_explanation: "It's impossible") visit admin_spending_proposals_path click_link spending_proposal.title From 91cedd78ab645506492c313fd15490913eea96bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Wed, 24 Feb 2016 18:53:08 +0100 Subject: [PATCH 3/6] adds new js manifest for admin --- app/assets/javascripts/admin.js | 15 +++++++++++++++ app/views/layouts/admin.html.erb | 2 +- config/initializers/assets.rb | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/admin.js diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js new file mode 100644 index 000000000..f6463175e --- /dev/null +++ b/app/assets/javascripts/admin.js @@ -0,0 +1,15 @@ +// This is a manifest file that'll be compiled into admin.js +// It will be included in the admin layout +// and will require all the files listed below. +// +//= require admin_valuators_forms + +var initialize_admin_modules = function() { + +}; + +$(function(){ + $(document).ready(initialize_admin_modules); + $(document).on('page:load', initialize_admin_modules); + $(document).on('ajax:complete', initialize_admin_modules); +}); diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index 012bea2ab..6f338e8b5 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -8,7 +8,7 @@ <%= content_for?(:title) ? yield(:title) : "Admin" %> <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "vendor/modernizr" %> - <%= javascript_include_tag "application", 'data-turbolinks-track' => true %> + <%= javascript_include_tag "application", "admin", 'data-turbolinks-track' => true %> <%= content_for :head %> <%= csrf_meta_tags %> <%= favicon_link_tag "favicon.ico" %> diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index a13ed5320..38f613094 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -10,6 +10,7 @@ Rails.application.config.assets.version = '1.0' # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # Rails.application.config.assets.precompile += %w( search.js ) Rails.application.config.assets.precompile += %w( ckeditor/* ) +Rails.application.config.assets.precompile += %w( admin.js ) Rails.application.config.assets.precompile += %w( ie_lt9.js ) Rails.application.config.assets.precompile += %w( stat_graphs.js ) Rails.application.config.assets.precompile += %w( print.css ) From 2cddad22d606ed674b3cd6552fae1af55dfcfa5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Wed, 24 Feb 2016 19:34:02 +0100 Subject: [PATCH 4/6] redux admin for spending proposals adds dossier info adds assignments to admin and valuators --- app/assets/javascripts/admin.js | 2 +- .../admin_valuators_forms.js.coffee | 17 +++++ app/assets/javascripts/stat_graphs.js | 1 - .../admin/spending_proposals_controller.rb | 18 +++-- .../_assigned_valuators.html.erb | 9 +++ .../assign_valuators.js.coffee | 1 + .../admin/spending_proposals/show.html.erb | 73 +++++++++++++++--- config/locales/admin.en.yml | 11 +++ config/locales/admin.es.yml | 13 +++- config/routes.rb | 6 +- .../features/admin/spending_proposals_spec.rb | 75 ++++++++++++++++++- 11 files changed, 198 insertions(+), 28 deletions(-) create mode 100644 app/assets/javascripts/admin_valuators_forms.js.coffee create mode 100644 app/views/admin/spending_proposals/_assigned_valuators.html.erb create mode 100644 app/views/admin/spending_proposals/assign_valuators.js.coffee diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index f6463175e..eb938c9b3 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -5,7 +5,7 @@ //= require admin_valuators_forms var initialize_admin_modules = function() { - + App.AdminValuatorsForms.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/admin_valuators_forms.js.coffee b/app/assets/javascripts/admin_valuators_forms.js.coffee new file mode 100644 index 000000000..559d82e50 --- /dev/null +++ b/app/assets/javascripts/admin_valuators_forms.js.coffee @@ -0,0 +1,17 @@ +App.AdminValuatorsForms = + + initialize: -> + $('#spending_proposal_administrator_id').unbind('change').on('change', -> + $('#administrator_assignment_form').submit() + false + ) + + $('#assign-valuators-link').unbind('click').on('click', -> + $('#valuators-assign-list').toggle("down") + false + ) + + $('.js-assign-valuators-check').unbind('change').on('change', -> + $('#valuators_assignment_form').submit() + false + ) diff --git a/app/assets/javascripts/stat_graphs.js b/app/assets/javascripts/stat_graphs.js index e21d88ff0..c5bc5b538 100644 --- a/app/assets/javascripts/stat_graphs.js +++ b/app/assets/javascripts/stat_graphs.js @@ -8,7 +8,6 @@ var initialize_stats_modules = function() { }; $(function(){ - $(document).ready(initialize_stats_modules); $(document).on('page:load', initialize_stats_modules); $(document).on('ajax:complete', initialize_stats_modules); diff --git a/app/controllers/admin/spending_proposals_controller.rb b/app/controllers/admin/spending_proposals_controller.rb index 3e0be1bf5..b758bd10d 100644 --- a/app/controllers/admin/spending_proposals_controller.rb +++ b/app/controllers/admin/spending_proposals_controller.rb @@ -1,25 +1,27 @@ class Admin::SpendingProposalsController < Admin::BaseController include FeatureFlags + feature_flag :spending_proposals load_and_authorize_resource - feature_flag :spending_proposals - def index @spending_proposals = @spending_proposals.includes([:geozone], [administrator: :user]).order(created_at: :desc).page(params[:page]) end def show + @admins = Administrator.includes(:user).all + @valuators = Valuator.includes(:user).all.order("users.username ASC") end - def accept - @spending_proposal.accept - redirect_to request.query_parameters.merge(action: :index) + def assign_admin + @spending_proposal.update(params.require(:spending_proposal).permit(:administrator_id)) + render nothing: true end - def reject - @spending_proposal.reject - redirect_to request.query_parameters.merge(action: :index) + def assign_valuators + params[:spending_proposal] ||= {} + params[:spending_proposal][:valuator_ids] ||= [] + @spending_proposal.update(params.require(:spending_proposal).permit(valuator_ids: [])) end end diff --git a/app/views/admin/spending_proposals/_assigned_valuators.html.erb b/app/views/admin/spending_proposals/_assigned_valuators.html.erb new file mode 100644 index 000000000..09e144dcd --- /dev/null +++ b/app/views/admin/spending_proposals/_assigned_valuators.html.erb @@ -0,0 +1,9 @@ +
    + <% @spending_proposal.valuators.each do |valuator| %> +
  • <%= valuator.name %> (<%= valuator.email %>)
  • + <% end %> + + <% if @spending_proposal.valuators.empty? %> +
  • <%= t("admin.spending_proposals.show.undefined") %>
  • + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/spending_proposals/assign_valuators.js.coffee b/app/views/admin/spending_proposals/assign_valuators.js.coffee new file mode 100644 index 000000000..afe581e59 --- /dev/null +++ b/app/views/admin/spending_proposals/assign_valuators.js.coffee @@ -0,0 +1 @@ +$('#assigned_valuators').html("<%= j(render 'assigned_valuators') %>") \ No newline at end of file diff --git a/app/views/admin/spending_proposals/show.html.erb b/app/views/admin/spending_proposals/show.html.erb index 016c5e630..a18a80f9e 100644 --- a/app/views/admin/spending_proposals/show.html.erb +++ b/app/views/admin/spending_proposals/show.html.erb @@ -1,25 +1,78 @@ -

<%= @spending_proposal.title %>

+

<%= t("admin.spending_proposals.show.heading") %> <%= @spending_proposal.id %>

+

<%= @spending_proposal.title %>

-<%= safe_html_with_links @spending_proposal.description.html_safe %> +<%= safe_html_with_links @spending_proposal.description %> <% if @spending_proposal.external_url.present? %>

<%= text_with_links @spending_proposal.external_url %>

<% end %> -

<%= t("admin.spending_proposals.show.by") %>: +

<%= t("admin.spending_proposals.show.info") %>

+ +

<%= t("admin.spending_proposals.show.by") %>: <%= link_to @spending_proposal.author.name, admin_user_path(@spending_proposal.author) %>

-

<%= t("admin.spending_proposals.show.association_name") %>: - <%= @spending_proposal.association_name %> -

-

<%= t("admin.spending_proposals.show.geozone") %>: + +<% if @spending_proposal.association_name.present? %> +

<%= t("admin.spending_proposals.show.association_name") %>: + <%= @spending_proposal.association_name %> +

+<% end %> + +

<%= t("admin.spending_proposals.show.geozone") %>: <%= geozone_name(@spending_proposal) %>

+ +

<%= t("admin.spending_proposals.show.sent") %>: + <%= l @spending_proposal.created_at, format: :datetime %>

+

<%= t("admin.spending_proposals.show.responsibles") %>

-

<%= t("admin.spending_proposals.show.dossier") %>:

+

<%= t("admin.spending_proposals.show.assigned_admin") %>: + <%= form_for(@spending_proposal, url: assign_admin_admin_spending_proposal_path(@spending_proposal), remote: true, html: {id: 'administrator_assignment_form'}) do |f| %> + <%= f.select :administrator_id, @admins.collect { |a| [ "#{a.name} (#{a.email})", a.id ] }, {include_blank: t("admin.spending_proposals.show.undefined"), label: false} %> + <% end %> +

-

<%= t("admin.spending_proposals.show.price") %>: <%= @spending_proposal.price %>

-

<%= t("admin.spending_proposals.show.feasibility") %>: <%= t("admin.spending_proposals.show.#{@spending_proposal.feasibility}") %>

+

<%= t("admin.spending_proposals.show.assigned_valuators") %>:

+
+ <%= render "assigned_valuators" %> +
+

<%= link_to t("admin.spending_proposals.show.assign_valuators"), "", id: "assign-valuators-link" %>

+ + + + +

<%= t("admin.spending_proposals.show.dossier") %>

+ +

<%= t("admin.spending_proposals.show.price") %> (<%= t("admin.spending_proposals.show.currency") %>): + <%= @spending_proposal.price.present? ? @spending_proposal.price : t("admin.spending_proposals.show.undefined") %> +

+<%= simple_format(safe_html_with_links(@spending_proposal.price_explanation.html_safe), {}, sanitize: false) if @spending_proposal.price_explanation.present? %> + +

<%= t("admin.spending_proposals.show.feasibility") %>: + <%= t("admin.spending_proposals.show.#{@spending_proposal.feasibility}") %> +

+<%= simple_format(safe_html_with_links(@spending_proposal.feasible_explanation.html_safe), {}, sanitize: false) if @spending_proposal.feasible_explanation.present? %> + +<% if @spending_proposal.valuation_finished %> +

<%= t("admin.spending_proposals.show.valuation_finished") %> +<% end %> + +<% if @spending_proposal.internal_comments.present? %> +

<%= t("admin.spending_proposals.show.internal_comments") %>

+ <%= simple_format(safe_html_with_links(@spending_proposal.internal_comments.html_safe), {}, sanitize: false) %> +<% end %> diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 6f90446d7..7d3aef540 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -145,15 +145,26 @@ en: unresolved: Unresolved title: Spending proposals for participatory budgeting show: + heading: Investment project + info: Author info association_name: Asociación by: Sent by + sent: Sent at geozone: Scope dossier: Dossier price: Price + currency: "€" feasibility: Feasibility feasible: Feasible not_feasible: Not feasible undefined: Undefined + valuation_finished: Valuation finished + internal_comments: Internal comments + responsibles: Responsibles + assigned_admin: Assigned admin + assigned_valuators: Assigned valuators + assign_valuators: Assign valuators + assign: Assign stats: show: stats_title: Stats diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 5a1ecafab..8d2815423 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -57,7 +57,7 @@ es: officials: Cargos públicos organizations: Organizaciones settings: Configuración global - spending_proposals: Propuestas de gasto + spending_proposals: Propuestas de inversión stats: Estadísticas moderators: index: @@ -145,15 +145,26 @@ es: unresolved: Sin resolver title: Propuestas de gasto para presupuestos participativos show: + heading: Propuesta de inversión + info: Datos de envío association_name: Asociación by: Enviada por + sent: Fecha de creación geozone: Ámbito dossier: Informe price: Coste + currency: "€" feasibility: Viabilidad feasible: Viable not_feasible: No viable undefined: Sin definir + valuation_finished: Informe finalizado + internal_comments: Commentarios internos + responsibles: Responsables + assigned_admin: Administrador asignado + assigned_valuators: Evaluadores asignados + assign_valuators: Asignar evaluadores + assign: Asignar stats: show: stats_title: Estadísticas diff --git a/config/routes.rb b/config/routes.rb index 7a8a99881..302ce602c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -131,10 +131,10 @@ Rails.application.routes.draw do end end - resources :spending_proposals, only: [:index, :show], path: 'investment_projects' do + resources :spending_proposals, only: [:index, :show] do member do - put :accept - put :reject + patch :assign_admin + patch :assign_valuators end end diff --git a/spec/features/admin/spending_proposals_spec.rb b/spec/features/admin/spending_proposals_spec.rb index de1906a76..a3a844d94 100644 --- a/spec/features/admin/spending_proposals_spec.rb +++ b/spec/features/admin/spending_proposals_spec.rb @@ -20,12 +20,17 @@ feature 'Admin spending proposals' do end scenario 'Show' do + administrator = create(:administrator, user: create(:user, username: 'Ana', email: 'ana@admins.org')) + valuator = create(:valuator, user: create(:user, username: 'Rachel', email: 'rachel@valuators.org')) spending_proposal = create(:spending_proposal, geozone: create(:geozone), association_name: 'People of the neighbourhood', price: 1234.56, feasible: false, - feasible_explanation: "It's impossible") + feasible_explanation: 'It is impossible', + administrator: administrator) + spending_proposal.valuators << valuator + visit admin_spending_proposals_path click_link spending_proposal.title @@ -35,9 +40,71 @@ feature 'Admin spending proposals' do expect(page).to have_content(spending_proposal.author.name) expect(page).to have_content(spending_proposal.association_name) expect(page).to have_content(spending_proposal.geozone.name) - expect(page).to have_content("1234.56") - expect(page).to have_content("Not feasible") - expect(page).to have_content("It's impossible") + expect(page).to have_content('1234.56') + expect(page).to have_content('Not feasible') + expect(page).to have_content('It is impossible') + expect(page).to have_select('spending_proposal[administrator_id]', selected: 'Ana (ana@admins.org)') + + within('#assigned_valuators') do + expect(page).to have_content('Rachel (rachel@valuators.org)') + end + end + + scenario 'Administrator assigment', :js do + spending_proposal = create(:spending_proposal) + + administrator = create(:administrator, user: create(:user, username: 'Ana', email: 'ana@admins.org')) + + visit admin_spending_proposal_path(spending_proposal) + + expect(page).to have_select('spending_proposal[administrator_id]', selected: 'Undefined') + select 'Ana (ana@admins.org)', from: 'spending_proposal[administrator_id]' + + visit admin_spending_proposal_path(spending_proposal) + + expect(page).to have_select('spending_proposal[administrator_id]', selected: 'Ana (ana@admins.org)') + end + + scenario 'Valuators assigments', :js do + spending_proposal = create(:spending_proposal) + + valuator1 = create(:valuator, user: create(:user, username: 'Valentina', email: 'v1@valuators.org')) + valuator2 = create(:valuator, user: create(:user, username: 'Valerian', email: 'v2@valuators.org')) + valuator3 = create(:valuator, user: create(:user, username: 'Val', email: 'v3@valuators.org')) + + visit admin_spending_proposal_path(spending_proposal) + + within('#assigned_valuators') do + expect(page).to have_content('Undefined') + expect(page).to_not have_content('Valentina (v1@valuators.org)') + expect(page).to_not have_content('Valerian (v2@valuators.org)') + expect(page).to_not have_content('Val (v3@valuators.org)') + end + + visit admin_spending_proposal_path(spending_proposal) + + click_link "Assign valuators" + + within('#valuators-assign-list') do + check "valuator_ids_#{valuator1.id}" + check "valuator_ids_#{valuator3.id}" + end + + within('#assigned_valuators') do + expect(page).to have_content('Valentina (v1@valuators.org)') + expect(page).to have_content('Val (v3@valuators.org)') + expect(page).to_not have_content('Undefined') + expect(page).to_not have_content('Valerian (v2@valuators.org)') + end + + visit admin_spending_proposal_path(spending_proposal) + + within('#assigned_valuators') do + expect(page).to have_content('Valentina (v1@valuators.org)') + expect(page).to have_content('Val (v3@valuators.org)') + expect(page).to_not have_content('Undefined') + expect(page).to_not have_content('Valerian (v2@valuators.org)') + end end end From 310217d65f055ada1e1b2c947e19dc9f39c61b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 24 Feb 2016 19:53:48 +0100 Subject: [PATCH 5/6] adds valuator and assignments to dev_seeds --- db/dev_seeds.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 0bee91cb2..f36ed5b36 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -41,6 +41,9 @@ admin.create_administrator moderator = create_user('mod@madrid.es', 'mod') moderator.create_moderator +valuator = create_user('valuator@madrid.es', 'valuator') +valuator.create_valuator + (1..10).each do |i| org_name = Faker::Company.name org_user = create_user("org#{i}@madrid.es", org_name) @@ -247,8 +250,6 @@ end puts "Creating Spending Proposals" -resolutions = ["accepted", "rejected", nil] - (1..30).each do |i| geozone = Geozone.reorder("RANDOM()").first author = User.reorder("RANDOM()").first @@ -258,12 +259,17 @@ resolutions = ["accepted", "rejected", nil] external_url: Faker::Internet.url, description: description, created_at: rand((Time.now - 1.week) .. Time.now), - resolution: resolutions.sample, geozone: [geozone, nil].sample, terms_of_service: "1") puts " #{spending_proposal.title}" end +puts "Creating Valuation Assignments" + +(1..17).to_a.sample.times do + SpendingProposal.reorder("RANDOM()").first.valuators << valuator.valuator +end + puts "Creating Legislation" Legislation.create!(title: 'Participatory Democracy', body: 'In order to achieve...') From bc0a8542c2b03167df1c1ed60dd046e6eea0318f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Thu, 25 Feb 2016 12:24:50 +0100 Subject: [PATCH 6/6] removes admin.js manifest and favors forms.js --- app/assets/javascripts/admin.js | 15 --------------- .../javascripts/admin_valuators_forms.js.coffee | 17 ----------------- app/assets/javascripts/application.js | 2 ++ app/assets/javascripts/forms.js.coffee | 12 ++++++++++++ .../admin/spending_proposals/show.html.erb | 6 +++--- app/views/layouts/admin.html.erb | 2 +- config/initializers/assets.rb | 1 - 7 files changed, 18 insertions(+), 37 deletions(-) delete mode 100644 app/assets/javascripts/admin.js delete mode 100644 app/assets/javascripts/admin_valuators_forms.js.coffee create mode 100644 app/assets/javascripts/forms.js.coffee diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js deleted file mode 100644 index eb938c9b3..000000000 --- a/app/assets/javascripts/admin.js +++ /dev/null @@ -1,15 +0,0 @@ -// This is a manifest file that'll be compiled into admin.js -// It will be included in the admin layout -// and will require all the files listed below. -// -//= require admin_valuators_forms - -var initialize_admin_modules = function() { - App.AdminValuatorsForms.initialize(); -}; - -$(function(){ - $(document).ready(initialize_admin_modules); - $(document).on('page:load', initialize_admin_modules); - $(document).on('ajax:complete', initialize_admin_modules); -}); diff --git a/app/assets/javascripts/admin_valuators_forms.js.coffee b/app/assets/javascripts/admin_valuators_forms.js.coffee deleted file mode 100644 index 559d82e50..000000000 --- a/app/assets/javascripts/admin_valuators_forms.js.coffee +++ /dev/null @@ -1,17 +0,0 @@ -App.AdminValuatorsForms = - - initialize: -> - $('#spending_proposal_administrator_id').unbind('change').on('change', -> - $('#administrator_assignment_form').submit() - false - ) - - $('#assign-valuators-link').unbind('click').on('click', -> - $('#valuators-assign-list').toggle("down") - false - ) - - $('.js-assign-valuators-check').unbind('change').on('change', -> - $('#valuators_assignment_form').submit() - false - ) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 98e378e69..aa27109d9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -40,6 +40,7 @@ //= require advanced_search //= require registration_form //= require suggest +//= require forms var initialize_modules = function() { App.Comments.initialize(); @@ -55,6 +56,7 @@ var initialize_modules = function() { App.AdvancedSearch.initialize(); App.RegistrationForm.initialize(); App.Suggest.initialize(); + App.Forms.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/forms.js.coffee b/app/assets/javascripts/forms.js.coffee new file mode 100644 index 000000000..a7d26cc02 --- /dev/null +++ b/app/assets/javascripts/forms.js.coffee @@ -0,0 +1,12 @@ +App.Forms = + + initialize: -> + $('.js-submit-on-change').unbind('change').on('change', -> + $(this).closest('form').submit() + false + ) + + $('.js-toggle-link').unbind('click').on('click', -> + $($(this).data('toggle-selector')).toggle("down") + false + ) diff --git a/app/views/admin/spending_proposals/show.html.erb b/app/views/admin/spending_proposals/show.html.erb index a18a80f9e..008921ffe 100644 --- a/app/views/admin/spending_proposals/show.html.erb +++ b/app/views/admin/spending_proposals/show.html.erb @@ -31,7 +31,7 @@

<%= t("admin.spending_proposals.show.assigned_admin") %>: <%= form_for(@spending_proposal, url: assign_admin_admin_spending_proposal_path(@spending_proposal), remote: true, html: {id: 'administrator_assignment_form'}) do |f| %> - <%= f.select :administrator_id, @admins.collect { |a| [ "#{a.name} (#{a.email})", a.id ] }, {include_blank: t("admin.spending_proposals.show.undefined"), label: false} %> + <%= f.select :administrator_id, @admins.collect { |a| [ "#{a.name} (#{a.email})", a.id ] }, {include_blank: t("admin.spending_proposals.show.undefined"), label: false}, class: "js-submit-on-change" %> <% end %>

@@ -40,7 +40,7 @@ <%= render "assigned_valuators" %> -

<%= link_to t("admin.spending_proposals.show.assign_valuators"), "", id: "assign-valuators-link" %>

+

<%= link_to t("admin.spending_proposals.show.assign_valuators"), "", class: "js-toggle-link", data: {"toggle-selector" => "#valuators-assign-list"} %>