/** * Quote Save Functionality * * Handles saving and updating quotes in the quote builder page. */ (function($) { 'use strict'; // Create a global object for quote save functionality window.EasyQuoteSave = { init: function() { this.bindEvents(); // Load quote data if in edit mode if ($('#quote-id').val()) { this.loadQuoteData(); } }, // Clear all error states clearErrorStates: function() { $('.form-input, .form-select, .form-textarea').removeClass('error'); $('.field-error').remove(); }, // Apply error states to specific fields applyErrorStates: function(errors) { this.clearErrorStates(); if (errors && typeof errors === 'object') { Object.keys(errors).forEach(fieldName => { const field = $(`[name="${fieldName}"]`); if (field.length) { field.addClass('error'); // Add error message below the field const errorMessage = errors[fieldName]; if (errorMessage) { const errorHtml = `
${errorMessage}
`; field.closest('div').append(errorHtml); } } }); } }, bindEvents: function() { // Count save buttons to make sure it exists const saveButtonCount = $('#save-quote-btn').length; // Only bind the click event if the button exists if (saveButtonCount > 0) { // Save quote button click handler - jQuery method $('#save-quote-btn').on('click', this.handleSaveButtonClick.bind(this)); } else { // Save button not found in the DOM } // Add form submit handler to trigger save button $('#quote-form').on('submit', function(e) { e.preventDefault(); $('#save-quote-btn').click(); }); // Clear error states when user starts typing $('.form-input, .form-select, .form-textarea').on('input change', function() { const field = $(this); if (field.hasClass('error')) { field.removeClass('error'); field.siblings('.field-error').remove(); } }); }, // Handle save button click event handleSaveButtonClick: function(e) { e.preventDefault(); this.saveQuote(); }, // Function to save or update quote saveQuote: function() { // Show loading state const $saveBtn = $('#save-quote-btn'); const originalBtnText = $saveBtn.html(); $saveBtn.html(' Saving...'); $saveBtn.prop('disabled', true); // Collect form data const formData = this.collectFormData(); // Backend validation will handle all field validation // No need for frontend validation since we have comprehensive backend validation // Prepare the request data const requestData = { action: 'easy_invoice_save_quote', nonce: $('#quote_nonce').val(), quote_data: formData }; // Send AJAX request $.ajax({ url: easyInvoice.ajaxUrl, type: 'POST', data: requestData, success: (response) => { // Restore button state $saveBtn.html(originalBtnText); $saveBtn.prop('disabled', false); if (response.success) { // Clear any previous error states this.clearErrorStates(); // Do not show toast here; global handler will do it // If the button says "Save Quote", update it to "Update Quote" if ($saveBtn.text().trim() === 'Save Quote') { $saveBtn.html(' Update Quote'); } // Update form with any returned data if needed if (response.data.quote) { // Update the quote ID if it was newly created if (response.data.quote.id && !$('#quote-id').val()) { $('#quote-id').val(response.data.quote.id); } // Update the quote status display if available if (response.data.quote.status) { $('#quote-status').val(response.data.quote.status); } // Reload client data if client_id is present if (response.data.quote.client_id) { // Update the global client data if we have it if (response.data.client && typeof easyInvoice !== 'undefined') { easyInvoice.clientData = response.data.client; // Update client display fields directly $('#client-name').val(response.data.client.name || ''); $('#client-email').val(response.data.client.email || ''); $('#client-phone').val(response.data.client.phone || ''); $('#client-address').val(response.data.client.address || ''); } } // Update any other fields if needed } } else { // Show error message const errorMessage = response.data && response.data.message ? response.data.message : 'Failed to save quote'; this.showNotification('error', errorMessage); // Apply error states if validation errors are provided if (response.data && response.data.errors) { this.applyErrorStates(response.data.errors); } } }, error: (xhr, status, error) => { // Restore button state $saveBtn.html(originalBtnText); $saveBtn.prop('disabled', false); // Show error notification this.showNotification('error', 'Network error occurred while saving quote. Please try again.'); } }); }, // Collect all form data collectFormData: function() { const formData = {}; // Get all form inputs $('#quote-form').find('input, select, textarea').each(function() { const $field = $(this); const name = $field.attr('name'); const type = $field.attr('type'); if (name && name !== 'quote_nonce') { let value; if (type === 'checkbox') { value = $field.is(':checked') ? '1' : '0'; } else if (type === 'radio') { if ($field.is(':checked')) { value = $field.val(); } } else { value = $field.val(); } if (value !== undefined) { formData[name] = value; } } }); // Collect items data formData.items = this.collectItemsData(); return formData; }, // Collect items data collectItemsData: function() { const items = []; $('.quote-item').each(function(index) { const $item = $(this); const itemData = { title: $item.find('input[name="items[' + index + '][title]"]').val() || '', description: $item.find('textarea[name="items[' + index + '][description]"]').val() || '', quantity: parseFloat($item.find('input[name="items[' + index + '][quantity]"]').val()) || 0, price: parseFloat($item.find('input[name="items[' + index + '][price]"]').val()) || 0, adjust_percentage: parseFloat($item.find('input[name="items[' + index + '][adjust_percentage]"]').val()) || 0, taxable: $item.find('input[name="items[' + index + '][taxable]"]').is(':checked') }; // Let payment manager calculate totals if (window.EasyInvoicePayment) { const baseTotal = itemData.quantity * itemData.price; itemData.total = baseTotal * (1 + itemData.adjust_percentage / 100); // Add tax and discount calculations from payment manager const settings = window.EasyInvoicePayment.settings; if (itemData.taxable && settings.taxRate > 0) { if (settings.calculationMethod === 'before_tax') { // Apply discount first, then tax const discountAmount = settings.discountType === 'percentage' ? itemData.total * (settings.discountValue / 100) : (settings.discountValue / items.length); // Split fixed discount evenly const afterDiscount = itemData.total - discountAmount; itemData.tax_amount = afterDiscount * (settings.taxRate / 100); itemData.total = afterDiscount + itemData.tax_amount; } else { // Calculate tax first, then apply discount itemData.tax_amount = itemData.total * (settings.taxRate / 100); const beforeDiscount = itemData.total + itemData.tax_amount; const discountAmount = settings.discountType === 'percentage' ? beforeDiscount * (settings.discountValue / 100) : (settings.discountValue / items.length); // Split fixed discount evenly itemData.total = beforeDiscount - discountAmount; } } } // Only add item if it has a title if (itemData.title.trim()) { items.push(itemData); } }); return items; }, // Load quote data for editing loadQuoteData: function() { // This would be implemented if needed for pre-populating form fields }, // Reload client data reloadClientData: function(clientId) { if (typeof easyInvoice !== 'undefined' && easyInvoice.ajaxUrl) { $.ajax({ url: easyInvoice.ajaxUrl, type: 'POST', data: { action: 'easy_invoice_get_client', client_id: clientId, nonce: easyInvoice.nonce }, success: function(response) { if (response.success && response.data.client) { // Update client display fields $('#client-name').val(response.data.client.name || ''); $('#client-email').val(response.data.client.email || ''); $('#client-phone').val(response.data.client.phone || ''); $('#client-address').val(response.data.client.address || ''); // Update global client data if (typeof easyInvoice !== 'undefined') { easyInvoice.clientData = response.data.client; } } } }); } }, // Show notification showNotification: function(type, message) { // Create notification element const notification = $(`

${message}

`); // Add to page $('body').append(notification); // Auto-remove after 5 seconds setTimeout(() => { notification.fadeOut(() => notification.remove()); }, 5000); }, // Update preview updatePreview: function() { // This would be implemented to update the live preview if (window.EasyInvoiceBuilder && typeof window.EasyInvoiceBuilder.updatePreview === 'function') { window.EasyInvoiceBuilder.updatePreview(); } } }; // Initialize when document is ready $(document).ready(function() { window.EasyQuoteSave.init(); }); })(jQuery);