# easy-invoice/2.1.1/assets/js/quote-save.js

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.1.1. 354 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.1.1/code/assets/js/quote-save.js
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.1.1/raw/assets/js/quote-save.js
- Modified: 2025-08-21T13:32:50+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.1.1/code/assets/js/quote-save.js#L10-L20`.

```javascript
/**
 * 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 = `<div class="field-error text-red-500 text-sm mt-1">${errorMessage}</div>`;
                            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('<i class="fas fa-spinner fa-spin mr-2"></i> 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('<i class="fas fa-save mr-2"></i> 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 = $(`
                <div class="fixed top-4 right-4 z-50 p-4 rounded-md shadow-lg max-w-sm ${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white">
                    <div class="flex items-center">
                        <div class="flex-shrink-0">
                            <i class="fas ${type === 'success' ? 'fa-check' : 'fa-exclamation-triangle'}"></i>
                        </div>
                        <div class="ml-3">
                            <p class="text-sm font-medium">${message}</p>
                        </div>
                        <div class="ml-auto pl-3">
                            <button class="text-white hover:text-gray-200" onclick="$(this).parent().parent().parent().remove()">
                                <i class="fas fa-times"></i>
                            </button>
                        </div>
                    </div>
                </div>
            `);
            
            // 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); 
```
