diff --git a/ns_app/public/js/customer_quick_entry.js b/ns_app/public/js/customer_quick_entry.js index 561d836..857beb8 100644 --- a/ns_app/public/js/customer_quick_entry.js +++ b/ns_app/public/js/customer_quick_entry.js @@ -1,58 +1,129 @@ frappe.provide("ns_app.customer"); -// Preserve original quick entry -const _make_quick_entry = frappe.ui.form.make_quick_entry; - console.log("NS APP CUSTOMER JS LOADED"); -// Override -frappe.ui.form.make_quick_entry = function (doctype, after_insert) { +$(document).ready(() => { - if (doctype === "Customer") { + setTimeout(() => { - console.log("NS App: Intercepted Customer Quick Entry"); + const TargetClass = + frappe.ui.form.CustomerQuickEntryForm; - let customer_name = ""; + if (!TargetClass) { - // Pull value from current form route options - if (frappe.route_options?.name) { - customer_name = frappe.route_options.name; + console.error( + "NS App: CustomerQuickEntryForm not found" + ); + + return; } - // Fallback: get typed value from active link field - if (!customer_name) { + // Prevent duplicate patching + if (TargetClass.__ns_patched) { - const active = document.activeElement; + console.log( + "NS App: already patched" + ); - if (active && active.value) { - customer_name = active.value; - } + return; } - console.log("NS App: Captured customer name:", customer_name); + console.log( + "NS App: patching CustomerQuickEntryForm" + ); - ns_app.customer.open_quick_entry({ - callback: after_insert, - customer_name: customer_name - }); + frappe.ui.form.CustomerQuickEntryForm = + class extends TargetClass { - return; - } + render_dialog() { - return _make_quick_entry.apply(this, arguments); -}; + console.log( + "NS App: render_dialog intercepted" + ); + + let customer_name = ""; + + // Route option first + if (frappe.route_options?.name) { + + customer_name = + frappe.route_options.name; + } + + // Focused field fallback + if (!customer_name) { + + const active = + document.activeElement; + + if ( + active && + active.value + ) { + + customer_name = + active.value; + } + } + + // cur_frm fallback + if ( + !customer_name && + typeof cur_frm !== + "undefined" && + cur_frm + ) { + + customer_name = + cur_frm.doc.customer || + cur_frm.doc.party_name || + ""; + } + + console.log( + "NS App: Captured customer name:", + customer_name + ); + + // DO NOT call super.render_dialog() + // This restores the fully custom dialog + + ns_app.customer.open_quick_entry({ + customer_name: + customer_name, + + callback: + this.after_insert + }); + } + }; + + TargetClass.__ns_patched = true; + + console.log( + "NS App: CustomerQuickEntryForm patched successfully" + ); + + }, 1000); + +}); ns_app.customer.open_quick_entry = function (opts = {}) { - console.log("NS App: Custom Customer Quick Entry OPENED"); + console.log( + "NS App: Custom Customer Quick Entry OPENED" + ); const d = new frappe.ui.Dialog({ + title: "New Customer", + size: "large", fields: [ // ───────── CUSTOMER ───────── + { fieldtype: "Section Break", label: "Customer Information" @@ -63,18 +134,22 @@ ns_app.customer.open_quick_entry = function (opts = {}) { label: "Customer Name", fieldtype: "Data", reqd: 1, - default: opts.customer_name || "", - description: "Enter the customer or company name" + default: + opts.customer_name || "", + description: + "Enter the customer or company name" }, { fieldname: "customer_type", label: "Customer Type", fieldtype: "Select", - options: "Company\nIndividual", + options: + "Company\nIndividual", default: "Company", reqd: 1, - description: "Select whether this customer is a company or individual" + description: + "Select whether this customer is a company or individual" }, { @@ -84,18 +159,23 @@ ns_app.customer.open_quick_entry = function (opts = {}) { options: "Customer Group", default: "Commercial", reqd: 1, - description: "Select the customer group" + description: + "Select the customer group" }, { fieldname: "custom_send_via", - label: "Preferred Delivery Method", + label: + "Preferred Delivery Method", fieldtype: "Select", - options: "mail\nemail\nfax", - description: "Choose how documents should be sent to the customer" + options: + "mail\nemail\nfax", + description: + "Choose how documents should be sent to the customer" }, // ───────── CONTACT ───────── + { fieldtype: "Section Break", label: "Primary Contact" @@ -106,36 +186,45 @@ ns_app.customer.open_quick_entry = function (opts = {}) { label: "Email Address", fieldtype: "Data", options: "Email", - description: "Enter the customer's email address" + description: + "Enter the customer's email address" }, { fieldname: "mobile_no", - label: "Mobile Phone Number", + label: + "Mobile Phone Number", fieldtype: "Data", reqd: 1, - description: "Enter the customer's mobile phone number" + description: + "Enter the customer's mobile phone number" }, // ───────── ADDRESS ───────── + { fieldtype: "Section Break", - label: "Address Information" + label: + "Address Information" }, { fieldname: "address_line1", - label: "Address Line 1", + label: + "Address Line 1", fieldtype: "Data", reqd: 1, - description: "Enter the street address" + description: + "Enter the street address" }, { fieldname: "address_line2", - label: "Address Line 2", + label: + "Address Line 2", fieldtype: "Data", - description: "Enter apartment, suite, or secondary address information" + description: + "Enter apartment, suite, or secondary address information" }, { @@ -143,21 +232,24 @@ ns_app.customer.open_quick_entry = function (opts = {}) { label: "ZIP Code", fieldtype: "Data", reqd: 1, - description: "Enter the ZIP or postal code" + description: + "Enter the ZIP or postal code" }, { fieldname: "city", label: "City", fieldtype: "Data", - description: "Enter the city" + description: + "Enter the city" }, { fieldname: "state", label: "State", fieldtype: "Data", - description: "Enter the state" + description: + "Enter the state" }, { @@ -165,40 +257,61 @@ ns_app.customer.open_quick_entry = function (opts = {}) { label: "Country", fieldtype: "Link", options: "Country", - default: "United States", - description: "Select the country" + default: + "United States", + description: + "Select the country" } ], - primary_action_label: "Create Customer", + primary_action_label: + "Create Customer", primary_action(values) { - console.log("NS App: Create Customer clicked", values); + console.log( + "NS App: Create Customer clicked", + values + ); d.disable_primary_action(); frappe.call({ - method: "ns_app.api.customer.create_customer_full", + + method: + "ns_app.api.customer.create_customer_full", + args: values, callback(r) { - console.log("NS App: Customer created", r.message); + console.log( + "NS App: Customer created", + r.message + ); d.hide(); frappe.show_alert({ - message: "Customer created via NS App", + + message: + "Customer created via NS App", + indicator: "green" }); - if (opts.callback) { - opts.callback(r.message); + if ( + opts.callback + ) { + + opts.callback( + r.message + ); } }, always() { + d.enable_primary_action(); } }); @@ -207,29 +320,38 @@ ns_app.customer.open_quick_entry = function (opts = {}) { d.show(); - // Accessibility labels for screen readers + // Accessibility labels + setTimeout(() => { d.fields.forEach(field => { - const control = d.get_field(field.fieldname); + const control = + d.get_field( + field.fieldname + ); - if (!control || !control.$input) return; + if ( + !control || + !control.$input + ) { + return; + } - // ARIA label control.$input.attr( "aria-label", - field.label || field.fieldname + field.label || + field.fieldname ); - // Screen reader title control.$input.attr( "title", - field.label || field.fieldname + field.label || + field.fieldname ); - // Placeholder text if (field.label) { + control.$input.attr( "placeholder", field.label @@ -237,63 +359,127 @@ ns_app.customer.open_quick_entry = function (opts = {}) { } }); - console.log("NS App: Accessibility labels applied"); + console.log( + "NS App: accessibility applied" + ); }, 300); - // ZIP auto-fill - d.fields_dict.pincode.df.onchange = () => { + // ZIP autofill - const zip = d.get_value("pincode"); + d.fields_dict.pincode.df.onchange = + () => { - if (!zip || zip.length < 5) return; + const zip = + d.get_value( + "pincode" + ); - console.log("NS App: ZIP lookup", zip); - - fetch(`https://api.zippopotam.us/us/${zip}`) - .then(r => r.ok ? r.json() : null) - .then(data => { - - if (!data || !data.places?.length) return; - - const p = data.places[0]; - - d.set_value("city", p["place name"]); - d.set_value("state", p["state"]); - d.set_value("country", data.country); - - console.log("NS App: ZIP autofill success"); - }) - .catch(() => {}); - }; - - // Prevent Enter from submitting unless primary button is focused - d.$wrapper.on("keydown", "input, select, textarea", function (e) { - - if (e.key === "Enter") { - - const active = document.activeElement; - - // Allow Enter ONLY if primary action button is focused if ( - active && - active.classList.contains("btn-primary") + !zip || + zip.length < 5 ) { return; } - e.preventDefault(); + console.log( + "NS App: ZIP lookup", + zip + ); - // Move to next field - const fields = d.$wrapper - .find("input, select, textarea") - .filter(":visible:not([disabled])"); + fetch( + `https://api.zippopotam.us/us/${zip}` + ) + .then(r => + r.ok + ? r.json() + : null + ) + .then(data => { - const index = fields.index(this); + if ( + !data || + !data.places?.length + ) { + return; + } - if (index > -1 && index + 1 < fields.length) { - fields.eq(index + 1).focus(); + const p = + data.places[0]; + + d.set_value( + "city", + p["place name"] + ); + + d.set_value( + "state", + p["state"] + ); + + d.set_value( + "country", + data.country + ); + + console.log( + "NS App: ZIP autofill success" + ); + }) + .catch(() => {}); + }; + + // Enter navigation + + d.$wrapper.on( + "keydown", + "input, select, textarea", + function (e) { + + if ( + e.key === "Enter" + ) { + + const active = + document.activeElement; + + // Allow submit only + // on primary button + + if ( + active && + active.classList.contains( + "btn-primary" + ) + ) { + return; + } + + e.preventDefault(); + + const fields = + d.$wrapper + .find( + "input, select, textarea" + ) + .filter( + ":visible:not([disabled])" + ); + + const index = + fields.index(this); + + if ( + index > -1 && + index + 1 < + fields.length + ) { + + fields + .eq(index + 1) + .focus(); + } } } - }); + ); }; \ No newline at end of file