Merge pull request #4374 from consul/budget_phases

Change budget phases design
This commit is contained in:
Javi Martín
2021-03-12 17:10:16 +01:00
committed by GitHub
55 changed files with 1088 additions and 516 deletions

View File

@@ -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: (

View File

@@ -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-"] {

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -21,4 +21,10 @@
text-transform: uppercase;
}
}
td time:last-of-type::after,
td small::before {
content: "";
display: block;
}
}

View File

@@ -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 {

View File

@@ -32,6 +32,7 @@
@import "sticky_overrides";
@import "tags";
@import "admin/**/*";
@import "budgets/*";
@import "sdg/**/*";
@import "sdg_management/*";
@import "sdg_management/**/*";

View File

@@ -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%;
}
}
}

View File

@@ -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;
}
}

View File

@@ -2155,7 +2155,7 @@ table {
}
}
+ .budget.expanded,
+ .budget-header,
+ .jumbo {
margin-top: 0;
}

View File

@@ -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 {

View File

@@ -0,0 +1,40 @@
<% if phases.present? %>
<table class="budget-phases-table table-for-mobile">
<caption><%= t("admin.budgets.edit.phases_caption") %></caption>
<thead>
<tr>
<th><%= t("admin.budgets.edit.phase") %></th>
<th><%= t("admin.budgets.edit.duration") %></th>
<th class="text-center"><%= t("admin.budgets.edit.enabled") %></th>
<th><%= t("admin.budgets.edit.actions") %></th>
</tr>
</thead>
<% phases.each do |phase| %>
<tr id="<%= dom_id(phase) %>" class="phase">
<td>
<%= phase.name %>
<% if phase.current? %>
<span class="label success"><strong><%= t("admin.budgets.edit.active") %></strong></span>
<% end %>
</td>
<td>
<% if phase.starts_at.present? || phase.ends_at.present? %>
<%= dates(phase) %>
<% else %>
<em><%= t("admin.budgets.edit.blank_dates") %></em>
<% end %>
</td>
<td class="text-center">
<%= enabled_text(phase) %>
</td>
<td>
<%= render Admin::TableActionsComponent.new(phase,
actions: [:edit],
edit_text: t("admin.budgets.edit.edit_phase")
) %>
</td>
</tr>
<% end %>
</table>
<% end %>

View File

@@ -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

View File

@@ -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

View File

@@ -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? %>
<h3><%= page_entries_info budgets %></h3>
<table class="budgets-table">
<thead>
<tr>
<th><%= t("admin.budgets.index.table_name") %></th>
<th><%= t("admin.budgets.index.table_phase") %></th>
<th><%= t("admin.budgets.index.table_duration") %></th>
<th><%= t("admin.actions.actions") %></th>
</tr>
</thead>
<tbody>
<% budgets.each do |budget| %>
<tr id="<%= dom_id(budget) %>" class="budget">
<td class="<%= "budget-completed" if budget.finished? %>">
<% if budget.finished? %>
<span>
<%= t("admin.budgets.index.table_completed") %>
</span>
<% end %>
<strong><%= budget.name %></strong>
</td>
<td>
<%= budget.current_phase.name %>
<small><%= phase_progress_text(budget) %></small>
</td>
<td>
<%= dates(budget) %>
<%= duration(budget) %>
</td>
<td>
<%= render Admin::Budgets::TableActionsComponent.new(budget) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= paginate budgets %>
<% else %>
<div class="callout primary">
<%= t("admin.budgets.index.no_budgets") %>
</div>
<% end %>

View File

@@ -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

View File

@@ -0,0 +1,53 @@
<section class="budget-phases">
<header>
<h2><%= t("budgets.index.all_phases") %></h2>
</header>
<ul class="phases-list tabs" data-tabs id="budget_phases_tabs" data-deep-link="true">
<% phases.each do |phase| %>
<li class="phase-title tabs-title <%= "is-active current-phase-tab" if phase.current? %>">
<a href="#<%= phase_dom_id(phase) %>">
<% if phase.current? %>
<span class="current-phase-timeline"><%= t("budgets.index.current_phase") %></span>
<% end %>
<span class="phase-name"><%= phase.name %></span>
</a>
</li>
<% end %>
</ul>
<div class="tabs-content" data-tabs-content="budget_phases_tabs">
<% phases.each do |phase| %>
<div id="<%= phase_dom_id(phase) %>" class="tabs-panel <%= "is-active" if phase.current? %>">
<div class="tabs-controls">
<% if phase == phases.first %>
<span class="budget-prev-phase-disabled"></span>
<% else %>
<a href="#<%= prev_phase_dom_id(phase) %>" title="<%= t("budgets.index.prev_phase") %>"
data-turbolinks="false"
class="budget-prev-phase">
<span><%= t("budgets.index.prev_phase") %></span>
</a>
<% end %>
<% if phase == phases.last %>
<span class="budget-next-phase-disabled"></span>
<% else %>
<a href="#<%= next_phase_dom_id(phase) %>" title="<%= t("budgets.index.next_phase") %>"
data-turbolinks="false"
class="budget-next-phase">
<span><%= t("budgets.index.next_phase") %></span>
</a>
<% end %>
</div>
<div class="budget-phase">
<h3><%= phase.name %></h3>
<p><%= start_date(phase) %> - <%= end_date(phase) %></p>
<%= auto_link_already_sanitized_html(wysiwyg(phase.description)) %>
</div>
</div>
<% end %>
</div>
</section>

View File

@@ -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

View File

@@ -0,0 +1,38 @@
<div class="budget-subheader">
<div class="small-12 medium-8 column info">
<span class="current-phase"><strong><%= t("budgets.index.current_phase") %></strong></span>
<h2><%= budget.current_phase.name %></h2>
</div>
<div class="small-12 medium-4 column">
<% if budget.accepting? %>
<% if current_user %>
<% if current_user.level_two_or_three_verified? %>
<div class="text-right">
<%= link_to t("budgets.investments.index.sidebar.create"),
new_budget_investment_path(budget),
class: "button" %>
</div>
<% else %>
<div class="callout warning">
<%= sanitize(t("budgets.investments.index.sidebar.verified_only",
verify: link_to_verify_account)) %>
</div>
<% end %>
<% else %>
<div class="callout primary">
<%= sanitize(t("budgets.investments.index.sidebar.not_logged_in",
sign_in: link_to_signin, sign_up: link_to_signup)) %>
</div>
<% end %>
<% end %>
<% if can?(:read_results, budget) %>
<div class="text-right">
<%= link_to t("budgets.show.see_results"),
budget_results_path(budget, heading_id: budget.headings.first),
class: "button expanded" %>
</div>
<% end %>
</div>
</div>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 %>
<div class="row">
<div class="small-12 medium-6 column">
<fieldset>
<legend aria-describedby="phase_duration_description">
<%= t("admin.budget_phases.edit.duration") %>
</legend>
<p class="help-text" id="phase_duration_description">
<%= t("admin.budget_phases.edit.duration_description") %>
</p>
<div class="date-field">
<%= f.date_field :starts_at, id: "start_date" %>
</div>
<div class="small-12 medium-6 column">
<div class="date-field">
<%= f.date_field :ends_at, id: "end_date" %>
</div>
</fieldset>
<div class="small-12 column margin">
<%= f.check_box :enabled %>
<span class="help-text" id="phase-summary-help-text">
<%= t("admin.budget_phases.edit.enabled_help_text") %>
</span>
</div>
<div class="row">
<%= f.translatable_fields do |translations_form| %>
<div class="small-12 column">
<%= translations_form.text_area :description,
maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH,
class: "html-area",
hint: t("admin.budget_phases.edit.description_help_text") %>
</div>
<div class="small-12 column">
<%= translations_form.text_area :summary,
maxlength: Budget::Phase::SUMMARY_MAX_LENGTH,
class: "html-area",
hint: t("admin.budget_phases.edit.summary_help_text") %>
</div>
<% end %>
</div>
<div class="row">
<div class="small-12 column margin-top">
<%= f.check_box :enabled %>
<span class="help-text" id="phase-summary-help-text">
<%= t("admin.budget_phases.edit.enabled_help_text") %>
</span>
<%= f.translatable_fields do |translations_form| %>
<div class="small-12 column">
<%= translations_form.text_field :name, hint: t("admin.budget_phases.edit.name_help_text") %>
</div>
</div>
<div class="row">
<div class="small-12 medium-4 large-3 column end margin-top">
<%= f.submit t("admin.budget_phases.edit.save_changes"), class: "button success expanded" %>
<div class="small-12 column">
<%= translations_form.text_area :description,
maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH,
class: "html-area",
hint: t("admin.budget_phases.edit.description_help_text") %>
</div>
</div>
<% end %>
<div class="small-12 column">
<%= f.submit t("admin.budget_phases.edit.save_changes"), class: "button success" %>
</div>
<% end %>

View File

@@ -1,9 +1,5 @@
<%= back_link_to edit_admin_budget_path(@phase.budget) %>
<div class="row">
<div class="small-12 column">
<h2><%= t("admin.budgets.edit.title") %> - <%= t("budgets.phase.#{@phase.kind}") %></h2>
</div>
</div>
<h2><%= t("admin.budgets.edit.title") %> - <%= @phase.name %></h2>
<%= render "/admin/budget_phases/form" %>

View File

@@ -44,48 +44,7 @@
</div>
<%= render Admin::Budgets::HelpComponent.new("budget_phases") %>
<% if @budget.phases.present? %>
<table id="budget-phases-table" class="table-for-mobile">
<thead>
<tr>
<th><%= t("admin.budgets.edit.phase") %></th>
<th><%= t("admin.budgets.edit.dates") %></th>
<th class="text-center"><%= t("admin.budgets.edit.enabled") %></th>
<th><%= t("admin.budgets.edit.actions") %></th>
</tr>
</thead>
<% @budget.phases.order(:id).each do |phase| %>
<tr id="<%= dom_id(phase) %>" class="phase">
<td>
<%= t("budgets.phase.#{phase.kind}") %>
<% if @budget.current_phase == phase %>
<span class="label success"><strong><%= t("admin.budgets.edit.active") %></strong></span>
<% end %>
</td>
<td>
<% 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 %>
<em><%= t("admin.budgets.edit.blank_dates") %></em>
<% end %>
</td>
<td class="text-center">
<span class="budget-phase-enabled <%= phase.enabled? ? "enabled" : "disabled" %>"></span>
</td>
<td>
<%= render Admin::TableActionsComponent.new(phase,
actions: [:edit],
edit_text: t("admin.budgets.edit.edit_phase")
) %>
</td>
</tr>
<% end %>
</table>
<% end %>
<%= render Admin::BudgetPhases::PhasesComponent.new(@budget) %>
</fieldset>
<% if @budget.persisted? %>

View File

@@ -1,47 +1 @@
<header>
<h2><%= t("admin.budgets.index.title") %></h2>
<%= link_to t("admin.budgets.index.new_link"), new_admin_budget_path %>
</header>
<%= render Admin::Budgets::HelpComponent.new("budgets") %>
<%= render "shared/filter_subnav", i18n_namespace: "admin.budgets.index" %>
<% if @budgets.any? %>
<h3><%= page_entries_info @budgets %></h3>
<table class="budgets-table">
<thead>
<tr>
<th><%= t("admin.budgets.index.table_name") %></th>
<th><%= t("admin.budgets.index.table_phase") %></th>
<th><%= t("admin.actions.actions") %></th>
</tr>
</thead>
<tbody>
<% @budgets.each do |budget| %>
<tr id="<%= dom_id(budget) %>" class="budget">
<td class="<%= "budget-completed" if budget.finished? %>">
<% if budget.finished? %>
<span>
<%= t("admin.budgets.index.table_completed") %>
</span>
<% end %>
<strong><%= budget.name %></strong>
</td>
<td class="small">
<%= t("budgets.phase.#{budget.phase}") %>
</td>
<td>
<%= render Admin::Budgets::TableActionsComponent.new(budget) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= paginate @budgets %>
<% else %>
<div class="callout primary">
<%= t("admin.budgets.index.no_budgets") %>
</div>
<% end %>
<%= render Admin::Budgets::IndexComponent.new(@budgets) %>

View File

@@ -1,3 +1,7 @@
<h2><%= t("admin.budgets.new.title") %></h2>
<%= back_link_to admin_budgets_path %>
<header>
<h2><%= t("admin.budgets.new.title") %></h2>
</header>
<%= render "/admin/budgets/form" %>

View File

@@ -1,31 +1,19 @@
<div class="row margin-top">
<div class="small-12 medium-9 column">
<div class="small-12 column">
<h2><%= t("budgets.index.finished_budgets") %></h2>
<div id="finished_budgets" class="budget-investments-list">
<div id="finished_budgets">
<% budgets.each do |budget| %>
<div class="budget-investment clear">
<div class="panel past-budgets">
<div class="row" data-equalizer data-equalizer-on="medium">
<div class="small-12 medium-6 column table" data-equalizer-watch>
<div class="table-cell align-middle">
<h3><%= budget.name %></h3>
</div>
</div>
<h3><%= budget.name %></h3>
<div class="small-12 medium-6 column table" data-equalizer-watch>
<div id="budget_<%= budget.id %>_results" class="table-cell align-middle">
<% if can?(:read_results, budget) %>
<%= link_to t("budgets.index.see_results"),
budget_results_path(budget),
class: "button" %>
<% end %>
<div id="budget_<%= budget.id %>_results">
<% 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" %>
</div>
</div>
<%= link_to t("budgets.index.milestones"), budget_executions_path(budget), class: "button" %>
</div>
</div>
</div>

View File

@@ -1,13 +0,0 @@
<ul class="no-bullet budget-timeline">
<% current_budget.published_phases.each do |phase| %>
<li class="phase <%= "is-active" if phase == current_budget.current_phase %>">
<h3><%= t("budgets.phase.#{phase.kind}") %></h3>
<span>
<%= 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? %>
</span>
<p><%= auto_link_already_sanitized_html(wysiwyg(phase.summary)) %></p>
</li>
<% end %>
</ul>

View File

@@ -1,4 +1,4 @@
<div class="expanded budget no-margin-top padding">
<div class="budget-header">
<div class="row">
<%= back_link_to @ballot_referer %>

View File

@@ -2,9 +2,9 @@
<%= render "shared/canonical", href: budget_group_url(filter: @current_filter) %>
<% end %>
<div class="expanded budget no-margin-top">
<div class="budget-header">
<div class="row">
<div class="small-12 medium-9 column padding">
<div class="small-12 medium-9 column">
<%= back_link_to budgets_path %>
<h2><%= t("budgets.groups.show.title") %></h2>
</div>

View File

@@ -7,55 +7,23 @@
<% end %>
<% if current_budget.present? %>
<div id="budget_heading" class="expanded budget">
<div class="row" data-equalizer data-equalizer-on="medium">
<div class="small-12 medium-9 column padding" data-equalizer-watch>
<h1><%= current_budget.name %></h1>
<div class="description">
<%= auto_link_already_sanitized_html wysiwyg(current_budget.description) %>
</div>
<p>
<%= link_to t("budgets.index.section_header.help"), "#section_help" %>
</p>
<div class="budget-header">
<div class="row">
<div class="small-12 column">
<h1><%= current_budget.name %></h1>
<div class="description">
<%= auto_link_already_sanitized_html wysiwyg(current_budget.description) %>
</div>
<div class="small-12 medium-3 column info padding" data-equalizer-watch>
<p>
<strong><%= t("budgets.show.phase") %></strong>
</p>
<h2><%= t("budgets.phase.#{current_budget.phase}") %></h2>
<%= 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 %>
<div class="callout warning margin-top">
<%= sanitize(t("budgets.investments.index.sidebar.verified_only",
verify: link_to_verify_account)) %>
</div>
<% end %>
<% else %>
<div class="callout primary margin-top">
<%= sanitize(t("budgets.investments.index.sidebar.not_logged_in",
sign_in: link_to_signin, sign_up: link_to_signup)) %>
</div>
<% 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 %>
<p>
<%= link_to t("budgets.index.section_header.help"), "#section_help" %>
</p>
</div>
</div>
</div>
<%= render Budgets::SubheaderComponent.new(current_budget) %>
<%= render Budgets::PhasesComponent.new(current_budget) %>
<div id="budget_info" class="budget-info">
<div class="row margin-top">
<div class="small-12 column">
@@ -111,11 +79,6 @@
<% end %>
</ul>
<% end %>
<div id="all_phases">
<h2><%= t("budgets.index.all_phases") %></h2>
<%= render "phases", budget: current_budget %>
</div>
</div>
</div>
@@ -124,9 +87,9 @@
<% end %>
</div>
<% else %>
<div class="expanded budget no-margin-top margin-bottom">
<div class="budget-header margin-bottom">
<div class="row">
<div class="small-12 medium-9 column padding">
<div class="small-12 medium-9 column">
<h1><%= t("budgets.index.title") %></h1>
</div>
</div>

View File

@@ -2,48 +2,20 @@
<%= render "shared/canonical", href: budget_url(@budget, filter: @current_filter) %>
<% end %>
<div class="expanded budget no-margin-top">
<div class="row" data-equalizer data-equalizer-on="medium">
<div class="small-12 medium-9 column padding" data-equalizer-watch>
<div class="budget-header">
<div class="row">
<div class="small-12 column">
<%= back_link_to budgets_path %>
<h1><%= @budget.name %></h1>
<%= auto_link_already_sanitized_html wysiwyg(@budget.description) %>
</div>
<div class="small-12 medium-3 column info padding" data-equalizer-watch>
<p>
<strong><%= t("budgets.show.phase") %></strong>
</p>
<h2><%= t("budgets.phase.#{@budget.phase}") %></h2>
<% 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 %>
<div class="callout warning margin-top">
<%= sanitize(t("budgets.investments.index.sidebar.verified_only",
verify: link_to_verify_account)) %>
</div>
<% end %>
<% else %>
<div class="callout primary margin-top">
<%= sanitize(t("budgets.investments.index.sidebar.not_logged_in",
sign_in: link_to_signin, sign_up: link_to_signup)) %>
</div>
<% end %>
<% end %>
<% if can?(:read_results, @budget) %>
<%= link_to t("budgets.show.see_results"),
budget_results_path(@budget),
class: "button margin-top expanded" %>
<% end %>
</div>
</div>
</div>
<%= render Budgets::SubheaderComponent.new(@budget) %>
<div class="row margin">
<div class="small-12 medium-9 column">
<% if @current_filter == "unfeasible" %>

View File

@@ -13,7 +13,7 @@
<% @budgets.each do |budget| %>
<tr id="<%= dom_id(budget) %>">
<td><%= budget.name %></td>
<td><%= budget.translated_phase %></td>
<td><%= budget.current_phase.name %></td>
<td align="right">
<%= link_to t("management.budgets.create_new_investment"),
new_management_budget_investment_path(budget),

View File

@@ -11,7 +11,7 @@
<tbody>
<tr id="<%= dom_id(@budget) %>">
<td><%= @budget.name %></td>
<td><%= @budget.translated_phase %></td>
<td><%= @budget.current_phase.name %></td>
<td align="right">
<%= link_to t("management.budgets.print_investments"),
print_management_budget_investments_path(@budget) %>

View File

@@ -13,7 +13,7 @@
<% @budgets.each do |budget| %>
<tr id="<%= dom_id(budget) %>">
<td><%= budget.name %></td>
<td><%= budget.translated_phase %></td>
<td><%= budget.current_phase.name %></td>
<td align="right">
<%= link_to t("management.budgets.support_investments"),
management_budget_investments_path(budget),

View File

@@ -16,7 +16,7 @@
<%= @budget.name %>
</td>
<td>
<%= t("budgets.phase.#{@budget.phase}") %>
<%= @budget.current_phase.name %>
</td>
<td>
<%= @investments.count %>

View File

@@ -230,6 +230,7 @@ en:
ends_at: "End date"
starts_at: "Start date"
budget/phase/translation:
name: "Name"
description: "Description"
summary: "Summary"
comment:

View File

@@ -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."

View File

@@ -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 <strong>%{count}</strong> projects out of <strong>%{limit}</strong>"
show:
group: Group
phase: Actual phase
unfeasible_title: Unfeasible investments
unfeasible: See unfeasible investments
unselected_title: Investments not selected for balloting phase

View File

@@ -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:

View File

@@ -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."

View File

@@ -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 <strong>%{count}</strong> proyectos de <strong>%{limit}</strong>"
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

View File

@@ -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!

View File

@@ -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

View File

@@ -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

View File

@@ -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 << "<br>"
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

View File

@@ -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

View File

@@ -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

View File

@@ -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<br>English summary"
expect(budget_phase.description_es).to eq "Spanish description"
expect(budget_phase.description_fr).to eq "French description<br>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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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: "<p>This is the summary for informing phase</p>")
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