diff --git a/Gemfile b/Gemfile
index 52f4200cf0..7a48096758 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,7 +14,7 @@ gem "pg", "~> 1.6.3"
# Web server.
gem "puma"
# Rails web framework.
-gem "rails", "~> 8.0.2"
+gem "rails", "~> 8.1.0"
###### MODELS / DATABASE #######
diff --git a/Gemfile.lock b/Gemfile.lock
index efaaca3764..9f574469f4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,29 +2,31 @@ GEM
remote: https://rubygems.org/
specs:
Ascii85 (2.0.1)
- actioncable (8.0.2.1)
- actionpack (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ action_text-trix (2.1.19)
+ railties
+ actioncable (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
- actionmailbox (8.0.2.1)
- actionpack (= 8.0.2.1)
- activejob (= 8.0.2.1)
- activerecord (= 8.0.2.1)
- activestorage (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ actionmailbox (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activestorage (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
mail (>= 2.8.0)
- actionmailer (8.0.2.1)
- actionpack (= 8.0.2.1)
- actionview (= 8.0.2.1)
- activejob (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ actionmailer (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ actionview (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
- actionpack (8.0.2.1)
- actionview (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ actionpack (8.1.3.1)
+ actionview (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@@ -32,42 +34,43 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
- actiontext (8.0.2.1)
- actionpack (= 8.0.2.1)
- activerecord (= 8.0.2.1)
- activestorage (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ actiontext (8.1.3.1)
+ action_text-trix (~> 2.1.15)
+ actionpack (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activestorage (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
- actionview (8.0.2.1)
- activesupport (= 8.0.2.1)
+ actionview (8.1.3.1)
+ activesupport (= 8.1.3.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
- activejob (8.0.2.1)
- activesupport (= 8.0.2.1)
+ activejob (8.1.3.1)
+ activesupport (= 8.1.3.1)
globalid (>= 0.3.6)
- activemodel (8.0.2.1)
- activesupport (= 8.0.2.1)
- activerecord (8.0.2.1)
- activemodel (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ activemodel (8.1.3.1)
+ activesupport (= 8.1.3.1)
+ activerecord (8.1.3.1)
+ activemodel (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
timeout (>= 0.4.0)
- activestorage (8.0.2.1)
- actionpack (= 8.0.2.1)
- activejob (= 8.0.2.1)
- activerecord (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ activestorage (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
marcel (~> 1.0)
- activesupport (8.0.2.1)
+ activesupport (8.1.3.1)
base64
- benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
+ json
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
@@ -112,7 +115,6 @@ GEM
nokogiri (~> 1, >= 1.10.8)
base64 (0.3.0)
bcrypt (3.1.22)
- benchmark (0.5.0)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@@ -547,20 +549,20 @@ GEM
rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
- rails (8.0.2.1)
- actioncable (= 8.0.2.1)
- actionmailbox (= 8.0.2.1)
- actionmailer (= 8.0.2.1)
- actionpack (= 8.0.2.1)
- actiontext (= 8.0.2.1)
- actionview (= 8.0.2.1)
- activejob (= 8.0.2.1)
- activemodel (= 8.0.2.1)
- activerecord (= 8.0.2.1)
- activestorage (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ rails (8.1.3.1)
+ actioncable (= 8.1.3.1)
+ actionmailbox (= 8.1.3.1)
+ actionmailer (= 8.1.3.1)
+ actionpack (= 8.1.3.1)
+ actiontext (= 8.1.3.1)
+ actionview (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activemodel (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activestorage (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
bundler (>= 1.15.0)
- railties (= 8.0.2.1)
+ railties (= 8.1.3.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -577,13 +579,14 @@ GEM
rails-html-sanitizer (1.7.0)
loofah (~> 2.25)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
- railties (8.0.2.1)
- actionpack (= 8.0.2.1)
- activesupport (= 8.0.2.1)
+ railties (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
+ tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.4.2)
@@ -838,7 +841,7 @@ DEPENDENCIES
pry-remote
puma
rack-mini-profiler
- rails (~> 8.0.2)
+ rails (~> 8.1.0)
rails-controller-testing
rails-erd
recaptcha
diff --git a/app/controllers/kits_controller.rb b/app/controllers/kits_controller.rb
index ec23122ba5..3718320ce5 100644
--- a/app/controllers/kits_controller.rb
+++ b/app/controllers/kits_controller.rb
@@ -19,6 +19,20 @@ def new
@kit.line_items.build
end
+ def validate
+ @kit = current_organization.kits.new(kit_params)
+ @kit.line_items.combine!
+ @kit.valid?
+ @kit.errors.add(:base, "At least one item is required") if @kit.line_items.empty?
+
+ if @kit.errors.none?
+ body = render_to_string(template: "kits/validate", formats: [:html], layout: false)
+ render json: {valid: true, body: body}
+ else
+ render json: {valid: false}
+ end
+ end
+
def create
kit_creation = KitCreateService.new(organization_id: current_organization.id, kit_params: kit_params)
kit_creation.call
diff --git a/app/javascript/controllers/confirmation_controller.js b/app/javascript/controllers/confirmation_controller.js
index bb04894b7f..ebb4056d65 100644
--- a/app/javascript/controllers/confirmation_controller.js
+++ b/app/javascript/controllers/confirmation_controller.js
@@ -15,6 +15,10 @@ import { Controller } from "@hotwired/stimulus"
* If the user clicks the "Yes..." button from the modal, it submits the form.
* If the user clicks the "No..." button from the modal, it closes and user remains on the same url.
+ *
+ * The button that opened the modal is remembered and passed back into requestSubmit, so that any other
+ * Stimulus controller composed onto the same form (e.g. duplicate-items) still sees it as `event.submitter`
+ * when the form is eventually (re)submitted.
*/
export default class extends Controller {
static targets = [
@@ -29,6 +33,8 @@ export default class extends Controller {
openModal(event) {
event.preventDefault();
+ this.submitter = event.currentTarget;
+
const formData = new FormData(this.formTarget);
const formObject = this.buildNestedObject(formData);
@@ -49,7 +55,7 @@ export default class extends Controller {
this.modalTarget.innerHTML = data.body;
$(this.modalTarget).modal("show");
} else {
- this.formTarget.requestSubmit();
+ this.formTarget.requestSubmit(this.submitter);
}
})
.catch((error) => {
@@ -57,7 +63,7 @@ export default class extends Controller {
// In this case, just submit the form as if the user had clicked Save.
// NICE TO HAVE: Send to bugsnag but need to install/configure https://www.npmjs.com/package/@bugsnag/js
console.log(`=== ConfirmationController ERROR ${error}`);
- this.formTarget.requestSubmit();
+ this.formTarget.requestSubmit(this.submitter);
});
}
@@ -106,6 +112,6 @@ export default class extends Controller {
$(this.modalTarget).find('#modalYes').prop('disabled', true);
$(this.modalTarget).find('#modalNo').prop('disabled', true);
$(this.modalTarget).modal("hide");
- this.formTarget.requestSubmit();
+ this.formTarget.requestSubmit(this.submitter);
}
}
diff --git a/app/javascript/controllers/duplicate_items_controller.js b/app/javascript/controllers/duplicate_items_controller.js
index f341a20c61..e2b2e7632b 100644
--- a/app/javascript/controllers/duplicate_items_controller.js
+++ b/app/javascript/controllers/duplicate_items_controller.js
@@ -6,10 +6,10 @@ export default class extends Controller {
connect() {
this.boundHandleSubmit = this.handleSubmit.bind(this)
this.element.addEventListener("submit", this.boundHandleSubmit)
-
+
// Disable Rails UJS for this form to prevent "Saving" state
this.element.removeAttribute('data-remote')
-
+
// Remove data-disable-with from all submit buttons
const buttons = this.element.querySelectorAll('input[type="submit"], button[type="submit"]')
buttons.forEach(button => {
@@ -23,9 +23,9 @@ export default class extends Controller {
if (!this.itemSubmitButtonTargets.includes(submitter)) return
event.preventDefault()
-
+
const duplicates = this.findDuplicates()
-
+
if (duplicates.length > 0) {
this.showModal(duplicates, submitter.name)
} else {
@@ -100,10 +100,10 @@ export default class extends Controller {
document.getElementById('duplicateItemsModal')?.remove()
document.body.insertAdjacentHTML('beforeend', modalHtml)
-
+
const modal = new bootstrap.Modal(document.getElementById('duplicateItemsModal'))
modal.show()
-
+
document.getElementById('confirmMerge').addEventListener('click', () => {
this.mergeAndSubmit(duplicates, buttonName)
})
@@ -115,29 +115,29 @@ export default class extends Controller {
// Separate the first entry from remaining entries
const [firstEntry, ...remainingEntries] = item.entries
-
+
// Update the first entry with the merged total
firstEntry.section.querySelector('input[name*="[quantity]"]').value = total
-
+
// Remove all duplicate entries from the form submission
remainingEntries.forEach(entry => entry.section.remove())
})
const modal = new bootstrap.Modal(document.getElementById('duplicateItemsModal'))
modal.hide()
-
+
this.submitForm(buttonName)
}
submitForm(buttonName) {
this.element.removeEventListener('submit', this.boundHandleSubmit)
-
+
const input = document.createElement('input')
input.type = 'hidden'
input.name = buttonName
input.value = '1'
this.element.appendChild(input)
-
+
this.element.submit()
}
}
diff --git a/app/views/kits/_form.html.erb b/app/views/kits/_form.html.erb
index f8cd43b4f1..e50b12a1ae 100644
--- a/app/views/kits/_form.html.erb
+++ b/app/views/kits/_form.html.erb
@@ -1,4 +1,4 @@
-<%= simple_form_for @kit, remote: request.xhr?, data: { controller: "form-input duplicate-items" }, html: { class: 'form-horizontal' } do |f| %>
+<%= simple_form_for @kit, remote: request.xhr?, data: { controller: "form-input duplicate-items", confirmation_target: "form" }, html: { class: 'form-horizontal' } do |f| %>