/**
* 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 = $(`
`);
// 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);