/** ========================= * DEFAULT ON LOAD FUNCTION * ========================= */ $(function () { // Prevent all forms from refreshing the page $("form").on("submit", function (e) { e.preventDefault(); }); // Initialize autocomplete for product search inputs $('input.product_search').each(function (i, el) { var $el = $(el); $el.autocomplete({ // Fetch data from server source: function (request, response) { // Abort previous request (prevent multiple firing) if ($el.data('xhr')) { $el.data('xhr').abort(); } // Prepare payload let q = prepare_form_data(0, true); q['keyword'] = request.term; let json = JSON.stringify(q); // Send AJAX request var xhr = $.ajax({ url: server_url + 'ics/api/engine/product_search.php', type: 'POST', dataType: 'json', data: { json: json }, // for CSRF validation headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }, success: function (res) { var aca = []; // Map result to autocomplete format $.each(res.result, function (i, item) { var temp = ""; temp += "
"; temp += "
"; temp += "SKU: " + (item.sku || 'N/A'); temp += "
"; temp += "
"; temp += item.product_name; temp += "
"; temp += "
"; aca.push({ label: temp, // HTML dropdown value: item.product_name, // Default value data: item // Full object }); }); response(aca); } }); // Save request reference $el.data('xhr', xhr); }, minLength: 0, delay: 300, // When selecting item select: function (e, ui) { if (ui.item && ui.item.data) { var product = ui.item.data; // Replace input value with SKU $(this) .val(product.sku) .attr("secondary", product.sku); $('input#product_name') .val(product.product_name) .attr("secondary", product.product_name) console.log("Input updated with SKU:", product.sku); return false; // Prevent overwrite by default value } } }) // Allow HTML rendering in dropdown .data("ui-autocomplete")._renderItem = function (ul, item) { return $("
  • ") .append($("
    ").html(item.label)) .appendTo(ul); }; // Add this ↓ $el.on('blur', function () { var secondary = $(this).attr('secondary'); if (secondary) { $(this).val(secondary); $('input#product_name').val($('input#product_name').attr('secondary')); } }); }); $('input.contact_search').each(function (i, el) { var $el = $(el); $el.autocomplete({ // Fetch data from server source: function (request, response) { // Abort previous request (prevent multiple firing) if ($el.data('xhr')) { $el.data('xhr').abort(); } // Prepare payload let q = prepare_form_data(0, true); q['keyword'] = request.term; let json = JSON.stringify(q); // Send AJAX request var xhr = $.ajax({ url: server_url + 'ics/api/engine/contact_search.php', type: 'POST', dataType: 'json', data: { json: json }, // for CSRF validation headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }, success: function (res) { var aca = []; // Map result to autocomplete format $.each(res.result, function (i, item) { var temp = ""; temp += "
    "; temp += "
    "; temp += "Tax ID: " + (item.tax_id || 'N/A'); temp += "
    "; temp += "
    "; temp += item.contact_name; temp += "
    "; temp += "
    "; aca.push({ label: temp, // HTML dropdown value: item.contact_name, // Default value data: item // Full object }); }); response(aca); } }); // Save request reference $el.data('xhr', xhr); }, minLength: 0, delay: 300, // When selecting item select: function (e, ui) { if (ui.item && ui.item.data) { var contact = ui.item.data; // Replace input value with SKU $(this) .val(contact.contact_name) .attr("data-id", contact.id) .attr("secondary", contact.contact_name); console.log("Input updated with contact name:", contact.contact_name); return false; // Prevent overwrite by default value } } }) // Allow HTML rendering in dropdown .data("ui-autocomplete")._renderItem = function (ul, item) { return $("
  • ") .append($("
    ").html(item.label)) .appendTo(ul); }; // Add this ↓ $el.on('blur', function () { var secondary = $(this).attr('secondary'); console.log($(this).attr('data-id')) if (secondary) { $('input#contact').val($('input#contact').attr('secondary')); } }); }); }); /** ========================= * PREPARE FORM DATA * ========================= */ function prepare_form_data(check_required, raw_data) { var q = {}; // Get session context const session_element = document.getElementById('session-context'); if (session_element) { q['company_id'] = session_element.dataset.companyId; q['otp'] = session_element.dataset.otp; } // Include GET parameters const url_params = new URLSearchParams(window.location.search); url_params.forEach((value, key) => { q[key] = value; }); // Collect form inputs $(".form-control, .form-select").each(function () { if (!$(this).attr("id")) return true; q[$(this).attr("id")] = $(this).val(); }); // Validate required fields if (check_required === 1) { const required_inputs = document.querySelectorAll('[required]'); let is_valid = true; required_inputs.forEach(input => { if (!input.value.trim()) { input.classList.add('is-invalid'); is_valid = false; } else { input.classList.remove('is-invalid'); input.classList.add('is-valid'); } }); if (!is_valid) { alert("Please fill in all mandatory fields."); isAjaxProcessing = false; return false; } } return (raw_data) ? q : JSON.stringify(q); } /** ========================= * AJAX WRAPPER * ========================= */ // Prevent double firing let isAjaxProcessing = false; function ajax_request(options) { if (isAjaxProcessing && options.queueLock !== false) { // Instead of rejecting, we just return a "never-ending" promise // or a resolved promise that does nothing. console.warn("Request is busy... ignoring click."); return new Promise(() => { }); // This stays pending and won't trigger .then or .catch } if (options.queueLock !== false) { isAjaxProcessing = true; } // Auto prepare form data if (options.autoPrepare === true) { let payloadJson = prepare_form_data(options.checkRequired ?? 0, true); if (payloadJson === false) { isAjaxProcessing = false; return Promise.reject("validation_failed"); } if(options.data){ Object.entries(options.data).forEach(([key, value]) => { payloadJson[key] = value; }); } if(options.action){ // modify action if(options.action === 'manage'){ options.action = (payloadJson['id'])?'update':'create'; } // add action to JSON payloadJson['action'] = options.action; }else{ isAjaxProcessing = false; return Promise.reject("please_define_action"); } options.data = { json: JSON.stringify(payloadJson) }; // IF formData exist, we pass as $_POST [not json] if (options.formData instanceof FormData) { // THE BYPASS: If formData exists, move all text data into it Object.entries(payloadJson).forEach(([key, value]) => { options.formData.append(key, value); }); // Override options.data with the full FormData object options.data = options.formData; } } // --- START MODIFIED $.AJAX BLOCK --- let isSendingFiles = (options.data instanceof FormData); if (options.debugMode) { isAjaxProcessing = false; console.log("REQUEST DATA:", options.data); } // Show loading overlay if (options.noLoading !== true) { $.LoadingOverlay("show", { imageColor: "#525252", imageAnimation: "2s rotate_right", background: "rgba(255,255,255,0.8)" }); } return $.ajax({ async: true, type: options.type || "POST", url: options.url, data: options.data, dataType: "json", // These two settings are only triggered when sending files processData: isSendingFiles ? false : true, contentType: isSendingFiles ? false : "application/x-www-form-urlencoded; charset=UTF-8", // for CSRF validation headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }) .then(function (res) { isAjaxProcessing = false; if (options.noLoading !== true) $.LoadingOverlay("hide"); if (options.debugMode) { console.log("RESPONSE:", res); return res; } if (!res || res.success != 1) { bootbox.alert(res?.message || "Unexpected error"); throw new Error(res?.message || "api_failed"); } options.onSuccess?.(res); return res; }) .catch(function (xhr) { isAjaxProcessing = false; $.LoadingOverlay("hide"); if (xhr instanceof Error) { throw xhr; } console.error("AJAX Error:", xhr?.responseText); bootbox.alert("Server error occurred."); throw xhr; }); } /** ========================= * GENERATE PAGINATION * ========================= */ function generate_pagination(table_id, total_records, records_per_page, current_page = 1) { records_per_page = records_per_page ?? prop_limit; const total_pages = Math.ceil(total_records / records_per_page); // 1. Find the table and thead const table = document.getElementById(table_id); let theme_class = ''; if (table) { const thead = table.querySelector('thead'); if (thead) { // 2. Look through classes for one that starts with "table-" const bootstrap_theme = Array.from(thead.classList).find(c => c.startsWith('table-')); if (bootstrap_theme) { // 3. Convert "table-secondary" to "page-item-secondary" theme_class = bootstrap_theme.replace('table-', 'page-item-'); } } } let pagination_html = ` Showing ${records_per_page} per page
    Total: ${total_records} `; return pagination_html; } function handlePageClick(table_id, element, pageNumber) { // 1. Remove 'active' class from all siblings within this specific pagination bar const paginationUl = element.closest('.pagination'); paginationUl.querySelectorAll('.page-item').forEach(li => { li.classList.remove('active'); }); // 2. Add 'active' class to the clicked parent
  • element.parentElement.classList.add('active'); // 3. Define the dynamic function name const dynamicFunctionName = `change_page_${table_id}`; // 4. Check if the specific dynamic function exists and call it if (typeof window[dynamicFunctionName] === "function") { window[dynamicFunctionName](pageNumber); } else { console.error(`Function ${dynamicFunctionName} not found!`); } } /** ========================= * FLATPICKR INITIALIZE * ========================= */ flatpickr(".flatpickr", { enableTime: true, time_24hr: true, defaultDate: new Date(), altInput: true, altFormat: "d/m/Y H:i:s", dateFormat: "Y-m-d H:i:s" }); /** ========================= * REAL-TIME REQUIRED VALIDATION * ========================= */ document.addEventListener('DOMContentLoaded', () => { const required_inputs = document.querySelectorAll('[required]'); required_inputs.forEach(input => { input.addEventListener('input', function () { if (this.value.trim() !== "") { this.classList.remove('is-invalid'); this.classList.add('is-valid'); } else { this.classList.remove('is-valid'); this.classList.add('is-invalid'); } }); }); }); /** * Helper function to format date strings (assuming input is in ISO format) */ function format_date(iso_string) { var split = iso_string.split(" "); if (split.length === 2) { var datePart = split[0].split("-"); } return (datePart) ? `${datePart[2]}/${datePart[1]}/${datePart[0]} ${split[1]}` : iso_string; } function format_number(value, decimal) { var n = Number(value); if (isNaN(n)) return '--'; if (decimal !== undefined) n = n.toFixed(decimal); var parts = n.toString().split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); return parts.join('.'); }