diff --git a/app/assets/javascripts/stories.js b/app/assets/javascripts/stories.js
index 909018e0..5e7da51b 100644
--- a/app/assets/javascripts/stories.js
+++ b/app/assets/javascripts/stories.js
@@ -26,3 +26,20 @@ document.addEventListener("DOMContentLoaded", () => {
});
});
});
+
+function updateStatusButton(color, status) {
+ const button = document.querySelector(".story-title .dropdown-wrapper > button");
+ button.className = `button ${color}`;
+
+ const span = button.querySelector("span");
+ span.textContent = status;
+
+ document.querySelector(":focus").blur();
+}
+
+function updateStatusLabel(status, storyId) {
+ let row = document.getElementById(`story_${storyId}`)
+ status_label = row.querySelector(".status > .story-status-badge")
+ status_label.textContent = status
+ status_label.classList.value = `story-status-badge ${status}`
+}
diff --git a/app/assets/stylesheets/3-atoms/_badges.scss b/app/assets/stylesheets/3-atoms/_badges.scss
index 2e170082..8a7ef17f 100644
--- a/app/assets/stylesheets/3-atoms/_badges.scss
+++ b/app/assets/stylesheets/3-atoms/_badges.scss
@@ -23,3 +23,29 @@
background-color: #57ce81;
}
}
+
+.story-status-badge {
+ display: inline-block;
+ padding: 7px 15px;
+ font-weight: 600;
+ line-height: 1;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: 20px;
+ font-size: 11px;
+ background-color: $orange;
+
+ &.approved {
+ background-color: $green;
+ border-color: #1d4ed8;
+ color: $white;
+ }
+
+ &.rejected {
+ background-color: $magenta;
+ border-color: #d77e72;
+ color: $white;
+ }
+
+}
diff --git a/app/assets/stylesheets/4-molecules/_tables.scss b/app/assets/stylesheets/4-molecules/_tables.scss
index fcf3c6d8..6f0b852e 100644
--- a/app/assets/stylesheets/4-molecules/_tables.scss
+++ b/app/assets/stylesheets/4-molecules/_tables.scss
@@ -11,7 +11,7 @@
}
.project-table__row {
display: grid;
- grid-template-columns: 1fr 70px 70px 260px;
+ grid-template-columns: 1fr 100px 70px 70px 260px;
align-items: center;
padding: 10px 0;
&.project-table__row--reports {
diff --git a/app/assets/stylesheets/stories.scss b/app/assets/stylesheets/stories.scss
index c9dc1431..3ace7f06 100644
--- a/app/assets/stylesheets/stories.scss
+++ b/app/assets/stylesheets/stories.scss
@@ -10,10 +10,12 @@
word-break: break-all;
}
-.story-description, .extra-info, .story_preview {
+.story-description,
+.extra-info,
+.story_preview {
margin-bottom: 25px;
font-size: 15px;
- p{
+ p {
margin-top: 1em;
}
em {
@@ -55,7 +57,6 @@
margin-bottom: 1.5em;
}
-
.modal p {
padding-bottom: 1.3em;
}
@@ -72,6 +73,7 @@
"title preview"
"description preview"
"extra extra-preview"
+ "status ."
"submit .";
grid-template-columns: repeat(2, minmax(50%, 1fr));
grid-column-gap: 10px;
@@ -94,6 +96,10 @@
&.story_extra_info {
grid-area: extra;
}
+
+ &.story_status {
+ grid-area: status;
+ }
}
.story_preview {
@@ -104,7 +110,8 @@
grid-area: extra-preview;
}
- .extra_info_preview .content, .story_preview .content {
+ .extra_info_preview .content,
+ .story_preview .content {
overflow: auto;
max-height: min(50vh, 700px);
// prevent long links from overflowing
@@ -131,6 +138,12 @@
padding: 5px;
}
+.story-title {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
.comments-section {
margin: 16px 0;
diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb
index a810161d..521dbee8 100644
--- a/app/controllers/stories_controller.rb
+++ b/app/controllers/stories_controller.rb
@@ -1,8 +1,8 @@
require "csv"
class StoriesController < ApplicationController
before_action :authenticate_user!
- before_action :find_project, except: [:bulk_destroy, :render_markdown, :edit, :update, :destroy, :show, :move]
- before_action :find_story, only: [:edit, :update, :destroy, :show, :move]
+ before_action :find_project, except: [:bulk_destroy, :render_markdown, :edit, :update, :destroy, :show, :move, :approve, :reject, :pending]
+ before_action :find_story, only: [:edit, :update, :destroy, :show, :move, :approve, :reject, :pending]
before_action :validate_url_product_id, only: [:edit, :update, :destroy, :show, :move]
before_action :ensure_unarchived!, except: [:show, :bulk_destroy, :render_markdown, :move]
@@ -127,6 +127,27 @@ def move
redirect_to @project
end
+ def approve
+ @story.approved!
+ respond_to do |format|
+ format.js { render "shared/update_status" }
+ end
+ end
+
+ def reject
+ @story.rejected!
+ respond_to do |format|
+ format.js { render "shared/update_status" }
+ end
+ end
+
+ def pending
+ @story.pending!
+ respond_to do |format|
+ format.js { render "shared/update_status" }
+ end
+ end
+
private
def find_project
@@ -143,7 +164,7 @@ def validate_url_product_id
end
def stories_params
- params.require(:story).permit(:title, :description, :extra_info, :project_id)
+ params.require(:story).permit(:title, :description, :extra_info, :project_id, :status)
end
def expected_csv_headers?(file)
diff --git a/app/helpers/stories_helper.rb b/app/helpers/stories_helper.rb
index 43e5cd8f..126f0ba8 100644
--- a/app/helpers/stories_helper.rb
+++ b/app/helpers/stories_helper.rb
@@ -1,2 +1,18 @@
module StoriesHelper
+ def status_label(story)
+ "#{story.status}".html_safe
+ end
+
+ def status_color(story)
+ return "green" if @story.approved?
+ return "magenta" if @story.rejected?
+
+ "orange"
+ end
+
+ def options_for_status_select(story, action)
+ return options_for_select({"Pending" => "pending", "Approved" => "approved"}, selected: story.status) if action == "new"
+
+ options_for_select({"Pending" => "pending", "Approved" => "approved", "Rejected" => "rejected"}, selected: story.status)
+ end
end
diff --git a/app/models/story.rb b/app/models/story.rb
index 07c0f439..572832b0 100644
--- a/app/models/story.rb
+++ b/app/models/story.rb
@@ -8,6 +8,8 @@ class Story < ApplicationRecord
before_create :add_position
+ enum :status, [:pending, :approved, :rejected]
+
scope :by_position, -> { order("stories.position ASC NULLS FIRST, stories.created_at ASC") }
def best_estimate_average
diff --git a/app/views/estimates/_update_row.js.erb b/app/views/estimates/_update_row.js.erb
index 6d7e1622..01260184 100644
--- a/app/views/estimates/_update_row.js.erb
+++ b/app/views/estimates/_update_row.js.erb
@@ -1,7 +1,7 @@
-let row = document.getElementById("story_<%= estimate.story_id %>")
-row.children[1].innerText = "<%= j(estimate.best_case_points.to_s) %>"
-row.children[2].innerText = "<%= j(estimate.worst_case_points.to_s) %>"
+updateStatusLabel("<%= estimate.story.status %>", "<%= estimate.story_id %>")
-let totals_row = document.querySelector('.project-table tfoot tr')
-totals_row.children[1].innerText = "<%= j @project.best_estimate_sum_per_user(current_user) %>"
-totals_row.children[2].innerText = "<%= j @project.worst_estimate_sum_per_user(current_user) %>"
\ No newline at end of file
+document.getElementById("best_estimate_<%= estimate.story_id %>").innerText = "<%= j(estimate.best_case_points.to_s) %>"
+document.getElementById("worst_estimate_<%= estimate.story_id %>").innerText = "<%= j(estimate.worst_case_points.to_s) %>"
+
+document.querySelector('.project-table tfoot tr > .best_estimates_total').innerText = "<%= j @project.best_estimate_sum_per_user(current_user) %>"
+document.querySelector('.project-table tfoot tr > .worst_estimates_total').innerText = "<%= j @project.worst_estimate_sum_per_user(current_user) %>"
diff --git a/app/views/estimates/create.js.erb b/app/views/estimates/create.js.erb
index 913e3a54..1f3aaf4c 100644
--- a/app/views/estimates/create.js.erb
+++ b/app/views/estimates/create.js.erb
@@ -1,11 +1,11 @@
(function(){
<% if @estimate.persisted? %>
<%= render partial: 'update_row', locals: {estimate: @estimate} %>
- const addEstimate = row.querySelector('.add-estimate')
+ const addEstimate = document.getElementById("story_<%= @estimate.story_id %>").querySelector('.add-estimate')
addEstimate.insertAdjacentHTML('afterend', "<%= j(link_to 'Edit Estimate', edit_project_story_estimate_path(@project.id, @estimate.story, @estimate.id), class: "button edit-estimate", remote: true) %>")
addEstimate.remove()
closeModal()
<% else %>
updateModal("New estimate", "<%= j(render partial: 'modal_body') %>")
<% end %>
-})()
\ No newline at end of file
+})()
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
index 9620c314..da676373 100644
--- a/app/views/projects/show.html.erb
+++ b/app/views/projects/show.html.erb
@@ -14,6 +14,7 @@
Story Title
+ Status
Best
EstimateWorst
Estimate
@@ -25,14 +26,15 @@