Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 11 additions & 11 deletions app/javascript/controllers/duplicate_items_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
})
Expand All @@ -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()
}
}
98 changes: 98 additions & 0 deletions app/javascript/controllers/kit_confirmation_controller.js
Original file line number Diff line number Diff line change
@@ -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 =>
`<tr><td>${item.name}</td><td>${item.quantity}</td></tr>`
).join('')

const modalHtml = `
<div class="modal fade" id="kitConfirmationModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Kit Creation Confirmation</h5>
<button type="button" class="close" data-bs-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<p class="lead">You are about to create a kit named
<span class="fw-bolder fst-italic" data-testid="kit-confirmation-name">${name}</span>
with value
<span class="fw-bolder fst-italic" data-testid="kit-confirmation-value">$${value}</span>
</p>
<table class="table">
<thead>
<tr>
<th>Item Name</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>${itemRows}</tbody>
</table>
<p>Please confirm that this is the correct composition of the kit. Note: You will
<span class="text-danger fw-bold">not</span> be able to edit the items contained in the kit.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No, I need to make changes</button>
<button type="button" class="btn btn-success" id="kitConfirmationYes">Yes, it's correct</button>
</div>
</div>
</div>
</div>
`

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)
})
}
}
8 changes: 6 additions & 2 deletions app/views/kits/_form.html.erb
Original file line number Diff line number Diff line change
@@ -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| %>
<section class="content">
<div class="container-fluid">
<div class="row">
Expand Down Expand Up @@ -36,7 +36,11 @@
</div>
<div class="card-footer">
<p><b>NB: You will not be able to change the composition of the kit once saved. Partner visibility and name can be changed via the kit's item.</b></p>
<%= submit_button({}, {"duplicate-items-target": "itemSubmitButton"}) %>
<%= submit_button({}, {
"duplicate-items-target": "itemSubmitButton",
"kit-confirmation-target": "submitButton",
action: "click->kit-confirmation#openModal"
}) %>
</div>
</div>
</div>
Expand Down
51 changes: 49 additions & 2 deletions spec/system/kit_system_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
Loading