Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[162] Build UI - Eval Form Step 2 - Evaluation Criteria Add/Remove Criteria #219

Merged
merged 31 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
64b9841
162 Initial eval criteria commit for eval form
cpreisinger Oct 18, 2024
16ed9dc
162 Initial temp styling for eval criteria
cpreisinger Oct 18, 2024
13c836e
Merge branch 'dev' of github.com:GSA/Challenge_platform into 162/eval…
cpreisinger Oct 18, 2024
9ade174
164 Add support for binary scale to eval criteria
cpreisinger Oct 22, 2024
765ae15
165 Add support for rating scale to eval criteria
cpreisinger Oct 22, 2024
a22b0e8
165 Fix bug with new criteria option labels
cpreisinger Oct 22, 2024
d77a3cc
165 Option label bug with form validation error
cpreisinger Oct 22, 2024
38d8f2c
165 Fix bug with rating options on new criteria
cpreisinger Oct 22, 2024
2af7b30
61 Accordion titles and collapse validity check
cpreisinger Oct 28, 2024
e55b445
Merge branch 'dev' of github.com:GSA/Challenge_platform into 162/eval…
cpreisinger Oct 28, 2024
ef80db5
Merge branch '165/evaluation-criteria-rating-scale' into 162/eval-for…
cpreisinger Oct 28, 2024
c23e016
61 Fix rspec failure with criteria option labels
cpreisinger Oct 28, 2024
eea695f
162 Initial eval criteria stimulus port
cpreisinger Oct 29, 2024
85725f6
Merge branch 'dev' of github.com:GSA/Challenge_platform into 162/eval…
cpreisinger Oct 29, 2024
db83008
update yarn.lock
stepchud Oct 29, 2024
3394377
61 Update dependency versions
cpreisinger Oct 29, 2024
1f1716f
Merge branch '162/eval-form-evaluation-criteria' of github.com:GSA/Ch…
cpreisinger Oct 29, 2024
027e1c4
set node-version for circleci
stepchud Oct 29, 2024
d3fb1be
61 Make eval criteria respect eval form disabled
cpreisinger Oct 29, 2024
7a7307f
61 Simplify eval criteria js for code climate1
cpreisinger Oct 29, 2024
9e04a71
61 Styling and add max length for eval criteria
cpreisinger Oct 30, 2024
a8afc05
61 Styled add another criteria button
cpreisinger Oct 30, 2024
2ac908c
61 Temp darker accordion styling for criteria
cpreisinger Oct 30, 2024
9d0f5b5
Merge branch 'dev' into 162/eval-form-evaluation-criteria
stepchud Oct 30, 2024
a631c66
Update .circleci/config.yml
stepchud Oct 30, 2024
a2e5321
revert structure.sql
stepchud Oct 30, 2024
ac91b8e
61 Remove unsued targets and code readability
cpreisinger Oct 31, 2024
6e2e07a
Merge branch '162/eval-form-evaluation-criteria' of github.com:GSA/Ch…
cpreisinger Oct 31, 2024
cfeb931
Merge branch 'dev' of github.com:GSA/Challenge_platform into 162/eval…
cpreisinger Oct 31, 2024
2d05253
61 Add the confirmation screen for eval form save
cpreisinger Oct 31, 2024
aba2de0
grammar
stepchud Oct 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ GEM
erubi (1.13.0)
factory_bot (6.5.0)
activesupport (>= 5.0.0)
faker (3.4.2)
faker (3.5.1)
i18n (>= 1.8.11, < 2)
faraday (2.12.0)
faraday-net_http (>= 2.0, < 3.4)
Expand Down Expand Up @@ -173,7 +173,7 @@ GEM
method_source (1.1.0)
mini_mime (1.1.5)
minitest (5.25.1)
msgpack (1.7.2)
msgpack (1.7.3)
net-http (0.4.1)
uri
net-imap (0.5.0)
Expand Down Expand Up @@ -266,13 +266,13 @@ GEM
regexp_parser (2.9.2)
reline (0.5.10)
io-console (~> 0.5)
rexml (3.3.8)
rspec-core (3.13.1)
rexml (3.3.9)
rspec-core (3.13.2)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.1)
rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (7.0.1)
Expand Down Expand Up @@ -303,14 +303,14 @@ GEM
rubocop-performance (1.22.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.26.2)
rubocop-rails (2.27.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.52.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-rspec (3.1.0)
rubocop-rspec (3.2.0)
rubocop (~> 1.61)
ruby-progressbar (1.13.0)
rubyzip (2.3.2)
Expand Down
Empty file removed app/assets/builds/.keep
Empty file.
17 changes: 17 additions & 0 deletions app/assets/stylesheets/application.sass.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,20 @@ dialog::backdrop {
background-color: black;
border-color: black;
}

#add-criteria-button.usa-button {
background-color: #4d8055;
}

// #criteria-list .usa-accordion__button {
// background-color: #162e51;
// background-image: url(asset-path('../builds/images/usa-icons-bg/add--white.svg', image)),linear-gradient(transparent,transparent);
// }

// #criteria-list .usa-accordion__button {
// background-color: #162e51;
// }

// #criteria-list .usa-accordion__button[aria-expanded=true] {
// background-image: url(assets/images/usa-icons-bg/remove-white.svg),linear-gradient(transparent,transparent);
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
// }
7 changes: 6 additions & 1 deletion app/controllers/evaluation_forms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ def set_evaluation_forms
def evaluation_form_params
permitted = params.require(:evaluation_form).
permit(:title, :instructions, :phase_id, :status, :comments_required,
:weighted_scoring, :publication_date, :closing_date, :challenge_id)
:weighted_scoring, :publication_date, :closing_date, :challenge_id,
evaluation_criteria_attributes: [
:id, :title, :description, :points_or_weight, :scoring_type,
:option_range_start, :option_range_end, :_destroy,
{ option_labels: {} }
])
closing_date = parse_closing_date(permitted[:closing_date])
closing_date ? permitted.merge({ closing_date: }) : permitted
end
Expand Down
20 changes: 20 additions & 0 deletions app/helpers/evaluation_forms_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ def inline_error(evaluation_form, field)
tag.span(error, class: "text-secondary font-body-2xs", id: "evaluation_form_#{field}_error")
end

def criteria_field_id(form, attribute, is_template)
prefix = "evaluation_form_evaluation_criteria_attributes"

if is_template
"#{prefix}_NEW_CRITERIA_#{attribute}"
else
"#{prefix}_#{form.options[:child_index]}_#{attribute}"
end
end

def criteria_field_name(form, attribute, is_template)
prefix = "evaluation_form[evaluation_criteria_attributes]"

if is_template
"#{prefix}[NEW_CRITERIA][#{attribute}]"
else
"#{prefix}[#{form.options[:child_index]}][#{attribute}]"
end
end

def eval_form_disabled?(evaluation_form)
evaluation_form.valid? && evaluation_form.phase.end_date < Time.zone.today
end
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/application.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import "./controllers"
import "./controllers";
221 changes: 221 additions & 0 deletions app/javascript/controllers/evaluation_criteria_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// app/javascript/controllers/evaluation_criteria_controller.js
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = [
"criteriaList",
"template",
"addButton",
"criteriaRow",
"scoringRadio",
"binaryOptions",
"ratingOptions",
"scaleOptions",
"hiddenOptionRangeStart",
"hiddenOptionRangeEnd",
"selectOptionRangeStart",
"selectOptionRangeEnd",
"criteriaLabelRow",
];

connect() {
this.counter = this.criteriaRowTargets.length;
}

addCriteria() {
this.counter++;
const newCriteria = this.templateTarget.cloneNode(true);

this.replacePlaceholders(newCriteria);
this.enableInputs(newCriteria);

this.criteriaListTarget.appendChild(newCriteria);

this.updateCriteriaTitles();
}

removeCriteria(event) {
stepchud marked this conversation as resolved.
Show resolved Hide resolved
const row = event.target.closest(".criteria-row");
const destroyField = row.querySelector(".destroy-evaluation-criteria");

if (destroyField) {
row.style.display = "none";
destroyField.value = "true";
this.disableInputs(row);
} else {
row.remove();
}

if (this.visibleRows().length == 0) {
return this.addCriteria();
}

this.updateCriteriaTitles();
}

toggleScoringType(event) {
const row = event.target.closest(".criteria-row");
const scoringType = row.querySelector(".scoring-type-radio:checked").value;

this.updateScoringOptions(row, scoringType);
}

toggleOptionRange(event) {
const row = event.target.closest(".criteria-row");
const start = row.querySelector(
".option-range-select.option-range-start"
).value;
const end = row.querySelector(
".option-range-select.option-range-end"
).value;

this.toggleOptionLabels(row, start, end);
}

replacePlaceholders(newCriteria) {
newCriteria.setAttribute("data-evaluation-criteria-target", "criteriaRow");
newCriteria.style.display = "block";
newCriteria.removeAttribute("id");

let accordionButton = newCriteria.querySelector(".usa-accordion__button");
let accordionContent = newCriteria.querySelector(".usa-accordion__content");

let accordionId = accordionContent
.getAttribute("id")
.replace("NEW_CRITERIA", this.counter);

accordionButton.setAttribute("aria-controls", accordionId);
accordionContent.setAttribute("id", accordionId);

newCriteria.querySelectorAll("[id]").forEach((el) => {
el.id = el.id.replace("NEW_CRITERIA", this.counter);
});

newCriteria.querySelectorAll("[name]").forEach((el) => {
el.name = el.name.replace("NEW_CRITERIA", this.counter);
});

newCriteria.querySelectorAll("label").forEach((label) => {
label.setAttribute(
"for",
label.getAttribute("for").replace("NEW_CRITERIA", this.counter)
);
});
}

updateCriteriaTitles() {
this.visibleRows().forEach((row, index) => {
const numberElement = row.querySelector(".criteria-number");
numberElement.textContent = index + 1;
});
}

updateScoringOptions(row, scoringType) {
const options = {
scaleOptions: row.querySelector(".criteria-scale-options"),
binaryOptions: row.querySelector(".criteria-binary-options"),
ratingOptions: row.querySelector(".criteria-rating-options"),
scaleOptionLabels: row.querySelector(".criteria-scale-option-labels"),
};

console.log(options);
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved

switch (scoringType) {
case "binary":
this.showBinaryOptions(options);
this.toggleOptionLabels(row, 0, 1);
break;
case "rating":
this.showRatingOptions(row, options);
break;
default:
this.hideAllOptions(options);
break;
}
}

showBinaryOptions(options) {
options.scaleOptions.style.display = "block";
options.binaryOptions.style.display = "block";
options.ratingOptions.style.display = "none";
this.enableInputs(options.binaryOptions);
this.disableInputs(options.ratingOptions);
}

showRatingOptions(row, options) {
options.scaleOptions.style.display = "block";
options.binaryOptions.style.display = "none";
options.ratingOptions.style.display = "block";
this.enableInputs(options.ratingOptions);
this.disableInputs(options.binaryOptions);
const start = parseInt(
row.querySelector(".option-range-select.option-range-start").value
);
const end = parseInt(
row.querySelector(".option-range-select.option-range-end").value
);
this.toggleOptionLabels(row, start, end);
}

hideAllOptions(options) {
options.scaleOptions.style.display = "none";
options.binaryOptions.style.display = "none";
options.ratingOptions.style.display = "none";
this.disableInputs(options.binaryOptions);
this.disableInputs(options.ratingOptions);
this.disableInputs(options.scaleOptionLabels);
}

toggleOptionLabels(row, start, end) {
row
.querySelectorAll(".criteria-option-label-row")
.forEach((labelRow, index) => {
labelRow.style.display =
index >= start && index <= end ? "flex" : "none";
const input = labelRow.querySelector("input");
input.disabled = !(index >= start && index <= end);
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
});
}

disableInputs(container) {
container.querySelectorAll("input, select, textarea").forEach((input) => {
if (input.type != "hidden") {
input.disabled = true;
}
});
}

enableInputs(container) {
container.querySelectorAll("input, select, textarea").forEach((input) => {
input.disabled = false;
});
}

visibleRows() {
return this.criteriaRowTargets.filter(
(row) => row.style.display !== "none"
);
}

validateInputs(event) {
const section = document.getElementById(
event.target
.closest(".usa-accordion__button")
.getAttribute("aria-controls")
);

if (this.checkRequiredFields(section)) {
return true;
} else {
event.preventDefault();
event.stopPropagation();
return false;
}
}

checkRequiredFields(section) {
return Array.from(section.querySelectorAll("[required]")).every((field) =>
field.checkValidity() ? true : field.reportValidity()
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
9 changes: 5 additions & 4 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName

import { application } from "./application"

import EvaluationFormController from "./evaluation_form_controller"
application.register("evaluation-form", EvaluationFormController)
import { application } from "./application";

import EvaluationFormController from "./evaluation_form_controller";
import EvaluationCriteriaController from "./evaluation_criteria_controller";
application.register("evaluation-form", EvaluationFormController);
application.register("evaluation-criteria", EvaluationCriteriaController);
6 changes: 3 additions & 3 deletions app/models/evaluation_criterion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ class EvaluationCriterion < ApplicationRecord
enum :scoring_type, { numeric: 0, rating: 1, binary: 2 }
attribute :option_range_start, :integer
attribute :option_range_end, :integer
attribute :option_labels, :json, default: -> { [] }
attribute :option_labels, :json, default: -> { {} }
attribute :evaluation_form_id, :integer

# Validations
validates :title, :description, :points_or_weight, presence: true
validates :title, length: { maximum: 150 }
validates :description, length: { maximum: 1000 }
validates :points_or_weight, numericality: { only_integer: true }
validates :title,
uniqueness: { scope: :evaluation_form_id, message: I18n.t("evaluation_criterion_unique_title_in_form") }
end
Loading
Loading