";
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) {
// 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
}
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) {
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('.');
}