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
14 changes: 14 additions & 0 deletions app/controllers/kits_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions app/javascript/controllers/confirmation_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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);

Expand All @@ -49,15 +55,15 @@ 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) => {
// Something went wrong in communication to server validation endpoint
// 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);
});
}

Expand Down Expand Up @@ -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);
}
}
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()
}
}
18 changes: 16 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", confirmation_target: "form" }, html: { class: 'form-horizontal' } do |f| %>
<section class="content">
<div class="container-fluid">
<div class="row">
Expand Down Expand Up @@ -34,11 +34,25 @@
</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",
action: "click->confirmation#openModal"
}) %>
</div>
</div>
</div>
</div>
</div>
</section>
<% end %>

<%# Confirmation modal: See confirmation_controller.js for how this gets displayed %>
<%# and app/controllers/kits_controller.rb#validate for how it gets populated. %>
<div id="kitConfirmationModal"
class="modal confirm"
aria-labelledby="kitConfirmationModal"
aria-hidden="true"
tabindex="-1"
data-bs-backdrop="static"
data-confirmation-target="modal">
</div>
4 changes: 3 additions & 1 deletion app/views/kits/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@
</div>
</section>

<%= render 'form' %>
<div data-controller="confirmation" data-confirmation-pre-check-path-value="<%= validate_kits_path(format: :json) %>">
<%= render 'form' %>
</div>
<%= render partial: "barcode_items/barcode_modal" %>
37 changes: 37 additions & 0 deletions app/views/kits/validate.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Kit Creation Confirmation</h5>
<button id="modalClose" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></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"><%= @kit.name %></span>
with value
<span class="fw-bolder fst-italic" data-testid="kit-confirmation-value">$<%= format("%.2f", @kit.value_in_dollars) %></span>
</p>
<table class="table">
<thead>
<tr>
<th>Item Name</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
<% @kit.line_items.each do |line_item| %>
<tr>
<td><%= line_item.name %></td>
<td><%= line_item.quantity %></td>
</tr>
<% end %>
</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 id="modalNo" type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="No I need to make changes">No, I need to make changes</button>
<button id="modalYes" type="button" class="btn btn-success" data-action="confirmation#submitForm">Yes, it's correct</button>
</div>
</div>
</div>
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ def set_up_flipper
end

resources :kits do
collection do
post :validate
end
member do
get :allocations
post :allocate
Expand Down
58 changes: 58 additions & 0 deletions spec/requests/kit_requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,64 @@
end
end

describe "POST #validate" do
let(:item) { create(:item, organization: organization) }

it "returns valid and the confirmation modal body for a valid kit" do
post validate_kits_url(format: :json), params: {
kit: {
name: "A new kit",
value_in_dollars: "10.10",
line_items_attributes: {"0": {item_id: item.id, quantity: 5}}
}
}

expect(response).to be_successful
json = JSON.parse(response.body)
expect(json["valid"]).to eq(true)
expect(json["body"]).to include("A new kit")
expect(json["body"]).to include("$10.10")
expect(json["body"]).to include(item.name)
end

it "returns invalid when the name is blank" do
post validate_kits_url(format: :json), params: {
kit: {
name: "",
value_in_dollars: "10.10",
line_items_attributes: {"0": {item_id: item.id, quantity: 5}}
}
}

expect(response).to be_successful
json = JSON.parse(response.body)
expect(json["valid"]).to eq(false)
expect(json["body"]).to be_nil
end

it "returns invalid when there are no line items" do
post validate_kits_url(format: :json), params: {
kit: {name: "A new kit", value_in_dollars: "10.10"}
}

expect(response).to be_successful
json = JSON.parse(response.body)
expect(json["valid"]).to eq(false)
end

it "does not persist the kit" do
expect {
post validate_kits_url(format: :json), params: {
kit: {
name: "A new kit",
value_in_dollars: "10.10",
line_items_attributes: {"0": {item_id: item.id, quantity: 5}}
}
}
}.not_to change(Kit, :count)
end
end

describe "GET #index" do
before do
# this shouldn't be shown
Expand Down
50 changes: 46 additions & 4 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_line_items_attributes_0_item_id"
find(:css, '#kit_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_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 @@ -210,7 +247,6 @@
describe "when missing required fields" do
it "displays error indicating missing field and preserves filled out fields" do
visit new_kit_path
kit_traits = attributes_for(:kit)

find(:css, '#kit_value_in_dollars').set('10.10')

Expand All @@ -221,8 +257,9 @@

click_button "Save"

expect(page).to have_no_css("#kitConfirmationModal", visible: true)
expect(page.find(".alert")).to have_content "Name can't be blank"
expect(page).to have_content(kit_traits[:quantity])
expect(page).to have_field("kit_line_items_attributes_0_quantity", with: quantity_per_kit.to_s)
expect(page).to have_content(item.name)
end
end
Expand Down Expand Up @@ -253,10 +290,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