diff --git a/app/assets/stylesheets/_consul_settings.scss b/app/assets/stylesheets/_consul_settings.scss index 736433875..2124fa115 100644 --- a/app/assets/stylesheets/_consul_settings.scss +++ b/app/assets/stylesheets/_consul_settings.scss @@ -74,6 +74,8 @@ $input-height: $line-height * 2; $icon-width: $line-height * 2; +$off-screen-left: -1000rem; + $sdg-icon-min-width: 40px; $sdg-colors: ( diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index c620a3751..e96a34cfd 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -212,6 +212,10 @@ $table-header: #ecf1f6; table { + caption { + @include element-invisible; + } + thead { &, @@ -1160,38 +1164,6 @@ table { } } -.budget-phase-enabled { - font-weight: bold; - padding-left: rem-calc(20); - position: relative; - text-decoration: none; - - &.enabled::before, - &.disabled::before { - font-family: "icons"; - left: 0; - position: absolute; - } - - &.enabled { - color: $check; - - &::before { - color: $check; - content: "\6c"; - } - } - - &.disabled { - color: #000; - - &::before { - color: #000; - content: "\76"; - } - } -} - .columns-selector { [class^="icon-"] { diff --git a/app/assets/stylesheets/admin/budget_phases/form.scss b/app/assets/stylesheets/admin/budget_phases/form.scss new file mode 100644 index 000000000..8e9be056f --- /dev/null +++ b/app/assets/stylesheets/admin/budget_phases/form.scss @@ -0,0 +1,25 @@ +.admin .budget-phases-form { + + fieldset { + margin-top: $line-height; + + > * { + @include grid-column; + } + + > .date-field { + + @include breakpoint(medium) { + width: 50%; + } + + @include breakpoint(large) { + width: 25%; + } + + &:last-child { + float: left; + } + } + } +} diff --git a/app/assets/stylesheets/admin/budget_phases/phases.scss b/app/assets/stylesheets/admin/budget_phases/phases.scss new file mode 100644 index 000000000..ca0269f23 --- /dev/null +++ b/app/assets/stylesheets/admin/budget_phases/phases.scss @@ -0,0 +1,27 @@ +.budget-phases-table { + + .budget-phase-enabled { + @include has-fa-icon(check, solid); + color: $check; + } + + .budget-phase-disabled { + @include has-fa-icon(times, solid); + color: $black; + } + + .budget-phase-enabled, + .budget-phase-disabled { + display: block; + font-size: 1px; + left: $off-screen-left; + line-height: 1; + position: relative; + + &::before { + font-size: $base-font-size; + left: -1 * $off-screen-left; + position: relative; + } + } +} diff --git a/app/assets/stylesheets/admin/budgets/form.scss b/app/assets/stylesheets/admin/budgets/form.scss index a5ae94ea4..0fef7a2e6 100644 --- a/app/assets/stylesheets/admin/budgets/form.scss +++ b/app/assets/stylesheets/admin/budgets/form.scss @@ -5,6 +5,10 @@ clear: both; margin-top: $line-height * 1.5; + &:first-of-type { + margin-top: 0; + } + legend { color: $admin-text; font-size: $small-font-size; diff --git a/app/assets/stylesheets/admin/budgets/index.scss b/app/assets/stylesheets/admin/budgets/index.scss index 183439343..1ce224b76 100644 --- a/app/assets/stylesheets/admin/budgets/index.scss +++ b/app/assets/stylesheets/admin/budgets/index.scss @@ -21,4 +21,10 @@ text-transform: uppercase; } } + + td time:last-of-type::after, + td small::before { + content: ""; + display: block; + } } diff --git a/app/assets/stylesheets/admin/table_actions.scss b/app/assets/stylesheets/admin/table_actions.scss index 51f1a5ee5..b217105ba 100644 --- a/app/assets/stylesheets/admin/table_actions.scss +++ b/app/assets/stylesheets/admin/table_actions.scss @@ -10,7 +10,7 @@ > :first-child { @include bottom-tooltip; - left: -10000px; + left: $off-screen-left; opacity: 0; transform: translateX(-50%); transition: opacity 0.3s, left 0s 0.3s; @@ -28,7 +28,7 @@ } &:not(:focus) > :first-child:hover { - left: -10000px; + left: $off-screen-left; } &::before { diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 149facb82..6ce3e2ede 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -32,6 +32,7 @@ @import "sticky_overrides"; @import "tags"; @import "admin/**/*"; +@import "budgets/*"; @import "sdg/**/*"; @import "sdg_management/*"; @import "sdg_management/**/*"; diff --git a/app/assets/stylesheets/budgets/phases.scss b/app/assets/stylesheets/budgets/phases.scss new file mode 100644 index 000000000..6796c9ba7 --- /dev/null +++ b/app/assets/stylesheets/budgets/phases.scss @@ -0,0 +1,187 @@ +.budget-phases { + background: $light; + border-top: 2px solid $border; + padding-bottom: $line-height * 2; + + > * { + @include grid-column-gutter; + @include grid-row; + } + + h2 { + font-size: $base-font-size; + text-align: center; + } + + .phases-list { + counter-reset: phases; + display: flex; + flex-wrap: wrap; + + .phase-name::before { + content: counter(phases); + counter-increment: phases; + display: block; + font-size: rem-calc(36); + font-weight: bold; + margin-bottom: $line-height / 2; + } + } + + .phase-title { + @include tabs-title; + margin: $line-height / 6; + margin-left: 0; + position: relative; + text-align: center; + + @supports(clip-path: polygon(0 0, 50% 50%, 0% 100%)) { + $triangle-width: 1em; + $item-margin: rem-calc(3); + + margin-left: calc(-1 * (#{$triangle-width} - #{$item-margin})); + margin-right: 0; + transform: translateX(calc(#{$triangle-width} - #{$item-margin})); + + &, + a { + clip-path: polygon(calc(100% - #{$triangle-width}) 0, 100% 50%, calc(100% - #{$triangle-width}) 100%, 0 100%, #{$triangle-width} 50%, 0 0); + } + + &:first-child { + &, + a { + clip-path: polygon(calc(100% - #{$triangle-width}) 0, 100% 50%, calc(100% - #{$triangle-width}) 100%, 0 100%, 0 0); + } + + a { + border-bottom-left-radius: rem-calc(6); + border-top-left-radius: rem-calc(6); + } + } + + &:last-child { + &, + a { + clip-path: polygon(100% 0, 100% 100%, 0 100%, #{$triangle-width} 50%, 0 0); + } + + a { + border-bottom-right-radius: rem-calc(6); + border-top-right-radius: rem-calc(6); + } + } + } + + a { + background: $white; + color: $black; + height: 100%; + padding: $line-height $line-height * 1.5; + + &:hover { + background: #e6e6e6; + color: $black; + } + + &:focus { + outline: none; + } + + &[aria-selected="true"], + &.is-active { + background: $dark; + color: $white; + font-weight: normal; + } + + &[aria-selected="true"]::after, + &.is-active::after { + content: none; + } + } + + &.current-phase-tab a { + background: $brand; + color: $white; + padding-top: $line-height / 2; + + &:hover { + background: $dark; + } + } + } + + .current-phase-timeline { + display: block; + font-size: $small-font-size; + font-weight: bold; + margin-bottom: $line-height / 2; + text-transform: uppercase; + } + + .tabs, + .tabs-content { + background: none; + border: 0; + } + + .tabs-panel { + padding: 0; + + h3 { + font-size: rem-calc(36); + } + } + + .tabs-controls { + display: flex; + + .budget-prev-phase, + .budget-next-phase, + .budget-prev-phase-disabled, + .budget-next-phase-disabled { + background: $brand; + border-radius: rem-calc(3); + color: $white; + display: flex; + font-size: rem-calc(36); + height: 1em * 4 / 3; + width: 1em * 4 / 3; + + &::before { + margin: auto; + } + + span { + @include element-invisible; + } + } + + > :first-child { + margin-right: 0.2em; + } + + .budget-prev-phase-disabled, + .budget-next-phase-disabled { + background: none; + color: #acb6b6; + } + + .budget-prev-phase, + .budget-prev-phase-disabled { + @include has-fa-icon(angle-left, solid); + } + + .budget-next-phase, + .budget-next-phase-disabled { + @include has-fa-icon(angle-right, solid); + } + } + + .budget-phase { + @include breakpoint(medium) { + width: 50%; + } + } +} diff --git a/app/assets/stylesheets/budgets/subheader.scss b/app/assets/stylesheets/budgets/subheader.scss new file mode 100644 index 000000000..ba51bd3bc --- /dev/null +++ b/app/assets/stylesheets/budgets/subheader.scss @@ -0,0 +1,17 @@ +.budget-subheader { + @include grid-row; + margin-bottom: $line-height; + margin-top: $line-height; + + .current-phase { + text-transform: uppercase; + } + + .button { + margin-top: $line-height / 2; + } + + .callout { + margin-bottom: 0; + } +} diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index 7e17cb531..944463b96 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -2155,7 +2155,7 @@ table { } } - + .budget.expanded, + + .budget-header, + .jumbo { margin-top: 0; } diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 4a3669547..764d4592c 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -628,10 +628,16 @@ } &.past-budgets { + display: flex; + flex-wrap: wrap; min-height: 0; - .button ~ .button { - margin-left: $line-height / 2; + > :not(:first-child) { + margin-left: auto; + } + + .button { + margin-left: $line-height; } } } @@ -1130,51 +1136,37 @@ // 06. Budgets // ----------- -.expanded { +.budget-header { + background: $budget; + margin-top: -$line-height; + padding-bottom: $line-height; + padding-top: $line-height; - &.budget { - background: $budget; - margin-top: -$line-height; + h1, + h2, + p, + a, + .back, + .icon-angle-left, + .description { + color: #fff; + } - h1, - h2, - p, - a, - .back, - .icon-angle-left, - .description { - color: #fff; - } + a { + text-decoration: underline; + } - a { - text-decoration: underline; - } + .confirmed { + font-size: rem-calc(24); + font-weight: bold; + } - .callout { + .info { + background: #6a2a72; - &.primary a { - color: $link; - } - } - - .button { - background: #fff; - color: $budget; - text-decoration: none; - } - - .confirmed { - font-size: rem-calc(24); - font-weight: bold; - } - - .info { - background: #6a2a72; - - p { - margin-bottom: 0; - text-transform: uppercase; - } + p { + margin-bottom: 0; + text-transform: uppercase; } } } @@ -1226,6 +1218,10 @@ } } +.current-phase { + color: $brand; +} + .groups-and-headings { .heading { @@ -1467,63 +1463,6 @@ } } -.budget-timeline { - border-left: 3px solid $budget; - margin-left: $line-height / 2; - padding: $line-height $line-height / 2; - - h3, - span, - p { - padding: 0 $line-height / 4; - } - - h3 { - margin-bottom: 0; - } - - span { - color: $text-medium; - display: block; - font-size: $small-font-size; - margin-bottom: $line-height / 2; - } - - .phase { - position: relative; - - &:not(:first-child) { - margin-top: $line-height * 1.5; - } - - &::before { - background-color: #fff; - border: 4px solid $budget; - border-radius: 100%; - content: ""; - height: 16px; - left: -22px; - position: absolute; - top: 6px; - width: 16px; - z-index: 99; - } - - &.is-active { - - h3 { - background: $budget; - color: #fff; - display: inline-block; - } - - &::before { - background-color: $budget; - } - } - } -} - .budgets-stats { .header { diff --git a/app/components/admin/budget_phases/phases_component.html.erb b/app/components/admin/budget_phases/phases_component.html.erb new file mode 100644 index 000000000..6f663578c --- /dev/null +++ b/app/components/admin/budget_phases/phases_component.html.erb @@ -0,0 +1,40 @@ +<% if phases.present? %> + + + + + + + + + + + + <% phases.each do |phase| %> + + + + + + + <% end %> +
<%= t("admin.budgets.edit.phases_caption") %>
<%= t("admin.budgets.edit.phase") %><%= t("admin.budgets.edit.duration") %><%= t("admin.budgets.edit.enabled") %><%= t("admin.budgets.edit.actions") %>
+ <%= phase.name %> + <% if phase.current? %> + <%= t("admin.budgets.edit.active") %> + <% end %> + + <% if phase.starts_at.present? || phase.ends_at.present? %> + <%= dates(phase) %> + <% else %> + <%= t("admin.budgets.edit.blank_dates") %> + <% end %> + + <%= enabled_text(phase) %> + + <%= render Admin::TableActionsComponent.new(phase, + actions: [:edit], + edit_text: t("admin.budgets.edit.edit_phase") + ) %> +
+<% end %> diff --git a/app/components/admin/budget_phases/phases_component.rb b/app/components/admin/budget_phases/phases_component.rb new file mode 100644 index 000000000..b27958f18 --- /dev/null +++ b/app/components/admin/budget_phases/phases_component.rb @@ -0,0 +1,25 @@ +class Admin::BudgetPhases::PhasesComponent < ApplicationComponent + attr_reader :budget + + def initialize(budget) + @budget = budget + end + + private + + def phases + budget.phases.order(:id) + end + + def dates(phase) + Admin::Budgets::DurationComponent.new(phase).dates + end + + def enabled_text(phase) + if phase.enabled? + tag.span t("shared.yes"), class: "budget-phase-enabled" + else + tag.span t("shared.no"), class: "budget-phase-disabled" + end + end +end diff --git a/app/components/admin/budgets/duration_component.rb b/app/components/admin/budgets/duration_component.rb new file mode 100644 index 000000000..8af1a3ad0 --- /dev/null +++ b/app/components/admin/budgets/duration_component.rb @@ -0,0 +1,29 @@ +class Admin::Budgets::DurationComponent < ApplicationComponent + attr_reader :durable + + def initialize(durable) + @durable = durable + end + + def dates + safe_join([formatted_start_date, "-", formatted_end_date], " ") + end + + def duration + distance_of_time_in_words(durable.starts_at, durable.ends_at) + end + + private + + def formatted_start_date + formatted_date(durable.starts_at) if durable.starts_at.present? + end + + def formatted_end_date + formatted_date(durable.ends_at - 1.second) if durable.ends_at.present? + end + + def formatted_date(time) + time_tag(time, format: :datetime) + end +end diff --git a/app/components/admin/budgets/index_component.html.erb b/app/components/admin/budgets/index_component.html.erb new file mode 100644 index 000000000..c371205f4 --- /dev/null +++ b/app/components/admin/budgets/index_component.html.erb @@ -0,0 +1,52 @@ +<%= header do %> + <%= link_to t("admin.budgets.index.new_link"), new_admin_budget_path %> +<% end %> + +<%= render Admin::Budgets::HelpComponent.new("budgets") %> +<%= render "shared/filter_subnav", i18n_namespace: "admin.budgets.index" %> + +<% if budgets.any? %> +

<%= page_entries_info budgets %>

+ + + + + + + + + + + + <% budgets.each do |budget| %> + + + + + + + <% end %> + +
<%= t("admin.budgets.index.table_name") %><%= t("admin.budgets.index.table_phase") %><%= t("admin.budgets.index.table_duration") %><%= t("admin.actions.actions") %>
"> + <% if budget.finished? %> + + <%= t("admin.budgets.index.table_completed") %> + + <% end %> + <%= budget.name %> + + <%= budget.current_phase.name %> + <%= phase_progress_text(budget) %> + + <%= dates(budget) %> + <%= duration(budget) %> + + <%= render Admin::Budgets::TableActionsComponent.new(budget) %> +
+ + <%= paginate budgets %> +<% else %> +
+ <%= t("admin.budgets.index.no_budgets") %> +
+<% end %> diff --git a/app/components/admin/budgets/index_component.rb b/app/components/admin/budgets/index_component.rb new file mode 100644 index 000000000..7523207da --- /dev/null +++ b/app/components/admin/budgets/index_component.rb @@ -0,0 +1,32 @@ +class Admin::Budgets::IndexComponent < ApplicationComponent + include Header + attr_reader :budgets + + def initialize(budgets) + @budgets = budgets + end + + def title + t("admin.budgets.index.title") + end + + private + + def phase_progress_text(budget) + t("admin.budgets.index.table_phase_progress", + current_phase_number: current_enabled_phase_number(budget), + total_phases: budget.phases.enabled.count) + end + + def current_enabled_phase_number(budget) + budget.phases.enabled.order(:id).pluck(:kind).index(budget.phase) + 1 + end + + def dates(budget) + Admin::Budgets::DurationComponent.new(budget).dates + end + + def duration(budget) + Admin::Budgets::DurationComponent.new(budget).duration + end +end diff --git a/app/components/budgets/phases_component.html.erb b/app/components/budgets/phases_component.html.erb new file mode 100644 index 000000000..d678181cf --- /dev/null +++ b/app/components/budgets/phases_component.html.erb @@ -0,0 +1,53 @@ +
+
+

<%= t("budgets.index.all_phases") %>

+
+ + + +
+ <% phases.each do |phase| %> +
"> + + +
+

<%= phase.name %>

+

<%= start_date(phase) %> - <%= end_date(phase) %>

+ <%= auto_link_already_sanitized_html(wysiwyg(phase.description)) %> +
+
+ <% end %> +
+
diff --git a/app/components/budgets/phases_component.rb b/app/components/budgets/phases_component.rb new file mode 100644 index 000000000..ee23c7070 --- /dev/null +++ b/app/components/budgets/phases_component.rb @@ -0,0 +1,34 @@ +class Budgets::PhasesComponent < ApplicationComponent + delegate :wysiwyg, :auto_link_already_sanitized_html, to: :helpers + attr_reader :budget + + def initialize(budget) + @budget = budget + end + + private + + def phases + budget.published_phases + end + + def start_date(phase) + time_tag(phase.starts_at.to_date, format: :long) if phase.starts_at.present? + end + + def end_date(phase) + time_tag(phase.ends_at.to_date - 1.day, format: :long) if phase.ends_at.present? + end + + def phase_dom_id(phase) + "#{phases.index(phase) + 1}-#{phase.name.parameterize}" + end + + def prev_phase_dom_id(phase) + phase_dom_id(phases[phases.index(phase) - 1]) + end + + def next_phase_dom_id(phase) + phase_dom_id(phases[phases.index(phase) + 1]) + end +end diff --git a/app/components/budgets/subheader_component.html.erb b/app/components/budgets/subheader_component.html.erb new file mode 100644 index 000000000..6e97da27a --- /dev/null +++ b/app/components/budgets/subheader_component.html.erb @@ -0,0 +1,38 @@ +
+
+ <%= t("budgets.index.current_phase") %> +

<%= budget.current_phase.name %>

+
+ +
+ <% if budget.accepting? %> + <% if current_user %> + <% if current_user.level_two_or_three_verified? %> +
+ <%= link_to t("budgets.investments.index.sidebar.create"), + new_budget_investment_path(budget), + class: "button" %> +
+ <% else %> +
+ <%= sanitize(t("budgets.investments.index.sidebar.verified_only", + verify: link_to_verify_account)) %> +
+ <% end %> + <% else %> +
+ <%= sanitize(t("budgets.investments.index.sidebar.not_logged_in", + sign_in: link_to_signin, sign_up: link_to_signup)) %> +
+ <% end %> + <% end %> + + <% if can?(:read_results, budget) %> +
+ <%= link_to t("budgets.show.see_results"), + budget_results_path(budget, heading_id: budget.headings.first), + class: "button expanded" %> +
+ <% end %> +
+
diff --git a/app/components/budgets/subheader_component.rb b/app/components/budgets/subheader_component.rb new file mode 100644 index 000000000..7d301d4ac --- /dev/null +++ b/app/components/budgets/subheader_component.rb @@ -0,0 +1,8 @@ +class Budgets::SubheaderComponent < ApplicationComponent + delegate :current_user, :link_to_signin, :link_to_signup, :link_to_verify_account, :can?, to: :helpers + attr_reader :budget + + def initialize(budget) + @budget = budget + end +end diff --git a/app/models/budget.rb b/app/models/budget.rb index b36535278..d6034c896 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -71,6 +71,14 @@ class Budget < ApplicationRecord phases.published.order(:id) end + def starts_at + phases.published.first.starts_at + end + + def ends_at + phases.published.last.ends_at + end + def description description_for_phase(phase) end @@ -155,10 +163,6 @@ class Budget < ApplicationRecord heading_ids.include?(heading.id) ? heading.price : -1 end - def translated_phase - I18n.t "budgets.phase.#{phase}" - end - def formatted_amount(amount) ActionController::Base.helpers.number_to_currency(amount, precision: 0, @@ -214,6 +218,7 @@ class Budget < ApplicationRecord Budget::Phase.create( budget: self, kind: phase, + name: I18n.t("budgets.phase.#{phase}"), prev_phase: phases&.last, starts_at: phases&.last&.ends_at || Date.current, ends_at: (phases&.last&.ends_at || Date.current) + 1.month diff --git a/app/models/budget/phase.rb b/app/models/budget/phase.rb index d8b59207f..4bc277b77 100644 --- a/app/models/budget/phase.rb +++ b/app/models/budget/phase.rb @@ -3,9 +3,9 @@ class Budget PHASE_KINDS = %w[informing accepting reviewing selecting valuating publishing_prices balloting reviewing_ballots finished].freeze PUBLISHED_PRICES_PHASES = %w[publishing_prices balloting reviewing_ballots finished].freeze - SUMMARY_MAX_LENGTH = 1000 DESCRIPTION_MAX_LENGTH = 2000 + translates :name, touch: true translates :summary, touch: true translates :description, touch: true include Globalizable @@ -15,7 +15,7 @@ class Budget belongs_to :next_phase, class_name: self.name, inverse_of: :prev_phase has_one :prev_phase, class_name: self.name, foreign_key: :next_phase_id, inverse_of: :next_phase - validates_translation :summary, length: { maximum: SUMMARY_MAX_LENGTH } + validates_translation :name, presence: true validates_translation :description, length: { maximum: DESCRIPTION_MAX_LENGTH } validates :budget, presence: true validates :kind, presence: true, uniqueness: { scope: :budget }, inclusion: { in: ->(*) { PHASE_KINDS }} @@ -62,6 +62,10 @@ class Budget in_phase_or_later?("balloting") end + def current? + budget.current_phase == self + end + private def adjust_date_ranges @@ -77,7 +81,7 @@ class Budget if enabled? && starts_at.present? && prev_enabled_phase.present? prev_enabled_phase.assign_attributes(ends_at: starts_at) if prev_enabled_phase.invalid_dates_range? - phase_name = I18n.t("budgets.phase.#{prev_enabled_phase.kind}") + phase_name = prev_enabled_phase.name error = I18n.t("budgets.phases.errors.prev_phase_dates_invalid", phase_name: phase_name) errors.add(:starts_at, error) end @@ -88,7 +92,7 @@ class Budget if enabled? && ends_at.present? && next_enabled_phase.present? next_enabled_phase.assign_attributes(starts_at: ends_at) if next_enabled_phase.invalid_dates_range? - phase_name = I18n.t("budgets.phase.#{next_enabled_phase.kind}") + phase_name = next_enabled_phase.name error = I18n.t("budgets.phases.errors.next_phase_dates_invalid", phase_name: phase_name) errors.add(:ends_at, error) end diff --git a/app/views/admin/budget_phases/_form.html.erb b/app/views/admin/budget_phases/_form.html.erb index 609261344..f54f2aad6 100644 --- a/app/views/admin/budget_phases/_form.html.erb +++ b/app/views/admin/budget_phases/_form.html.erb @@ -1,50 +1,49 @@ <%= render "shared/globalize_locales", resource: @phase %> -<%= translatable_form_for [:admin, @phase.budget, @phase] do |f| %> +<%= 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_area :description, - maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH, - class: "html-area", - hint: t("admin.budget_phases.edit.description_help_text") %> -
- -
- <%= translations_form.text_area :summary, - maxlength: Budget::Phase::SUMMARY_MAX_LENGTH, - class: "html-area", - hint: t("admin.budget_phases.edit.summary_help_text") %> -
- <% end %> -
- -
-
- <%= 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") %>
-
-
-
- <%= f.submit t("admin.budget_phases.edit.save_changes"), class: "button success expanded" %> +
+ <%= 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/views/admin/budget_phases/edit.html.erb b/app/views/admin/budget_phases/edit.html.erb index 9cf9a6745..452c11f96 100644 --- a/app/views/admin/budget_phases/edit.html.erb +++ b/app/views/admin/budget_phases/edit.html.erb @@ -1,9 +1,5 @@ <%= back_link_to edit_admin_budget_path(@phase.budget) %> -
-
-

<%= t("admin.budgets.edit.title") %> - <%= t("budgets.phase.#{@phase.kind}") %>

-
-
+

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

<%= render "/admin/budget_phases/form" %> diff --git a/app/views/admin/budgets/_form.html.erb b/app/views/admin/budgets/_form.html.erb index 4702a7ae2..295f1234a 100644 --- a/app/views/admin/budgets/_form.html.erb +++ b/app/views/admin/budgets/_form.html.erb @@ -44,48 +44,7 @@
<%= render Admin::Budgets::HelpComponent.new("budget_phases") %> - - <% if @budget.phases.present? %> - - - - - - - - - - - <% @budget.phases.order(:id).each do |phase| %> - - - - - - - <% end %> -
<%= t("admin.budgets.edit.phase") %><%= t("admin.budgets.edit.dates") %><%= t("admin.budgets.edit.enabled") %><%= t("admin.budgets.edit.actions") %>
- <%= t("budgets.phase.#{phase.kind}") %> - <% if @budget.current_phase == phase %> - <%= t("admin.budgets.edit.active") %> - <% end %> - - <% if phase.starts_at.present? || phase.ends_at.present? %> - <%= l(phase.starts_at.to_date) if phase.starts_at.present? %> - - - <%= l(phase.ends_at.to_date) if phase.ends_at.present? %> - <% else %> - <%= t("admin.budgets.edit.blank_dates") %> - <% end %> - - "> - - <%= render Admin::TableActionsComponent.new(phase, - actions: [:edit], - edit_text: t("admin.budgets.edit.edit_phase") - ) %> -
- <% end %> + <%= render Admin::BudgetPhases::PhasesComponent.new(@budget) %> <% if @budget.persisted? %> diff --git a/app/views/admin/budgets/index.html.erb b/app/views/admin/budgets/index.html.erb index 64e6cb946..37943d5be 100644 --- a/app/views/admin/budgets/index.html.erb +++ b/app/views/admin/budgets/index.html.erb @@ -1,47 +1 @@ -
-

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

- <%= link_to t("admin.budgets.index.new_link"), new_admin_budget_path %> -
- -<%= render Admin::Budgets::HelpComponent.new("budgets") %> -<%= render "shared/filter_subnav", i18n_namespace: "admin.budgets.index" %> - -<% if @budgets.any? %> -

<%= page_entries_info @budgets %>

- - - - - - - - - - - <% @budgets.each do |budget| %> - - - - - - <% end %> - -
<%= t("admin.budgets.index.table_name") %><%= t("admin.budgets.index.table_phase") %><%= t("admin.actions.actions") %>
"> - <% if budget.finished? %> - - <%= t("admin.budgets.index.table_completed") %> - - <% end %> - <%= budget.name %> - - <%= t("budgets.phase.#{budget.phase}") %> - - <%= render Admin::Budgets::TableActionsComponent.new(budget) %> -
- - <%= paginate @budgets %> -<% else %> -
- <%= t("admin.budgets.index.no_budgets") %> -
-<% end %> +<%= render Admin::Budgets::IndexComponent.new(@budgets) %> diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb index a9d08340c..12bdf42f8 100644 --- a/app/views/admin/budgets/new.html.erb +++ b/app/views/admin/budgets/new.html.erb @@ -1,3 +1,7 @@ -

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

+<%= back_link_to admin_budgets_path %> + +
+

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

+
<%= render "/admin/budgets/form" %> diff --git a/app/views/budgets/_finished.html.erb b/app/views/budgets/_finished.html.erb index 60b715917..f99db6525 100644 --- a/app/views/budgets/_finished.html.erb +++ b/app/views/budgets/_finished.html.erb @@ -1,31 +1,19 @@
-
+

<%= t("budgets.index.finished_budgets") %>

-
+
<% budgets.each do |budget| %>
-
-
-
-

<%= budget.name %>

-
-
+

<%= budget.name %>

-
-
- <% if can?(:read_results, budget) %> - <%= link_to t("budgets.index.see_results"), - budget_results_path(budget), - class: "button" %> - <% end %> +
+ <% if can?(:read_results, budget) %> + <%= link_to t("budgets.index.see_results"), budget_results_path(budget), class: "button" %> + <% end %> - <%= link_to t("budgets.index.milestones"), - budget_executions_path(budget), - class: "button" %> -
-
+ <%= link_to t("budgets.index.milestones"), budget_executions_path(budget), class: "button" %>
diff --git a/app/views/budgets/_phases.html.erb b/app/views/budgets/_phases.html.erb deleted file mode 100644 index 9a05b8ae7..000000000 --- a/app/views/budgets/_phases.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -
    - <% current_budget.published_phases.each do |phase| %> -
  • "> -

    <%= t("budgets.phase.#{phase.kind}") %>

    - - <%= l(phase.starts_at.to_date, format: :long) if phase.starts_at.present? %> - - - <%= l(phase.ends_at.to_date - 1.day, format: :long) if phase.ends_at.present? %> - -

    <%= auto_link_already_sanitized_html(wysiwyg(phase.summary)) %>

    -
  • - <% end %> -
diff --git a/app/views/budgets/ballot/_ballot.html.erb b/app/views/budgets/ballot/_ballot.html.erb index e8e79300f..c320a1652 100644 --- a/app/views/budgets/ballot/_ballot.html.erb +++ b/app/views/budgets/ballot/_ballot.html.erb @@ -1,4 +1,4 @@ -
+
<%= back_link_to @ballot_referer %> diff --git a/app/views/budgets/groups/show.html.erb b/app/views/budgets/groups/show.html.erb index 5ebb18ace..b7565a265 100644 --- a/app/views/budgets/groups/show.html.erb +++ b/app/views/budgets/groups/show.html.erb @@ -2,9 +2,9 @@ <%= render "shared/canonical", href: budget_group_url(filter: @current_filter) %> <% end %> -
+
-
+
<%= back_link_to budgets_path %>

<%= t("budgets.groups.show.title") %>

diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb index 4121e817d..73f53bbd9 100644 --- a/app/views/budgets/index.html.erb +++ b/app/views/budgets/index.html.erb @@ -7,55 +7,23 @@ <% end %> <% if current_budget.present? %> -
-
-
- -

<%= current_budget.name %>

-
- <%= auto_link_already_sanitized_html wysiwyg(current_budget.description) %> -
-

- <%= link_to t("budgets.index.section_header.help"), "#section_help" %> -

+
+
+
+

<%= current_budget.name %>

+
+ <%= auto_link_already_sanitized_html wysiwyg(current_budget.description) %>
-
-

- <%= t("budgets.show.phase") %> -

-

<%= t("budgets.phase.#{current_budget.phase}") %>

- - <%= link_to t("budgets.index.section_header.all_phases"), "#all_phases" %> - - <% if current_budget.accepting? %> - <% if current_user %> - <% if current_user.level_two_or_three_verified? %> - <%= link_to t("budgets.investments.index.sidebar.create"), - new_budget_investment_path(current_budget), - class: "button margin-top expanded" %> - <% else %> -
- <%= sanitize(t("budgets.investments.index.sidebar.verified_only", - verify: link_to_verify_account)) %> -
- <% end %> - <% else %> -
- <%= sanitize(t("budgets.investments.index.sidebar.not_logged_in", - sign_in: link_to_signin, sign_up: link_to_signup)) %> -
- <% end %> - <% end %> - - <% if can?(:read_results, current_budget) %> - <%= link_to t("budgets.show.see_results"), - budget_results_path(current_budget, heading_id: current_budget.headings.first), - class: "button margin-top expanded" %> - <% end %> +

+ <%= link_to t("budgets.index.section_header.help"), "#section_help" %> +

+ <%= render Budgets::SubheaderComponent.new(current_budget) %> + <%= render Budgets::PhasesComponent.new(current_budget) %> +
@@ -111,11 +79,6 @@ <% end %> <% end %> - -
-

<%= t("budgets.index.all_phases") %>

- <%= render "phases", budget: current_budget %> -
@@ -124,9 +87,9 @@ <% end %>
<% else %> -
+
-
+

<%= t("budgets.index.title") %>

diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb index a35affe18..f7e6617e0 100644 --- a/app/views/budgets/show.html.erb +++ b/app/views/budgets/show.html.erb @@ -2,48 +2,20 @@ <%= render "shared/canonical", href: budget_url(@budget, filter: @current_filter) %> <% end %> -
-
-
+
+
+
<%= back_link_to budgets_path %>

<%= @budget.name %>

<%= auto_link_already_sanitized_html wysiwyg(@budget.description) %>
-
-

- <%= t("budgets.show.phase") %> -

-

<%= t("budgets.phase.#{@budget.phase}") %>

- - <% if @budget.accepting? %> - <% if current_user %> - <% if current_user.level_two_or_three_verified? %> - <%= link_to t("budgets.investments.index.sidebar.create"), new_budget_investment_path(@budget), class: "button margin-top expanded" %> - <% else %> -
- <%= sanitize(t("budgets.investments.index.sidebar.verified_only", - verify: link_to_verify_account)) %> -
- <% end %> - <% else %> -
- <%= sanitize(t("budgets.investments.index.sidebar.not_logged_in", - sign_in: link_to_signin, sign_up: link_to_signup)) %> -
- <% end %> - <% end %> - - <% if can?(:read_results, @budget) %> - <%= link_to t("budgets.show.see_results"), - budget_results_path(@budget), - class: "button margin-top expanded" %> - <% end %> -
+<%= render Budgets::SubheaderComponent.new(@budget) %> +
<% if @current_filter == "unfeasible" %> diff --git a/app/views/management/budgets/create_investments.html.erb b/app/views/management/budgets/create_investments.html.erb index 9c9f00909..2390f8c51 100644 --- a/app/views/management/budgets/create_investments.html.erb +++ b/app/views/management/budgets/create_investments.html.erb @@ -13,7 +13,7 @@ <% @budgets.each do |budget| %> <%= budget.name %> - <%= budget.translated_phase %> + <%= budget.current_phase.name %> <%= link_to t("management.budgets.create_new_investment"), new_management_budget_investment_path(budget), diff --git a/app/views/management/budgets/print_investments.html.erb b/app/views/management/budgets/print_investments.html.erb index ebc7ed768..9bb4d7ea3 100644 --- a/app/views/management/budgets/print_investments.html.erb +++ b/app/views/management/budgets/print_investments.html.erb @@ -11,7 +11,7 @@ <%= @budget.name %> - <%= @budget.translated_phase %> + <%= @budget.current_phase.name %> <%= link_to t("management.budgets.print_investments"), print_management_budget_investments_path(@budget) %> diff --git a/app/views/management/budgets/support_investments.html.erb b/app/views/management/budgets/support_investments.html.erb index 49ddc9461..4da6e5fe9 100644 --- a/app/views/management/budgets/support_investments.html.erb +++ b/app/views/management/budgets/support_investments.html.erb @@ -13,7 +13,7 @@ <% @budgets.each do |budget| %> <%= budget.name %> - <%= budget.translated_phase %> + <%= budget.current_phase.name %> <%= link_to t("management.budgets.support_investments"), management_budget_investments_path(budget), diff --git a/app/views/valuation/budgets/index.html.erb b/app/views/valuation/budgets/index.html.erb index 1e98cf23e..fae10257b 100644 --- a/app/views/valuation/budgets/index.html.erb +++ b/app/views/valuation/budgets/index.html.erb @@ -16,7 +16,7 @@ <%= @budget.name %> - <%= t("budgets.phase.#{@budget.phase}") %> + <%= @budget.current_phase.name %> <%= @investments.count %> diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml index 91d497b96..a00013b70 100644 --- a/config/locales/en/activerecord.yml +++ b/config/locales/en/activerecord.yml @@ -230,6 +230,7 @@ en: ends_at: "End date" starts_at: "Start date" budget/phase/translation: + name: "Name" description: "Description" summary: "Summary" comment: diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 5404db063..85a5ed4b4 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -76,8 +76,10 @@ en: help: "Participatory budgets allow citizens to propose and decide directly how to spend part of the budget, with monitoring and rigorous evaluation of proposals by the institution." budget_investments: Manage projects table_completed: Completed + table_duration: "Duration" table_name: Name table_phase: Phase + table_phase_progress: "(%{current_phase_number}/%{total_phases})" edit_groups: Edit headings groups edit_budget: Edit budget admin_ballots: Admin ballots @@ -94,7 +96,8 @@ en: publish: "Publish budget" delete: Delete budget phase: Phase - dates: Dates + phases_caption: "Phases" + duration: "Duration" enabled: Enabled actions: Actions edit_phase: Edit phase @@ -173,9 +176,11 @@ en: 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." budget_phases: edit: - summary_help_text: This text will inform the user about the phase. To show it even if the phase is not active, select the checkbox below 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." enabled_help_text: This phase will be public in the budget's phases timeline, as well as active for any other purpose + name_help_text: "This is the title of the phase users will read on the header whenever this phase is active." save_changes: Save changes index: help: "Participatory budgets have different phases. Here you can enable or disable phases and also customize each individual phase." diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml index 758ab8c84..e653cfdd7 100644 --- a/config/locales/en/budgets.yml +++ b/config/locales/en/budgets.yml @@ -64,8 +64,10 @@ en: icon_alt: Participatory budgets icon title: Participatory budgets help: Help with participatory budgets - all_phases: See all phases - all_phases: Budget investment's phases + all_phases: Participatory budgets phases + next_phase: Next phase + prev_phase: Previous phase + current_phase: Current phase map: Budget investments' proposals located geographically investment_proyects: List of all investment projects unfeasible_investment_proyects: List of all unfeasible investment projects @@ -176,7 +178,6 @@ en: other: "You have selected %{count} projects out of %{limit}" show: group: Group - phase: Actual phase unfeasible_title: Unfeasible investments unfeasible: See unfeasible investments unselected_title: Investments not selected for balloting phase diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index 65f17649b..35f62237e 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -230,6 +230,7 @@ es: ends_at: "Fecha de fin" starts_at: "Fecha de inicio" budget/phase/translation: + name: "Nombre" description: "Descripción" summary: "Resumen" comment: diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 0a9f28479..c6cab0bef 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -76,8 +76,10 @@ es: help: "Los presupuestos participativos permiten que los ciudadanos propongan y decidan de manera directa cómo gastar parte del presupuesto, con un seguimiento y evaluación riguroso de las propuestas por parte de la institución." budget_investments: Gestionar proyectos de gasto table_completed: Completado + table_duration: "Duración" table_name: Nombre table_phase: Fase + table_phase_progress: "(%{current_phase_number}/%{total_phases})" edit_groups: Editar grupos de partidas edit_budget: Editar presupuesto admin_ballots: Gestionar urnas @@ -94,7 +96,8 @@ es: publish: "Publicar presupuesto" delete: Eliminar presupuesto phase: Fase - dates: Fechas + phases_caption: "Fases" + duration: "Duración" enabled: Habilitada actions: Acciones edit_phase: Editar fase @@ -173,9 +176,11 @@ es: 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." budget_phases: edit: - summary_help_text: Este texto informará al usuario sobre la fase. Para mostrarlo aunque la fase no esté activa, marca la opción de más abajo. description_help_text: Este texto aparecerá en la cabecera cuando la fase esté activa + duration: "Duración de la fase" + duration_description: "El período de tiempo que esta fase estará activa." 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 index: help: "Los presupuestos participativos tienen distintas fases. Aquí puedes habilitar o deshabilitar fases y también personalizar cada una de las fases." diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml index 86b563134..34f06c306 100644 --- a/config/locales/es/budgets.yml +++ b/config/locales/es/budgets.yml @@ -64,8 +64,10 @@ es: icon_alt: Icono de Presupuestos participativos title: Presupuestos participativos help: Ayuda sobre presupuestos participativos - all_phases: Ver todas las fases all_phases: Fases de los presupuestos participativos + next_phase: Fase siguiente + prev_phase: Fase anterior + current_phase: Fase actual map: Proyectos localizables geográficamente investment_proyects: Ver lista completa de proyectos de gasto unfeasible_investment_proyects: Ver lista de proyectos de gasto inviables @@ -176,7 +178,6 @@ es: other: "Has seleccionado %{count} proyectos de %{limit}" show: group: Grupo - phase: Fase actual unfeasible_title: Proyectos de gasto inviables unfeasible: Ver los proyectos inviables unselected_title: Proyectos no seleccionados para la votación final diff --git a/db/dev_seeds/budgets.rb b/db/dev_seeds/budgets.rb index e8a1269bd..5b7c892b5 100644 --- a/db/dev_seeds/budgets.rb +++ b/db/dev_seeds/budgets.rb @@ -43,6 +43,7 @@ section "Creating Budgets" do budget.phases.each do |phase| random_locales.map do |locale| Globalize.with_locale(locale) do + phase.name = "Name for locale #{locale}" phase.description = "Description for locale #{locale}" phase.summary = "Summary for locale #{locale}" phase.save! diff --git a/db/migrate/20200309100127_add_name_to_budget_phases.rb b/db/migrate/20200309100127_add_name_to_budget_phases.rb new file mode 100644 index 000000000..b1b231f73 --- /dev/null +++ b/db/migrate/20200309100127_add_name_to_budget_phases.rb @@ -0,0 +1,7 @@ +class AddNameToBudgetPhases < ActiveRecord::Migration[5.2] + def change + change_table :budget_phase_translations do |t| + t.string :name + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 27cc45b9d..5d116c21b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -286,6 +286,7 @@ ActiveRecord::Schema.define(version: 2021_01_23_100638) do t.datetime "updated_at", null: false t.text "description" t.text "summary" + t.string "name" t.index ["budget_phase_id"], name: "index_budget_phase_translations_on_budget_phase_id" t.index ["locale"], name: "index_budget_phase_translations_on_locale" end diff --git a/lib/tasks/budgets.rake b/lib/tasks/budgets.rake index c5ffa9ef6..b6d1ed73a 100644 --- a/lib/tasks/budgets.rake +++ b/lib/tasks/budgets.rake @@ -29,4 +29,40 @@ namespace :budgets do end end end + + desc "Copies the Budget::Phase summary into description" + task phases_summary_to_description: :environment do + ApplicationLogger.new.info "Adding budget phases summary to descriptions" + + Budget::Phase::Translation.find_each do |translation| + if translation.summary.present? + translation.description << "
" + translation.description << translation.summary + translation.update!(summary: nil) if translation.save + end + end + end + + desc "Add name to existing budget phases" + task add_name_to_existing_phases: :environment do + ApplicationLogger.new.info "Adding names to budgets phases" + + Budget::Phase.find_each do |phase| + if phase.translations.present? + phase.translations.each do |translation| + unless translation.name.present? + if I18n.available_locales.include? translation.locale + locale = translation.locale + else + locale = I18n.default_locale + end + + translation.update!(name: I18n.t("budgets.phase.#{phase.kind}", locale: locale)) + end + end + else + phase.translations.create!(name: I18n.t("budgets.phase.#{phase.kind}"), locale: I18n.default_locale) + end + end + end end diff --git a/lib/tasks/consul.rake b/lib/tasks/consul.rake index b4775c285..49ffa920c 100644 --- a/lib/tasks/consul.rake +++ b/lib/tasks/consul.rake @@ -8,6 +8,8 @@ namespace :consul do task "execute_release_1.3.0_tasks": [ "db:load_sdg", "db:calculate_tsv", - "budgets:set_published" + "budgets:set_published", + "budgets:phases_summary_to_description", + "budgets:add_name_to_existing_phases" ] end diff --git a/spec/components/admin/budgets/duration_component_spec.rb b/spec/components/admin/budgets/duration_component_spec.rb new file mode 100644 index 000000000..193d45e7c --- /dev/null +++ b/spec/components/admin/budgets/duration_component_spec.rb @@ -0,0 +1,57 @@ +require "rails_helper" + +describe Admin::Budgets::DurationComponent, type: :component do + describe "#dates" do + it "shows both dates when both are defined" do + durable = double( + starts_at: Time.zone.local(2015, 8, 1, 12, 0, 0), + ends_at: Time.zone.local(2016, 9, 30, 16, 30, 00) + ) + + dates = Admin::Budgets::DurationComponent.new(durable).dates + + render dates + + expect(page.text).to eq "2015-08-01 12:00:00 - 2016-09-30 16:29:59" + expect(dates).to be_html_safe + end + + it "shows the start date when no end date is defined" do + durable = double(starts_at: Time.zone.local(2015, 8, 1, 12, 0, 0), ends_at: nil) + render Admin::Budgets::DurationComponent.new(durable).dates + + expect(page.text).to eq "2015-08-01 12:00:00 - " + end + + it "shows the end date when no start date is defined" do + durable = double(starts_at: nil, ends_at: Time.zone.local(2016, 9, 30, 16, 30, 00)) + + render Admin::Budgets::DurationComponent.new(durable).dates + + expect(page.text).to eq "- 2016-09-30 16:29:59" + end + end + + describe "#duration" do + it "describes the total duration in human language" do + durable = double( + starts_at: Time.zone.local(2015, 8, 1, 12, 0, 0), + ends_at: Time.zone.local(2016, 9, 30, 16, 30, 00) + ) + + render Admin::Budgets::DurationComponent.new(durable).duration + + expect(page.text).to eq "about 1 year" + end + end + + attr_reader :content + + def render(content) + @content = content + end + + def page + Capybara::Node::Simple.new(content) + end +end diff --git a/spec/lib/tasks/budgets_spec.rb b/spec/lib/tasks/budgets_spec.rb index 609e218c7..e0bd86c6e 100644 --- a/spec/lib/tasks/budgets_spec.rb +++ b/spec/lib/tasks/budgets_spec.rb @@ -1,70 +1,159 @@ require "rails_helper" -describe Budget do - let(:run_rake_task) do - Rake::Task["budgets:set_published"].reenable - Rake.application.invoke_task("budgets:set_published") +describe "budget tasks" do + describe "set_published" do + let(:run_rake_task) do + Rake::Task["budgets:set_published"].reenable + Rake.application.invoke_task("budgets:set_published") + end + + it "does not change anything if the published attribute is set" do + budget = create(:budget, published: false, phase: "accepting") + + run_rake_task + budget.reload + + expect(budget.phase).to eq "accepting" + expect(budget.published).to be false + end + + it "publishes budgets which are not in draft mode" do + budget = create(:budget, published: nil, phase: "accepting") + + run_rake_task + budget.reload + + expect(budget.phase).to eq "accepting" + expect(budget.published).to be true + end + + it "changes the published attribute to false on drafting budgets" do + stub_const("Budget::Phase::PHASE_KINDS", ["drafting"] + Budget::Phase::PHASE_KINDS) + budget = create(:budget, published: nil) + budget.update_column(:phase, "drafting") + stub_const("Budget::Phase::PHASE_KINDS", Budget::Phase::PHASE_KINDS - ["drafting"]) + + run_rake_task + budget.reload + + expect(budget.published).to be false + expect(budget.phase).to eq "informing" + end + + it "changes the phase to the first enabled phase" do + budget = create(:budget, published: nil) + budget.update_column(:phase, "drafting") + budget.phases.informing.update!(enabled: false) + + expect(budget.phase).to eq "drafting" + + run_rake_task + budget.reload + + expect(budget.phase).to eq "accepting" + expect(budget.published).to be false + end + + it "enables and select the informing phase if there are not any enabled phases" do + budget = create(:budget, published: nil) + budget.update_column(:phase, "drafting") + budget.phases.each { |phase| phase.update!(enabled: false) } + + expect(budget.phase).to eq "drafting" + + run_rake_task + budget.reload + + expect(budget.phase).to eq "informing" + expect(budget.phases.informing.enabled).to be true + expect(budget.published).to be false + end end - it "does not change anything if the published attribute is set" do - budget = create(:budget, published: false, phase: "accepting") + describe "phases_summary_to_description" do + let(:run_rake_task) do + Rake::Task["budgets:phases_summary_to_description"].reenable + Rake.application.invoke_task("budgets:phases_summary_to_description") + end - run_rake_task - budget.reload + it "appends the content of summary to the content of description" do + budget = create(:budget) + budget_phase = budget.phases.informing + budget_phase.update!( + description_en: "English description", + description_es: "Spanish description", + description_fr: "French description", + name_es: "Spanish name", + name_fr: "French name", + summary_en: "English summary", + summary_fr: "French summary" + ) - expect(budget.phase).to eq "accepting" - expect(budget.published).to be false + run_rake_task + + budget_phase.reload + expect(budget_phase.description_en).to eq "English description
English summary" + expect(budget_phase.description_es).to eq "Spanish description" + expect(budget_phase.description_fr).to eq "French description
French summary" + expect(budget_phase.summary).to be nil + end end - it "publishes budgets which are not in draft mode" do - budget = create(:budget, published: nil, phase: "accepting") + describe "add_name_to_existing_phases" do + let(:run_rake_task) do + Rake::Task["budgets:add_name_to_existing_phases"].reenable + Rake.application.invoke_task("budgets:add_name_to_existing_phases") + end - run_rake_task - budget.reload + it "adds the name to existing budget phases" do + budget = create(:budget) + informing_phase = budget.phases.informing + accepting_phase = budget.phases.accepting - expect(budget.phase).to eq "accepting" - expect(budget.published).to be true - end + accepting_phase.update!(name_en: "Custom accepting", name_es: "Aceptando personalizado") + informing_phase.translations.create!(locale: :es, name: "temp") + informing_phase.translations.update_all(name: "") - it "changes the published attribute to false on drafting budgets" do - stub_const("Budget::Phase::PHASE_KINDS", ["drafting"] + Budget::Phase::PHASE_KINDS) - budget = create(:budget, published: nil) - budget.update_column(:phase, "drafting") - stub_const("Budget::Phase::PHASE_KINDS", Budget::Phase::PHASE_KINDS - ["drafting"]) + expect(informing_phase.name_en).to eq "" + expect(informing_phase.name_es).to eq "" + expect(informing_phase.name_fr).to be nil + expect(accepting_phase.name_en).to eq "Custom accepting" + expect(accepting_phase.name_es).to eq "Aceptando personalizado" + expect(accepting_phase.name_fr).to be nil - run_rake_task - budget.reload + run_rake_task - expect(budget.published).to be false - expect(budget.phase).to eq "informing" - end + expect(informing_phase.reload.name_en).to eq "Information" + expect(informing_phase.reload.name_es).to eq "Información" + expect(informing_phase.reload.name_fr).to be nil + expect(accepting_phase.reload.name_en).to eq "Custom accepting" + expect(accepting_phase.reload.name_es).to eq "Aceptando personalizado" + expect(accepting_phase.reload.name_fr).to be nil + end - it "changes the phase to the first enabled phase" do - budget = create(:budget, published: nil) - budget.update_column(:phase, "drafting") - budget.phases.informing.update!(enabled: false) + it "adds the name in default locale to existing translations no longer available" do + budget = create(:budget) + informing_phase = budget.phases.informing + obsolete_translation = informing_phase.translations.build(locale: :fiction) + obsolete_translation.save!(validate: false) - expect(budget.phase).to eq "drafting" + expect(obsolete_translation.reload.name).to be nil - run_rake_task - budget.reload + run_rake_task - expect(budget.phase).to eq "accepting" - expect(budget.published).to be false - end + expect(obsolete_translation.reload.name).to eq "Information" + end - it "enables and select the informing phase if there are not any enabled phases" do - budget = create(:budget, published: nil) - budget.update_column(:phase, "drafting") - budget.phases.each { |phase| phase.update!(enabled: false) } + it "adds a default translation to phases with no translations" do + budget = create(:budget) + informing_phase = budget.phases.informing + informing_phase.translations.destroy_all - expect(budget.phase).to eq "drafting" + expect(informing_phase.reload.name).to be nil - run_rake_task - budget.reload + run_rake_task - expect(budget.phase).to eq "informing" - expect(budget.phases.informing.enabled).to be true - expect(budget.published).to be false + expect(informing_phase.reload.name).to eq "Information" + end end end diff --git a/spec/system/admin/budget_phases_spec.rb b/spec/system/admin/budget_phases_spec.rb index 96d8fc367..19320e86b 100644 --- a/spec/system/admin/budget_phases_spec.rb +++ b/spec/system/admin/budget_phases_spec.rb @@ -9,7 +9,6 @@ describe "Admin budget phases" do fill_in "start_date", with: Date.current + 1.day fill_in "end_date", with: Date.current + 12.days - fill_in_ckeditor "Summary", with: "New summary of the phase." fill_in_ckeditor "Description", with: "New description of the phase." uncheck "budget_phase_enabled" click_button "Save changes" @@ -19,9 +18,27 @@ describe "Admin budget phases" do expect(budget.current_phase.starts_at.to_date).to eq((Date.current + 1.day).to_date) expect(budget.current_phase.ends_at.to_date).to eq((Date.current + 12.days).to_date) - expect(budget.current_phase.summary).to include("New summary of the phase.") expect(budget.current_phase.description).to include("New description of the phase.") expect(budget.current_phase.enabled).to be(false) end + + scenario "Show default phase name or custom if present" do + visit edit_admin_budget_path(budget) + + within_table "Phases" do + expect(page).to have_content "Accepting projects" + expect(page).not_to have_content "My phase custom name" + + within("tr", text: "Accepting projects") { click_link "Edit phase" } + end + + fill_in "Name", with: "My phase custom name" + click_button "Save changes" + + within_table "Phases" do + expect(page).to have_content "My phase custom name" + expect(page).not_to have_content "Accepting projects" + end + end end end diff --git a/spec/system/admin/budgets_spec.rb b/spec/system/admin/budgets_spec.rb index 0fb597c31..e4e27b64a 100644 --- a/spec/system/admin/budgets_spec.rb +++ b/spec/system/admin/budgets_spec.rb @@ -41,11 +41,11 @@ describe "Admin budgets", :admin do end scenario "Displaying budgets" do - budget = create(:budget) + budget = create(:budget, :accepting) visit admin_budgets_path - expect(page).to have_content(budget.name) - expect(page).to have_content(translated_phase_name(phase_kind: budget.phase)) + expect(page).to have_content budget.name + expect(page).to have_content "Accepting projects" end scenario "Filters by phase" do @@ -245,35 +245,53 @@ describe "Admin budgets", :admin do end context "Edit" do - let!(:budget) { create(:budget) } + let(:budget) { create(:budget) } scenario "Show phases table" do - budget.update!(phase: "selecting") + travel_to(Date.new(2015, 7, 15)) do + budget.update!(phase: "selecting") + budget.phases.valuating.update!(enabled: false) - visit admin_budgets_path - click_link "Edit budget" + visit edit_admin_budget_path(budget) - expect(page).to have_select("budget_phase", selected: "Selecting projects") + expect(page).to have_select "Phase", selected: "Selecting projects" - within "#budget-phases-table" do - Budget::Phase::PHASE_KINDS.each do |phase_kind| - break if phase_kind == Budget::Phase::PHASE_KINDS.last + expect(page).to have_table "Phases", with_cols: [ + [ + "Information", + "Accepting projects", + "Reviewing projects", + "Selecting projects Active", + "Valuating projects", + "Publishing projects prices", + "Voting projects", + "Reviewing voting" + ], + [ + "2015-07-15 00:00:00 - 2015-08-14 23:59:59", + "2015-08-15 00:00:00 - 2015-09-14 23:59:59", + "2015-09-15 00:00:00 - 2015-10-14 23:59:59", + "2015-10-15 00:00:00 - 2015-11-14 23:59:59", + "2015-11-15 00:00:00 - 2015-12-14 23:59:59", + "2015-11-15 00:00:00 - 2016-01-14 23:59:59", + "2016-01-15 00:00:00 - 2016-02-14 23:59:59", + "2016-02-15 00:00:00 - 2016-03-14 23:59:59" + ], + [ + "Yes", + "Yes", + "Yes", + "Yes", + "No", + "Yes", + "Yes", + "Yes" + ] + ] - phase_index = Budget::Phase::PHASE_KINDS.index(phase_kind) - next_phase_kind = Budget::Phase::PHASE_KINDS[phase_index + 1] - next_phase_name = translated_phase_name(phase_kind: next_phase_kind) - expect(translated_phase_name(phase_kind: phase_kind)).to appear_before(next_phase_name) - end - - budget.phases.each do |phase| - edit_phase_link = edit_admin_budget_budget_phase_path(budget, phase) - - within "#budget_phase_#{phase.id}" do - expect(page).to have_content(translated_phase_name(phase_kind: phase.kind)) - expect(page).to have_content("#{phase.starts_at.to_date} - #{phase.ends_at.to_date}") - expect(page).to have_css(".budget-phase-enabled.enabled") - expect(page).to have_link("Edit phase", href: edit_phase_link) - expect(page).to have_content("Active") if budget.current_phase == phase + within_table "Phases" do + within "tr", text: "Information" do + expect(page).to have_link "Edit phase" end end end @@ -402,7 +420,3 @@ describe "Admin budgets", :admin do end end end - -def translated_phase_name(phase_kind: kind) - I18n.t("budgets.phase.#{phase_kind}") -end diff --git a/spec/system/admin/translatable_spec.rb b/spec/system/admin/translatable_spec.rb index 0e0e2ae18..b39ac2646 100644 --- a/spec/system/admin/translatable_spec.rb +++ b/spec/system/admin/translatable_spec.rb @@ -416,7 +416,7 @@ describe "Admin edit translatable records", :admin do let(:translatable) { create(:budget).phases.last } scenario "Shows first available fallback" do - translatable.update!({ description_fr: "Phase en Français", summary_fr: "Phase résumé" }) + translatable.update!({ name_fr: "Name en Français", description_fr: "Phase en Français" }) visit edit_admin_budget_budget_phase_path(translatable.budget, translatable) @@ -428,8 +428,9 @@ describe "Admin edit translatable records", :admin do click_button "Save changes" visit budgets_path + click_link "Name en Français" - expect(page).to have_content "Phase résumé" + expect(page).to have_content "Phase en Français" end end diff --git a/spec/system/budgets/budgets_spec.rb b/spec/system/budgets/budgets_spec.rb index 7e9c31723..f3e0ba3a2 100644 --- a/spec/system/budgets/budgets_spec.rb +++ b/spec/system/budgets/budgets_spec.rb @@ -38,19 +38,21 @@ describe "Budgets" do visit budgets_path - within("#budget_heading") do + within(".budget-header") do expect(page).to have_content(budget.name) expect(page).to have_content(budget.description) - expect(page).to have_content("Actual phase") - expect(page).to have_content("Information") expect(page).to have_link("Help with participatory budgets") - expect(page).to have_link("See all phases") + end + + within(".budget-subheader") do + expect(page).to have_content "Current phase" + expect(page).to have_content "Information" end budget.update!(phase: "publishing_prices") visit budgets_path - within("#budget_heading") do + within(".budget-subheader") do expect(page).to have_content("Publishing projects prices") end @@ -208,63 +210,72 @@ describe "Budgets" do phases.informing.update!(starts_at: "30-12-2017", ends_at: "31-12-2017", enabled: true, description: "Description of informing phase", - summary: "

This is the summary for informing phase

") + name: "Custom name for informing phase") phases.accepting.update!(starts_at: "01-01-2018", ends_at: "10-01-2018", enabled: true, description: "Description of accepting phase", - summary: "This is the summary for accepting phase") + name: "Custom name for accepting phase") phases.reviewing.update!(starts_at: "11-01-2018", ends_at: "20-01-2018", enabled: false, - description: "Description of reviewing phase", - summary: "This is the summary for reviewing phase") + description: "Description of reviewing phase") phases.selecting.update!(starts_at: "21-01-2018", ends_at: "01-02-2018", enabled: true, description: "Description of selecting phase", - summary: "This is the summary for selecting phase") + name: "Custom name for selecting phase") phases.valuating.update!(starts_at: "10-02-2018", ends_at: "20-02-2018", enabled: false, - description: "Description of valuating phase", - summary: "This is the summary for valuating phase") + description: "Description of valuating phase") phases.publishing_prices.update!(starts_at: "21-02-2018", ends_at: "01-03-2018", enabled: false, - description: "Description of publishing prices phase", - summary: "This is the summary for publishing_prices phase") + description: "Description of publishing prices phase") phases.balloting.update!(starts_at: "02-03-2018", ends_at: "10-03-2018", enabled: true, - description: "Description of balloting phase", - summary: "This is the summary for balloting phase") + description: "Description of balloting phase") phases.reviewing_ballots.update!(starts_at: "11-03-2018", ends_at: "20-03-2018", enabled: false, - description: "Description of reviewing ballots phase", - summary: "This is the summary for reviewing_ballots phase") + description: "Description of reviewing ballots phase") phases.finished.update!(starts_at: "21-03-2018", ends_at: "30-03-2018", enabled: true, - description: "Description of finished phase", - summary: "This is the summary for finished phase") + description: "Description of finished phase") visit budgets_path - expect(page).not_to have_content "This is the summary for reviewing phase" + expect(page).not_to have_content "Description of reviewing phase" expect(page).not_to have_content "January 11, 2018 - January 20, 2018" - expect(page).not_to have_content "This is the summary for valuating phase" + expect(page).not_to have_content "Description of valuating phase" expect(page).not_to have_content "February 10, 2018 - February 20, 2018" - expect(page).not_to have_content "This is the summary for publishing_prices phase" + expect(page).not_to have_content "Description of publishing_prices phase" expect(page).not_to have_content "February 21, 2018 - March 01, 2018" - expect(page).not_to have_content "This is the summary for reviewing_ballots phase" + expect(page).not_to have_content "Description of reviewing_ballots phase" expect(page).not_to have_content "March 11, 2018 - March 20, 2018" - expect(page).to have_content "This is the summary for informing phase" + expect(page).to have_content "Description of informing phase" expect(page).to have_content "December 30, 2017 - December 31, 2017" - expect(page).to have_content "This is the summary for accepting phase" + expect(page).to have_content "Description of accepting phase" expect(page).to have_content "January 01, 2018 - January 20, 2018" - expect(page).to have_content "This is the summary for selecting phase" + expect(page).to have_content "Description of selecting phase" expect(page).to have_content "January 21, 2018 - March 01, 2018" - expect(page).to have_content "This is the summary for balloting phase" + expect(page).to have_content "Description of balloting phase" expect(page).to have_content "March 02, 2018 - March 20, 2018" - expect(page).to have_content "This is the summary for finished phase" + expect(page).to have_content "Description of finished phase" expect(page).to have_content "March 21, 2018 - March 29, 2018" - expect(page).to have_css(".phase.is-active", count: 1) + expect(page).to have_css(".tabs-panel.is-active", count: 1) + + within("#budget_phases_tabs") do + expect(page).to have_link "Custom name for informing phase" + expect(page).to have_link "Custom name for accepting phase" + expect(page).to have_link "Custom name for selecting phase" + expect(page).to have_link phases.balloting.name + expect(page).to have_link "Current phase #{phases.finished.name}" + end + + click_link "Custom name for accepting phase" + + within("#2-custom-name-for-accepting-phase") do + expect(page).to have_link("Previous phase", href: "#1-custom-name-for-informing-phase") + expect(page).to have_link("Next phase", href: "#3-custom-name-for-selecting-phase") + end end context "Index map" do