diff --git a/app/assets/javascripts/admin/budgets_wizard/creation_step.js b/app/assets/javascripts/admin/budgets_wizard/creation_step.js new file mode 100644 index 000000000..fa1ffcebb --- /dev/null +++ b/app/assets/javascripts/admin/budgets_wizard/creation_step.js @@ -0,0 +1,20 @@ +(function() { + "use strict"; + App.AdminBudgetsWizardCreationStep = { + initialize: function() { + var element, add_button, cancel_button; + + element = $(".admin .budget-creation-step"); + add_button = element.find(".add"); + cancel_button = element.find(".delete"); + + add_button.click(function() { + $(this).attr("aria-expanded", true).parent().find(":input:visible:first").focus(); + }); + + cancel_button.click(function() { + add_button.attr("aria-expanded", false).focus(); + }); + } + }; +}).call(this); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 774ea5c78..51a7a4d55 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -113,6 +113,7 @@ //= require columns_selector //= require budget_edit_associations //= require datepicker +//= require_tree ./admin //= require_tree ./sdg //= require_tree ./sdg_management @@ -166,6 +167,7 @@ var initialize_modules = function() { if ($("#js-columns-selector").length) { App.ColumnsSelector.initialize(); } + App.AdminBudgetsWizardCreationStep.initialize(); App.BudgetEditAssociations.initialize(); App.Datepicker.initialize(); App.SDGRelatedListSelector.initialize(); diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 8aede0497..17e221cf7 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -258,6 +258,11 @@ $table-header: #ecf1f6; [type="submit"] ~ a { margin-left: $line-height / 2; } + + [type="checkbox"] { + margin-bottom: 0; + vertical-align: middle; + } } hr { diff --git a/app/assets/stylesheets/admin/budget_groups/form.scss b/app/assets/stylesheets/admin/budget_groups/form.scss new file mode 100644 index 000000000..a34dbaf8a --- /dev/null +++ b/app/assets/stylesheets/admin/budget_groups/form.scss @@ -0,0 +1,3 @@ +.admin .budget-groups-form { + @include full-width-form; +} diff --git a/app/assets/stylesheets/admin/budget_headings/form.scss b/app/assets/stylesheets/admin/budget_headings/form.scss new file mode 100644 index 000000000..430601bcc --- /dev/null +++ b/app/assets/stylesheets/admin/budget_headings/form.scss @@ -0,0 +1,3 @@ +.admin .budget-headings-form { + @include full-width-form; +} diff --git a/app/assets/stylesheets/admin/budgets/form.scss b/app/assets/stylesheets/admin/budgets/form.scss index 0fef7a2e6..9d0407030 100644 --- a/app/assets/stylesheets/admin/budgets/form.scss +++ b/app/assets/stylesheets/admin/budgets/form.scss @@ -1,4 +1,5 @@ .admin .budgets-form { + @include full-width-form; > fieldset { border-top: 4px solid $admin-border-color; @@ -17,8 +18,4 @@ text-transform: uppercase; } } - - .globalize-languages { - max-width: none; - } } diff --git a/app/assets/stylesheets/admin/budgets_wizard/creation_step.scss b/app/assets/stylesheets/admin/budgets_wizard/creation_step.scss new file mode 100644 index 000000000..e7e860508 --- /dev/null +++ b/app/assets/stylesheets/admin/budgets_wizard/creation_step.scss @@ -0,0 +1,50 @@ +.budget-creation-step { + + .add { + @include has-fa-icon(plus-square, solid); + @include regular-button; + + font-weight: bold; + padding-left: rem-calc(10); + + &::before { + margin-right: rem-calc(12); + } + + &[aria-expanded="false"] { + ~ :not(.next-step) { + display: none; + } + } + + &[aria-expanded="true"] { + display: none; + + ~ .next-step { + display: none; + } + } + } + + .cancel { + display: block; + margin-bottom: $line-height; + } + + .next-step { + @include regular-button; + } + + a { + &.next-step { + @include button-style($success-color, auto, auto); + } + } + + p { + &.next-step { + @include button-style($secondary-color, auto, auto); + @include button-disabled; + } + } +} diff --git a/app/assets/stylesheets/admin/budgets_wizard/creation_timeline.scss b/app/assets/stylesheets/admin/budgets_wizard/creation_timeline.scss new file mode 100644 index 000000000..67a79ef0c --- /dev/null +++ b/app/assets/stylesheets/admin/budgets_wizard/creation_timeline.scss @@ -0,0 +1,34 @@ +.creation-timeline { + display: flex; + list-style-type: none; + margin: $line-height * 2 0; + position: relative; + + li { + border-top: 4px solid $brand; + display: inline-block; + font-size: $small-font-size; + font-weight: bold; + padding: $line-height / 2 $line-height * 3 0; + text-transform: uppercase; + + &::before { + background: $brand; + border-radius: 50%; + content: ""; + height: 20px; + margin-left: $line-height / 2; + position: absolute; + top: -8px; + width: 20px; + } + + &[aria-current] ~ * { + border-color: $admin-border-color; + + &::before { + background: $admin-border-color; + } + } + } +} diff --git a/app/assets/stylesheets/admin/budgets_wizard/headings/group_switcher.scss b/app/assets/stylesheets/admin/budgets_wizard/headings/group_switcher.scss new file mode 100644 index 000000000..e0c4109f3 --- /dev/null +++ b/app/assets/stylesheets/admin/budgets_wizard/headings/group_switcher.scss @@ -0,0 +1,50 @@ +.budget-group-switcher { + margin-bottom: $line-height; + + p { + margin-bottom: 0; + } + + > .menu > li { + + > button { + align-items: center; + border: $admin-border; + border-radius: $button-radius; + display: inline-flex; + padding: rem-calc(11) rem-calc(16); + + &:active { + color: inherit; + } + + &::after { + @include css-triangle($dropdownmenu-arrow-size, currentcolor, down); + margin-left: 0.2em; + } + } + } + + .menu.is-dropdown-submenu { + margin: 0; + min-width: 100%; + padding: 0; + + li { + margin-bottom: 0; + } + + a { + cursor: default; + padding: rem-calc(11) rem-calc(16); + width: 100%; + + &:focus, + &:hover { + @include brand-background; + text-decoration: none; + outline: none; + } + } + } +} diff --git a/app/assets/stylesheets/mixins/buttons.scss b/app/assets/stylesheets/mixins/buttons.scss index 9e6fea4a1..5ef6a960d 100644 --- a/app/assets/stylesheets/mixins/buttons.scss +++ b/app/assets/stylesheets/mixins/buttons.scss @@ -1,4 +1,4 @@ -%button { +@mixin base-button { font-size: $base-font-size; &:focus, @@ -7,16 +7,20 @@ } } +%button { + @include base-button; +} + @mixin regular-button($color: $brand) { @include button($background: $color); @include inverted-selection; - @extend %button; + @include base-button; } @mixin hollow-button($color: $link) { @include button($style: hollow, $background: $color); @include normal-selection; - @extend %button; + @include base-button; margin-bottom: 0; } diff --git a/app/assets/stylesheets/mixins/layouts.scss b/app/assets/stylesheets/mixins/layouts.scss index ee5422be9..38f53974e 100644 --- a/app/assets/stylesheets/mixins/layouts.scss +++ b/app/assets/stylesheets/mixins/layouts.scss @@ -21,3 +21,9 @@ margin-top: 0; } } + +@mixin full-width-form { + .globalize-languages { + max-width: none; + } +} diff --git a/app/components/admin/budget_groups/groups_component.html.erb b/app/components/admin/budget_groups/groups_component.html.erb new file mode 100644 index 000000000..2b8f1d596 --- /dev/null +++ b/app/components/admin/budget_groups/groups_component.html.erb @@ -0,0 +1,33 @@ +<% if groups.any? %> +

<%= t("admin.budget_groups.amount", count: groups.count) %>

+ + + + + + + + + + + <% groups.each do |group| %> + + + + + + + <% end %> + +
<%= t("admin.budget_groups.name") %><%= Budget::Group.human_attribute_name(:max_votable_headings) %><%= t("admin.budget_groups.headings_name") %><%= t("admin.actions.actions") %>
<%= group.name %><%= group.max_votable_headings %><%= group.headings.count %> + <%= render Admin::TableActionsComponent.new(group) do |actions| %> + <%= actions.link_to t("admin.budget_groups.headings_manage"), + headings_path(actions, group), + class: "headings-link" %> + <% end %> +
+<% else %> +
+ <%= t("admin.budget_groups.no_groups") %> +
+<% end %> diff --git a/app/components/admin/budget_groups/groups_component.rb b/app/components/admin/budget_groups/groups_component.rb new file mode 100644 index 000000000..51bf60649 --- /dev/null +++ b/app/components/admin/budget_groups/groups_component.rb @@ -0,0 +1,17 @@ +class Admin::BudgetGroups::GroupsComponent < ApplicationComponent + attr_reader :groups + + def initialize(groups) + @groups = groups + end + + private + + def budget + @budget ||= groups.first.budget + end + + def headings_path(table_actions_component, group) + send("#{table_actions_component.namespace}_budget_group_headings_path", group.budget, group) + end +end diff --git a/app/components/admin/budget_headings/headings_component.html.erb b/app/components/admin/budget_headings/headings_component.html.erb new file mode 100644 index 000000000..9e32dcbe5 --- /dev/null +++ b/app/components/admin/budget_headings/headings_component.html.erb @@ -0,0 +1,39 @@ +<% if headings.any? %> +

<%= t("admin.budget_headings.amount", count: headings.count) %>

+ + + + + + <% if budget.approval_voting? %> + + <% end %> + + + + + + + <% headings.each do |heading| %> + + + + <% if budget.approval_voting? %> + + <% end %> + + + + + <% end %> + +
<%= Budget::Heading.human_attribute_name(:name) %><%= Budget::Heading.human_attribute_name(:price) %><%= Budget::Heading.human_attribute_name(:max_ballot_lines) %><%= Budget::Heading.human_attribute_name(:population) %><%= Budget::Heading.human_attribute_name(:allow_custom_content) %><%= t("admin.actions.actions") %>
<%= heading.name %><%= budget.formatted_heading_price(heading) %><%= heading.max_ballot_lines %><%= heading.population %> + <%= heading.allow_custom_content ? t("admin.shared.true_value") : t("admin.shared.false_value") %> + + <%= render Admin::TableActionsComponent.new(heading) %> +
+<% else %> +
+ <%= t("admin.budget_headings.no_headings") %> +
+<% end %> diff --git a/app/components/admin/budget_headings/headings_component.rb b/app/components/admin/budget_headings/headings_component.rb new file mode 100644 index 000000000..3b6594f23 --- /dev/null +++ b/app/components/admin/budget_headings/headings_component.rb @@ -0,0 +1,17 @@ +class Admin::BudgetHeadings::HeadingsComponent < ApplicationComponent + attr_reader :headings + + def initialize(headings) + @headings = headings + end + + private + + def group + @group ||= headings.first.group + end + + def budget + @budget ||= headings.first.budget + end +end diff --git a/app/components/admin/budget_phases/form_component.html.erb b/app/components/admin/budget_phases/form_component.html.erb new file mode 100644 index 000000000..78f10c235 --- /dev/null +++ b/app/components/admin/budget_phases/form_component.html.erb @@ -0,0 +1,49 @@ +<%= render "shared/globalize_locales", resource: phase %> + +<%= translatable_form_for [namespace, phase.budget, phase], html: { class: "budget-phases-form" } do |f| %> + + <%= render "shared/errors", resource: phase %> + +
+ + <%= t("admin.budget_phases.edit.duration") %> + + +

+ <%= t("admin.budget_phases.edit.duration_description") %> +

+ +
+ <%= f.date_field :starts_at, id: "start_date" %> +
+ +
+ <%= f.date_field :ends_at, id: "end_date" %> +
+
+ +
+ <%= f.check_box :enabled %> + + + <%= t("admin.budget_phases.edit.enabled_help_text") %> + +
+ + <%= f.translatable_fields do |translations_form| %> +
+ <%= translations_form.text_field :name, hint: t("admin.budget_phases.edit.name_help_text") %> +
+ +
+ <%= translations_form.text_area :description, + maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH, + class: "html-area", + hint: t("admin.budget_phases.edit.description_help_text") %> +
+ <% end %> + +
+ <%= f.submit t("admin.budget_phases.edit.save_changes"), class: "button success" %> +
+<% end %> diff --git a/app/components/admin/budget_phases/form_component.rb b/app/components/admin/budget_phases/form_component.rb new file mode 100644 index 000000000..49c6cf36c --- /dev/null +++ b/app/components/admin/budget_phases/form_component.rb @@ -0,0 +1,11 @@ +class Admin::BudgetPhases::FormComponent < ApplicationComponent + include TranslatableFormHelper + include GlobalizeHelper + include Admin::Namespace + + attr_reader :phase + + def initialize(phase) + @phase = phase + end +end diff --git a/app/components/admin/budget_phases/phases_component.html.erb b/app/components/admin/budget_phases/phases_component.html.erb index 6f663578c..787508634 100644 --- a/app/components/admin/budget_phases/phases_component.html.erb +++ b/app/components/admin/budget_phases/phases_component.html.erb @@ -26,7 +26,7 @@ <% end %> - <%= enabled_text(phase) %> + <%= enabled_cell(phase) %> <%= render Admin::TableActionsComponent.new(phase, diff --git a/app/components/admin/budget_phases/phases_component.rb b/app/components/admin/budget_phases/phases_component.rb index b27958f18..25a2d0280 100644 --- a/app/components/admin/budget_phases/phases_component.rb +++ b/app/components/admin/budget_phases/phases_component.rb @@ -1,8 +1,9 @@ class Admin::BudgetPhases::PhasesComponent < ApplicationComponent - attr_reader :budget + attr_reader :budget, :form - def initialize(budget) + def initialize(budget, form: nil) @budget = budget + @form = form end private @@ -15,6 +16,20 @@ class Admin::BudgetPhases::PhasesComponent < ApplicationComponent Admin::Budgets::DurationComponent.new(phase).dates end + def enabled_cell(phase) + if form + form.fields_for :phases, phase do |phase_fields| + phase_fields.check_box :enabled, + label: false, + aria: { + label: t("admin.budgets.edit.enable_phase", phase: phase.name) + } + end + else + enabled_text(phase) + end + end + def enabled_text(phase) if phase.enabled? tag.span t("shared.yes"), class: "budget-phase-enabled" diff --git a/app/views/admin/budgets/_form.html.erb b/app/components/admin/budgets/form_component.html.erb similarity index 56% rename from app/views/admin/budgets/_form.html.erb rename to app/components/admin/budgets/form_component.html.erb index 295f1234a..57a3f8d44 100644 --- a/app/views/admin/budgets/_form.html.erb +++ b/app/components/admin/budgets/form_component.html.erb @@ -1,8 +1,8 @@ -<%= translatable_form_for [:admin, @budget], html: { class: "budgets-form" } do |f| %> +<%= translatable_form_for [namespace, budget], html: { class: "budgets-form" } do |f| %>
<%= t("admin.budgets.edit.info.budget_settings") %> - <%= render "shared/globalize_locales", resource: @budget %> - <%= render "shared/errors", resource: @budget %> + <%= render "shared/globalize_locales", resource: budget %> + <%= render "shared/errors", resource: budget %> <%= f.translatable_fields do |translations_form| %>
@@ -13,11 +13,11 @@ <% end %>
- <%= f.select :voting_style, budget_voting_styles_select_options %> + <%= f.select :voting_style, voting_styles_select_options %>
- <%= f.select :currency_symbol, budget_currency_symbol_select_options %> + <%= f.select :currency_symbol, currency_symbol_select_options %>
@@ -26,52 +26,55 @@ <% %w[administrators valuators].each do |staff| %>
- <%= link_to t("admin.budgets.edit.#{staff}", count: @budget.send(staff).count), + <%= link_to t("admin.budgets.edit.#{staff}", count: budget.send(staff).count), "#", class: "button expanded hollow js-budget-show-#{staff}-list js-budget-show-users-list", data: { toggle: "#{staff}_list", texts: t("admin.budgets.edit.#{staff}") } %>
<% end %> - <%= render "/admin/budgets/association", assignable_type: "administrators", assignables: @admins, form: f %> - <%= render "/admin/budgets/association", assignable_type: "valuators", assignables: @valuators, form: f %> + <%= render "/admin/budgets/association", assignable_type: "administrators", assignables: admins, form: f %> + <%= render "/admin/budgets/association", assignable_type: "valuators", assignables: valuators, form: f %> -
- <%= t("admin.budgets.edit.info.phases_settings") %> -
- <%= f.select :phase, budget_phases_select_options %> -
+ <% unless wizard? %> +
+ <%= t("admin.budgets.edit.info.phases_settings") %> +
+ <%= f.select :phase, phases_select_options %> +
- <%= render Admin::Budgets::HelpComponent.new("budget_phases") %> - <%= render Admin::BudgetPhases::PhasesComponent.new(@budget) %> -
+ <%= render Admin::BudgetPhases::PhasesComponent.new(budget) %> +
- <% if @budget.persisted? %> <%= render "admin/shared/show_results_fields", form: f %> <% end %>
- <%= f.submit nil, class: "button success" %> + <% if wizard? %> + <%= f.submit t("admin.budgets_wizard.budgets.continue"), class: "button success expanded" %> + <% else %> + <%= f.submit nil, class: "button success" %> + <% end %>
- <% if display_calculate_winners_button?(@budget) %> - <%= link_to calculate_winner_button_text(@budget), - calculate_winners_admin_budget_path(@budget), + <% if display_calculate_winners_button?(budget) %> + <%= link_to calculate_winner_button_text(budget), + calculate_winners_admin_budget_path(budget), method: :put, class: "button hollow" %> <% end %> - <% if @budget.has_winning_investments? %> + <% if budget.has_winning_investments? %> <%= link_to t("budgets.show.see_results"), - budget_results_path(@budget), + budget_results_path(budget), class: "button hollow margin-left" %> <% end %> - <% if @budget.persisted? %> + <% if budget.persisted? %> <%= link_to t("admin.budgets.edit.delete"), - admin_budget_path(@budget), + admin_budget_path(budget), method: :delete, class: "delete float-right margin-left" %> <% end %> diff --git a/app/components/admin/budgets/form_component.rb b/app/components/admin/budgets/form_component.rb new file mode 100644 index 000000000..3d57663f0 --- /dev/null +++ b/app/components/admin/budgets/form_component.rb @@ -0,0 +1,41 @@ +class Admin::Budgets::FormComponent < ApplicationComponent + include TranslatableFormHelper + include GlobalizeHelper + include Admin::Namespace + + attr_reader :budget, :wizard + alias_method :wizard?, :wizard + delegate :display_calculate_winners_button?, + :calculate_winner_button_text, + :calculate_winners_admin_budget_path, + to: :helpers + + def initialize(budget, wizard: false) + @budget = budget + @wizard = wizard + end + + def voting_styles_select_options + Budget::VOTING_STYLES.map do |style| + [Budget.human_attribute_name("voting_style_#{style}"), style] + end + end + + def currency_symbol_select_options + Budget::CURRENCY_SYMBOLS.map { |cs| [cs, cs] } + end + + def phases_select_options + Budget::Phase::PHASE_KINDS.map { |ph| [t("budgets.phase.#{ph}"), ph] } + end + + private + + def admins + @admins ||= Administrator.includes(:user) + end + + def valuators + @valuators ||= Valuator.includes(:user).order(description: :asc).order("users.email ASC") + end +end diff --git a/app/components/admin/budgets/index_component.html.erb b/app/components/admin/budgets/index_component.html.erb index feb881352..35e6fce7f 100644 --- a/app/components/admin/budgets/index_component.html.erb +++ b/app/components/admin/budgets/index_component.html.erb @@ -1,5 +1,5 @@ <%= header do %> - <%= link_to t("admin.budgets.index.new_link"), new_admin_budget_path %> + <%= link_to t("admin.budgets.index.new_link"), new_admin_budgets_wizard_budget_path %> <% end %> <%= render Admin::Budgets::HelpComponent.new("budgets") %> diff --git a/app/components/admin/budgets/table_actions_component.html.erb b/app/components/admin/budgets/table_actions_component.html.erb index e2d86ae80..9eba6926a 100644 --- a/app/components/admin/budgets/table_actions_component.html.erb +++ b/app/components/admin/budgets/table_actions_component.html.erb @@ -1,4 +1,8 @@ -<%= render Admin::TableActionsComponent.new(budget, actions: [:edit], edit_text: t("admin.budgets.index.edit_budget")) do %> +<%= render Admin::TableActionsComponent.new(budget, + edit_text: t("admin.budgets.index.edit_budget"), + destroy_confirmation: t("admin.actions.confirm_delete", resource_name: t("admin.budgets.shared.resource_name"), + name: budget.name) + ) do %> <%= link_to t("admin.budgets.index.budget_investments"), admin_budget_budget_investments_path(budget_id: budget.id), class: "investments-link" %> diff --git a/app/components/admin/budgets_wizard/budgets/edit_component.html.erb b/app/components/admin/budgets_wizard/budgets/edit_component.html.erb new file mode 100644 index 000000000..672f3e8ca --- /dev/null +++ b/app/components/admin/budgets_wizard/budgets/edit_component.html.erb @@ -0,0 +1,6 @@ +<%= back_link_to admin_budgets_path %> + +<%= header %> + +<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("budget") %> +<%= render Admin::Budgets::FormComponent.new(budget, wizard: true) %> diff --git a/app/components/admin/budgets_wizard/budgets/edit_component.rb b/app/components/admin/budgets_wizard/budgets/edit_component.rb new file mode 100644 index 000000000..7b3d686c9 --- /dev/null +++ b/app/components/admin/budgets_wizard/budgets/edit_component.rb @@ -0,0 +1,12 @@ +class Admin::BudgetsWizard::Budgets::EditComponent < ApplicationComponent + include Header + attr_reader :budget + + def initialize(budget) + @budget = budget + end + + def title + t("admin.budgets.edit.title") + 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 new file mode 100644 index 000000000..672f3e8ca --- /dev/null +++ b/app/components/admin/budgets_wizard/budgets/new_component.html.erb @@ -0,0 +1,6 @@ +<%= back_link_to admin_budgets_path %> + +<%= header %> + +<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("budget") %> +<%= render Admin::Budgets::FormComponent.new(budget, wizard: true) %> diff --git a/app/components/admin/budgets_wizard/budgets/new_component.rb b/app/components/admin/budgets_wizard/budgets/new_component.rb new file mode 100644 index 000000000..09502de89 --- /dev/null +++ b/app/components/admin/budgets_wizard/budgets/new_component.rb @@ -0,0 +1,12 @@ +class Admin::BudgetsWizard::Budgets::NewComponent < ApplicationComponent + include Header + attr_reader :budget + + def initialize(budget) + @budget = budget + end + + def title + t("admin.budgets.new.title") + end +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 new file mode 100644 index 000000000..b6ecad267 --- /dev/null +++ b/app/components/admin/budgets_wizard/creation_step_component.html.erb @@ -0,0 +1,19 @@ +
+ + + <%= 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 %> +
diff --git a/app/components/admin/budgets_wizard/creation_step_component.rb b/app/components/admin/budgets_wizard/creation_step_component.rb new file mode 100644 index 000000000..2bd220146 --- /dev/null +++ b/app/components/admin/budgets_wizard/creation_step_component.rb @@ -0,0 +1,22 @@ +class Admin::BudgetsWizard::CreationStepComponent < ApplicationComponent + attr_reader :record, :next_step_path + + def initialize(record, next_step_path) + @record = record + @next_step_path = next_step_path + end + + private + + def show_form? + record.errors.any? + end + + def i18n_namespace + i18n_namespace_with_budget.gsub("budget_", "") + end + + def i18n_namespace_with_budget + record.class.table_name + end +end diff --git a/app/components/admin/budgets_wizard/creation_timeline_component.html.erb b/app/components/admin/budgets_wizard/creation_timeline_component.html.erb new file mode 100644 index 000000000..3d0063898 --- /dev/null +++ b/app/components/admin/budgets_wizard/creation_timeline_component.html.erb @@ -0,0 +1,7 @@ +
    + <% steps.each do |step| %> +
  1. > + <%= t("admin.budgets_wizard.creation_timeline.#{step}") %> +
  2. + <% end %> +
diff --git a/app/components/admin/budgets_wizard/creation_timeline_component.rb b/app/components/admin/budgets_wizard/creation_timeline_component.rb new file mode 100644 index 000000000..7fe7ffcf2 --- /dev/null +++ b/app/components/admin/budgets_wizard/creation_timeline_component.rb @@ -0,0 +1,13 @@ +class Admin::BudgetsWizard::CreationTimelineComponent < ApplicationComponent + attr_reader :current_step + + def initialize(current_step) + @current_step = current_step + end + + private + + def steps + %w[budget groups headings phases] + end +end diff --git a/app/components/admin/budgets_wizard/groups/creation_step_component.html.erb b/app/components/admin/budgets_wizard/groups/creation_step_component.html.erb new file mode 100644 index 000000000..9f1c1ab33 --- /dev/null +++ b/app/components/admin/budgets_wizard/groups/creation_step_component.html.erb @@ -0,0 +1,3 @@ +<%= render Admin::BudgetsWizard::CreationStepComponent.new(group, next_step_path) do %> + <%= render "/admin/budget_groups/form", group: group, path: form_path, action: "create" %> +<% end %> diff --git a/app/components/admin/budgets_wizard/groups/creation_step_component.rb b/app/components/admin/budgets_wizard/groups/creation_step_component.rb new file mode 100644 index 000000000..8a5e3bfa9 --- /dev/null +++ b/app/components/admin/budgets_wizard/groups/creation_step_component.rb @@ -0,0 +1,26 @@ +class Admin::BudgetsWizard::Groups::CreationStepComponent < ApplicationComponent + attr_reader :group, :next_step_group + + def initialize(group, next_step_group) + @group = group + @next_step_group = next_step_group + end + + private + + def budget + group.budget + end + + def form_path + admin_budgets_wizard_budget_groups_path(budget) + end + + def next_step_path + admin_budgets_wizard_budget_group_headings_path(budget, next_step_group) if next_step_enabled? + end + + def next_step_enabled? + next_step_group.present? + end +end diff --git a/app/components/admin/budgets_wizard/groups/edit_component.html.erb b/app/components/admin/budgets_wizard/groups/edit_component.html.erb new file mode 100644 index 000000000..fc92c2114 --- /dev/null +++ b/app/components/admin/budgets_wizard/groups/edit_component.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_budgets_wizard_budget_groups_path(budget) %> + +<%= header %> + +<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("groups") %> + +<%= render "/admin/budget_groups/form", group: group, path: form_path, action: "submit" %> diff --git a/app/components/admin/budgets_wizard/groups/edit_component.rb b/app/components/admin/budgets_wizard/groups/edit_component.rb new file mode 100644 index 000000000..cc8c8fbfe --- /dev/null +++ b/app/components/admin/budgets_wizard/groups/edit_component.rb @@ -0,0 +1,22 @@ +class Admin::BudgetsWizard::Groups::EditComponent < ApplicationComponent + include Header + attr_reader :group + + def initialize(group) + @group = group + end + + def budget + group.budget + end + + def title + budget.name + end + + private + + def form_path + admin_budgets_wizard_budget_group_path(budget, group) + end +end diff --git a/app/components/admin/budgets_wizard/groups/index_component.html.erb b/app/components/admin/budgets_wizard/groups/index_component.html.erb new file mode 100644 index 000000000..04a0059aa --- /dev/null +++ b/app/components/admin/budgets_wizard/groups/index_component.html.erb @@ -0,0 +1,9 @@ +<%= back_link_to edit_admin_budgets_wizard_budget_path(budget), t("admin.budgets_wizard.groups.back") %> + +<%= header %> + +<%= render Admin::Budgets::HelpComponent.new("budget_groups") %> +<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("groups") %> + +<%= render Admin::BudgetGroups::GroupsComponent.new(groups) %> +<%= 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 new file mode 100644 index 000000000..d15414e54 --- /dev/null +++ b/app/components/admin/budgets_wizard/groups/index_component.rb @@ -0,0 +1,17 @@ +class Admin::BudgetsWizard::Groups::IndexComponent < ApplicationComponent + include Header + attr_reader :groups, :new_group + + def initialize(groups, new_group) + @groups = groups + @new_group = new_group + end + + def budget + @new_group.budget + end + + def title + budget.name + end +end diff --git a/app/components/admin/budgets_wizard/headings/creation_step_component.html.erb b/app/components/admin/budgets_wizard/headings/creation_step_component.html.erb new file mode 100644 index 000000000..a8ca3d7b4 --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/creation_step_component.html.erb @@ -0,0 +1,3 @@ +<%= render Admin::BudgetsWizard::CreationStepComponent.new(heading, next_step_path) do %> + <%= render "/admin/budget_headings/form", heading: heading, path: form_path, action: "create" %> +<% 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 new file mode 100644 index 000000000..6c07c6670 --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/creation_step_component.rb @@ -0,0 +1,25 @@ +class Admin::BudgetsWizard::Headings::CreationStepComponent < ApplicationComponent + attr_reader :heading + + def initialize(heading) + @heading = heading + end + + private + + def budget + heading.budget + end + + def form_path + admin_budgets_wizard_budget_group_headings_path(heading.group.budget, heading.group) + end + + def next_step_path + admin_budgets_wizard_budget_budget_phases_path(budget) if next_step_enabled? + end + + def next_step_enabled? + budget.headings.any? + end +end diff --git a/app/components/admin/budgets_wizard/headings/edit_component.html.erb b/app/components/admin/budgets_wizard/headings/edit_component.html.erb new file mode 100644 index 000000000..5a269b582 --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/edit_component.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_budgets_wizard_budget_group_headings_path(budget, group) %> + +<%= header %> + +<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("headings") %> + +<%= render "/admin/budget_headings/form", heading: heading, path: form_path, action: "submit" %> diff --git a/app/components/admin/budgets_wizard/headings/edit_component.rb b/app/components/admin/budgets_wizard/headings/edit_component.rb new file mode 100644 index 000000000..ee6250b23 --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/edit_component.rb @@ -0,0 +1,26 @@ +class Admin::BudgetsWizard::Headings::EditComponent < ApplicationComponent + include Header + attr_reader :heading + + def initialize(heading) + @heading = heading + end + + def budget + heading.budget + end + + def group + heading.group + end + + def title + heading.name + end + + private + + def form_path + admin_budgets_wizard_budget_group_heading_path(budget, group, heading) + end +end diff --git a/app/components/admin/budgets_wizard/headings/group_switcher_component.html.erb b/app/components/admin/budgets_wizard/headings/group_switcher_component.html.erb new file mode 100644 index 000000000..4fe2ba830 --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/group_switcher_component.html.erb @@ -0,0 +1,21 @@ +
+ <% if other_groups.one? %> +

+ <%= t("admin.budget_headings.group_switcher.currently_showing", group: group.name) %> + <%= link_to t("admin.budget_headings.group_switcher.the_other_group", group: other_groups.first.name), + headings_path(other_groups.first) %> +

+ <% else %> +

<%= t("admin.budget_headings.group_switcher.currently_showing", group: group.name) %>

+ + <% end %> +
diff --git a/app/components/admin/budgets_wizard/headings/group_switcher_component.rb b/app/components/admin/budgets_wizard/headings/group_switcher_component.rb new file mode 100644 index 000000000..b6e490ad9 --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/group_switcher_component.rb @@ -0,0 +1,25 @@ +class Admin::BudgetsWizard::Headings::GroupSwitcherComponent < ApplicationComponent + attr_reader :group + + def initialize(group) + @group = group + end + + def render? + other_groups.any? + end + + private + + def budget + group.budget + end + + def other_groups + @other_groups ||= budget.groups.sort_by_name - [group] + end + + def headings_path(group) + admin_budgets_wizard_budget_group_headings_path(budget, group) + end +end diff --git a/app/components/admin/budgets_wizard/headings/index_component.html.erb b/app/components/admin/budgets_wizard/headings/index_component.html.erb new file mode 100644 index 000000000..21e0d538d --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/index_component.html.erb @@ -0,0 +1,10 @@ +<%= back_link_to admin_budgets_wizard_budget_groups_path(budget), t("admin.budget_headings.index.back") %> + +<%= header %> + +<%= 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) %> +<%= render Admin::BudgetsWizard::Headings::CreationStepComponent.new(new_heading) %> diff --git a/app/components/admin/budgets_wizard/headings/index_component.rb b/app/components/admin/budgets_wizard/headings/index_component.rb new file mode 100644 index 000000000..76aa584a9 --- /dev/null +++ b/app/components/admin/budgets_wizard/headings/index_component.rb @@ -0,0 +1,21 @@ +class Admin::BudgetsWizard::Headings::IndexComponent < ApplicationComponent + include Header + attr_reader :headings, :new_heading + + def initialize(headings, new_heading) + @headings = headings + @new_heading = new_heading + end + + def budget + group.budget + end + + def group + new_heading.group + end + + def title + t("admin.budget_headings.index.title", budget: budget.name, group: group.name) + end +end diff --git a/app/components/admin/budgets_wizard/phases/edit_component.html.erb b/app/components/admin/budgets_wizard/phases/edit_component.html.erb new file mode 100644 index 000000000..44131384c --- /dev/null +++ b/app/components/admin/budgets_wizard/phases/edit_component.html.erb @@ -0,0 +1,6 @@ +<%= back_link_to admin_budgets_wizard_budget_budget_phases_path(budget) %> + +<%= header %> + +<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("phases") %> +<%= render "/admin/budget_phases/form" %> diff --git a/app/components/admin/budgets_wizard/phases/edit_component.rb b/app/components/admin/budgets_wizard/phases/edit_component.rb new file mode 100644 index 000000000..68645ed95 --- /dev/null +++ b/app/components/admin/budgets_wizard/phases/edit_component.rb @@ -0,0 +1,22 @@ +class Admin::BudgetsWizard::Phases::EditComponent < ApplicationComponent + include Header + attr_reader :phase + + def initialize(phase) + @phase = phase + end + + def budget + phase.budget + end + + def title + "#{t("admin.budget_phases.edit.title")} - #{phase.name}" + end + + private + + def form_path + admin_budgets_wizard_budget_budget_phases_path(budget) + end +end diff --git a/app/components/admin/budgets_wizard/phases/index_component.html.erb b/app/components/admin/budgets_wizard/phases/index_component.html.erb new file mode 100644 index 000000000..a97812cfb --- /dev/null +++ b/app/components/admin/budgets_wizard/phases/index_component.html.erb @@ -0,0 +1,11 @@ +<%= back_link_to back_link_path, t("admin.budgets_wizard.phases.back") %> + +<%= header %> + +<%= render Admin::Budgets::HelpComponent.new("budget_phases") %> +<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("phases") %> + +<%= form_for budget, url: update_all_admin_budgets_wizard_budget_budget_phases_path(budget) do |f| %> + <%= render Admin::BudgetPhases::PhasesComponent.new(budget, form: f) %> + <%= f.submit t("admin.budgets_wizard.phases.continue"), class: "button success" %> +<% end %> diff --git a/app/components/admin/budgets_wizard/phases/index_component.rb b/app/components/admin/budgets_wizard/phases/index_component.rb new file mode 100644 index 000000000..a1b680003 --- /dev/null +++ b/app/components/admin/budgets_wizard/phases/index_component.rb @@ -0,0 +1,18 @@ +class Admin::BudgetsWizard::Phases::IndexComponent < ApplicationComponent + include Header + attr_reader :budget + + def initialize(budget) + @budget = budget + end + + def title + t("admin.budget_phases.index.title", budget: budget.name) + end + + private + + def back_link_path + admin_budgets_wizard_budget_group_headings_path(budget, budget.groups.first) + end +end diff --git a/app/components/admin/menu_component.rb b/app/components/admin/menu_component.rb index e95f9f9cc..15e15f2b1 100644 --- a/app/components/admin/menu_component.rb +++ b/app/components/admin/menu_component.rb @@ -13,7 +13,7 @@ class Admin::MenuComponent < ApplicationComponent end def budgets? - controller_name.starts_with?("budget") + controller_name.starts_with?("budget") || controller_path =~ /budgets_wizard/ end def polls? diff --git a/app/components/admin/table_actions_component.rb b/app/components/admin/table_actions_component.rb index 9a5bf5134..7cc9697c5 100644 --- a/app/components/admin/table_actions_component.rb +++ b/app/components/admin/table_actions_component.rb @@ -1,7 +1,7 @@ class Admin::TableActionsComponent < ApplicationComponent include TableActionLink + include Admin::Namespace attr_reader :record, :options - delegate :namespace, to: :helpers def initialize(record = nil, **options) @record = record diff --git a/app/components/concerns/admin/namespace.rb b/app/components/concerns/admin/namespace.rb new file mode 100644 index 000000000..e43e9a28b --- /dev/null +++ b/app/components/concerns/admin/namespace.rb @@ -0,0 +1,9 @@ +module Admin::Namespace + def namespace + if controller.class.name.starts_with?("Admin::BudgetsWizard") + :admin_budgets_wizard + else + helpers.namespace.to_sym + end + end +end diff --git a/app/controllers/admin/budget_groups_controller.rb b/app/controllers/admin/budget_groups_controller.rb index 1a19d5296..edbe24f2e 100644 --- a/app/controllers/admin/budget_groups_controller.rb +++ b/app/controllers/admin/budget_groups_controller.rb @@ -1,64 +1,22 @@ class Admin::BudgetGroupsController < Admin::BaseController - include Translatable - include FeatureFlags - feature_flag :budgets + include Admin::BudgetGroupsActions - before_action :load_budget - before_action :load_group, except: [:index, :new, :create] + before_action :load_groups, only: :index def index - @groups = @budget.groups.order(:id) end def new @group = @budget.groups.new end - def edit - end - - def create - @group = @budget.groups.new(budget_group_params) - if @group.save - redirect_to groups_index, notice: t("admin.budget_groups.create.notice") - else - render :new - end - end - - def update - if @group.update(budget_group_params) - redirect_to groups_index, notice: t("admin.budget_groups.update.notice") - else - render :edit - end - end - - def destroy - if @group.headings.any? - redirect_to groups_index, alert: t("admin.budget_groups.destroy.unable_notice") - else - @group.destroy! - redirect_to groups_index, notice: t("admin.budget_groups.destroy.success_notice") - end - end - private - def load_budget - @budget = Budget.find_by_slug_or_id! params[:budget_id] - end - - def load_group - @group = @budget.groups.find_by_slug_or_id! params[:id] - end - def groups_index admin_budget_groups_path(@budget) end - def budget_group_params - valid_attributes = [:max_votable_headings] - params.require(:budget_group).permit(*valid_attributes, translation_params(Budget::Group)) + def new_action + :new end end diff --git a/app/controllers/admin/budget_headings_controller.rb b/app/controllers/admin/budget_headings_controller.rb index da701cb7f..366641a8a 100644 --- a/app/controllers/admin/budget_headings_controller.rb +++ b/app/controllers/admin/budget_headings_controller.rb @@ -1,69 +1,20 @@ class Admin::BudgetHeadingsController < Admin::BaseController - include Translatable - include FeatureFlags - feature_flag :budgets - - before_action :load_budget - before_action :load_group - before_action :load_heading, except: [:index, :new, :create] + include Admin::BudgetHeadingsActions def index - @headings = @group.headings.order(:id) end def new @heading = @group.headings.new end - def edit - end - - def create - @heading = @group.headings.new(budget_heading_params) - if @heading.save - redirect_to headings_index, notice: t("admin.budget_headings.create.notice") - else - render :new - end - end - - def update - if @heading.update(budget_heading_params) - redirect_to headings_index, notice: t("admin.budget_headings.update.notice") - else - render :edit - end - end - - def destroy - if @heading.can_be_deleted? - @heading.destroy! - redirect_to headings_index, notice: t("admin.budget_headings.destroy.success_notice") - else - redirect_to headings_index, alert: t("admin.budget_headings.destroy.unable_notice") - end - end - private - def load_budget - @budget = Budget.find_by_slug_or_id! params[:budget_id] - end - - def load_group - @group = @budget.groups.find_by_slug_or_id! params[:group_id] - end - - def load_heading - @heading = @group.headings.find_by_slug_or_id! params[:id] - end - def headings_index admin_budget_group_headings_path(@budget, @group) end - def budget_heading_params - valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude, :max_ballot_lines] - params.require(:budget_heading).permit(*valid_attributes, translation_params(Budget::Heading)) + def new_action + :new end end diff --git a/app/controllers/admin/budget_phases_controller.rb b/app/controllers/admin/budget_phases_controller.rb index 1ff8dcedb..ac2a763e1 100644 --- a/app/controllers/admin/budget_phases_controller.rb +++ b/app/controllers/admin/budget_phases_controller.rb @@ -1,28 +1,9 @@ class Admin::BudgetPhasesController < Admin::BaseController - include Translatable - - before_action :load_phase, only: [:edit, :update] - - def edit - end - - def update - if @phase.update(budget_phase_params) - notice = t("flash.actions.save_changes.notice") - redirect_to edit_admin_budget_path(@phase.budget), notice: notice - else - render :edit - end - end + include Admin::BudgetPhasesActions private - def load_phase - @phase = Budget::Phase.find(params[:id]) - end - - def budget_phase_params - valid_attributes = [:starts_at, :ends_at, :enabled] - params.require(:budget_phase).permit(*valid_attributes, translation_params(Budget::Phase)) + def phases_index + edit_admin_budget_path(@phase.budget) end end diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index b6adc0a0a..572d55fc1 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -6,8 +6,7 @@ class Admin::BudgetsController < Admin::BaseController has_filters %w[all open finished], only: :index - before_action :load_budget, except: [:index, :new, :create] - before_action :load_staff, only: [:new, :create, :edit, :update, :show] + before_action :load_budget, except: [:index] load_and_authorize_resource def index @@ -18,9 +17,6 @@ class Admin::BudgetsController < Admin::BaseController render :edit end - def new - end - def edit end @@ -47,15 +43,6 @@ class Admin::BudgetsController < Admin::BaseController end end - def create - @budget = Budget.new(budget_params.merge(published: false)) - if @budget.save - redirect_to edit_admin_budget_path(@budget), notice: t("admin.budgets.create.notice") - else - render :new - end - end - def destroy if @budget.investments.any? redirect_to admin_budgets_path, alert: t("admin.budgets.destroy.unable_notice") @@ -83,9 +70,4 @@ class Admin::BudgetsController < Admin::BaseController def load_budget @budget = Budget.find_by_slug_or_id! params[:id] end - - def load_staff - @admins = Administrator.includes(:user) - @valuators = Valuator.includes(:user).order(description: :asc).order("users.email ASC") - end end diff --git a/app/controllers/admin/budgets_wizard/budgets_controller.rb b/app/controllers/admin/budgets_wizard/budgets_controller.rb new file mode 100644 index 000000000..4100d951a --- /dev/null +++ b/app/controllers/admin/budgets_wizard/budgets_controller.rb @@ -0,0 +1,47 @@ +class Admin::BudgetsWizard::BudgetsController < Admin::BaseController + include Translatable + include FeatureFlags + feature_flag :budgets + + load_and_authorize_resource + + def new + end + + def edit + end + + def create + @budget.published = false + + if @budget.save + redirect_to groups_index, notice: t("admin.budgets.create.notice") + else + render :new + end + end + + def update + if @budget.update(budget_params) + redirect_to groups_index, notice: t("admin.budgets.update.notice") + else + render :edit + end + end + + private + + def budget_params + params.require(:budget).permit(*allowed_params) + end + + def allowed_params + valid_attributes = [:currency_symbol, :voting_style, administrator_ids: [], valuator_ids: []] + + valid_attributes + [translation_params(Budget)] + end + + def groups_index + admin_budgets_wizard_budget_groups_path(@budget) + end +end diff --git a/app/controllers/admin/budgets_wizard/groups_controller.rb b/app/controllers/admin/budgets_wizard/groups_controller.rb new file mode 100644 index 000000000..4d8c8895c --- /dev/null +++ b/app/controllers/admin/budgets_wizard/groups_controller.rb @@ -0,0 +1,19 @@ +class Admin::BudgetsWizard::GroupsController < Admin::BaseController + include Admin::BudgetGroupsActions + + before_action :load_groups, only: [:index, :create] + + def index + @group = @budget.groups.new + end + + private + + def groups_index + admin_budgets_wizard_budget_groups_path(@budget) + end + + def new_action + :index + end +end diff --git a/app/controllers/admin/budgets_wizard/headings_controller.rb b/app/controllers/admin/budgets_wizard/headings_controller.rb new file mode 100644 index 000000000..5eb78e5d3 --- /dev/null +++ b/app/controllers/admin/budgets_wizard/headings_controller.rb @@ -0,0 +1,19 @@ +class Admin::BudgetsWizard::HeadingsController < Admin::BaseController + include Admin::BudgetHeadingsActions + + before_action :load_headings, only: [:index, :create] + + def index + @heading = @group.headings.new + end + + private + + def headings_index + admin_budgets_wizard_budget_group_headings_path(@budget, @group) + end + + def new_action + :index + end +end diff --git a/app/controllers/admin/budgets_wizard/phases_controller.rb b/app/controllers/admin/budgets_wizard/phases_controller.rb new file mode 100644 index 000000000..5376e93b7 --- /dev/null +++ b/app/controllers/admin/budgets_wizard/phases_controller.rb @@ -0,0 +1,26 @@ +class Admin::BudgetsWizard::PhasesController < Admin::BaseController + include Admin::BudgetPhasesActions + + def index + end + + def update_all + @budget.update!(phases_params) + + redirect_to admin_budgets_path, notice: t("admin.budgets_wizard.phases.update_all.notice") + end + + private + + def phases_index + admin_budgets_wizard_budget_budget_phases_path(@phase.budget) + end + + def phases_params + params.require(:budget).permit(allowed_phases_params) + end + + def allowed_phases_params + { phases_attributes: [:id, :enabled] } + end +end diff --git a/app/controllers/concerns/admin/budget_groups_actions.rb b/app/controllers/concerns/admin/budget_groups_actions.rb new file mode 100644 index 000000000..c26d98c1b --- /dev/null +++ b/app/controllers/concerns/admin/budget_groups_actions.rb @@ -0,0 +1,60 @@ +module Admin::BudgetGroupsActions + extend ActiveSupport::Concern + + included do + include Translatable + include FeatureFlags + feature_flag :budgets + + before_action :load_budget + before_action :load_group, only: [:edit, :update, :destroy] + end + + def edit + end + + def create + @group = @budget.groups.new(budget_group_params) + if @group.save + redirect_to groups_index, notice: t("admin.budget_groups.create.notice") + else + render new_action + end + end + + def update + if @group.update(budget_group_params) + redirect_to groups_index, notice: t("admin.budget_groups.update.notice") + else + render :edit + end + end + + def destroy + if @group.headings.any? + redirect_to groups_index, alert: t("admin.budget_groups.destroy.unable_notice") + else + @group.destroy! + redirect_to groups_index, notice: t("admin.budget_groups.destroy.success_notice") + end + end + + private + + def load_budget + @budget = Budget.find_by_slug_or_id! params[:budget_id] + end + + def load_groups + @groups = @budget.groups.order(:id) + end + + def load_group + @group = @budget.groups.find_by_slug_or_id! params[:id] + end + + def budget_group_params + valid_attributes = [:max_votable_headings] + params.require(:budget_group).permit(*valid_attributes, translation_params(Budget::Group)) + end +end diff --git a/app/controllers/concerns/admin/budget_headings_actions.rb b/app/controllers/concerns/admin/budget_headings_actions.rb new file mode 100644 index 000000000..4bc125288 --- /dev/null +++ b/app/controllers/concerns/admin/budget_headings_actions.rb @@ -0,0 +1,66 @@ +module Admin::BudgetHeadingsActions + extend ActiveSupport::Concern + + included do + include Translatable + include FeatureFlags + feature_flag :budgets + + before_action :load_budget + before_action :load_group + before_action :load_headings, only: :index + before_action :load_heading, only: [:edit, :update, :destroy] + end + + def edit + end + + def create + @heading = @group.headings.new(budget_heading_params) + if @heading.save + redirect_to headings_index, notice: t("admin.budget_headings.create.notice") + else + render new_action + end + end + + def update + if @heading.update(budget_heading_params) + redirect_to headings_index, notice: t("admin.budget_headings.update.notice") + else + render :edit + end + end + + def destroy + if @heading.can_be_deleted? + @heading.destroy! + redirect_to headings_index, notice: t("admin.budget_headings.destroy.success_notice") + else + redirect_to headings_index, alert: t("admin.budget_headings.destroy.unable_notice") + end + end + + private + + def load_budget + @budget = Budget.find_by_slug_or_id! params[:budget_id] + end + + def load_group + @group = @budget.groups.find_by_slug_or_id! params[:group_id] + end + + def load_headings + @headings = @group.headings.order(:id) + end + + def load_heading + @heading = @group.headings.find_by_slug_or_id! params[:id] + end + + def budget_heading_params + valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude, :max_ballot_lines] + params.require(:budget_heading).permit(*valid_attributes, translation_params(Budget::Heading)) + end +end diff --git a/app/controllers/concerns/admin/budget_phases_actions.rb b/app/controllers/concerns/admin/budget_phases_actions.rb new file mode 100644 index 000000000..80a1af51c --- /dev/null +++ b/app/controllers/concerns/admin/budget_phases_actions.rb @@ -0,0 +1,36 @@ +module Admin::BudgetPhasesActions + extend ActiveSupport::Concern + + included do + include Translatable + + before_action :load_budget + before_action :load_phase, only: [:edit, :update] + end + + def edit + end + + def update + if @phase.update(budget_phase_params) + redirect_to phases_index, notice: t("flash.actions.save_changes.notice") + else + render :edit + end + end + + private + + def load_budget + @budget = Budget.find_by_slug_or_id!(params[:budget_id]) + end + + def load_phase + @phase = @budget.phases.find(params[:id]) + end + + def budget_phase_params + valid_attributes = [:starts_at, :ends_at, :enabled] + params.require(:budget_phase).permit(*valid_attributes, translation_params(Budget::Phase)) + end +end diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb index 6d5751302..e07b73192 100644 --- a/app/helpers/budgets_helper.rb +++ b/app/helpers/budgets_helper.rb @@ -1,10 +1,4 @@ module BudgetsHelper - def budget_voting_styles_select_options - Budget::VOTING_STYLES.map do |style| - [Budget.human_attribute_name("voting_style_#{style}"), style] - end - end - def csv_params csv_params = params.clone.merge(format: :csv) csv_params = csv_params.to_unsafe_h.map { |k, v| [k.to_sym, v] }.to_h @@ -12,14 +6,6 @@ module BudgetsHelper csv_params end - def budget_phases_select_options - Budget::Phase::PHASE_KINDS.map { |ph| [t("budgets.phase.#{ph}"), ph] } - end - - def budget_currency_symbol_select_options - Budget::CURRENCY_SYMBOLS.map { |cs| [cs, cs] } - end - def namespaced_budget_investment_path(investment, options = {}) case namespace when "management" diff --git a/app/models/budget.rb b/app/models/budget.rb index c685323f5..dba0d6e0b 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -42,6 +42,7 @@ class Budget < ApplicationRecord has_one :poll after_create :generate_phases + accepts_nested_attributes_for :phases scope :published, -> { where(published: true) } scope :drafting, -> { where.not(id: published) } diff --git a/app/views/admin/budget_groups/_form.html.erb b/app/views/admin/budget_groups/_form.html.erb index ac89d033d..5e86059c0 100644 --- a/app/views/admin/budget_groups/_form.html.erb +++ b/app/views/admin/budget_groups/_form.html.erb @@ -1,30 +1,23 @@ -<%= render "shared/globalize_locales", resource: @group %> +<%= translatable_form_for group, url: path, html: { class: "budget-groups-form" } do |f| %> + <%= render "shared/globalize_locales", resource: group %> -<%= translatable_form_for [:admin, @budget, @group], url: path do |f| %> + <%= render "shared/errors", resource: group %> - <%= render "shared/errors", resource: @group %> - -
- <%= f.translatable_fields do |translations_form| %> -
- <%= translations_form.text_field :name, maxlength: 50 %> -
- <% end %> -
- - <% if @group.persisted? %> -
-
- <%= f.select :max_votable_headings, - (1..@group.headings.count), - hint: t("admin.budget_groups.form.max_votable_headings_info") %> -
+ <%= f.translatable_fields do |translations_form| %> +
+ <%= translations_form.text_field :name, maxlength: 50 %>
<% end %> -
-
- <%= f.submit t("admin.budget_groups.form.#{action}"), class: "button success" %> + <% if group.persisted? && group.headings.any? %> +
+ <%= f.select :max_votable_headings, + (1..group.headings.count), + hint: t("admin.budget_groups.form.max_votable_headings_info") %>
+ <% end %> + +
+ <%= f.submit t("admin.budget_groups.form.#{action}"), class: "button hollow" %>
<% end %> diff --git a/app/views/admin/budget_groups/edit.html.erb b/app/views/admin/budget_groups/edit.html.erb index 0730a4206..c43b26d38 100644 --- a/app/views/admin/budget_groups/edit.html.erb +++ b/app/views/admin/budget_groups/edit.html.erb @@ -1,3 +1,3 @@ <%= render "header", action: "edit" %> -<%= render "form", path: admin_budget_group_path(@budget, @group), action: "submit" %> +<%= render "form", group: @group, path: admin_budget_group_path(@budget, @group), action: "submit" %> diff --git a/app/views/admin/budget_groups/index.html.erb b/app/views/admin/budget_groups/index.html.erb index 2e20ac1ac..f89d3ee20 100644 --- a/app/views/admin/budget_groups/index.html.erb +++ b/app/views/admin/budget_groups/index.html.erb @@ -8,37 +8,4 @@ <%= render Admin::Budgets::HelpComponent.new("budget_groups") %> - -<% if @groups.any? %> -

<%= t("admin.budget_groups.amount", count: @groups.count) %>

- - - - - - - - - - - <% @groups.each do |group| %> - - - - - - - <% end %> - -
<%= t("admin.budget_groups.name") %><%= Budget::Group.human_attribute_name(:max_votable_headings) %><%= t("admin.budget_groups.headings_name") %><%= t("admin.actions.actions") %>
<%= group.name %><%= group.max_votable_headings %><%= group.headings.count %> - <%= render Admin::TableActionsComponent.new(group) do |actions| %> - <%= actions.link_to t("admin.budget_groups.headings_manage"), - admin_budget_group_headings_path(@budget, group), - class: "headings-link" %> - <% end %> -
-<% else %> -
- <%= t("admin.budget_groups.no_groups") %> -
-<% end %> +<%= render Admin::BudgetGroups::GroupsComponent.new(@groups) %> diff --git a/app/views/admin/budget_groups/new.html.erb b/app/views/admin/budget_groups/new.html.erb index 461427d44..098ac5446 100644 --- a/app/views/admin/budget_groups/new.html.erb +++ b/app/views/admin/budget_groups/new.html.erb @@ -1,3 +1,3 @@ <%= render "header", action: "create" %> -<%= render "form", path: admin_budget_groups_path(@budget), action: "create" %> +<%= render "form", group: @group, path: admin_budget_groups_path(@budget), action: "create" %> diff --git a/app/views/admin/budget_headings/_form.html.erb b/app/views/admin/budget_headings/_form.html.erb index 06eae3926..f1424bb10 100644 --- a/app/views/admin/budget_headings/_form.html.erb +++ b/app/views/admin/budget_headings/_form.html.erb @@ -1,44 +1,40 @@ -<%= render "shared/globalize_locales", resource: @heading %> +<%= translatable_form_for heading, url: path, html: { class: "budget-headings-form" } do |f| %> + <%= render "shared/globalize_locales", resource: heading %> -<%= translatable_form_for [:admin, @budget, @group, @heading], url: path do |f| %> + <%= render "shared/errors", resource: heading %> - <%= render "shared/errors", resource: @heading %> + <%= f.translatable_fields do |translations_form| %> +
+ <%= translations_form.text_field :name, maxlength: 50 %> +
+ <% end %> -
- <%= f.translatable_fields do |translations_form| %> -
- <%= translations_form.text_field :name, maxlength: 50 %> -
+
+ <%= f.text_field :price, maxlength: 8 %> + + <% if heading.budget.approval_voting? %> + <%= f.number_field :max_ballot_lines, + hint: t("admin.budget_headings.form.max_ballot_lines_info") %> <% end %> + + <%= f.text_field :population, + maxlength: 8, + data: { toggle_focus: "population-info" }, + hint: t("admin.budget_headings.form.population_info") %> + + <%= f.text_field :latitude, maxlength: 22 %> + <%= f.text_field :longitude, maxlength: 22 %> +

+ <%= t("admin.budget_headings.form.coordinates_info") %> +

+ + <%= f.check_box :allow_custom_content %> +

+ <%= t("admin.budget_headings.form.content_blocks_info") %> +

-
-
- <%= f.text_field :price, maxlength: 8 %> - - <% if @heading.budget.approval_voting? %> - <%= f.number_field :max_ballot_lines, - hint: t("admin.budget_headings.form.max_ballot_lines_info") %> - - <% end %> - - <%= f.text_field :population, - maxlength: 8, - data: { toggle_focus: "population-info" }, - hint: t("admin.budget_headings.form.population_info") %> - - <%= f.text_field :latitude, maxlength: 22 %> - <%= f.text_field :longitude, maxlength: 22 %> -

- <%= t("admin.budget_headings.form.coordinates_info") %> -

- - <%= f.check_box :allow_custom_content %> -

- <%= t("admin.budget_headings.form.content_blocks_info") %> -

- - <%= f.submit t("admin.budget_headings.form.#{action}"), class: "button success" %> -
+
+ <%= f.submit t("admin.budget_headings.form.#{action}"), class: "button hollow" %>
<% end %> diff --git a/app/views/admin/budget_headings/edit.html.erb b/app/views/admin/budget_headings/edit.html.erb index 124d07510..2102d8e3c 100644 --- a/app/views/admin/budget_headings/edit.html.erb +++ b/app/views/admin/budget_headings/edit.html.erb @@ -1,3 +1,3 @@ <%= render "header", action: "edit" %> -<%= render "form", path: admin_budget_group_heading_path(@budget, @group, @heading), action: "submit" %> +<%= render "form", heading: @heading, path: admin_budget_group_heading_path(@budget, @group, @heading), action: "submit" %> diff --git a/app/views/admin/budget_headings/index.html.erb b/app/views/admin/budget_headings/index.html.erb index 4487a8f4b..48bfb4c9e 100644 --- a/app/views/admin/budget_headings/index.html.erb +++ b/app/views/admin/budget_headings/index.html.erb @@ -1,48 +1,9 @@ <%= back_link_to admin_budget_groups_path(@budget), t("admin.budget_headings.index.back") %>
-

<%= "#{@budget.name} / #{@group.name}" %>

+

<%= t("admin.budget_headings.index.title", budget: @budget.name, group: @group.name) %>

<%= link_to t("admin.budget_headings.form.create"), new_admin_budget_group_heading_path %>
<%= render Admin::Budgets::HelpComponent.new("budget_headings") %> - -<% if @headings.any? %> -

<%= t("admin.budget_headings.amount", count: @headings.count) %>

- - - - - - <% if @budget.approval_voting? %> - - <% end %> - - - - - - - <% @headings.each do |heading| %> - - - - <% if @budget.approval_voting? %> - - <% end %> - - - - - <% end %> - -
<%= Budget::Heading.human_attribute_name(:name) %><%= Budget::Heading.human_attribute_name(:price) %><%= Budget::Heading.human_attribute_name(:max_ballot_lines) %><%= Budget::Heading.human_attribute_name(:population) %><%= Budget::Heading.human_attribute_name(:allow_custom_content) %><%= t("admin.actions.actions") %>
<%= heading.name %><%= @budget.formatted_heading_price(heading) %><%= heading.max_ballot_lines %><%= heading.population %> - <%= heading.allow_custom_content ? t("admin.shared.true_value") : t("admin.shared.false_value") %> - - <%= render Admin::TableActionsComponent.new(heading) %> -
-<% else %> -
- <%= t("admin.budget_headings.no_headings") %> -
-<% end %> +<%= render Admin::BudgetHeadings::HeadingsComponent.new(@headings) %> diff --git a/app/views/admin/budget_headings/new.html.erb b/app/views/admin/budget_headings/new.html.erb index 0566a5a5e..3e1810fbc 100644 --- a/app/views/admin/budget_headings/new.html.erb +++ b/app/views/admin/budget_headings/new.html.erb @@ -1,3 +1,3 @@ <%= render "header", action: "create" %> -<%= render "form", path: admin_budget_group_headings_path(@budget, @group), action: "create" %> +<%= render "form", heading: @heading, path: admin_budget_group_headings_path(@budget, @group), action: "create" %> diff --git a/app/views/admin/budget_phases/_form.html.erb b/app/views/admin/budget_phases/_form.html.erb index f54f2aad6..e8a7ca310 100644 --- a/app/views/admin/budget_phases/_form.html.erb +++ b/app/views/admin/budget_phases/_form.html.erb @@ -1,49 +1 @@ -<%= render "shared/globalize_locales", resource: @phase %> - -<%= translatable_form_for [:admin, @phase.budget, @phase], html: { class: "budget-phases-form" } do |f| %> - - <%= render "shared/errors", resource: @phase %> - -
- - <%= t("admin.budget_phases.edit.duration") %> - - -

- <%= t("admin.budget_phases.edit.duration_description") %> -

- -
- <%= f.date_field :starts_at, id: "start_date" %> -
- -
- <%= f.date_field :ends_at, id: "end_date" %> -
-
- -
- <%= f.check_box :enabled %> - - - <%= t("admin.budget_phases.edit.enabled_help_text") %> - -
- - <%= f.translatable_fields do |translations_form| %> -
- <%= translations_form.text_field :name, hint: t("admin.budget_phases.edit.name_help_text") %> -
- -
- <%= translations_form.text_area :description, - maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH, - class: "html-area", - hint: t("admin.budget_phases.edit.description_help_text") %> -
- <% end %> - -
- <%= f.submit t("admin.budget_phases.edit.save_changes"), class: "button success" %> -
-<% end %> +<%= render Admin::BudgetPhases::FormComponent.new(@phase) %> diff --git a/app/views/admin/budget_phases/edit.html.erb b/app/views/admin/budget_phases/edit.html.erb index 452c11f96..27fbdd585 100644 --- a/app/views/admin/budget_phases/edit.html.erb +++ b/app/views/admin/budget_phases/edit.html.erb @@ -1,5 +1 @@ -<%= back_link_to edit_admin_budget_path(@phase.budget) %> - -

<%= t("admin.budgets.edit.title") %> - <%= @phase.name %>

- -<%= render "/admin/budget_phases/form" %> +<%= render Admin::BudgetsWizard::Phases::EditComponent.new(@phase) %> diff --git a/app/views/admin/budgets/edit.html.erb b/app/views/admin/budgets/edit.html.erb index 70cdcfd69..ad5d44f76 100644 --- a/app/views/admin/budgets/edit.html.erb +++ b/app/views/admin/budgets/edit.html.erb @@ -5,4 +5,4 @@ <%= render Admin::Budgets::DraftingComponent.new(@budget) %> -<%= render "/admin/budgets/form" %> +<%= render Admin::Budgets::FormComponent.new(@budget) %> diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb deleted file mode 100644 index 12bdf42f8..000000000 --- a/app/views/admin/budgets/new.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<%= back_link_to admin_budgets_path %> - -
-

<%= t("admin.budgets.new.title") %>

-
- -<%= render "/admin/budgets/form" %> diff --git a/app/views/admin/budgets_wizard/budgets/edit.html.erb b/app/views/admin/budgets_wizard/budgets/edit.html.erb new file mode 100644 index 000000000..51f56237c --- /dev/null +++ b/app/views/admin/budgets_wizard/budgets/edit.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Budgets::EditComponent.new(@budget) %> diff --git a/app/views/admin/budgets_wizard/budgets/new.html.erb b/app/views/admin/budgets_wizard/budgets/new.html.erb new file mode 100644 index 000000000..6ebfd4521 --- /dev/null +++ b/app/views/admin/budgets_wizard/budgets/new.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Budgets::NewComponent.new(@budget) %> diff --git a/app/views/admin/budgets_wizard/groups/edit.html.erb b/app/views/admin/budgets_wizard/groups/edit.html.erb new file mode 100644 index 000000000..eaf72fbd1 --- /dev/null +++ b/app/views/admin/budgets_wizard/groups/edit.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Groups::EditComponent.new(@group) %> diff --git a/app/views/admin/budgets_wizard/groups/index.html.erb b/app/views/admin/budgets_wizard/groups/index.html.erb new file mode 100644 index 000000000..51d32c7fb --- /dev/null +++ b/app/views/admin/budgets_wizard/groups/index.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Groups::IndexComponent.new(@groups, @group) %> diff --git a/app/views/admin/budgets_wizard/headings/edit.html.erb b/app/views/admin/budgets_wizard/headings/edit.html.erb new file mode 100644 index 000000000..63deb1260 --- /dev/null +++ b/app/views/admin/budgets_wizard/headings/edit.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Headings::EditComponent.new(@heading) %> diff --git a/app/views/admin/budgets_wizard/headings/index.html.erb b/app/views/admin/budgets_wizard/headings/index.html.erb new file mode 100644 index 000000000..ed647b65a --- /dev/null +++ b/app/views/admin/budgets_wizard/headings/index.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Headings::IndexComponent.new(@headings, @heading) %> diff --git a/app/views/admin/budgets_wizard/phases/edit.html.erb b/app/views/admin/budgets_wizard/phases/edit.html.erb new file mode 100644 index 000000000..27fbdd585 --- /dev/null +++ b/app/views/admin/budgets_wizard/phases/edit.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Phases::EditComponent.new(@phase) %> diff --git a/app/views/admin/budgets_wizard/phases/index.html.erb b/app/views/admin/budgets_wizard/phases/index.html.erb new file mode 100644 index 000000000..1fa77a2d1 --- /dev/null +++ b/app/views/admin/budgets_wizard/phases/index.html.erb @@ -0,0 +1 @@ +<%= render Admin::BudgetsWizard::Phases::IndexComponent.new(@budget) %> diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 71933acf9..97bb35653 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -147,6 +147,9 @@ 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.budget_investments.index.filter*" - "admin.organizations.index.filter*" - "admin.hidden_users.index.filter*" diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml index 802b5179a..e76c00725 100644 --- a/config/locales/en/activerecord.yml +++ b/config/locales/en/activerecord.yml @@ -220,7 +220,7 @@ en: latitude: "Latitude (optional)" longitude: "Longitude (optional)" name: "Heading name" - price: "Amount" + price: "Money amount" population: "Population (optional)" max_ballot_lines: "Votes allowed" budget/heading/translation: diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index e0f3f20ca..144cb6503 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -14,6 +14,7 @@ en: edit: Edit configure: Configure delete: Delete + confirm_delete: "Are you sure? This action will delete %{resource_name} '%{name}' and can't be undone." officing_booth: title: "You are officing the booth located at %{booth}. If this is not correct, do not continue and call the help phone number. Thank you." banners: @@ -106,6 +107,7 @@ en: enabled: Enabled actions: Actions edit_phase: Edit phase + enable_phase: "Enable %{phase} phase" active: Active blank_dates: Dates are blank administrators: @@ -133,6 +135,8 @@ en: calculate: Calculate Winner Investments calculated: Winners being calculated, it may take a minute. recalculate: Recalculate Winner Investments + shared: + resource_name: "the budget" budget_groups: name: "Name" headings_name: "Headings" @@ -156,6 +160,7 @@ en: 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." amount: @@ -176,11 +181,18 @@ en: create: "Create new heading" edit: "Edit heading" submit: "Save heading" + group_switcher: + currently_showing: "Showing headings from the %{group} group." + different_group: "Manage headings from a different group" + 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: edit: + title: "Edit phase" description_help_text: This text will appear in the header when the phase is active duration: "Phase's duration" duration_description: "The period of time this phase will be active." @@ -189,6 +201,7 @@ en: 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: heading_filter_all: All headings @@ -284,6 +297,24 @@ en: tags_placeholder: "Write the tags you want separated by commas (,)" undefined: Undefined search_unfeasible: Search unfeasible + budgets_wizard: + creation_timeline: + budget: Budget + groups: Groups + headings: Headings + phases: Phases + budgets: + continue: "Continue to groups" + groups: + back: "Go back to edit budget" + continue: "Continue to headings" + headings: + continue: "Continue to phases" + phases: + back: "Go back to headings" + continue: "Finish" + update_all: + notice: "Phases configured successfully" milestones: index: table_id: "ID" diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index cef90c618..5811a7afe 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -220,7 +220,7 @@ es: latitude: "Latitud (opcional)" longitude: "Longitud (opcional)" name: "Nombre de la partida" - price: "Cantidad" + price: "Cantidad de dinero" population: "Población (opcional)" max_ballot_lines: "Votos permitidos" budget/heading/translation: diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 3e6db5a74..e45e5bb13 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -13,6 +13,7 @@ es: unmark_featured: Quitar destacado edit: Editar configure: Configurar + confirm_delete: "¿Estás seguro? Esta acción borrará %{resource_name} '%{name}' y no se puede deshacer." delete: Borrar officing_booth: title: "Estás ahora mismo en la mesa ubicada en %{booth}. Si esto no es correcto no sigas adelante y llama al teléfono de incidencias. Gracias." @@ -106,6 +107,7 @@ es: enabled: Habilitada actions: Acciones edit_phase: Editar fase + enable_phase: "Habilitar fase de %{phase}" active: Activa blank_dates: Sin fechas administrators: @@ -133,6 +135,8 @@ es: calculate: Calcular proyectos ganadores calculated: Calculando ganadores, puede tardar un minuto. recalculate: Recalcular proyectos ganadores + shared: + resource_name: "el presupuesto" budget_groups: name: "Nombre" headings_name: "Partidas" @@ -156,6 +160,7 @@ es: 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." amount: @@ -176,9 +181,15 @@ es: create: "Crear nueva partida" edit: "Editar partida" submit: "Guardar partida" + group_switcher: + currently_showing: "Mostrando las partidas del grupo: %{group}" + different_group: "Ir a partidas de otro grupo" + 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: edit: description_help_text: Este texto aparecerá en la cabecera cuando la fase esté activa @@ -187,8 +198,10 @@ es: enabled_help_text: Esta fase será pública en el calendario de fases del presupuesto y estará activa para otros propósitos name_help_text: "Este es el título de la fase que los usuarios leerán en el encabezado cuando la fase esté activa." 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: heading_filter_all: Todas las partidas @@ -284,6 +297,24 @@ es: tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)" undefined: Sin definir search_unfeasible: Buscar inviables + budgets_wizard: + creation_timeline: + budget: Presupuesto + groups: Grupos + headings: Partidas + phases: Fases + budgets: + continue: "Continuar a grupos" + groups: + back: "Volver a editar presupuesto" + continue: "Continuar a partidas" + headings: + continue: "Continuar a fases" + phases: + back: "Volver a partidas" + continue: "Finalizar" + update_all: + notice: "Fases configuradas con éxito" milestones: index: table_id: "ID" diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 21712e04a..8749178b1 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -51,7 +51,7 @@ namespace :admin do end end - resources :budgets do + resources :budgets, except: [:create, :new] do member do patch :publish put :calculate_winners @@ -72,6 +72,18 @@ namespace :admin do resources :budget_phases, only: [:edit, :update] end + namespace :budgets_wizard do + resources :budgets, only: [:create, :new, :edit, :update] do + resources :groups, only: [:index, :create, :edit, :update, :destroy] do + resources :headings, only: [:index, :create, :edit, :update, :destroy] + end + + resources :phases, as: "budget_phases", only: [:index, :edit, :update] do + collection { patch :update_all } + end + end + end + resources :milestone_statuses, only: [:index, :new, :create, :update, :edit, :destroy] resources :signature_sheets, only: [:index, :new, :create, :show] diff --git a/spec/components/admin/budgets/form_component_spec.rb b/spec/components/admin/budgets/form_component_spec.rb new file mode 100644 index 000000000..1abacdeee --- /dev/null +++ b/spec/components/admin/budgets/form_component_spec.rb @@ -0,0 +1,16 @@ +require "rails_helper" + +describe Admin::Budgets::FormComponent, type: :component do + describe "#voting_styles_select_options" do + it "provides vote kinds" do + types = [ + ["Knapsack", "knapsack"], + ["Approval", "approval"] + ] + + component = Admin::Budgets::FormComponent.new(double) + + expect(component.voting_styles_select_options).to eq(types) + end + end +end diff --git a/spec/components/admin/budgets/table_actions_component_spec.rb b/spec/components/admin/budgets/table_actions_component_spec.rb index 97f20083f..119fdffd4 100644 --- a/spec/components/admin/budgets/table_actions_component_spec.rb +++ b/spec/components/admin/budgets/table_actions_component_spec.rb @@ -8,15 +8,16 @@ describe Admin::Budgets::TableActionsComponent, type: :component do allow(ViewComponent::Base).to receive(:test_controller).and_return("Admin::BaseController") end - it "renders links to edit budget, manage investments and edit groups and manage ballots" do + it "renders links to edit and delete budget, manage investments and edit groups and manage ballots" do render_inline component - expect(page).to have_css "a", count: 5 + expect(page).to have_css "a", count: 6 expect(page).to have_link "Manage projects", href: /investments/ expect(page).to have_link "Edit headings groups", href: /groups/ expect(page).to have_link "Edit budget", href: /edit/ expect(page).to have_link "Admin ballots" expect(page).to have_link "Preview budget", href: /budgets/ + expect(page).to have_link "Delete", href: /budgets/ end it "renders link to create new poll for budgets without polls" do diff --git a/spec/components/admin/budgets_wizard/headings/group_switcher_component_spec.rb b/spec/components/admin/budgets_wizard/headings/group_switcher_component_spec.rb new file mode 100644 index 000000000..140db350d --- /dev/null +++ b/spec/components/admin/budgets_wizard/headings/group_switcher_component_spec.rb @@ -0,0 +1,41 @@ +require "rails_helper" + +describe Admin::BudgetsWizard::Headings::GroupSwitcherComponent, type: :component do + it "is not rendered for budgets with one group" do + group = create(:budget_group, budget: create(:budget)) + + render_inline Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group) + + expect(page.text).to be_empty + expect(page).not_to have_css ".budget-group-switcher" + end + + it "renders a link to switch group for budgets with two groups" do + budget = create(:budget) + group = create(:budget_group, budget: budget, name: "Parks") + create(:budget_group, budget: budget, name: "Recreation") + + render_inline Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group) + + expect(page).to have_content "Showing headings from the Parks group" + expect(page).to have_link "Manage headings from the Recreation group." + expect(page).not_to have_css "ul" + end + + it "renders a menu to switch group for budgets with more than two groups" do + budget = create(:budget) + group = create(:budget_group, budget: budget, name: "Parks") + create(:budget_group, budget: budget, name: "Recreation") + create(:budget_group, budget: budget, name: "Entertainment") + + render_inline Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group) + + expect(page).to have_content "Showing headings from the Parks group" + expect(page).to have_button "Manage headings from a different group" + + within "button + ul" do + expect(page).to have_link "Recreation" + expect(page).to have_link "Entertainment" + end + end +end diff --git a/spec/helpers/budgets_helper_spec.rb b/spec/helpers/budgets_helper_spec.rb deleted file mode 100644 index 832f510d8..000000000 --- a/spec/helpers/budgets_helper_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require "rails_helper" - -describe BudgetsHelper do - describe "#budget_voting_styles_select_options" do - it "provides vote kinds" do - types = [ - ["Knapsack", "knapsack"], - ["Approval", "approval"] - ] - - expect(budget_voting_styles_select_options).to eq(types) - end - end -end diff --git a/spec/system/admin/budget_groups_spec.rb b/spec/system/admin/budget_groups_spec.rb index c74b82177..090375235 100644 --- a/spec/system/admin/budget_groups_spec.rb +++ b/spec/system/admin/budget_groups_spec.rb @@ -129,6 +129,25 @@ describe "Admin budget groups", :admin do expect(page).to have_field "Maximum number of headings in which a user can select projects", with: "2" end + describe "Select for maxium number of headings to select projects" do + scenario "is present if there are headings in the group" do + group = create(:budget_group, budget: budget) + create(:budget_heading, group: group) + + visit edit_admin_budget_group_path(budget, group) + + expect(page).to have_field "Maximum number of headings in which a user can select projects" + end + + scenario "is not present if there are no headings in the group" do + group = create(:budget_group, budget: budget) + + visit edit_admin_budget_group_path(budget, group) + + expect(page).not_to have_field "Maximum number of headings in which a user can select projects" + end + end + scenario "Changing name for current locale will update the slug if budget is in draft phase" do group = create(:budget_group, budget: budget, name: "Old English Name") diff --git a/spec/system/admin/budget_headings_spec.rb b/spec/system/admin/budget_headings_spec.rb index 710770ed5..ccf258706 100644 --- a/spec/system/admin/budget_headings_spec.rb +++ b/spec/system/admin/budget_headings_spec.rb @@ -88,7 +88,7 @@ describe "Admin budget headings", :admin do click_link "Create new heading" fill_in "Heading name", with: "All City" - fill_in "Amount", with: "1000" + fill_in "Money amount", with: "1000" fill_in "Population (optional)", with: "10000" check "Allow content block" @@ -115,7 +115,7 @@ describe "Admin budget headings", :admin do click_button "Create new heading" expect(page).not_to have_content "Heading created successfully!" - expect(page).to have_css(".is-invalid-label", text: "Amount") + expect(page).to have_css(".is-invalid-label", text: "Money amount") expect(page).to have_content "can't be blank" end @@ -134,7 +134,7 @@ describe "Admin budget headings", :admin do expect(page).to have_field "Votes allowed", with: 1 fill_in "Heading name", with: "All City" - fill_in "Amount", with: "1000" + fill_in "Money amount", with: "1000" fill_in "Votes allowed", with: 14 click_button "Create new heading" @@ -152,7 +152,7 @@ describe "Admin budget headings", :admin do within("#budget_heading_#{heading.id}") { click_link "Edit" } expect(page).to have_field "Heading name", with: heading.name - expect(page).to have_field "Amount", with: heading.price + expect(page).to have_field "Money amount", with: heading.price expect(page).to have_field "Population (optional)", with: heading.population expect(page).to have_field "Longitude (optional)", with: heading.longitude expect(page).to have_field "Latitude (optional)", with: heading.latitude @@ -204,14 +204,14 @@ describe "Admin budget headings", :admin do visit edit_admin_budget_group_heading_path(budget, group, heading) expect(page).to have_field "Heading name", with: "All City" - expect(page).to have_field "Amount", with: 1000 + expect(page).to have_field "Money amount", with: 1000 expect(page).to have_field "Population (optional)", with: 10000 expect(page).to have_field "Longitude (optional)", with: 20.50 expect(page).to have_field "Latitude (optional)", with: -10.50 expect(find_field("Allow content block")).to be_checked fill_in "Heading name", with: "Districts" - fill_in "Amount", with: "2000" + fill_in "Money amount", with: "2000" fill_in "Population (optional)", with: "20000" fill_in "Longitude (optional)", with: "-40.47" fill_in "Latitude (optional)", with: "25.25" @@ -222,7 +222,7 @@ describe "Admin budget headings", :admin do visit edit_admin_budget_group_heading_path(budget, group, heading) expect(page).to have_field "Heading name", with: "Districts" - expect(page).to have_field "Amount", with: 2000 + expect(page).to have_field "Money amount", with: 2000 expect(page).to have_field "Population (optional)", with: 20000 expect(page).to have_field "Longitude (optional)", with: -40.47 expect(page).to have_field "Latitude (optional)", with: 25.25 diff --git a/spec/system/admin/budget_phases_spec.rb b/spec/system/admin/budget_phases_spec.rb index ce5c22c1c..36dff7ade 100644 --- a/spec/system/admin/budget_phases_spec.rb +++ b/spec/system/admin/budget_phases_spec.rb @@ -32,7 +32,7 @@ describe "Admin budget phases" do within("tr", text: "Accepting projects") { click_link "Edit phase" } end - expect(page).to have_css "h2", exact_text: "Edit Participatory budget - Accepting projects" + expect(page).to have_css "h2", exact_text: "Edit phase - Accepting projects" fill_in "Name", with: "My phase custom name" click_button "Save changes" diff --git a/spec/system/admin/budgets_spec.rb b/spec/system/admin/budgets_spec.rb index e6a5ab6a4..1f5a755d8 100644 --- a/spec/system/admin/budgets_spec.rb +++ b/spec/system/admin/budgets_spec.rb @@ -82,87 +82,21 @@ describe "Admin budgets", :admin do end end end - end - context "New" do - scenario "Create budget - Knapsack voting (default)" do - visit admin_budgets_path - click_link "Create new budget" - - fill_in "Name", with: "M30 - Summer campaign" - select "Accepting projects", from: "budget[phase]" - - click_button "Create Budget" - - expect(page).to have_content "New participatory budget created successfully!" - expect(page).to have_field "Name", with: "M30 - Summer campaign" - expect(page).to have_select "Final voting style", selected: "Knapsack" - end - - scenario "Create budget - Approval voting" do - admin = Administrator.first + scenario "Delete budget from index" do + create(:budget, name: "To be deleted") visit admin_budgets_path - click_link "Create new budget" - fill_in "Name", with: "M30 - Summer campaign" - select "Accepting projects", from: "budget[phase]" - select "Approval", from: "Final voting style" - click_button "Create Budget" + within "tr", text: "To be deleted" do + message = "Are you sure? This action will delete the budget 'To be deleted' and can't be undone." - expect(page).to have_content "New participatory budget created successfully!" - expect(page).to have_field "Name", with: "M30 - Summer campaign" - expect(page).to have_select "Final voting style", selected: "Approval" + accept_confirm(message) { click_link "Delete" } + end - click_link "Select administrators" - - expect(page).to have_field admin.name - end - - scenario "Name is mandatory" do - visit new_admin_budget_path - click_button "Create Budget" - - expect(page).not_to have_content "New participatory budget created successfully!" - expect(page).to have_css(".is-invalid-label", text: "Name") - end - - scenario "Name should be unique" do - create(:budget, name: "Existing Name") - - visit new_admin_budget_path - fill_in "Name", with: "Existing Name" - click_button "Create Budget" - - expect(page).not_to have_content "New participatory budget created successfully!" - expect(page).to have_css(".is-invalid-label", text: "Name") - expect(page).to have_css("small.form-error", text: "has already been taken") - end - - scenario "Do not show results and stats settings on new budget" do - visit new_admin_budget_path - - expect(page).not_to have_content "Show results and stats" - expect(page).not_to have_field "Show results" - expect(page).not_to have_field "Show stats" - expect(page).not_to have_field "Show advanced stats" - end - end - - context "Create" do - scenario "A new budget is always created in draft mode" do - visit admin_budgets_path - click_link "Create new budget" - - fill_in "Name", with: "M30 - Summer campaign" - select "Accepting projects", from: "budget[phase]" - - click_button "Create Budget" - - expect(page).to have_content "New participatory budget created successfully!" - expect(page).to have_content "This participatory budget is in draft mode" - expect(page).to have_link "Preview budget" - expect(page).to have_link "Publish budget" + expect(page).to have_content("Budget deleted successfully") + expect(page).to have_content("There are no budgets.") + expect(page).not_to have_content "To be deleted" end end @@ -197,8 +131,7 @@ describe "Admin budgets", :admin do let(:heading) { create(:budget_heading, budget: budget) } scenario "Destroy a budget without investments" do - visit admin_budgets_path - click_link "Edit budget" + visit edit_admin_budget_path(budget) click_link "Delete budget" expect(page).to have_content("Budget deleted successfully") @@ -209,8 +142,7 @@ describe "Admin budgets", :admin do budget.administrators << Administrator.first budget.valuators << create(:valuator) - visit admin_budgets_path - click_link "Edit budget" + visit edit_admin_budget_path(budget) click_link "Delete budget" expect(page).to have_content "Budget deleted successfully" @@ -220,8 +152,7 @@ describe "Admin budgets", :admin do scenario "Try to destroy a budget with investments" do create(:budget_investment, heading: heading) - visit admin_budgets_path - click_link "Edit budget" + visit edit_admin_budget_path(budget) click_link "Delete budget" expect(page).to have_content("You cannot delete a budget that has associated investments") diff --git a/spec/system/admin/budgets_wizard/budgets_spec.rb b/spec/system/admin/budgets_wizard/budgets_spec.rb new file mode 100644 index 000000000..ac4722bad --- /dev/null +++ b/spec/system/admin/budgets_wizard/budgets_spec.rb @@ -0,0 +1,132 @@ +require "rails_helper" + +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" + + fill_in "Name", with: "M30 - Summer campaign" + click_button "Continue to groups" + + expect(page).to have_content "New participatory budget created successfully!" + + click_link "Go back to edit budget" + + expect(page).to have_field "Name", with: "M30 - Summer campaign" + expect(page).to have_select "Final voting style", selected: "Knapsack" + end + + scenario "Create budget - Approval voting" do + admin = Administrator.first + + visit admin_budgets_path + click_link "Create new budget" + + fill_in "Name", with: "M30 - Summer campaign" + select "Approval", from: "Final voting style" + click_button "Continue to groups" + + expect(page).to have_content "New participatory budget created successfully!" + + click_link "Go back to edit budget" + + expect(page).to have_field "Name", with: "M30 - Summer campaign" + expect(page).to have_select "Final voting style", selected: "Approval" + + click_link "Select administrators" + + expect(page).to have_field admin.name + end + + scenario "Submit the form with errors" do + visit new_admin_budgets_wizard_budget_path + click_button "Continue to groups" + + expect(page).not_to have_content "New participatory budget created successfully!" + expect(page).to have_css ".is-invalid-label", text: "Name" + expect(page).to have_css ".creation-timeline" + end + + scenario "Name should be unique" do + create(:budget, name: "Existing Name") + + visit new_admin_budgets_wizard_budget_path + fill_in "Name", with: "Existing Name" + click_button "Continue to groups" + + expect(page).not_to have_content "New participatory budget created successfully!" + expect(page).to have_css(".is-invalid-label", text: "Name") + expect(page).to have_css("small.form-error", text: "has already been taken") + end + + scenario "Do not show results and stats settings on new budget" do + visit new_admin_budgets_wizard_budget_path + + expect(page).not_to have_content "Show results and stats" + expect(page).not_to have_field "Show results" + expect(page).not_to have_field "Show stats" + expect(page).not_to have_field "Show advanced stats" + end + end + + describe "Create" do + scenario "A new budget is always created in draft mode" do + visit admin_budgets_path + click_link "Create new budget" + + fill_in "Name", with: "M30 - Summer campaign" + + click_button "Continue to groups" + + expect(page).to have_content "New participatory budget created successfully!" + + within("#side_menu") { click_link "Participatory budgets" } + within("tr", text: "M30 - Summer campaign") { click_link "Edit budget" } + + expect(page).to have_content "This participatory budget is in draft mode" + expect(page).to have_link "Preview budget" + expect(page).to have_link "Publish budget" + end + end + + describe "Edit" do + scenario "update budget" do + budget = create(:budget, name: "Budget wiht typo") + + visit admin_budgets_wizard_budget_groups_path(budget) + + click_link "Go back to edit budget" + + expect(page).to have_content "Edit Participatory budget" + expect(page).to have_css ".creation-timeline" + expect(page).to have_field "Name", with: "Budget wiht typo" + + fill_in "Name", with: "Budget without typos" + click_button "Continue to groups" + + expect(page).to have_content "Participatory budget updated successfully" + expect(page).to have_content "Budget without typos" + expect(page).to have_css ".creation-timeline" + expect(page).to have_content "There are no groups" + end + + scenario "submit the form with errors and then without errors" do + budget = create(:budget, name: "Budget wiht typo") + + visit edit_admin_budgets_wizard_budget_path(budget) + fill_in "Name", with: "" + click_button "Continue to groups" + + expect(page).to have_css "#error_explanation" + + fill_in "Name", with: "Budget without typos" + click_button "Continue to groups" + + expect(page).to have_content "Participatory budget updated successfully" + expect(page).to have_content "Budget without typos" + expect(page).to have_css ".creation-timeline" + expect(page).to have_content "There are no groups" + end + end +end diff --git a/spec/system/admin/budgets_wizard/groups_spec.rb b/spec/system/admin/budgets_wizard/groups_spec.rb new file mode 100644 index 000000000..be2e84d05 --- /dev/null +++ b/spec/system/admin/budgets_wizard/groups_spec.rb @@ -0,0 +1,127 @@ +require "rails_helper" + +describe "Budgets wizard, groups step", :admin do + let(:budget) { create(:budget, :drafting) } + + describe "New" do + scenario "create group" do + visit admin_budgets_wizard_budget_groups_path(budget) + + within "#side_menu" do + expect(page).to have_css ".is-active", exact_text: "Participatory budgets" + end + + expect(page).to have_content "Continue to headings" + expect(page).not_to have_link "Continue to headings" + + 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!" + expect(page).to have_content "All City" + expect(page).to have_button "Add new group" + expect(page).to have_link "Continue to headings" + end + + scenario "cancel creating a group" do + visit admin_budgets_wizard_budget_groups_path(budget) + + expect(page).not_to have_field "Group name" + expect(page).not_to have_button "Cancel" + expect(page).to have_content "Continue to headings" + + click_button "Add new group" + + expect(page).to have_field "Group name" + expect(page).not_to have_button "Add new group" + expect(page).not_to have_content "Continue to headings" + + click_button "Cancel" + + expect(page).to have_button "Add new group" + expect(page).not_to have_field "Group name" + expect(page).not_to have_button "Cancel" + expect(page).to have_content "Continue to headings" + end + + scenario "submit the form with errors" do + visit admin_budgets_wizard_budget_groups_path(budget) + click_button "Add new group" + + click_button "Create new group" + + expect(page).not_to have_content "Group created successfully!" + expect(page).to have_css ".is-invalid-label", text: "Group name" + expect(page).to have_css ".creation-timeline" + expect(page).to have_content "can't be blank" + expect(page).to have_button "Create new group" + expect(page).to have_button "Cancel" + expect(page).not_to have_button "Add new group" + expect(page).not_to have_content "Continue to headings" + end + end + + describe "Edit" do + scenario "update group" do + create(:budget_group, budget: budget, name: "Group wiht a typo") + + visit admin_budgets_wizard_budget_groups_path(budget) + + expect(page).to have_css ".creation-timeline" + + within("tr", text: "Group wiht a typo") { click_link "Edit" } + fill_in "Group name", with: "Group without typos" + click_button "Save group" + + expect(page).to have_content "Group updated successfully" + expect(page).to have_css ".creation-timeline" + expect(page).to have_css "td", exact_text: "Group without typos" + end + + scenario "submit the form with errors and then without errors" do + group = create(:budget_group, budget: budget, name: "Group wiht a typo") + + visit edit_admin_budgets_wizard_budget_group_path(budget, group) + fill_in "Group name", with: "" + click_button "Save group" + + expect(page).to have_css "#error_explanation" + + fill_in "Group name", with: "Group without typos" + click_button "Save group" + + expect(page).to have_content "Group updated successfully" + expect(page).to have_css ".creation-timeline" + expect(page).to have_css "td", exact_text: "Group without typos" + end + end + + describe "Destroy" do + scenario "delete a group without headings" do + create(:budget_group, budget: budget, name: "Delete me!") + + visit admin_budgets_wizard_budget_groups_path(budget) + within("tr", text: "Delete me!") { accept_confirm { click_link "Delete" } } + + expect(page).to have_content "Group deleted successfully" + expect(page).not_to have_content "Delete me!" + expect(page).to have_css ".creation-timeline" + end + + scenario "try to delete a group with headings" do + group = create(:budget_group, budget: budget, name: "Don't delete me!") + create(:budget_heading, group: group) + + visit admin_budgets_wizard_budget_groups_path(budget) + + within("tr", text: "Don't delete me!") { accept_confirm { click_link "Delete" } } + + expect(page).to have_content "You cannot delete a Group that has associated headings" + expect(page).to have_content "Don't delete me!" + expect(page).to have_css ".creation-timeline" + end + end +end diff --git a/spec/system/admin/budgets_wizard/headings_spec.rb b/spec/system/admin/budgets_wizard/headings_spec.rb new file mode 100644 index 000000000..d3823347e --- /dev/null +++ b/spec/system/admin/budgets_wizard/headings_spec.rb @@ -0,0 +1,148 @@ +require "rails_helper" + +describe "Budgets wizard, headings step", :admin do + let(:budget) { create(:budget, :drafting) } + let(:group) { create(:budget_group, budget: budget, name: "Default group") } + + describe "Index" do + scenario "back to a previous step" do + visit admin_budgets_wizard_budget_group_headings_path(budget, group) + + within "#side_menu" do + expect(page).to have_css ".is-active", exact_text: "Participatory budgets" + end + + click_link "Go back to groups" + + expect(page).to have_css "tr", text: "Default group" + expect(page).to have_css ".creation-timeline" + end + + scenario "change to another group" do + economy = create(:budget_group, budget: budget, name: "Economy") + health = create(:budget_group, budget: budget, name: "Health") + create(:budget_group, budget: budget, name: "Technology") + + create(:budget_heading, group: economy, name: "Banking") + create(:budget_heading, group: health, name: "Hospitals") + + visit admin_budgets_wizard_budget_group_headings_path(budget, economy) + + within(".heading") do + expect(page).to have_content "Banking" + expect(page).not_to have_content "Hospitals" + end + + expect(page).not_to have_link "Health" + + click_button "Manage headings from a different group" + click_link "Health" + + within(".heading") do + expect(page).to have_content "Hospitals" + expect(page).not_to have_content "Banking" + end + expect(page).to have_css ".creation-timeline" + end + end + + describe "New" do + scenario "cancel creating a heading" do + visit admin_budgets_wizard_budget_group_headings_path(budget, group) + + expect(page).not_to have_field "Heading name" + expect(page).not_to have_button "Cancel" + expect(page).to have_content "Continue to phases" + + click_button "Add new heading" + + expect(page).to have_field "Heading name" + expect(page).not_to have_button "Add new heading" + expect(page).not_to have_content "Continue to phases" + + click_button "Cancel" + + expect(page).to have_button "Add new heading" + expect(page).not_to have_field "Heading name" + expect(page).not_to have_button "Cancel" + expect(page).to have_content "Continue to phases" + end + + scenario "submit the form with errors" do + visit admin_budgets_wizard_budget_group_headings_path(budget, group) + click_button "Add new heading" + + click_button "Create new heading" + + expect(page).not_to have_content "Heading created successfully!" + expect(page).to have_css(".is-invalid-label", text: "Heading name") + expect(page).to have_content "can't be blank" + expect(page).to have_button "Create new heading" + expect(page).to have_button "Cancel" + expect(page).not_to have_button "Add new heading" + expect(page).not_to have_content "Continue to phases" + end + end + + describe "Edit" do + scenario "update heading" do + create(:budget_heading, group: group, name: "Heading wiht a typo") + + visit admin_budgets_wizard_budget_group_headings_path(budget, group) + + expect(page).to have_css ".creation-timeline" + + within("tr", text: "Heading wiht a typo") { click_link "Edit" } + fill_in "Heading name", with: "Heading without typos" + click_button "Save heading" + + expect(page).to have_content "Heading updated successfully" + expect(page).to have_css ".creation-timeline" + expect(page).to have_css "td", exact_text: "Heading without typos" + end + + scenario "submit the form with errors and then without errors" do + heading = create(:budget_heading, group: group, name: "Heading wiht a typo") + + visit edit_admin_budgets_wizard_budget_group_heading_path(budget, group, heading) + fill_in "Heading name", with: "" + click_button "Save heading" + + expect(page).to have_css "#error_explanation" + expect(page).to have_css ".creation-timeline" + + fill_in "Heading name", with: "Heading without typos" + click_button "Save heading" + + expect(page).to have_content "Heading updated successfully" + expect(page).to have_css ".creation-timeline" + expect(page).to have_css "td", exact_text: "Heading without typos" + end + end + + describe "Destroy" do + scenario "delete a heading without investments" do + create(:budget_heading, group: group, name: "Delete me!") + + visit admin_budgets_wizard_budget_group_headings_path(budget, group) + within("tr", text: "Delete me!") { accept_confirm { click_link "Delete" } } + + expect(page).to have_content "Heading deleted successfully" + expect(page).not_to have_content "Delete me!" + expect(page).to have_css ".creation-timeline" + end + + scenario "try to delete a heading with investments" do + heading = create(:budget_heading, group: group, name: "Don't delete me!") + create(:budget_investment, heading: heading) + + visit admin_budgets_wizard_budget_group_headings_path(budget, group) + + within("tr", text: "Don't delete me!") { accept_confirm { click_link "Delete" } } + + expect(page).to have_content "You cannot delete a Heading that has associated investments" + expect(page).to have_content "Don't delete me!" + expect(page).to have_css ".creation-timeline" + end + end +end diff --git a/spec/system/admin/budgets_wizard/phases_spec.rb b/spec/system/admin/budgets_wizard/phases_spec.rb new file mode 100644 index 000000000..eb2e0b50b --- /dev/null +++ b/spec/system/admin/budgets_wizard/phases_spec.rb @@ -0,0 +1,86 @@ +require "rails_helper" + +describe "Budgets wizard, phases step", :admin do + let(:budget) { create(:budget, :drafting) } + let!(:heading) { create(:budget_heading, budget: budget) } + + describe "Index" do + scenario "back to a previous step" do + heading.update!(name: "Main Park") + + visit admin_budgets_wizard_budget_budget_phases_path(budget) + + within "#side_menu" do + expect(page).to have_css ".is-active", exact_text: "Participatory budgets" + end + + click_link "Go back to headings" + + expect(page).to have_css "tr", text: "Main Park" + expect(page).to have_css ".creation-timeline" + end + + scenario "Enable and disable phases" do + visit admin_budgets_wizard_budget_budget_phases_path(budget) + + uncheck "Enable Information phase" + uncheck "Enable Reviewing voting phase" + + click_button "Finish" + + expect(page).to have_content "Phases configured successfully" + + visit edit_admin_budget_path(budget) + + within "tr", text: "Information" do + expect(page).to have_css ".budget-phase-disabled", visible: :all + end + + within "tr", text: "Reviewing voting" do + expect(page).to have_css ".budget-phase-disabled", visible: :all + end + + within "tr", text: "Accepting projects" do + expect(page).to have_css ".budget-phase-enabled", visible: :all + end + + within "tr", text: "Voting projects" do + expect(page).to have_css ".budget-phase-enabled", visible: :all + end + end + end + + describe "Edit" do + scenario "update phase" do + visit admin_budgets_wizard_budget_budget_phases_path(budget) + + expect(page).to have_css ".creation-timeline" + + 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" } + end + + scenario "submit the form with errors and then without errors" do + phase = budget.phases.accepting + + visit edit_admin_budgets_wizard_budget_budget_phase_path(budget, phase) + fill_in "Name", with: "" + click_button "Save changes" + + expect(page).to have_css "#error_explanation" + expect(page).to have_css ".creation-timeline" + + fill_in "Name", with: "Welcoming 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 "Welcoming projects" } + end + end +end diff --git a/spec/system/budgets/ballots_spec.rb b/spec/system/budgets/ballots_spec.rb index 535192c8d..56678662a 100644 --- a/spec/system/budgets/ballots_spec.rb +++ b/spec/system/budgets/ballots_spec.rb @@ -637,7 +637,7 @@ describe "Ballots" do in_browser(:admin) do login_as admin_user visit edit_admin_budget_group_heading_path(budget, states, new_york) - fill_in "Amount", with: 10 + fill_in "Money amount", with: 10 click_button "Save heading" expect(page).to have_content "Heading updated successfully"