From b1b2cfa8b157601ca2f773ada45d6147a6288805 Mon Sep 17 00:00:00 2001 From: taitus Date: Wed, 2 Jun 2021 11:19:04 +0200 Subject: [PATCH 1/8] Add new time format :short_datetime Improve DurationComponent for render time without seconds. I believe that adding the seconds in this component is not necessary. --- .../admin/budgets/duration_component.rb | 2 +- config/locales/en/rails.yml | 1 + config/locales/es/rails.yml | 5 +++-- .../admin/budgets/duration_component_spec.rb | 6 +++--- spec/system/admin/budgets_spec.rb | 16 ++++++++-------- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/components/admin/budgets/duration_component.rb b/app/components/admin/budgets/duration_component.rb index 3e7a80207..177845928 100644 --- a/app/components/admin/budgets/duration_component.rb +++ b/app/components/admin/budgets/duration_component.rb @@ -24,6 +24,6 @@ class Admin::Budgets::DurationComponent < ApplicationComponent end def formatted_date(time) - time_tag(time, format: :datetime) + time_tag(time, format: :short_datetime) end end diff --git a/config/locales/en/rails.yml b/config/locales/en/rails.yml index 460857624..1c653dbde 100644 --- a/config/locales/en/rails.yml +++ b/config/locales/en/rails.yml @@ -199,5 +199,6 @@ en: default: "%a, %d %b %Y %H:%M:%S %z" long: "%B %d, %Y %H:%M" short: "%d %b %H:%M" + short_datetime: "%Y-%m-%d %H:%M" api: "%Y-%m-%d %H" pm: pm diff --git a/config/locales/es/rails.yml b/config/locales/es/rails.yml index eaff6932d..eade2c082 100644 --- a/config/locales/es/rails.yml +++ b/config/locales/es/rails.yml @@ -9,7 +9,7 @@ es: - vie - sáb abbr_month_names: - - + - - ene - feb - mar @@ -35,7 +35,7 @@ es: long: "%d de %B de %Y" short: "%d de %b" month_names: - - + - - enero - febrero - marzo @@ -193,5 +193,6 @@ es: default: "%A, %d de %B de %Y %H:%M:%S %z" long: "%d de %B de %Y %H:%M" short: "%d de %b %H:%M" + short_datetime: "%d/%m/%Y %H:%M" api: "%d/%m/%Y %H" pm: pm diff --git a/spec/components/admin/budgets/duration_component_spec.rb b/spec/components/admin/budgets/duration_component_spec.rb index fbbd51b73..5e6c78e24 100644 --- a/spec/components/admin/budgets/duration_component_spec.rb +++ b/spec/components/admin/budgets/duration_component_spec.rb @@ -12,7 +12,7 @@ describe Admin::Budgets::DurationComponent, type: :component do render dates - expect(page.text).to eq "2015-08-01 12:00:00 - 2016-09-30 16:29:59" + expect(page.text).to eq "2015-08-01 12:00 - 2016-09-30 16:29" expect(dates).to be_html_safe end @@ -20,7 +20,7 @@ describe Admin::Budgets::DurationComponent, type: :component do durable = double(starts_at: Time.zone.local(2015, 8, 1, 12, 0, 0), ends_at: nil) render Admin::Budgets::DurationComponent.new(durable).dates - expect(page.text).to eq "2015-08-01 12:00:00 - " + expect(page.text).to eq "2015-08-01 12:00 - " end it "shows the end date when no start date is defined" do @@ -28,7 +28,7 @@ describe Admin::Budgets::DurationComponent, type: :component do render Admin::Budgets::DurationComponent.new(durable).dates - expect(page.text).to eq "- 2016-09-30 16:29:59" + expect(page.text).to eq "- 2016-09-30 16:29" end end diff --git a/spec/system/admin/budgets_spec.rb b/spec/system/admin/budgets_spec.rb index bab9408fe..bb41724dc 100644 --- a/spec/system/admin/budgets_spec.rb +++ b/spec/system/admin/budgets_spec.rb @@ -202,14 +202,14 @@ describe "Admin budgets", :admin do "Reviewing voting" ], [ - "2015-07-15 00:00:00 - 2015-08-14 23:59:59", - "2015-08-15 00:00:00 - 2015-09-14 23:59:59", - "2015-09-15 00:00:00 - 2015-10-14 23:59:59", - "2015-10-15 00:00:00 - 2015-11-14 23:59:59", - "2015-11-15 00:00:00 - 2015-12-14 23:59:59", - "2015-11-15 00:00:00 - 2016-01-14 23:59:59", - "2016-01-15 00:00:00 - 2016-02-14 23:59:59", - "2016-02-15 00:00:00 - 2016-03-14 23:59:59" + "2015-07-15 00:00 - 2015-08-14 23:59", + "2015-08-15 00:00 - 2015-09-14 23:59", + "2015-09-15 00:00 - 2015-10-14 23:59", + "2015-10-15 00:00 - 2015-11-14 23:59", + "2015-11-15 00:00 - 2015-12-14 23:59", + "2015-11-15 00:00 - 2016-01-14 23:59", + "2016-01-15 00:00 - 2016-02-14 23:59", + "2016-02-15 00:00 - 2016-03-14 23:59" ], [ "Yes", From 0b81f7f621e2c337abc5bdfba30a21e5adc769b8 Mon Sep 17 00:00:00 2001 From: Julian Herrero Date: Sun, 15 Mar 2020 06:51:52 +0100 Subject: [PATCH 2/8] Allow creating a budget in single or multiple mode Co-Authored-By: decabeza --- .../budgets/budget_multiple_heading_icon.png | Bin 0 -> 32204 bytes .../budgets/budget_single_heading_icon.png | Bin 0 -> 24282 bytes app/assets/stylesheets/admin.scss | 3 +- .../admin/budgets/heading_mode.scss | 77 ++++++++++++++++++ .../admin/budgets/form_component.html.erb | 2 + .../budgets/heading_mode_component.html.erb | 17 ++++ .../admin/budgets/heading_mode_component.rb | 11 +++ .../admin/budgets/index_component.html.erb | 3 +- .../budgets_wizard/budgets/new_component.rb | 7 +- .../budgets_wizard/budgets_controller.rb | 16 +++- config/i18n-tasks.yml | 1 + config/locales/en/admin.yml | 14 +++- config/locales/es/admin.yml | 14 +++- .../admin/budgets_wizard/budgets_spec.rb | 9 +- .../admin/budgets_wizard/wizard_spec.rb | 26 ++++++ 15 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 app/assets/images/budgets/budget_multiple_heading_icon.png create mode 100644 app/assets/images/budgets/budget_single_heading_icon.png create mode 100644 app/assets/stylesheets/admin/budgets/heading_mode.scss create mode 100644 app/components/admin/budgets/heading_mode_component.html.erb create mode 100644 app/components/admin/budgets/heading_mode_component.rb create mode 100644 spec/system/admin/budgets_wizard/wizard_spec.rb diff --git a/app/assets/images/budgets/budget_multiple_heading_icon.png b/app/assets/images/budgets/budget_multiple_heading_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..14a3ec1eb36dfe977d022a3e0d1d1c5f08d61377 GIT binary patch literal 32204 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/budgets/budget_single_heading_icon.png b/app/assets/images/budgets/budget_single_heading_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2a1f72e35a4e892f34015c8c480cb63bcee4f662 GIT binary patch literal 24282 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%0D83hOig-$~Vm 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 bd_(M`XjH$I?g0ERS6)Fz^>d|^Y1sb&yq^?q literal 0 HcmV?d00001 diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index fbb7a3176..a634ecf07 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -665,7 +665,8 @@ code { display: flex; flex-wrap: wrap; - a { + a, + button { @include regular-button; margin-left: auto; } diff --git a/app/assets/stylesheets/admin/budgets/heading_mode.scss b/app/assets/stylesheets/admin/budgets/heading_mode.scss new file mode 100644 index 000000000..d3ee8eadd --- /dev/null +++ b/app/assets/stylesheets/admin/budgets/heading_mode.scss @@ -0,0 +1,77 @@ +.admin .heading-mode { + border-radius: rem-calc(12); + color: $admin-text; + display: flex; + flex-wrap: wrap; + max-width: $global-width * 0.6; + width: 100%; + + section { + @include grid-column-gutter; + display: flex; + flex-flow: column; + margin-bottom: $line-height; + width: 100%; + + @include breakpoint(medium) { + width: 50%; + + &:last-of-type { + border-left: $admin-border; + } + } + + &.single-heading-option h2::after { + content: image-url("budgets/budget_single_heading_icon.png"); + } + + &.multiple-heading-option h2::after { + content: image-url("budgets/budget_multiple_heading_icon.png"); + } + + h2::after { + display: block; + text-align: center; + } + } + + h1 { + margin-bottom: $line-height * 2; + margin-top: $line-height; + text-transform: uppercase; + width: 100%; + } + + h1, + h2 { + font-size: rem-calc(24); + text-align: center; + } + + h2 { + font-weight: 500; + padding-top: $line-height; + } + + p { + font-size: $small-font-size; + font-style: italic; + flex: 1; + } + + a { + @include hollow-button; + margin: 0 auto; + max-width: max-content; + } + + .close-button { + border: 3px solid $admin-text; + border-radius: 50%; + color: $admin-text; + font-size: rem-calc(23); + font-weight: bold; + height: rem-calc(30); + width: rem-calc(30); + } +} diff --git a/app/components/admin/budgets/form_component.html.erb b/app/components/admin/budgets/form_component.html.erb index 12464d641..0c0e8aac4 100644 --- a/app/components/admin/budgets/form_component.html.erb +++ b/app/components/admin/budgets/form_component.html.erb @@ -1,4 +1,6 @@ <%= translatable_form_for [namespace, budget], html: { class: "budgets-form" } do |f| %> + <%= hidden_field_tag(:mode, helpers.budget_mode) if helpers.respond_to?(:budget_mode) %> +

<%= t("admin.budgets.edit.info.budget_settings") %> <%= render "shared/globalize_locales", resource: budget %> diff --git a/app/components/admin/budgets/heading_mode_component.html.erb b/app/components/admin/budgets/heading_mode_component.html.erb new file mode 100644 index 000000000..3e8e0f7be --- /dev/null +++ b/app/components/admin/budgets/heading_mode_component.html.erb @@ -0,0 +1,17 @@ +
+

<%= t("#{i18n_namespace}.title") %>

+ + <% modes.each do |mode| %> +
+

<%= sanitize(t("#{i18n_namespace}.#{mode}.title")) %>

+

<%= t("#{i18n_namespace}.#{mode}.description") %>

+ <%= link_to t("#{i18n_namespace}.#{mode}.link"), + new_admin_budgets_wizard_budget_path(mode: mode), data: { close: "" } %> +
+ <% end %> + + +
diff --git a/app/components/admin/budgets/heading_mode_component.rb b/app/components/admin/budgets/heading_mode_component.rb new file mode 100644 index 000000000..00cfea5bb --- /dev/null +++ b/app/components/admin/budgets/heading_mode_component.rb @@ -0,0 +1,11 @@ +class Admin::Budgets::HeadingModeComponent < ApplicationComponent + private + + def i18n_namespace + "admin.budgets_wizard.heading_mode" + end + + def modes + %w[single multiple] + end +end diff --git a/app/components/admin/budgets/index_component.html.erb b/app/components/admin/budgets/index_component.html.erb index 35e6fce7f..d59fb2c58 100644 --- a/app/components/admin/budgets/index_component.html.erb +++ b/app/components/admin/budgets/index_component.html.erb @@ -1,7 +1,8 @@ <%= header do %> - <%= link_to t("admin.budgets.index.new_link"), new_admin_budgets_wizard_budget_path %> + <% end %> +<%= render Admin::Budgets::HeadingModeComponent.new %> <%= render Admin::Budgets::HelpComponent.new("budgets") %> <%= render "shared/filter_subnav", i18n_namespace: "admin.budgets.index" %> diff --git a/app/components/admin/budgets_wizard/budgets/new_component.rb b/app/components/admin/budgets_wizard/budgets/new_component.rb index 09502de89..38fbb9bfa 100644 --- a/app/components/admin/budgets_wizard/budgets/new_component.rb +++ b/app/components/admin/budgets_wizard/budgets/new_component.rb @@ -1,12 +1,17 @@ class Admin::BudgetsWizard::Budgets::NewComponent < ApplicationComponent include Header attr_reader :budget + delegate :single_heading?, to: :helpers def initialize(budget) @budget = budget end def title - t("admin.budgets.new.title") + if single_heading? + t("admin.budgets.new.title_single") + else + t("admin.budgets.new.title_multiple") + end end end diff --git a/app/controllers/admin/budgets_wizard/budgets_controller.rb b/app/controllers/admin/budgets_wizard/budgets_controller.rb index 68e590dec..aaa5a7b73 100644 --- a/app/controllers/admin/budgets_wizard/budgets_controller.rb +++ b/app/controllers/admin/budgets_wizard/budgets_controller.rb @@ -6,6 +6,8 @@ class Admin::BudgetsWizard::BudgetsController < Admin::BaseController load_and_authorize_resource + helper_method :budget_mode, :single_heading? + def new end @@ -44,6 +46,18 @@ class Admin::BudgetsWizard::BudgetsController < Admin::BaseController end def groups_index - admin_budgets_wizard_budget_groups_path(@budget) + admin_budgets_wizard_budget_groups_path(@budget, url_params) + end + + def budget_mode + params[:mode] + end + + def single_heading? + budget_mode == "single" + end + + def url_params + budget_mode.present? ? { mode: budget_mode } : {} end end diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 97bb35653..131bc8807 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -151,6 +151,7 @@ ignore_unused: - "admin.budget_headings.index.*.help_block" - "admin.budget_phases.index.help_block" - "admin.budget_investments.index.filter*" + - "admin.budgets_wizard.heading_mode.*" - "admin.organizations.index.filter*" - "admin.hidden_users.index.filter*" - "admin.hidden_budget_investments.index.filter*" diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index b6d1329b3..a9c4cf03a 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -133,7 +133,8 @@ en: unable_notice: You cannot delete a budget that has associated investments unable_notice_polls: You cannot delete a budget that has an associated poll new: - title: New participatory budget + title_multiple: New multiple headings budget + title_single: New single heading budget winners: calculate: Calculate Winner Investments calculated: Winners being calculated, it may take a minute. @@ -316,6 +317,16 @@ en: continue: "Continue to headings" headings: continue: "Continue to phases" + heading_mode: + multiple: + description: "Create a process with multiple groups, districts or topics. You have something like several bags of money. For example, 25,000€ for \"North District\", 30,000€ for \"Center District\", 28,000€ for \"South District\" and the whole city. Or for several themes like sport, culture or health." + link: "Create multiple headings budget" + title: "Multiple headings budget" + single: + description: "Create a process with a group, district or topic. You have something like a bag of money. For example, 25,000€ for the district, or 10,000€ for culture." + link: "Create single heading budget" + title: "Single heading budget" + title: "Participatory budget type" phases: back: "Go back to headings" continue: "Finish" @@ -1357,6 +1368,7 @@ en: example_url: "https://consulproject.org" show_results_and_stats: "Show results and stats" results_and_stats_reminder: "Marking these checkboxes the results and/or stats will be publicly available and every user will see them." + close_modal: Close modal example_url: "https://consulproject.org" geozones: index: diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index a5d7fe170..40f196d30 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -133,7 +133,8 @@ es: unable_notice: No se puede eliminar un presupuesto con proyectos asociados unable_notice_polls: No se puede eliminar un presupuesto con una votación asociada new: - title: Nuevo presupuesto ciudadano + title_multiple: Nuevo presupuesto de múltiples partidas + title_single: Nuevo presupuesto de partida única winners: calculate: Calcular proyectos ganadores calculated: Calculando ganadores, puede tardar un minuto. @@ -316,6 +317,16 @@ es: continue: "Continuar a partidas" headings: continue: "Continuar a fases" + heading_mode: + multiple: + description: "Crea un proceso con múltiples grupos, distritos o temas. Tienes algo como varias bolsas de dinero. Por ejemplo, 25.000€ para \"Distrito norte\", 30.000€ para \"Distrito centro\", 28.000€ para \"Distrito sur\" y toda la ciudad. O para varios temas como deporte, cultura o salud." + link: "Crear presupuesto de múltiples partidas" + title: "Presupuesto de múltiples partidas" + single: + description: "Crea un proceso con un grupo, distrito o tema. Tienes algo como una bolsa de dinero. Por ejemplo, 25.000€ para un distrito, o 10.000€ para cultura." + link: "Crear presupuesto de partida única" + title: "Presupuesto de partida única" + title: Tipo de presupuesto participativo phases: back: "Volver a partidas" continue: "Finalizar" @@ -1356,6 +1367,7 @@ es: example_url: "https://consulproject.org" show_results_and_stats: "Mostrar resultados y estadísticas" results_and_stats_reminder: "Si marcas estas casillas los resultados y/o estadísticas serán públicos y podrán verlos todos los usuarios." + close_modal: Cerrar ventana emergente example_url: "https://consulproject.org" geozones: index: diff --git a/spec/system/admin/budgets_wizard/budgets_spec.rb b/spec/system/admin/budgets_wizard/budgets_spec.rb index ac4722bad..a062d9492 100644 --- a/spec/system/admin/budgets_wizard/budgets_spec.rb +++ b/spec/system/admin/budgets_wizard/budgets_spec.rb @@ -4,7 +4,8 @@ describe "Budgets wizard, first step", :admin do describe "New" do scenario "Create budget - Knapsack voting (default)" do visit admin_budgets_path - click_link "Create new budget" + click_button "Create new budget" + click_link "Create multiple headings budget" fill_in "Name", with: "M30 - Summer campaign" click_button "Continue to groups" @@ -21,7 +22,8 @@ describe "Budgets wizard, first step", :admin do admin = Administrator.first visit admin_budgets_path - click_link "Create new budget" + click_button "Create new budget" + click_link "Create multiple headings budget" fill_in "Name", with: "M30 - Summer campaign" select "Approval", from: "Final voting style" @@ -73,7 +75,8 @@ describe "Budgets wizard, first step", :admin do describe "Create" do scenario "A new budget is always created in draft mode" do visit admin_budgets_path - click_link "Create new budget" + click_button "Create new budget" + click_link "Create multiple headings budget" fill_in "Name", with: "M30 - Summer campaign" diff --git a/spec/system/admin/budgets_wizard/wizard_spec.rb b/spec/system/admin/budgets_wizard/wizard_spec.rb new file mode 100644 index 000000000..f042ab2a3 --- /dev/null +++ b/spec/system/admin/budgets_wizard/wizard_spec.rb @@ -0,0 +1,26 @@ +require "rails_helper" + +describe "Budgets creation wizard", :admin do + scenario "Creation of a single-heading budget by steps" do + visit admin_budgets_path + click_button "Create new budget" + click_link "Create single heading budget" + + fill_in "Name", with: "Single heading budget" + click_button "Continue to groups" + + expect(page).to have_content "New participatory budget created successfully!" + end + + scenario "Creation of a multiple-headings budget by steps" do + visit admin_budgets_path + click_button "Create new budget" + click_link "Create multiple headings budget" + + fill_in "Name", with: "Multiple headings budget" + click_button "Continue to groups" + + expect(page).to have_content "New participatory budget created successfully!" + expect(page).to have_content "There are no groups." + end +end From 9fcae141a652a15ba1228477efed8d03153c1c27 Mon Sep 17 00:00:00 2001 From: Julian Herrero Date: Sun, 15 Mar 2020 06:51:52 +0100 Subject: [PATCH 3/8] Adapt group step to single and multiple budget mode Co-Authored-By: decabeza --- .../admin/budgets/form_component.html.erb | 2 +- .../admin/budgets_wizard/base_component.rb | 3 ++ .../creation_step_component.html.erb | 34 +++++++++++-------- .../budgets_wizard/creation_step_component.rb | 2 +- .../groups/creation_step_component.rb | 6 +++- .../groups/index_component.html.erb | 4 ++- .../budgets_wizard/groups/index_component.rb | 2 +- .../headings/index_component.html.erb | 2 +- .../headings/index_component.rb | 10 +++++- .../model_field_component.html.erb | 1 + .../budgets_wizard/model_field_component.rb | 2 ++ .../admin/budgets_wizard/base_controller.rb | 17 ++++++++++ .../budgets_wizard/budgets_controller.rb | 16 +-------- .../admin/budgets_wizard/groups_controller.rb | 14 ++++++-- .../budgets_wizard/headings_controller.rb | 2 +- app/views/admin/budget_groups/_form.html.erb | 8 ++++- config/locales/en/admin.yml | 2 ++ config/locales/es/admin.yml | 2 ++ .../admin/budgets_wizard/groups_spec.rb | 20 +++++++++++ .../admin/budgets_wizard/wizard_spec.rb | 25 ++++++++++++++ 20 files changed, 132 insertions(+), 42 deletions(-) create mode 100644 app/components/admin/budgets_wizard/base_component.rb create mode 100644 app/components/admin/budgets_wizard/model_field_component.html.erb create mode 100644 app/components/admin/budgets_wizard/model_field_component.rb create mode 100644 app/controllers/admin/budgets_wizard/base_controller.rb diff --git a/app/components/admin/budgets/form_component.html.erb b/app/components/admin/budgets/form_component.html.erb index 0c0e8aac4..6000b586d 100644 --- a/app/components/admin/budgets/form_component.html.erb +++ b/app/components/admin/budgets/form_component.html.erb @@ -1,5 +1,5 @@ <%= translatable_form_for [namespace, budget], html: { class: "budgets-form" } do |f| %> - <%= hidden_field_tag(:mode, helpers.budget_mode) if helpers.respond_to?(:budget_mode) %> + <%= render Admin::BudgetsWizard::ModelFieldComponent.new %>
<%= t("admin.budgets.edit.info.budget_settings") %> diff --git a/app/components/admin/budgets_wizard/base_component.rb b/app/components/admin/budgets_wizard/base_component.rb new file mode 100644 index 000000000..3d96f6a07 --- /dev/null +++ b/app/components/admin/budgets_wizard/base_component.rb @@ -0,0 +1,3 @@ +class Admin::BudgetsWizard::BaseComponent < ApplicationComponent + delegate :single_heading?, :url_params, to: :helpers +end diff --git a/app/components/admin/budgets_wizard/creation_step_component.html.erb b/app/components/admin/budgets_wizard/creation_step_component.html.erb index b6ecad267..b42ce98e5 100644 --- a/app/components/admin/budgets_wizard/creation_step_component.html.erb +++ b/app/components/admin/budgets_wizard/creation_step_component.html.erb @@ -1,19 +1,23 @@
- - - <%= content %> - - - - <% if next_step_path %> - <%= link_to t("admin.budgets_wizard.#{i18n_namespace}.continue"), - next_step_path, - class: "next-step" %> + <% if single_heading? %> + <%= content %> <% else %> -

- <%= t("admin.budgets_wizard.#{i18n_namespace}.continue") %> -

+ + + <%= content %> + + + + <% if next_step_path %> + <%= link_to t("admin.budgets_wizard.#{i18n_namespace}.continue"), + next_step_path, + class: "next-step" %> + <% else %> +

+ <%= t("admin.budgets_wizard.#{i18n_namespace}.continue") %> +

+ <% end %> <% end %>
diff --git a/app/components/admin/budgets_wizard/creation_step_component.rb b/app/components/admin/budgets_wizard/creation_step_component.rb index 2bd220146..575ac11e1 100644 --- a/app/components/admin/budgets_wizard/creation_step_component.rb +++ b/app/components/admin/budgets_wizard/creation_step_component.rb @@ -1,4 +1,4 @@ -class Admin::BudgetsWizard::CreationStepComponent < ApplicationComponent +class Admin::BudgetsWizard::CreationStepComponent < Admin::BudgetsWizard::BaseComponent attr_reader :record, :next_step_path def initialize(record, next_step_path) diff --git a/app/components/admin/budgets_wizard/groups/creation_step_component.rb b/app/components/admin/budgets_wizard/groups/creation_step_component.rb index 8a5e3bfa9..f2faeea77 100644 --- a/app/components/admin/budgets_wizard/groups/creation_step_component.rb +++ b/app/components/admin/budgets_wizard/groups/creation_step_component.rb @@ -13,7 +13,11 @@ class Admin::BudgetsWizard::Groups::CreationStepComponent < ApplicationComponent end def form_path - admin_budgets_wizard_budget_groups_path(budget) + if group.persisted? + admin_budgets_wizard_budget_group_path(budget, group) + else + admin_budgets_wizard_budget_groups_path(budget) + end end def next_step_path diff --git a/app/components/admin/budgets_wizard/groups/index_component.html.erb b/app/components/admin/budgets_wizard/groups/index_component.html.erb index 04a0059aa..f79361821 100644 --- a/app/components/admin/budgets_wizard/groups/index_component.html.erb +++ b/app/components/admin/budgets_wizard/groups/index_component.html.erb @@ -5,5 +5,7 @@ <%= render Admin::Budgets::HelpComponent.new("budget_groups") %> <%= render Admin::BudgetsWizard::CreationTimelineComponent.new("groups") %> -<%= render Admin::BudgetGroups::GroupsComponent.new(groups) %> +<% unless single_heading? %> + <%= render Admin::BudgetGroups::GroupsComponent.new(groups) %> +<% end %> <%= render Admin::BudgetsWizard::Groups::CreationStepComponent.new(new_group, groups.first) %> diff --git a/app/components/admin/budgets_wizard/groups/index_component.rb b/app/components/admin/budgets_wizard/groups/index_component.rb index d15414e54..5c9b206b1 100644 --- a/app/components/admin/budgets_wizard/groups/index_component.rb +++ b/app/components/admin/budgets_wizard/groups/index_component.rb @@ -1,4 +1,4 @@ -class Admin::BudgetsWizard::Groups::IndexComponent < ApplicationComponent +class Admin::BudgetsWizard::Groups::IndexComponent < Admin::BudgetsWizard::BaseComponent include Header attr_reader :groups, :new_group diff --git a/app/components/admin/budgets_wizard/headings/index_component.html.erb b/app/components/admin/budgets_wizard/headings/index_component.html.erb index 21e0d538d..631bb2286 100644 --- a/app/components/admin/budgets_wizard/headings/index_component.html.erb +++ b/app/components/admin/budgets_wizard/headings/index_component.html.erb @@ -1,4 +1,4 @@ -<%= back_link_to admin_budgets_wizard_budget_groups_path(budget), t("admin.budget_headings.index.back") %> +<%= back_link_to admin_budgets_wizard_budget_groups_path(budget, url_params), back_link_text %> <%= header %> diff --git a/app/components/admin/budgets_wizard/headings/index_component.rb b/app/components/admin/budgets_wizard/headings/index_component.rb index 76aa584a9..f789a00e8 100644 --- a/app/components/admin/budgets_wizard/headings/index_component.rb +++ b/app/components/admin/budgets_wizard/headings/index_component.rb @@ -1,4 +1,4 @@ -class Admin::BudgetsWizard::Headings::IndexComponent < ApplicationComponent +class Admin::BudgetsWizard::Headings::IndexComponent < Admin::BudgetsWizard::BaseComponent include Header attr_reader :headings, :new_heading @@ -18,4 +18,12 @@ class Admin::BudgetsWizard::Headings::IndexComponent < ApplicationComponent def title t("admin.budget_headings.index.title", budget: budget.name, group: group.name) end + + def back_link_text + if single_heading? + t("admin.budgets_wizard.headings.single.back") + else + t("admin.budget_headings.index.back") + end + end end diff --git a/app/components/admin/budgets_wizard/model_field_component.html.erb b/app/components/admin/budgets_wizard/model_field_component.html.erb new file mode 100644 index 000000000..f862d30b7 --- /dev/null +++ b/app/components/admin/budgets_wizard/model_field_component.html.erb @@ -0,0 +1 @@ +<%= hidden_field_tag(:mode, helpers.budget_mode) if helpers.respond_to?(:budget_mode) %> diff --git a/app/components/admin/budgets_wizard/model_field_component.rb b/app/components/admin/budgets_wizard/model_field_component.rb new file mode 100644 index 000000000..adb2d32ad --- /dev/null +++ b/app/components/admin/budgets_wizard/model_field_component.rb @@ -0,0 +1,2 @@ +class Admin::BudgetsWizard::ModelFieldComponent < ApplicationComponent +end diff --git a/app/controllers/admin/budgets_wizard/base_controller.rb b/app/controllers/admin/budgets_wizard/base_controller.rb new file mode 100644 index 000000000..d8798c8e7 --- /dev/null +++ b/app/controllers/admin/budgets_wizard/base_controller.rb @@ -0,0 +1,17 @@ +class Admin::BudgetsWizard::BaseController < Admin::BaseController + helper_method :budget_mode, :single_heading?, :url_params + + private + + def budget_mode + params[:mode] + end + + def single_heading? + budget_mode == "single" + end + + def url_params + budget_mode.present? ? { mode: budget_mode } : {} + end +end diff --git a/app/controllers/admin/budgets_wizard/budgets_controller.rb b/app/controllers/admin/budgets_wizard/budgets_controller.rb index aaa5a7b73..e3efe9739 100644 --- a/app/controllers/admin/budgets_wizard/budgets_controller.rb +++ b/app/controllers/admin/budgets_wizard/budgets_controller.rb @@ -1,4 +1,4 @@ -class Admin::BudgetsWizard::BudgetsController < Admin::BaseController +class Admin::BudgetsWizard::BudgetsController < Admin::BudgetsWizard::BaseController include Translatable include ImageAttributes include FeatureFlags @@ -6,8 +6,6 @@ class Admin::BudgetsWizard::BudgetsController < Admin::BaseController load_and_authorize_resource - helper_method :budget_mode, :single_heading? - def new end @@ -48,16 +46,4 @@ class Admin::BudgetsWizard::BudgetsController < Admin::BaseController def groups_index admin_budgets_wizard_budget_groups_path(@budget, url_params) end - - def budget_mode - params[:mode] - end - - def single_heading? - budget_mode == "single" - end - - def url_params - budget_mode.present? ? { mode: budget_mode } : {} - end end diff --git a/app/controllers/admin/budgets_wizard/groups_controller.rb b/app/controllers/admin/budgets_wizard/groups_controller.rb index 4d8c8895c..b8d180a64 100644 --- a/app/controllers/admin/budgets_wizard/groups_controller.rb +++ b/app/controllers/admin/budgets_wizard/groups_controller.rb @@ -1,16 +1,24 @@ -class Admin::BudgetsWizard::GroupsController < Admin::BaseController +class Admin::BudgetsWizard::GroupsController < Admin::BudgetsWizard::BaseController include Admin::BudgetGroupsActions before_action :load_groups, only: [:index, :create] def index - @group = @budget.groups.new + if single_heading? + @group = @budget.groups.first_or_initialize("name_#{I18n.locale}" => @budget.name) + else + @group = @budget.groups.new + end end private def groups_index - admin_budgets_wizard_budget_groups_path(@budget) + if single_heading? + admin_budgets_wizard_budget_group_headings_path(@budget, @group, url_params) + else + admin_budgets_wizard_budget_groups_path(@budget, url_params) + end end def new_action diff --git a/app/controllers/admin/budgets_wizard/headings_controller.rb b/app/controllers/admin/budgets_wizard/headings_controller.rb index 5eb78e5d3..505536ca3 100644 --- a/app/controllers/admin/budgets_wizard/headings_controller.rb +++ b/app/controllers/admin/budgets_wizard/headings_controller.rb @@ -1,4 +1,4 @@ -class Admin::BudgetsWizard::HeadingsController < Admin::BaseController +class Admin::BudgetsWizard::HeadingsController < Admin::BudgetsWizard::BaseController include Admin::BudgetHeadingsActions before_action :load_headings, only: [:index, :create] diff --git a/app/views/admin/budget_groups/_form.html.erb b/app/views/admin/budget_groups/_form.html.erb index 5e86059c0..4c70672cf 100644 --- a/app/views/admin/budget_groups/_form.html.erb +++ b/app/views/admin/budget_groups/_form.html.erb @@ -3,6 +3,8 @@ <%= render "shared/errors", resource: group %> + <%= render Admin::BudgetsWizard::ModelFieldComponent.new %> + <%= f.translatable_fields do |translations_form| %>
<%= translations_form.text_field :name, maxlength: 50 %> @@ -18,6 +20,10 @@ <% end %>
- <%= f.submit t("admin.budget_groups.form.#{action}"), class: "button hollow" %> + <% if respond_to?(:single_heading?) && single_heading? %> + <%= f.submit t("admin.budgets_wizard.groups.continue"), class: "button success" %> + <% else %> + <%= f.submit t("admin.budget_groups.form.#{action}"), class: "button hollow" %> + <% end %>
<% end %> diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index a9c4cf03a..c5bdd6707 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -317,6 +317,8 @@ en: continue: "Continue to headings" headings: continue: "Continue to phases" + single: + back: "Go back to edit group" heading_mode: multiple: description: "Create a process with multiple groups, districts or topics. You have something like several bags of money. For example, 25,000€ for \"North District\", 30,000€ for \"Center District\", 28,000€ for \"South District\" and the whole city. Or for several themes like sport, culture or health." diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 40f196d30..b1902d74d 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -317,6 +317,8 @@ es: continue: "Continuar a partidas" headings: continue: "Continuar a fases" + single: + back: "Volver a editar grupo" heading_mode: multiple: description: "Crea un proceso con múltiples grupos, distritos o temas. Tienes algo como varias bolsas de dinero. Por ejemplo, 25.000€ para \"Distrito norte\", 30.000€ para \"Distrito centro\", 28.000€ para \"Distrito sur\" y toda la ciudad. O para varios temas como deporte, cultura o salud." diff --git a/spec/system/admin/budgets_wizard/groups_spec.rb b/spec/system/admin/budgets_wizard/groups_spec.rb index be2e84d05..e5e237c87 100644 --- a/spec/system/admin/budgets_wizard/groups_spec.rb +++ b/spec/system/admin/budgets_wizard/groups_spec.rb @@ -97,6 +97,26 @@ describe "Budgets wizard, groups step", :admin do expect(page).to have_css ".creation-timeline" expect(page).to have_css "td", exact_text: "Group without typos" end + + scenario "update group in single heading budget" do + visit admin_budgets_wizard_budget_groups_path(budget, mode: "single") + fill_in "Group name", with: "Group wiht typo" + click_button "Continue to headings" + + click_link "Go back to edit group" + + expect(page).to have_field "Group name", with: "Group wiht typo" + + fill_in "Group name", with: "Group without typos" + click_button "Continue to headings" + + expect(page).to have_content "Group updated successfully" + + visit admin_budget_groups_path(budget) + + expect(page).to have_content "There is 1 group" + within("tbody tr") { expect(page).to have_content "Group without typos" } + end end describe "Destroy" do diff --git a/spec/system/admin/budgets_wizard/wizard_spec.rb b/spec/system/admin/budgets_wizard/wizard_spec.rb index f042ab2a3..afbc7abbe 100644 --- a/spec/system/admin/budgets_wizard/wizard_spec.rb +++ b/spec/system/admin/budgets_wizard/wizard_spec.rb @@ -10,6 +10,11 @@ describe "Budgets creation wizard", :admin do click_button "Continue to groups" expect(page).to have_content "New participatory budget created successfully!" + expect(page).to have_field "Group name", with: "Single heading budget" + + click_button "Continue to headings" + + expect(page).to have_content "Group created successfully" end scenario "Creation of a multiple-headings budget by steps" do @@ -22,5 +27,25 @@ describe "Budgets creation wizard", :admin do expect(page).to have_content "New participatory budget created successfully!" expect(page).to have_content "There are no groups." + + click_button "Add new group" + fill_in "Group name", with: "All city" + click_button "Create new group" + + expect(page).to have_content "Group created successfully!" + within("table") { expect(page).to have_content "All city" } + expect(page).not_to have_content "There are no groups." + + click_button "Add new group" + fill_in "Group name", with: "Districts" + click_button "Create new group" + + expect(page).to have_content "Group created successfully!" + within("table") { expect(page).to have_content "Districts" } + + click_link "Continue to headings" + + expect(page).to have_content "Showing headings from the All city group" + expect(page).to have_content "There are no headings." end end From e3510fc29dbac304f348e5b7957215f1433a3106 Mon Sep 17 00:00:00 2001 From: Julian Herrero Date: Sun, 15 Mar 2020 06:51:52 +0100 Subject: [PATCH 4/8] Adapt heading step to single and multiple budget mode Co-Authored-By: decabeza --- .../admin/budgets_wizard/base_component.rb | 4 ++ .../headings/creation_step_component.rb | 6 ++- .../headings/index_component.html.erb | 6 ++- .../phases/index_component.html.erb | 2 +- .../budgets_wizard/phases/index_component.rb | 8 +++- .../budgets_wizard/headings_controller.rb | 12 +++++- .../admin/budgets_wizard/phases_controller.rb | 2 +- .../admin/budget_headings/_form.html.erb | 8 +++- config/locales/en/admin.yml | 5 ++- config/locales/es/admin.yml | 5 ++- .../admin/budgets_wizard/headings_spec.rb | 23 +++++++++++ .../admin/budgets_wizard/wizard_spec.rb | 39 +++++++++++++++++++ 12 files changed, 108 insertions(+), 12 deletions(-) diff --git a/app/components/admin/budgets_wizard/base_component.rb b/app/components/admin/budgets_wizard/base_component.rb index 3d96f6a07..b2aba71cd 100644 --- a/app/components/admin/budgets_wizard/base_component.rb +++ b/app/components/admin/budgets_wizard/base_component.rb @@ -1,3 +1,7 @@ class Admin::BudgetsWizard::BaseComponent < ApplicationComponent delegate :single_heading?, :url_params, to: :helpers + + def budget_mode + helpers.budget_mode || "multiple" + end end diff --git a/app/components/admin/budgets_wizard/headings/creation_step_component.rb b/app/components/admin/budgets_wizard/headings/creation_step_component.rb index 6c07c6670..cd8e0ada1 100644 --- a/app/components/admin/budgets_wizard/headings/creation_step_component.rb +++ b/app/components/admin/budgets_wizard/headings/creation_step_component.rb @@ -12,7 +12,11 @@ class Admin::BudgetsWizard::Headings::CreationStepComponent < ApplicationCompone end def form_path - admin_budgets_wizard_budget_group_headings_path(heading.group.budget, heading.group) + if heading.persisted? + admin_budgets_wizard_budget_group_heading_path(heading.group.budget, heading.group, heading) + else + admin_budgets_wizard_budget_group_headings_path(heading.group.budget, heading.group) + end end def next_step_path diff --git a/app/components/admin/budgets_wizard/headings/index_component.html.erb b/app/components/admin/budgets_wizard/headings/index_component.html.erb index 631bb2286..b2ce1fd4f 100644 --- a/app/components/admin/budgets_wizard/headings/index_component.html.erb +++ b/app/components/admin/budgets_wizard/headings/index_component.html.erb @@ -5,6 +5,8 @@ <%= render Admin::Budgets::HelpComponent.new("budget_headings") %> <%= render Admin::BudgetsWizard::CreationTimelineComponent.new("headings") %> -<%= render Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group) %> -<%= render Admin::BudgetHeadings::HeadingsComponent.new(headings) %> +<% unless single_heading? %> + <%= render Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group) %> + <%= render Admin::BudgetHeadings::HeadingsComponent.new(headings) %> +<% end %> <%= render Admin::BudgetsWizard::Headings::CreationStepComponent.new(new_heading) %> diff --git a/app/components/admin/budgets_wizard/phases/index_component.html.erb b/app/components/admin/budgets_wizard/phases/index_component.html.erb index a97812cfb..a613e34d0 100644 --- a/app/components/admin/budgets_wizard/phases/index_component.html.erb +++ b/app/components/admin/budgets_wizard/phases/index_component.html.erb @@ -1,4 +1,4 @@ -<%= back_link_to back_link_path, t("admin.budgets_wizard.phases.back") %> +<%= back_link_to back_link_path, back_link_text %> <%= header %> diff --git a/app/components/admin/budgets_wizard/phases/index_component.rb b/app/components/admin/budgets_wizard/phases/index_component.rb index a1b680003..b0236804f 100644 --- a/app/components/admin/budgets_wizard/phases/index_component.rb +++ b/app/components/admin/budgets_wizard/phases/index_component.rb @@ -1,4 +1,4 @@ -class Admin::BudgetsWizard::Phases::IndexComponent < ApplicationComponent +class Admin::BudgetsWizard::Phases::IndexComponent < Admin::BudgetsWizard::BaseComponent include Header attr_reader :budget @@ -13,6 +13,10 @@ class Admin::BudgetsWizard::Phases::IndexComponent < ApplicationComponent private def back_link_path - admin_budgets_wizard_budget_group_headings_path(budget, budget.groups.first) + admin_budgets_wizard_budget_group_headings_path(budget, budget.groups.first, url_params) + end + + def back_link_text + t("admin.budgets_wizard.phases.#{budget_mode}.back") end end diff --git a/app/controllers/admin/budgets_wizard/headings_controller.rb b/app/controllers/admin/budgets_wizard/headings_controller.rb index 505536ca3..3b4ee532f 100644 --- a/app/controllers/admin/budgets_wizard/headings_controller.rb +++ b/app/controllers/admin/budgets_wizard/headings_controller.rb @@ -4,13 +4,21 @@ class Admin::BudgetsWizard::HeadingsController < Admin::BudgetsWizard::BaseContr before_action :load_headings, only: [:index, :create] def index - @heading = @group.headings.new + if single_heading? + @heading = @group.headings.first_or_initialize + else + @heading = @group.headings.new + end end private def headings_index - admin_budgets_wizard_budget_group_headings_path(@budget, @group) + if single_heading? + admin_budgets_wizard_budget_budget_phases_path(@budget, url_params) + else + admin_budgets_wizard_budget_group_headings_path(@budget, @group, url_params) + end end def new_action diff --git a/app/controllers/admin/budgets_wizard/phases_controller.rb b/app/controllers/admin/budgets_wizard/phases_controller.rb index 5376e93b7..785f092f9 100644 --- a/app/controllers/admin/budgets_wizard/phases_controller.rb +++ b/app/controllers/admin/budgets_wizard/phases_controller.rb @@ -1,4 +1,4 @@ -class Admin::BudgetsWizard::PhasesController < Admin::BaseController +class Admin::BudgetsWizard::PhasesController < Admin::BudgetsWizard::BaseController include Admin::BudgetPhasesActions def index diff --git a/app/views/admin/budget_headings/_form.html.erb b/app/views/admin/budget_headings/_form.html.erb index f1424bb10..ba8110542 100644 --- a/app/views/admin/budget_headings/_form.html.erb +++ b/app/views/admin/budget_headings/_form.html.erb @@ -3,6 +3,8 @@ <%= render "shared/errors", resource: heading %> + <%= render Admin::BudgetsWizard::ModelFieldComponent.new %> + <%= f.translatable_fields do |translations_form| %>
<%= translations_form.text_field :name, maxlength: 50 %> @@ -35,6 +37,10 @@
- <%= f.submit t("admin.budget_headings.form.#{action}"), class: "button hollow" %> + <% if respond_to?(:single_heading?) && single_heading? %> + <%= f.submit t("admin.budgets_wizard.headings.continue"), class: "button success" %> + <% else %> + <%= f.submit t("admin.budget_headings.form.#{action}"), class: "button hollow" %> + <% end %>
<% end %> diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index c5bdd6707..d17e64eb3 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -330,8 +330,11 @@ en: title: "Single heading budget" title: "Participatory budget type" phases: - back: "Go back to headings" continue: "Finish" + multiple: + back: "Go back to headings" + single: + back: "Go back to edit heading" update_all: notice: "Phases configured successfully" milestones: diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index b1902d74d..f6c1ce87c 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -330,8 +330,11 @@ es: title: "Presupuesto de partida única" title: Tipo de presupuesto participativo phases: - back: "Volver a partidas" continue: "Finalizar" + multiple: + back: "Volver a partidas" + single: + back: "Volver a editar partida" update_all: notice: "Fases configuradas con éxito" milestones: diff --git a/spec/system/admin/budgets_wizard/headings_spec.rb b/spec/system/admin/budgets_wizard/headings_spec.rb index d3823347e..de4c9dfcb 100644 --- a/spec/system/admin/budgets_wizard/headings_spec.rb +++ b/spec/system/admin/budgets_wizard/headings_spec.rb @@ -118,6 +118,29 @@ describe "Budgets wizard, headings step", :admin do expect(page).to have_css ".creation-timeline" expect(page).to have_css "td", exact_text: "Heading without typos" end + + scenario "update heading in single heading budget" do + visit admin_budgets_wizard_budget_group_headings_path(budget, group, mode: "single") + fill_in "Heading name", with: "Heading wiht typo" + fill_in "Money amount", with: "300000" + click_button "Continue to phases" + + expect(page).to have_content "Heading created successfully" + + click_link "Go back to edit heading" + + expect(page).to have_field "Heading name", with: "Heading wiht typo" + + fill_in "Heading name", with: "Heading without typos" + click_button "Continue to phases" + + expect(page).to have_content "Heading updated successfully" + + visit admin_budget_group_headings_path(budget, group) + + expect(page).to have_content "There is 1 heading" + within("tbody tr") { expect(page).to have_content "Heading without typos" } + end end describe "Destroy" do diff --git a/spec/system/admin/budgets_wizard/wizard_spec.rb b/spec/system/admin/budgets_wizard/wizard_spec.rb index afbc7abbe..0d2216ef6 100644 --- a/spec/system/admin/budgets_wizard/wizard_spec.rb +++ b/spec/system/admin/budgets_wizard/wizard_spec.rb @@ -15,6 +15,12 @@ describe "Budgets creation wizard", :admin do click_button "Continue to headings" expect(page).to have_content "Group created successfully" + + fill_in "Heading name", with: "One and only heading" + fill_in "Money amount", with: "1000000" + click_button "Continue to phases" + + expect(page).to have_css ".budget-phases-table" end scenario "Creation of a multiple-headings budget by steps" do @@ -47,5 +53,38 @@ describe "Budgets creation wizard", :admin do expect(page).to have_content "Showing headings from the All city group" expect(page).to have_content "There are no headings." + + click_button "Add new heading" + fill_in "Heading name", with: "All city" + fill_in "Money amount", with: "1000000" + click_button "Create new heading" + + expect(page).to have_content "Heading created successfully!" + within("table") { expect(page).to have_content "All city" } + expect(page).not_to have_content "There are no headings." + + click_link "Manage headings from the Districts group." + expect(page).to have_content "There are no headings." + + click_button "Add new heading" + fill_in "Heading name", with: "North" + fill_in "Money amount", with: "500000" + click_button "Create new heading" + + expect(page).to have_content "Heading created successfully!" + within("table") { expect(page).to have_content "North" } + expect(page).not_to have_content "There are no headings." + + click_button "Add new heading" + fill_in "Heading name", with: "South" + fill_in "Money amount", with: "500000" + click_button "Create new heading" + + expect(page).to have_content "Heading created successfully!" + within("table") { expect(page).to have_content "South" } + + click_link "Continue to phases" + + expect(page).to have_css ".budget-phases-table" end end From 4c6de86a7233c57f1594e69aaad70758c1cf0076 Mon Sep 17 00:00:00 2001 From: Julian Herrero Date: Sun, 15 Mar 2020 06:51:52 +0100 Subject: [PATCH 5/8] Adapt phases step to single and multiple budget mode Co-Authored-By: decabeza --- .../budget_phases/form_component.html.erb | 2 + .../budget_phases/phases_component.html.erb | 1 + .../admin/budget_phases/phases_component.rb | 6 ++ .../admin/budgets_wizard/phases_controller.rb | 2 +- .../admin/budgets_wizard/phases_spec.rb | 13 +++++ .../admin/budgets_wizard/wizard_spec.rb | 58 +++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) diff --git a/app/components/admin/budget_phases/form_component.html.erb b/app/components/admin/budget_phases/form_component.html.erb index e8e0822c0..3fcaab81c 100644 --- a/app/components/admin/budget_phases/form_component.html.erb +++ b/app/components/admin/budget_phases/form_component.html.erb @@ -4,6 +4,8 @@ <%= render "shared/errors", resource: phase %> + <%= render Admin::BudgetsWizard::ModelFieldComponent.new %> +
<%= t("admin.budget_phases.edit.duration") %> diff --git a/app/components/admin/budget_phases/phases_component.html.erb b/app/components/admin/budget_phases/phases_component.html.erb index 787508634..836ca925e 100644 --- a/app/components/admin/budget_phases/phases_component.html.erb +++ b/app/components/admin/budget_phases/phases_component.html.erb @@ -31,6 +31,7 @@ <%= render Admin::TableActionsComponent.new(phase, actions: [:edit], + edit_path: edit_path(phase), edit_text: t("admin.budgets.edit.edit_phase") ) %> diff --git a/app/components/admin/budget_phases/phases_component.rb b/app/components/admin/budget_phases/phases_component.rb index 25a2d0280..383f46c53 100644 --- a/app/components/admin/budget_phases/phases_component.rb +++ b/app/components/admin/budget_phases/phases_component.rb @@ -37,4 +37,10 @@ class Admin::BudgetPhases::PhasesComponent < ApplicationComponent tag.span t("shared.no"), class: "budget-phase-disabled" end end + + def edit_path(phase) + if helpers.respond_to?(:single_heading?) && helpers.single_heading? + edit_admin_budgets_wizard_budget_budget_phase_path(budget, phase, helpers.url_params) + end + end end diff --git a/app/controllers/admin/budgets_wizard/phases_controller.rb b/app/controllers/admin/budgets_wizard/phases_controller.rb index 785f092f9..59903c3b5 100644 --- a/app/controllers/admin/budgets_wizard/phases_controller.rb +++ b/app/controllers/admin/budgets_wizard/phases_controller.rb @@ -13,7 +13,7 @@ class Admin::BudgetsWizard::PhasesController < Admin::BudgetsWizard::BaseControl private def phases_index - admin_budgets_wizard_budget_budget_phases_path(@phase.budget) + admin_budgets_wizard_budget_budget_phases_path(@phase.budget, url_params) end def phases_params diff --git a/spec/system/admin/budgets_wizard/phases_spec.rb b/spec/system/admin/budgets_wizard/phases_spec.rb index eb2e0b50b..e1f8e7be2 100644 --- a/spec/system/admin/budgets_wizard/phases_spec.rb +++ b/spec/system/admin/budgets_wizard/phases_spec.rb @@ -82,5 +82,18 @@ describe "Budgets wizard, phases step", :admin do expect(page).to have_css ".creation-timeline" within_table("Phases") { expect(page).to have_content "Welcoming projects" } end + + scenario "update phase in single heading budget" do + visit admin_budgets_wizard_budget_budget_phases_path(budget, mode: "single") + + within("tr", text: "Selecting projects") { click_link "Edit phase" } + fill_in "Name", with: "Choosing projects" + click_button "Save changes" + + expect(page).to have_content "Changes saved" + expect(page).to have_css ".creation-timeline" + within_table("Phases") { expect(page).to have_content "Choosing projects" } + expect(page).to have_link "Go back to edit heading" + end end end diff --git a/spec/system/admin/budgets_wizard/wizard_spec.rb b/spec/system/admin/budgets_wizard/wizard_spec.rb index 0d2216ef6..2cd89198e 100644 --- a/spec/system/admin/budgets_wizard/wizard_spec.rb +++ b/spec/system/admin/budgets_wizard/wizard_spec.rb @@ -21,6 +21,26 @@ describe "Budgets creation wizard", :admin do click_button "Continue to phases" expect(page).to have_css ".budget-phases-table" + + click_button "Finish" + + expect(page).to have_content "Phases configured successfully" + + within "tr", text: "Single heading budget" do + click_link "Edit headings groups" + end + + expect(page).to have_content "There is 1 group" + + within "tr", text: "Single heading budget" do + click_link "Manage headings" + end + + expect(page).to have_content "There is 1 heading" + + within "tbody" do + expect(page).to have_content "One and only heading" + end end scenario "Creation of a multiple-headings budget by steps" do @@ -86,5 +106,43 @@ describe "Budgets creation wizard", :admin do click_link "Continue to phases" expect(page).to have_css ".budget-phases-table" + + within("tr", text: "Voting projects") { click_link "Edit phase" } + fill_in "Name", with: "Custom phase name" + uncheck "Phase enabled" + click_button "Save changes" + + expect(page).to have_content "Changes saved" + + within "table" do + expect(page).to have_content "Custom phase name" + expect(page).not_to have_content "Voting projects" + end + + click_button "Finish" + + expect(page).to have_content "Phases configured successfully" + + within "tr", text: "Multiple headings budget" do + click_link "Edit headings groups" + end + + expect(page).to have_content "There are 2 groups" + + within "tbody" do + expect(page).to have_content "All city" + expect(page).to have_content "Districts" + end + + within "tr", text: "Districts" do + click_link "Manage headings" + end + + expect(page).to have_content "There are 2 headings" + + within "tbody" do + expect(page).to have_content "North" + expect(page).to have_content "South" + end end end From f126f2c15424d026ab48cc61082e27f1849cc4bd Mon Sep 17 00:00:00 2001 From: Julian Herrero Date: Sun, 15 Mar 2020 06:51:52 +0100 Subject: [PATCH 6/8] Improve label of budget phase selector Co-Authored-By: decabeza --- config/locales/en/activerecord.yml | 2 +- config/locales/es/activerecord.yml | 2 +- spec/system/admin/budget_investments_spec.rb | 2 +- spec/system/admin/budgets_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml index 5539f91b0..5b12917fd 100644 --- a/config/locales/en/activerecord.yml +++ b/config/locales/en/activerecord.yml @@ -155,7 +155,7 @@ en: description_balloting: "Description during Balloting phase" description_reviewing_ballots: "Description during Reviewing Ballots phase" description_finished: "Description when the budget is finished" - phase: "Phase" + phase: "Active phase" currency_symbol: "Currency" voting_style: "Final voting style" voting_style_knapsack: "Knapsack" diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index d37a8a155..43bd93700 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -155,7 +155,7 @@ es: description_balloting: "Descripción durante la fase de votación" description_reviewing_ballots: "Descripción durante la fase de revisión de votos" description_finished: "Descripción cuando el presupuesto ha finalizado / Resultados" - phase: "Fase" + phase: "Fase activa" currency_symbol: "Divisa" voting_style: "Estilo de la votación final" voting_style_knapsack: Bolsa de dinero diff --git a/spec/system/admin/budget_investments_spec.rb b/spec/system/admin/budget_investments_spec.rb index 1aece2f90..00291061d 100644 --- a/spec/system/admin/budget_investments_spec.rb +++ b/spec/system/admin/budget_investments_spec.rb @@ -529,7 +529,7 @@ describe "Admin budget investments", :admin do expect(page).to have_link "Calculate Winner Investments" - select "Accepting projects", from: "Phase" + select "Accepting projects", from: "Active phase" click_button "Update Budget" expect(page).to have_content "Participatory budget updated successfully" diff --git a/spec/system/admin/budgets_spec.rb b/spec/system/admin/budgets_spec.rb index bb41724dc..9ea8780d2 100644 --- a/spec/system/admin/budgets_spec.rb +++ b/spec/system/admin/budgets_spec.rb @@ -188,7 +188,7 @@ describe "Admin budgets", :admin do visit edit_admin_budget_path(budget) - expect(page).to have_select "Phase", selected: "Selecting projects" + expect(page).to have_select "Active phase", selected: "Selecting projects" expect(page).to have_table "Phases", with_cols: [ [ From 51bca665330203a3d633327e4379a0cd779ae23a Mon Sep 17 00:00:00 2001 From: Julian Herrero Date: Sun, 15 Mar 2020 06:51:52 +0100 Subject: [PATCH 7/8] Adapt help component to multiple and single budget mode --- app/components/admin/budgets/help_component.rb | 10 +++++++++- .../budgets_wizard/budgets/new_component.html.erb | 1 + .../budgets_wizard/groups/index_component.html.erb | 2 +- .../headings/index_component.html.erb | 2 +- .../budgets_wizard/phases/index_component.html.erb | 2 +- app/views/admin/budget_groups/index.html.erb | 2 +- app/views/admin/budget_headings/index.html.erb | 2 +- config/i18n-tasks.yml | 4 +--- config/locales/en/admin.yml | 13 +++++++++---- config/locales/es/admin.yml | 13 +++++++++---- 10 files changed, 34 insertions(+), 17 deletions(-) diff --git a/app/components/admin/budgets/help_component.rb b/app/components/admin/budgets/help_component.rb index ecde7a43d..bd47604d4 100644 --- a/app/components/admin/budgets/help_component.rb +++ b/app/components/admin/budgets/help_component.rb @@ -5,9 +5,17 @@ class Admin::Budgets::HelpComponent < ApplicationComponent @i18n_namespace = i18n_namespace end + def budget_mode + (helpers.budget_mode if helpers.respond_to?(:budget_mode)) || "multiple" + end + private def text - t("admin.#{i18n_namespace}.index.help") + if t("admin.budgets.help.#{i18n_namespace}").is_a?(Hash) + t("admin.budgets.help.#{i18n_namespace}.#{budget_mode}") + else + t("admin.budgets.help.#{i18n_namespace}") + end end end diff --git a/app/components/admin/budgets_wizard/budgets/new_component.html.erb b/app/components/admin/budgets_wizard/budgets/new_component.html.erb index 672f3e8ca..f5c54c957 100644 --- a/app/components/admin/budgets_wizard/budgets/new_component.html.erb +++ b/app/components/admin/budgets_wizard/budgets/new_component.html.erb @@ -2,5 +2,6 @@ <%= header %> +<%= render Admin::Budgets::HelpComponent.new("budgets") %> <%= render Admin::BudgetsWizard::CreationTimelineComponent.new("budget") %> <%= render Admin::Budgets::FormComponent.new(budget, wizard: true) %> diff --git a/app/components/admin/budgets_wizard/groups/index_component.html.erb b/app/components/admin/budgets_wizard/groups/index_component.html.erb index f79361821..d9b859b3a 100644 --- a/app/components/admin/budgets_wizard/groups/index_component.html.erb +++ b/app/components/admin/budgets_wizard/groups/index_component.html.erb @@ -2,7 +2,7 @@ <%= header %> -<%= render Admin::Budgets::HelpComponent.new("budget_groups") %> +<%= render Admin::Budgets::HelpComponent.new("groups") %> <%= render Admin::BudgetsWizard::CreationTimelineComponent.new("groups") %> <% unless single_heading? %> diff --git a/app/components/admin/budgets_wizard/headings/index_component.html.erb b/app/components/admin/budgets_wizard/headings/index_component.html.erb index b2ce1fd4f..211f74d63 100644 --- a/app/components/admin/budgets_wizard/headings/index_component.html.erb +++ b/app/components/admin/budgets_wizard/headings/index_component.html.erb @@ -2,7 +2,7 @@ <%= header %> -<%= render Admin::Budgets::HelpComponent.new("budget_headings") %> +<%= render Admin::Budgets::HelpComponent.new("headings") %> <%= render Admin::BudgetsWizard::CreationTimelineComponent.new("headings") %> <% unless single_heading? %> diff --git a/app/components/admin/budgets_wizard/phases/index_component.html.erb b/app/components/admin/budgets_wizard/phases/index_component.html.erb index a613e34d0..e0cf8c9b7 100644 --- a/app/components/admin/budgets_wizard/phases/index_component.html.erb +++ b/app/components/admin/budgets_wizard/phases/index_component.html.erb @@ -2,7 +2,7 @@ <%= header %> -<%= render Admin::Budgets::HelpComponent.new("budget_phases") %> +<%= render Admin::Budgets::HelpComponent.new("phases") %> <%= render Admin::BudgetsWizard::CreationTimelineComponent.new("phases") %> <%= form_for budget, url: update_all_admin_budgets_wizard_budget_budget_phases_path(budget) do |f| %> diff --git a/app/views/admin/budget_groups/index.html.erb b/app/views/admin/budget_groups/index.html.erb index f89d3ee20..dbddbab42 100644 --- a/app/views/admin/budget_groups/index.html.erb +++ b/app/views/admin/budget_groups/index.html.erb @@ -7,5 +7,5 @@ <%= link_to t("admin.budget_groups.form.create"), new_admin_budget_group_path %> -<%= render Admin::Budgets::HelpComponent.new("budget_groups") %> +<%= render Admin::Budgets::HelpComponent.new("groups") %> <%= render Admin::BudgetGroups::GroupsComponent.new(@groups) %> diff --git a/app/views/admin/budget_headings/index.html.erb b/app/views/admin/budget_headings/index.html.erb index 48bfb4c9e..fcc2b9886 100644 --- a/app/views/admin/budget_headings/index.html.erb +++ b/app/views/admin/budget_headings/index.html.erb @@ -5,5 +5,5 @@ <%= link_to t("admin.budget_headings.form.create"), new_admin_budget_group_heading_path %> -<%= render Admin::Budgets::HelpComponent.new("budget_headings") %> +<%= render Admin::Budgets::HelpComponent.new("headings") %> <%= render Admin::BudgetHeadings::HeadingsComponent.new(@headings) %> diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 131bc8807..aa6a54f0a 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -147,9 +147,7 @@ ignore_unused: - "admin.hidden_proposal_notifications.index.filter*" - "admin.budgets.index.filter*" - "admin.budgets.edit.(administrators|valuators).*" - - "admin.budget_groups.index.*.help_block" - - "admin.budget_headings.index.*.help_block" - - "admin.budget_phases.index.help_block" + - "admin.budgets.help.*" - "admin.budget_investments.index.filter*" - "admin.budgets_wizard.heading_mode.*" - "admin.organizations.index.filter*" diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index d17e64eb3..0ca710f60 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -78,7 +78,6 @@ en: all: All open: Open finished: Finished - help: "Participatory budgets allow citizens to propose and decide directly how to spend part of the budget, with monitoring and rigorous evaluation of proposals by the institution." budget_investments: Manage projects table_completed: Completed table_draft: "Draft" @@ -135,6 +134,15 @@ en: new: title_multiple: New multiple headings budget title_single: New single heading budget + help: + budgets: "Participatory budgets allow citizens to propose and decide directly how to spend part of the budget, with monitoring and rigorous evaluation of proposals by the institution." + groups: + multiple: "Groups are meant to organize headings. After a group is created and it contais headings, it's possible to determine in how many headings a user can vote per group." + single: "Groups are meant to organize headings, since this budget will only contain one heading, the same name can be used because it will not appear anywhere. You can just continue with the next step." + headings: + multiple: "Headings are meant to divide the money of the participatory budget. Here you can add headings for this group and assign the amount of money that will be used for each heading." + single: "Headings are meant to divide the money of the participatory budget. Since this budget will only contain one heading, this is the place where you stablish the money that will be spent in this participaroty budget." + phases: "Participatory budgets have different phases. Here you can enable or disable phases and also customize each individual phase." winners: calculate: Calculate Winner Investments calculated: Winners being calculated, it may take a minute. @@ -163,7 +171,6 @@ en: submit: "Save group" index: back: "Go back to budgets" - help: "Groups are meant to organize headings. After a group is created and it contais headings, it's possible to determine in how many headings a user can vote per group." new_button: "Add new group" budget_headings: no_headings: "There are no headings." @@ -191,7 +198,6 @@ en: the_other_group: "Manage headings from the %{group} group." index: back: "Go back to groups" - help: "Headings are meant to divide the money of the participatory budget. Here you can add headings for this group and assign the amount of money that will be used for each heading." new_button: "Add new heading" title: "%{budget} / %{group} headings" budget_phases: @@ -207,7 +213,6 @@ en: name_help_text: "This is the title of the phase users will read on the header whenever this phase is active." save_changes: Save changes index: - help: "Participatory budgets have different phases. Here you can enable or disable phases and also customize each individual phase." title: "%{budget} phases" budget_investments: index: diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index f6c1ce87c..e164ad910 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -78,7 +78,6 @@ es: all: Todos open: Abiertos finished: Terminados - help: "Los presupuestos participativos permiten que los ciudadanos propongan y decidan de manera directa cómo gastar parte del presupuesto, con un seguimiento y evaluación riguroso de las propuestas por parte de la institución." budget_investments: Gestionar proyectos de gasto table_completed: Completado table_draft: "Borrador" @@ -135,6 +134,15 @@ es: new: title_multiple: Nuevo presupuesto de múltiples partidas title_single: Nuevo presupuesto de partida única + help: + budgets: "Los presupuestos participativos permiten que los ciudadanos propongan y decidan de manera directa cómo gastar parte del presupuesto, con un seguimiento y evaluación riguroso de las propuestas por parte de la institución." + groups: + multiple: "Los grupos sirven para organizar las partidas del presupuesto. Después de que un grupo sea creado y éste contenga partidas, es posible determinar el número de partidas a las que un usuario puede votar por grupo." + single: "Los grupos sirven para organizar las partidas del presupuesto, como este presupuesto sólo contendrá una partida, se puede usar el mismo nombre y éste no aparecerá en ninguna parte. Puedes continuar con el siguiente paso." + headings: + multiple: "Las partidas sirven para dividir el dinero del presupuesto participativo. Aquí puedes ir añadiendo partidas para cada grupo y establecer la cantidad de dinero que se gastará en cada partida." + single: "Las partidas sirven para dividir el dinero del presupuesto participativo. Como este presupuesto solo tendrá una partida aquí podrás establecer la cantidad de dinero que se gastará en este presupuesto participativo." + phases: "Los presupuestos participativos tienen distintas fases. Aquí puedes habilitar o deshabilitar fases y también personalizar cada una de las fases." winners: calculate: Calcular proyectos ganadores calculated: Calculando ganadores, puede tardar un minuto. @@ -163,7 +171,6 @@ es: submit: "Guardar grupo" index: back: "Volver a presupuestos" - help: "Los grupos sirven para organizar las partidas del presupuesto. Después de que un grupo sea creado y éste contenga partidas, es posible determinar el número de partidas a las que un usuario puede votar por grupo." new_button: "Añadir un grupo nuevo" budget_headings: no_headings: "No hay partidas." @@ -191,7 +198,6 @@ es: the_other_group: "Ir a partidas del grupo %{group}." index: back: "Volver a grupos" - help: "Las partidas sirven para dividir el dinero del presupuesto participativo. Aquí puedes ir añadiendo partidas para cada grupo y establecer la cantidad de dinero que se gastará en cada partida." new_button: "Añadir una partida nueva" title: "Partidas de %{budget} / %{group}" budget_phases: @@ -207,7 +213,6 @@ es: save_changes: Guardar cambios title: "Editar fase" index: - help: "Los presupuestos participativos tienen distintas fases. Aquí puedes habilitar o deshabilitar fases y también personalizar cada una de las fases." title: "Fases de %{budget}" budget_investments: index: From 17b4fb58c981c98c652d0382b7e0008d7b24fb23 Mon Sep 17 00:00:00 2001 From: Julian Herrero Date: Sun, 15 Mar 2020 06:51:52 +0100 Subject: [PATCH 8/8] Add type to budget index table Co-Authored-By: decabeza --- .../admin/budgets/index_component.html.erb | 2 + .../admin/budgets/index_component.rb | 10 ++++ app/models/budget.rb | 4 ++ config/locales/en/admin.yml | 4 ++ config/locales/es/admin.yml | 4 ++ .../admin/budgets/index_component_spec.rb | 53 +++++++++++++++---- spec/models/budget_spec.rb | 39 ++++++++++++++ spec/system/admin/budgets_spec.rb | 6 ++- 8 files changed, 110 insertions(+), 12 deletions(-) diff --git a/app/components/admin/budgets/index_component.html.erb b/app/components/admin/budgets/index_component.html.erb index d59fb2c58..fa4d60d4b 100644 --- a/app/components/admin/budgets/index_component.html.erb +++ b/app/components/admin/budgets/index_component.html.erb @@ -14,6 +14,7 @@ <%= t("admin.budgets.index.table_name") %> <%= t("admin.budgets.index.table_phase") %> + <%= t("admin.budgets.index.table_budget_type") %> <%= t("admin.budgets.index.table_duration") %> <%= t("admin.actions.actions") %> @@ -29,6 +30,7 @@ <%= budget.current_phase.name %> <%= phase_progress_text(budget) %> + <%= type(budget) %> <%= dates(budget) %> <%= duration(budget) %> diff --git a/app/components/admin/budgets/index_component.rb b/app/components/admin/budgets/index_component.rb index 58203a206..53f1d5d6f 100644 --- a/app/components/admin/budgets/index_component.rb +++ b/app/components/admin/budgets/index_component.rb @@ -26,6 +26,16 @@ class Admin::Budgets::IndexComponent < ApplicationComponent budget.phases.enabled.order(:id).pluck(:kind).index(budget.phase) || -1 end + def type(budget) + if budget.single_heading? + t("admin.budgets.index.type_single") + elsif budget.headings.blank? + t("admin.budgets.index.type_pending") + else + t("admin.budgets.index.type_multiple") + end + end + def dates(budget) Admin::Budgets::DurationComponent.new(budget).dates end diff --git a/app/models/budget.rb b/app/models/budget.rb index f17b38f59..a1ee86954 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -162,6 +162,10 @@ class Budget < ApplicationRecord current_phase&.balloting_or_later? end + def single_heading? + groups.one? && headings.one? + end + def heading_price(heading) heading_ids.include?(heading.id) ? heading.price : -1 end diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 0ca710f60..647ad90fd 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -79,12 +79,16 @@ en: open: Open finished: Finished budget_investments: Manage projects + table_budget_type: "Type" table_completed: Completed table_draft: "Draft" table_duration: "Duration" table_name: Name table_phase: Phase table_phase_progress: "(%{current_phase_number}/%{total_phases})" + type_multiple: "Multiple headings" + type_pending: "Pending: No headings yet" + type_single: "Single heading" edit_groups: Edit headings groups edit_budget: Edit budget admin_ballots: Admin ballots diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index e164ad910..7971a0665 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -79,12 +79,16 @@ es: open: Abiertos finished: Terminados budget_investments: Gestionar proyectos de gasto + table_budget_type: "Tipo" table_completed: Completado table_draft: "Borrador" table_duration: "Duración" table_name: Nombre table_phase: Fase table_phase_progress: "(%{current_phase_number}/%{total_phases})" + type_multiple: "Múltiples partidas" + type_pending: "Pendiente: Aún no hay partidas" + type_single: "Partida única" edit_groups: Editar grupos de partidas edit_budget: Editar presupuesto admin_ballots: Gestionar urnas diff --git a/spec/components/admin/budgets/index_component_spec.rb b/spec/components/admin/budgets/index_component_spec.rb index c8e94ed6a..e55a30cc7 100644 --- a/spec/components/admin/budgets/index_component_spec.rb +++ b/spec/components/admin/budgets/index_component_spec.rb @@ -7,21 +7,54 @@ describe Admin::Budgets::IndexComponent, type: :component do allow_any_instance_of(Admin::BudgetsController).to receive(:current_filter).and_return("all") end - it "displays current phase zero for budgets with no current phase" do - budget = create(:budget, :accepting, name: "Not enabled phase") - budget.phases.accepting.update!(enabled: false) + describe "#phase_progress_text" do + it "displays current phase zero for budgets with no current phase" do + budget = create(:budget, :accepting, name: "Not enabled phase") + budget.phases.accepting.update!(enabled: false) - render_inline Admin::Budgets::IndexComponent.new(Budget.page(1)) + render_inline Admin::Budgets::IndexComponent.new(Budget.page(1)) - expect(page.find("tr", text: "Not enabled phase")).to have_content "0/8" + expect(page.find("tr", text: "Not enabled phase")).to have_content "0/8" + end + + it "displays phase zero out of zero for budgets with no enabled phases" do + budget = create(:budget, name: "Without phases") + budget.phases.each { |phase| phase.update!(enabled: false) } + + render_inline Admin::Budgets::IndexComponent.new(Budget.page(1)) + + expect(page.find("tr", text: "Without phases")).to have_content "0/0" + end end - it "displays phase zero out of zero for budgets with no enabled phases" do - budget = create(:budget, name: "Without phases") - budget.phases.each { |phase| phase.update!(enabled: false) } + describe "#type" do + let(:budget) { create(:budget, name: "With type") } - render_inline Admin::Budgets::IndexComponent.new(Budget.page(1)) + it "displays 'single heading' for budgets with one heading" do + create(:budget_heading, budget: budget) - expect(page.find("tr", text: "Without phases")).to have_content "0/0" + render_inline Admin::Budgets::IndexComponent.new(Budget.page(1)) + + expect(page.find("thead")).to have_content "Type" + expect(page.find("tr", text: "With type")).to have_content "Single heading" + end + + it "displays 'multiple headings' for budgets with multiple headings" do + 2.times { create(:budget_heading, budget: budget) } + + render_inline Admin::Budgets::IndexComponent.new(Budget.page(1)) + + expect(page.find("thead")).to have_content "Type" + expect(page.find("tr", text: "With type")).to have_content "Multiple headings" + end + + it "displays 'pending: no headings yet' for budgets without headings" do + create(:budget, name: "Without headings") + + render_inline Admin::Budgets::IndexComponent.new(Budget.page(1)) + + expect(page.find("thead")).to have_content "Type" + expect(page.find("tr", text: "Without headings")).to have_content "Pending: No headings yet" + end end end diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index bbe5835f1..75c2e0234 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -447,4 +447,43 @@ describe Budget do expect(Valuator.count).to be 1 end end + + describe "#single_heading?" do + it "returns false for budgets with no groups nor headings" do + expect(create(:budget).single_heading?).to be false + end + + it "returns false for budgets with one group and no headings" do + create(:budget_group, budget: budget) + + expect(budget.single_heading?).to be false + end + + it "returns false for budgets with multiple groups and one heading" do + 2.times { create(:budget_group, budget: budget) } + create(:budget_heading, group: budget.groups.last) + + expect(budget.single_heading?).to be false + end + + it "returns false for budgets with one group and multiple headings" do + group = create(:budget_group, budget: budget) + 2.times { create(:budget_heading, group: group) } + + expect(budget.single_heading?).to be false + end + + it "returns false for budgets with one group and multiple headings" do + 2.times { create(:budget_group, budget: budget) } + 2.times { create(:budget_heading, group: budget.groups.sample) } + + expect(budget.single_heading?).to be false + end + + it "returns true for budgets with one group and one heading" do + create(:budget_heading, group: create(:budget_group, budget: budget)) + + expect(budget.single_heading?).to be true + end + end end diff --git a/spec/system/admin/budgets_spec.rb b/spec/system/admin/budgets_spec.rb index 9ea8780d2..b7c2b9395 100644 --- a/spec/system/admin/budgets_spec.rb +++ b/spec/system/admin/budgets_spec.rb @@ -30,8 +30,10 @@ describe "Admin budgets", :admin do budget = create(:budget, :accepting) visit admin_budgets_path - expect(page).to have_content budget.name - expect(page).to have_content "Accepting projects" + within "tr", text: budget.name do + expect(page).to have_content "Accepting projects" + expect(page).to have_content "Pending: No headings yet" + end end scenario "Filters by phase" do