/**
* Invoice Builder for Easy Invoice
* Handles invoice items, client selection, and form management
*/
(function($) {
'use strict';
// Check if we're on an invoice page
const isInvoicePage = window.location.href.includes('easy-invoice-builder') ||
window.location.href.includes('easy-invoice-new') ||
$('.invoice-item').length > 0;
if (!isInvoicePage) {
return;
}
// Invoice Builder object
window.EasyInvoiceBuilder = {
// Default values
settings: {
itemCounter: 0,
items: [],
editMode: false,
invoiceId: 0
},
// Initialize the invoice builder
init: function() {
// Initialize field helpers for dynamic field handling
this.initFieldHelpers();
// Set up event handlers
this.setupEventHandlers();
// Initialize existing items
this.initializeExistingItems();
// Set up payment manager
if (window.EasyInvoicePayment) {
window.EasyInvoicePayment.init();
}
},
// Set up event handlers for invoice-related elements
setupEventHandlers: function() {
var self = this;
// Add item button
// First unbind any existing click handlers to prevent duplication
$('.add-item-button').off('click').on('click', function(e) {
e.preventDefault();
self.addNewItem();
});
// Collapse all items button - use event delegation since it might be in a hidden tab
$(document).off('click', '#collapse-all-items').on('click', '#collapse-all-items', function(e) {
e.preventDefault();
e.stopPropagation(); // Prevent event bubbling
self.collapseAllItems();
});
// Add sample items button
// $('#add-sample-items').off('click').on('click', function(e) {
// e.preventDefault();
// e.stopPropagation(); // Prevent event bubbling
// self.addSampleItems();
// });
// Individual item sample data buttons (delegate to handle dynamically added buttons)
$(document).off('click', '.fill-sample-data-btn').on('click', '.fill-sample-data-btn', function(e) {
e.preventDefault();
e.stopPropagation();
var $item = $(this).closest('.invoice-item');
self.fillItemWithSampleData($item);
});
// Individual item collapse toggles - use event delegation as fallback
$(document).off('click', '.item-collapse-toggle').on('click', '.item-collapse-toggle', function(e) {
e.preventDefault();
e.stopPropagation();
var $item = $(this).closest('.invoice-item');
var itemContent = $item.find('.item-content');
var summaryElement = $item.find('.item-collapsed-summary');
var sampleButton = $item.find('.fill-sample-data-btn');
var icon = $(this).find('i');
if (itemContent.is(':visible')) {
// Collapsing - update summary and change icon
self.updateItemSummary($item);
itemContent.slideUp(200);
summaryElement.slideDown(200);
sampleButton.hide(); // Hide sample button when collapsed
icon.removeClass('fa-chevron-down').addClass('fa-chevron-right');
// Add compact styling to the collapsed item
$item.addClass('collapsed-item');
} else {
// Expanding - hide summary and change icon
itemContent.slideDown(200);
summaryElement.slideUp(200);
sampleButton.show(); // Show sample button when expanded
icon.removeClass('fa-chevron-right').addClass('fa-chevron-down');
// Remove compact styling from the expanded item
$item.removeClass('collapsed-item');
}
// Update the preview to reflect changes
if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
window.EasyInvoicePayment.updateTotals();
} else if (typeof updatePreview === 'function') {
updatePreview();
}
});
// Send invoice button
$('#send_invoice').off('click').on('click', function(e) {
e.preventDefault();
self.sendInvoice();
});
// Reset form button
$('#reset_form').off('click').on('click', function(e) {
e.preventDefault();
if (confirm('Are you sure you want to reset the form? All unsaved changes will be lost.')) {
self.resetForm();
}
});
// Handle tab navigation
$('.tab-button').off('click').on('click', function(e) {
e.preventDefault();
var targetTab = $(this).data('tab');
// Hide all tabs
$('.tab-content').removeClass('active').addClass('hidden');
// Remove active class and reset border styling for all tabs
$('.tab-button').removeClass('active')
.removeClass('border-indigo-500 text-indigo-600')
.addClass('border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300');
// Show the target tab
$('#' + targetTab).removeClass('hidden').addClass('active');
// Add active class and update border styling to clicked tab
$(this).addClass('active')
.removeClass('border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300')
.addClass('border-indigo-500 text-indigo-600');
// If switching to items tab, ensure collapse button is properly bound
if (targetTab === 'items-tab') {
setTimeout(function() {
if ($('#collapse-all-items').length > 0) {
// Re-attach event handler to ensure it works
$('#collapse-all-items').off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
EasyInvoiceBuilder.collapseAllItems();
});
} else {
// Collapse button not found in items tab
}
}, 100);
}
});
},
// Set up proper styling for the initially active tab
setupInitialTabState: function() {
// Find the tab that has the 'active' class
var $activeTab = $('.tab-button.active');
// If no active tab is found, default to the first tab
if ($activeTab.length === 0) {
$activeTab = $('.tab-button').first();
$activeTab.addClass('active');
}
// Apply the correct styling to the active tab
$activeTab.removeClass('border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300')
.addClass('border-indigo-500 text-indigo-600');
// Show the corresponding tab content
var targetTab = $activeTab.data('tab');
$('.tab-content').removeClass('active').addClass('hidden');
$('#' + targetTab).removeClass('hidden').addClass('active');
},
// Initialize invoice items from saved data or create a default empty item
// REMOVED: Items should be initialized from PHP/HTML, not JavaScript
// Add a new empty item
addNewItem: function() {
// Get the template
var template = document.getElementById('invoice-item-template');
if (!template) {
// Invoice item template not found
return null;
}
// Clone the template
var clone = template.content.cloneNode(true);
var newItem = $(clone);
// Get the next item index
var itemIndex = this.settings.itemCounter++;
// Generate a unique ID for the item
var itemId = 'item_' + Date.now() + '_' + itemIndex;
// Update item ID
newItem.find('.invoice-item').attr('id', itemId);
// Update field indices to use the correct item index
this.updateItemFieldIndices(newItem, itemIndex);
// Set header to 'New Item' for newly added items
newItem.find('h3').text('New Item');
// Add the item to the container
$('.invoice-items-container').append(newItem);
// Set up event handlers
this.setupItemEvents($('#' + itemId));
this.updateItemNumbers();
// Update totals
if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
window.EasyInvoicePayment.updateTotals();
}
return $('#' + itemId);
},
// Update field indices in an item to use the correct item index
updateItemFieldIndices: function($item, itemIndex) {
// Update all input fields - match both items[0][fieldname] and items[-1][fieldname]
$item.find('input[name*="items["]').each(function() {
var oldName = $(this).attr('name');
var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + itemIndex + '][');
$(this).attr('name', newName);
});
// Update all textarea fields - match both items[0][fieldname] and items[-1][fieldname]
$item.find('textarea[name*="items["]').each(function() {
var oldName = $(this).attr('name');
var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + itemIndex + '][');
$(this).attr('name', newName);
});
// Update all select fields - match both items[0][fieldname] and items[-1][fieldname]
$item.find('select[name*="items["]').each(function() {
var oldName = $(this).attr('name');
var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + itemIndex + '][');
$(this).attr('name', newName);
});
// Update field IDs to be unique (handle both _-1 and _0)
$item.find('[id*="_-1"], [id*="_0"]').each(function() {
var oldId = $(this).attr('id');
var newId = oldId.replace(/_(?:-1|0)/, '_' + itemIndex);
$(this).attr('id', newId);
// Update corresponding label for attribute
var $label = $item.find('label[for="' + oldId + '"]');
if ($label.length) {
$label.attr('for', newId);
}
});
},
// Set up event handlers for a specific item
setupItemEvents: function($item) {
var self = this;
// Handle quantity, price, and adjustment percentage changes
$item.find('input[name*="[quantity]"], input[name*="[price]"], input[name*="[adjust_percentage]"]').off('input').on('input', function() {
self.calculateItemTotal($item);
});
// Remove item button
$item.find('.remove-item').off('click').on('click', function() {
self.removeItem($item);
});
// Handle taxable checkbox
$item.find('input[name*="[taxable]"]').off('change').on('change', function() {
if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
window.EasyInvoicePayment.updateTotals();
}
});
// Handle collapse/expand - unbind previous handlers first
var $collapseToggle = $item.find('.item-collapse-toggle');
if ($collapseToggle.length === 0) {
// No collapse toggle found for item
return;
}
$collapseToggle.off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation(); // Prevent event bubbling
var itemContent = $item.find('.item-content');
var summaryElement = $item.find('.item-collapsed-summary');
var sampleButton = $item.find('.fill-sample-data-btn');
var icon = $(this).find('i');
if (itemContent.is(':visible')) {
// Collapsing - update summary and change icon
self.updateItemSummary($item);
itemContent.slideUp(200);
summaryElement.slideDown(200);
sampleButton.hide(); // Hide sample button when collapsed
icon.removeClass('fa-chevron-down').addClass('fa-chevron-right');
// Add compact styling to the collapsed item
$item.addClass('collapsed-item');
} else {
// Expanding - hide summary and change icon
itemContent.slideDown(200);
summaryElement.slideUp(200);
sampleButton.show(); // Show sample button when expanded
icon.removeClass('fa-chevron-right').addClass('fa-chevron-down');
// Remove compact styling from the expanded item
$item.removeClass('collapsed-item');
}
// Update the preview to reflect changes
if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
window.EasyInvoicePayment.updateTotals();
} else if (typeof updatePreview === 'function') {
updatePreview();
}
});
// Update the title in the header when title field changes
$item.find('input[name*="[title]"]').off('input').on('input', function() {
var title = $(this).val() || 'Invoice Item';
var shortTitle = title.length > 30 ? title.substring(0, 30) + '...' : title;
$item.find('h3').text(shortTitle);
});
},
// Calculate total for a specific item
calculateItemTotal: function($item) {
// Use dynamic field helpers for calculation
if (window.EasyInvoiceFieldHelpers && window.EasyInvoiceFieldHelpers.calculateFieldValue) {
var total = window.EasyInvoiceFieldHelpers.calculateFieldValue('total', $item);
if (total !== null) {
window.EasyInvoiceFieldHelpers.setFieldValue('total', total, $item);
// Update summary fields directly
var quantity = window.EasyInvoiceFieldHelpers.getFieldValue('quantity', $item);
var price = window.EasyInvoiceFieldHelpers.getFieldValue('price', $item);
$item.find('.quantity-summary').text(quantity || '0');
$item.find('.price-summary').text((parseFloat(price) || 0).toFixed(2));
$item.find('.total-summary').text((parseFloat(total) || 0).toFixed(2));
}
} else {
// Fallback to hardcoded calculation
var quantity = parseFloat($item.find('input[name*="[quantity]"]').val()) || 0;
var price = parseFloat($item.find('input[name*="[price]"]').val()) || 0;
var adjustPercentage = 0;
// Only apply adjust percentage if the adjust field is enabled
if (window.easyInvoice && window.easyInvoice.showAdjustField) {
adjustPercentage = parseFloat($item.find('input[name*="[adjust_percentage]"]').val()) || 0;
}
var baseTotal = quantity * price;
var total = baseTotal * (1 + adjustPercentage / 100);
// Update the total field
var $totalField = $item.find('input[name*="[total]"]');
$totalField.val(total.toFixed(2));
// Update the collapsed summary
$item.find('.quantity-summary').text(quantity);
$item.find('.price-summary').text(price.toFixed(2));
$item.find('.total-summary').text(total.toFixed(2));
}
},
// Update the collapsed summary of an item
updateItemSummary: function($item) {
// Use dynamic field helpers for summary updates
if (window.EasyInvoiceFieldHelpers) {
var quantity = window.EasyInvoiceFieldHelpers.getFieldValue('quantity', $item);
var price = window.EasyInvoiceFieldHelpers.getFieldValue('price', $item);
var total = window.EasyInvoiceFieldHelpers.getFieldValue('total', $item);
// Update summary fields directly since updateSummaryFields doesn't exist
$item.find('.quantity-summary').text(quantity || '0');
$item.find('.price-summary').text((parseFloat(price) || 0).toFixed(2));
$item.find('.total-summary').text((parseFloat(total) || 0).toFixed(2));
} else {
// Fallback to hardcoded summary
var quantity = parseFloat($item.find('input[name*="[quantity]"]').val()) || 0;
var price = parseFloat($item.find('input[name*="[price]"]').val()) || 0;
var adjustPercentage = 0;
// Only apply adjust percentage if the adjust field is enabled
if (window.easyInvoice && window.easyInvoice.showAdjustField) {
adjustPercentage = parseFloat($item.find('input[name*="[adjust_percentage]"]').val()) || 0;
}
var baseTotal = quantity * price;
var total = baseTotal * (1 + adjustPercentage / 100);
$item.find('.quantity-summary').text(quantity);
$item.find('.price-summary').text(price.toFixed(2));
$item.find('.total-summary').text(total.toFixed(2));
}
},
// Remove an item from the invoice
removeItem: function($item) {
var self = this;
$item.addClass('opacity-0');
setTimeout(function() {
$item.remove();
// Ensure at least one item remains
if ($('.invoice-item').length === 0) {
self.addNewItem();
} else {
self.updateItemNumbers();
}
// Update totals
if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
window.EasyInvoicePayment.updateTotals();
}
}.bind(this), 200);
},
updateItemNumbers: function() {
var self = this;
$('.invoice-items-container .invoice-item').each(function(index) {
var itemNumber = index + 1;
var title = $(this).find('input[name*="[title]"]').val();
var shortTitle = title ? (title.length > 30 ? title.substring(0, 30) + '...' : title) : 'New Item';
$(this).find('h3').text(shortTitle);
// Only update field indices if they don't already match the current index
var $item = $(this);
var firstField = $item.find('input[name*="items["]').first();
if (firstField.length > 0) {
var fieldName = firstField.attr('name');
var currentIndex = fieldName.match(/items\[(\d+)\]/);
if (currentIndex && parseInt(currentIndex[1]) !== index) {
// Field index doesn't match, update it
self.updateItemFieldIndices($item, index);
}
}
});
self.settings.itemCounter = $('.invoice-items-container .invoice-item').length;
},
// Format currency value
formatCurrency: function(value) {
var symbol = '$';
// Use the currency symbol from payment manager if available
if (window.EasyInvoicePayment && window.EasyInvoicePayment.settings.currencySymbol) {
symbol = window.EasyInvoicePayment.settings.currencySymbol;
}
return symbol + parseFloat(value).toFixed(2);
},
// Get all current invoice items
getItems: function() {
var items = [];
$('.invoice-item').each(function(index) {
var $item = $(this);
var item = {
id: $item.attr('id')
};
// Use dynamic field helpers to get all field values
if (window.EasyInvoiceFieldHelpers && window.EasyInvoiceFieldConfig) {
Object.keys(window.EasyInvoiceFieldConfig).forEach(function(fieldName) {
var value = window.EasyInvoiceFieldHelpers.getFieldValue(fieldName, $item);
item[fieldName] = value;
});
} else {
// Fallback to hardcoded field names
item.name = $item.find('input[name*="[title]"]').val() || '';
item.description = $item.find('textarea[name*="[description]"]').val() || '';
item.quantity = parseFloat($item.find('input[name*="[quantity]"]').val()) || 0;
item.price = parseFloat($item.find('input[name*="[price]"]').val()) || 0;
item.taxable = $item.find('input[name*="[taxable]"]').is(':checked');
}
items.push(item);
});
return items;
},
// Set up client selection functionality
setupClientSelection: function() {
var self = this;
var isInitialLoad = true; // Flag to prevent AJAX calls on initial load
// Client dropdown change
$('#client_id').on('change', function() {
var clientId = $(this).val();
// Skip AJAX call if this is the initial load
if (isInitialLoad) {
isInitialLoad = false;
return;
}
if (clientId === 'new') {
// Show new client modal
$('#add_client_modal').show();
} else if (clientId !== '') {
// Load client data (ajax call or from already available data)
self.loadClientData(clientId);
}
});
// Close modal button
$('.close-modal').on('click', function() {
$('#add_client_modal').hide();
// Reset client dropdown if no client was selected
if ($('#client_id').val() === 'new') {
$('#client_id').val('');
}
});
// Submit new client form
$('#add_client_form').on('submit', function(e) {
e.preventDefault();
self.addNewClient();
});
// Reset the flag after a short delay to allow for user interactions
setTimeout(function() {
isInitialLoad = false;
}, 500);
},
// Load client data when a client is selected
loadClientData: function(clientId) {
// First check if we have client data already loaded from PHP
if (typeof easyInvoice !== 'undefined' && easyInvoice.clientData && easyInvoice.clientData.id == clientId) {
var client = easyInvoice.clientData;
// Update client info fields
$('#client_name_display').text(client.name || '');
$('#client_email_display').text(client.email || '');
$('#client_phone_display').text(client.phone || '');
$('#client_address_display').html((client.address || '').replace(/\n/g, ' '));
// Show client info section
$('#client_info').show();
return;
}
// For dynamic client selection (not initial load), make an AJAX call to get client data
$.ajax({
url: easyInvoice.ajaxUrl,
type: 'POST',
data: {
action: 'easy_invoice_get_client',
nonce: easyInvoice.nonce,
client_id: clientId
},
success: function(response) {
if (response.success) {
var client = response.data;
// Update client info fields
$('#client_name_display').text(client.name);
$('#client_email_display').text(client.email);
$('#client_phone_display').text(client.phone || '');
$('#client_address_display').html(client.address.replace(/\n/g, ' ') || '');
// Show client info section
$('#client_info').show();
} else {
// Error loading client data
}
},
error: function(xhr, status, error) {
// AJAX error loading client data
}
});
},
// Add a new client via AJAX
addNewClient: function() {
var self = this;
var clientData = {
name: $('#new_client_name').val(),
email: $('#new_client_email').val(),
phone: $('#new_client_phone').val(),
address: $('#new_client_address').val(),
notes: $('#new_client_notes').val()
};
// Validate required fields
if (!clientData.name || !clientData.email) {
if (typeof EasyInvoiceToast !== 'undefined') {
EasyInvoiceToast.show('error', 'Client name and email are required.');
}
return;
}
// Send AJAX request to add client
$.ajax({
url: easyInvoice.ajaxUrl,
type: 'POST',
data: {
action: 'easy_invoice_add_client',
nonce: easyInvoice.nonce,
client_data: clientData
},
success: function(response) {
if (response.success) {
var newClient = response.data.client;
var newClientId = response.data.client_id;
// Add new client to dropdown
$('#client_id').append($('