Got the UI working

This commit is contained in:
2026-03-04 07:55:35 -05:00
parent eb6beea5fa
commit 681747e08b
9 changed files with 69 additions and 2645 deletions

View File

@@ -9,4 +9,4 @@
</p>
<%= render "line_items/issue_form" %>
<%= render "line_items/issue_form", f: f %>

View File

@@ -1,31 +1,17 @@
<%= form_with model: @issue do |f| %>
<% @issue.line_items.build if @issue.line_items.empty? %>
<!-- Existing issue fields -->
<h3>Invoice Line Items</h3>
<div
data-controller="nested-form"
data-nested-form-wrapper-selector-value=".line-item"
>
<div data-nested-form-target="container">
<%= f.fields_for :invoice_line_items do |item_form| %>
<%= render "line_items/invoice_line_item_fields", f: item_form %>
<% end %>
</div>
<template data-nested-form-target="template">
<%= f.fields_for :invoice_line_items,
LineItem.new,
child_index: "NEW_RECORD" do |item_form| %>
<%= render "line_items/invoice_line_item_fields", f: item_form %>
<% end %>
</template>
<button type="button" data-action="nested-form#add">
Add Line Item
</button>
<div data-nested-form data-wrapper-selector=".line-item">
<div data-nested-form-container>
<%= f.fields_for :line_items do |item_form| %>
<%= render "line_items/line_item_fields", f: item_form %>
<% end %>
</div>
<%= f.submit %>
<% end %>
<template data-nested-form-template>
<%= f.fields_for :line_items, LineItem.new, child_index: "NEW_RECORD" do |item_form| %>
<%= render "line_items/line_item_fields", f: item_form %>
<% end %>
</template>
<button type="button" data-nested-form-add>Add Line Item</button>
</div>

View File

@@ -2,12 +2,12 @@
<%= f.hidden_field :id %>
<%= f.text_field :description, placeholder: "Description" %>
<%= f.number_field :quantity, step: 1 ,placeholder: "Quantity"%>
<%= f.number_field :quantity, step: 1, placeholder: "Quantity" %>
<%= f.number_field :unit_price, step: 0.01, placeholder: "Unit Price" %>
<%= f.hidden_field :_destroy %>
<button type="button" data-action="nested-form#remove">
<button type="button" data-nested-form-remove>
Remove
</button>
</div>

View File

@@ -1,9 +0,0 @@
document.addEventListener("DOMContentLoaded", () => {
if (typeof Stimulus === "undefined") {
console.error("Stimulus is not loaded. Make sure the UMD script is included first.");
return;
}
const application = Stimulus.Application.start();
application.register("nested-form", window.NestedFormController);
});

View File

@@ -1,19 +1,53 @@
(function() {
class NestedFormController extends Stimulus.Controller {
static targets = ["container", "template"]
(function () {
function initNestedForms() {
document.querySelectorAll("[data-nested-form]").forEach(function (wrapper) {
if (wrapper.dataset.initialized === "true") return;
wrapper.dataset.initialized = "true";
add(event) {
event.preventDefault();
const content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime());
this.containerTarget.insertAdjacentHTML("beforeend", content);
}
const container = wrapper.querySelector("[data-nested-form-container]");
const template = wrapper.querySelector("[data-nested-form-template]");
remove(event) {
event.preventDefault();
event.target.closest(".nested-fields").remove();
}
if (!container || !template) return;
wrapper.addEventListener("click", function (event) {
const addButton = event.target.closest("[data-nested-form-add]");
const removeButton = event.target.closest("[data-nested-form-remove]");
// ADD
if (addButton) {
event.preventDefault();
const content = template.innerHTML.replace(
/NEW_RECORD/g,
Date.now().toString()
);
container.insertAdjacentHTML("beforeend", content);
}
// REMOVE
if (removeButton) {
event.preventDefault();
const lineItem = removeButton.closest(wrapper.dataset.wrapperSelector);
if (!lineItem) return;
const destroyField = lineItem.querySelector("input[name*='_destroy']");
if (destroyField) {
destroyField.value = "1";
lineItem.style.display = "none";
} else {
lineItem.remove();
}
}
});
});
}
// Expose globally so index.js can access it
window.NestedFormController = NestedFormController;
// Works for full load
document.addEventListener("DOMContentLoaded", initNestedForms);
// Works for Turbo navigation
document.addEventListener("turbo:load", initNestedForms);
})();

File diff suppressed because it is too large Load Diff

View File

@@ -64,7 +64,8 @@ module RedmineQbo
locals: {
search_customer: search_customer,
customer_id: customer_id,
select_estimate: select_estimate
select_estimate: select_estimate,
f: context[:form]
}
}
)

View File

@@ -16,11 +16,9 @@ module RedmineQbo
# Load the javascript to support the autocomplete forms
def view_layouts_base_html_head(context = {})
safe_join([
'<script src="https://unpkg.com/@hotwired/stimulus/dist/stimulus.umd.js"></script>'.html_safe,
javascript_include_tag( 'application.js', plugin: :redmine_qbo),
javascript_include_tag( 'autocomplete-rails.js', plugin: :redmine_qbo),
javascript_include_tag( 'checkbox_controller.js', plugin: :redmine_qbo),
javascript_include_tag( 'index.js', plugin: :redmine_qbo),
javascript_include_tag( 'nested_form_controller.js', plugin: :redmine_qbo)
])
end

View File

@@ -23,6 +23,8 @@ module RedmineQbo
belongs_to :customer_token, primary_key: :id
belongs_to :estimate, primary_key: :id
has_and_belongs_to_many :invoices
has_many :line_items, dependent: :destroy
accepts_nested_attributes_for :line_items, allow_destroy: true
before_save :titlize_subject
after_commit :enqueue_billing, on: :update