/** * 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($('