From 0b00d042a181152e77f8c40b9687fb80538f1172 Mon Sep 17 00:00:00 2001 From: Rodrigo Virgilio Date: Mon, 13 Jul 2026 23:42:54 +0100 Subject: [PATCH] Add confirmation step on kit creation (fixes #5566) --- .../controllers/duplicate_items_controller.js | 22 ++--- .../kit_confirmation_controller.js | 98 +++++++++++++++++++ app/views/kits/_form.html.erb | 8 +- spec/system/kit_system_spec.rb | 51 +++++++++- 4 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 app/javascript/controllers/kit_confirmation_controller.js 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/javascript/controllers/kit_confirmation_controller.js b/app/javascript/controllers/kit_confirmation_controller.js new file mode 100644 index 0000000000..c086bc9e10 --- /dev/null +++ b/app/javascript/controllers/kit_confirmation_controller.js @@ -0,0 +1,98 @@ +import { Controller } from "@hotwired/stimulus" + +/** + * Connects to data-controller="kit-confirmation" on the new/edit Kit form. + * Shows a preview of the kit's name, value, and item composition when the + * user clicks Save, before the form is actually submitted. Composed with + * the (shared, kit-agnostic) duplicate-items controller: confirming here + * re-submits the form so duplicate-items can run its own check afterward. + */ +export default class extends Controller { + static targets = ["submitButton"] + + openModal(event) { + const submitter = event.currentTarget + + if (!this.submitButtonTargets.includes(submitter)) return + + event.preventDefault() + + this.showConfirmationModal(submitter) + } + + collectLineItems() { + const items = [] + + this.element.querySelectorAll('select[name*="[item_id]"]').forEach(select => { + const itemId = select.value + const itemText = select.options[select.selectedIndex]?.text + const section = select.closest('.line_item_section') + const quantityInput = section?.querySelector('input[name*="[quantity]"]') + const quantity = parseInt(quantityInput?.value) || 0 + + if (!itemId || itemText === "Choose an item" || quantity === 0) return + + items.push({ name: itemText, quantity }) + }) + + return items + } + + showConfirmationModal(submitter) { + const name = this.element.querySelector('#kit_name')?.value || '' + const value = parseFloat(this.element.querySelector('#kit_value_in_dollars')?.value || 0).toFixed(2) + const items = this.collectLineItems() + + const itemRows = items.map(item => + `${item.name}${item.quantity}` + ).join('') + + const modalHtml = ` + + ` + + document.getElementById('kitConfirmationModal')?.remove() + document.body.insertAdjacentHTML('beforeend', modalHtml) + + const modal = new bootstrap.Modal(document.getElementById('kitConfirmationModal')) + modal.show() + + document.getElementById('kitConfirmationYes').addEventListener('click', () => { + modal.hide() + this.element.requestSubmit(submitter) + }) + } +} diff --git a/app/views/kits/_form.html.erb b/app/views/kits/_form.html.erb index 515b14a88a..17f8a68c59 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 kit-confirmation" }, html: { class: 'form-horizontal' } do |f| %>
@@ -36,7 +36,11 @@
diff --git a/spec/system/kit_system_spec.rb b/spec/system/kit_system_spec.rb index 1b72d660b7..310f0fd082 100644 --- a/spec/system/kit_system_spec.rb +++ b/spec/system/kit_system_spec.rb @@ -47,11 +47,48 @@ click_button "Save" + expect(page).to have_css("#kitConfirmationModal", visible: true) + within "#kitConfirmationModal" do + expect(page).to have_content("Kit Creation Confirmation") + expect(find(:element, "data-testid": "kit-confirmation-name")).to have_text(kit_traits[:name]) + expect(find(:element, "data-testid": "kit-confirmation-value")).to have_text("$10.10") + expect(page).to have_content(item.name) + expect(page).to have_content(quantity_per_kit.to_s) + expect(page).to have_css(".text-danger", text: "not") + click_button "Yes, it's correct" + end + expect(page.find(".alert")).to have_content "Kit created successfully" expect(page).to have_content(kit_traits[:name]) expect(page).to have_content("#{quantity_per_kit} #{item.name}") end + it "returns to the form with values intact when declining the confirmation" do + visit new_kit_path + kit_traits = attributes_for(:kit) + + fill_in "Name", with: kit_traits[:name] + find(:css, '#kit_value_in_dollars').set('10.10') + + item = Item.last + quantity_per_kit = 5 + select item.name, from: "kit_item_line_items_attributes_0_item_id" + find(:css, '#kit_item_line_items_attributes_0_quantity').set(quantity_per_kit) + + click_button "Save" + + expect(page).to have_css("#kitConfirmationModal", visible: true) + within "#kitConfirmationModal" do + click_button "No, I need to make changes" + end + + expect(page).to have_no_css("#kitConfirmationModal", visible: true) + expect(page).to have_current_path(new_kit_path) + expect(page).to have_no_content("Kit created successfully") + expect(page).to have_field("Name", with: kit_traits[:name]) + expect(page).to have_field("kit_item_line_items_attributes_0_quantity", with: quantity_per_kit.to_s) + end + it "can add items correctly" do visit new_kit_path new_barcode = "1234567890" @@ -221,6 +258,11 @@ click_button "Save" + expect(page).to have_css("#kitConfirmationModal", visible: true) + within "#kitConfirmationModal" do + click_button "Yes, it's correct" + end + expect(page.find(".alert")).to have_content "Name can't be blank" expect(page).to have_content(kit_traits[:quantity]) expect(page).to have_content(item.name) @@ -253,10 +295,15 @@ fill_in quantity_input[:id], with: "15" end - # Try to save - should trigger duplicate detection modal + # Try to save - the confirmation preview appears first click_button "Save" - # JavaScript modal should appear + expect(page).to have_css("#kitConfirmationModal", visible: true) + within "#kitConfirmationModal" do + click_button "Yes, it's correct" + end + + # Confirming then triggers duplicate detection expect(page).to have_css("#duplicateItemsModal", visible: true) expect(page).to have_content("Multiple Item Entries Detected") expect(page).to have_content("Merge Items")