PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.1
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / assets / js / quote-save.js

quote-save.js in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.1, at assets/js/quote-save.js

354 lines 14.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Quote Save Functionality
3 *
4 * Handles saving and updating quotes in the quote builder page.
5 */
6 (function($) {
7 'use strict';
8
9 // Create a global object for quote save functionality
10 window.EasyQuoteSave = {
11 init: function() {
12
13 this.bindEvents();
14
15 // Load quote data if in edit mode
16 if ($('#quote-id').val()) {
17 this.loadQuoteData();
18 }
19 },
20
21 // Clear all error states
22 clearErrorStates: function() {
23 $('.form-input, .form-select, .form-textarea').removeClass('error');
24 $('.field-error').remove();
25 },
26
27 // Apply error states to specific fields
28 applyErrorStates: function(errors) {
29 this.clearErrorStates();
30
31 if (errors && typeof errors === 'object') {
32 Object.keys(errors).forEach(fieldName => {
33 const field = $(`[name="${fieldName}"]`);
34 if (field.length) {
35 field.addClass('error');
36
37 // Add error message below the field
38 const errorMessage = errors[fieldName];
39 if (errorMessage) {
40 const errorHtml = `<div class="field-error text-red-500 text-sm mt-1">${errorMessage}</div>`;
41 field.closest('div').append(errorHtml);
42 }
43 }
44 });
45 }
46 },
47
48 bindEvents: function() {
49
50
51 // Count save buttons to make sure it exists
52 const saveButtonCount = $('#save-quote-btn').length;
53
54 // Only bind the click event if the button exists
55 if (saveButtonCount > 0) {
56 // Save quote button click handler - jQuery method
57 $('#save-quote-btn').on('click', this.handleSaveButtonClick.bind(this));
58 } else {
59 // Save button not found in the DOM
60 }
61
62 // Add form submit handler to trigger save button
63 $('#quote-form').on('submit', function(e) {
64 e.preventDefault();
65 $('#save-quote-btn').click();
66 });
67
68 // Clear error states when user starts typing
69 $('.form-input, .form-select, .form-textarea').on('input change', function() {
70 const field = $(this);
71 if (field.hasClass('error')) {
72 field.removeClass('error');
73 field.siblings('.field-error').remove();
74 }
75 });
76 },
77
78 // Handle save button click event
79 handleSaveButtonClick: function(e) {
80 e.preventDefault();
81 this.saveQuote();
82 },
83
84 // Function to save or update quote
85 saveQuote: function() {
86
87 // Show loading state
88 const $saveBtn = $('#save-quote-btn');
89 const originalBtnText = $saveBtn.html();
90 $saveBtn.html('<i class="fas fa-spinner fa-spin mr-2"></i> Saving...');
91 $saveBtn.prop('disabled', true);
92
93 // Collect form data
94 const formData = this.collectFormData();
95
96
97
98 // Backend validation will handle all field validation
99 // No need for frontend validation since we have comprehensive backend validation
100
101 // Prepare the request data
102 const requestData = {
103 action: 'easy_invoice_save_quote',
104 nonce: $('#quote_nonce').val(),
105 quote_data: formData
106 };
107
108
109
110 // Send AJAX request
111 $.ajax({
112 url: easyInvoice.ajaxUrl,
113 type: 'POST',
114 data: requestData,
115
116 success: (response) => {
117
118 // Restore button state
119 $saveBtn.html(originalBtnText);
120 $saveBtn.prop('disabled', false);
121
122 if (response.success) {
123 // Clear any previous error states
124 this.clearErrorStates();
125
126 // Do not show toast here; global handler will do it
127
128 // If the button says "Save Quote", update it to "Update Quote"
129 if ($saveBtn.text().trim() === 'Save Quote') {
130 $saveBtn.html('<i class="fas fa-save mr-2"></i> Update Quote');
131 }
132
133 // Update form with any returned data if needed
134 if (response.data.quote) {
135 // Update the quote ID if it was newly created
136 if (response.data.quote.id && !$('#quote-id').val()) {
137 $('#quote-id').val(response.data.quote.id);
138 }
139
140 // Update the quote status display if available
141 if (response.data.quote.status) {
142 $('#quote-status').val(response.data.quote.status);
143 }
144
145 // Reload client data if client_id is present
146 if (response.data.quote.client_id) {
147 // Update the global client data if we have it
148 if (response.data.client && typeof easyInvoice !== 'undefined') {
149 easyInvoice.clientData = response.data.client;
150
151 // Update client display fields directly
152 $('#client-name').val(response.data.client.name || '');
153 $('#client-email').val(response.data.client.email || '');
154 $('#client-phone').val(response.data.client.phone || '');
155 $('#client-address').val(response.data.client.address || '');
156 }
157 }
158
159 // Update any other fields if needed
160 }
161
162
163 } else {
164 // Show error message
165 const errorMessage = response.data && response.data.message ? response.data.message : 'Failed to save quote';
166 this.showNotification('error', errorMessage);
167
168 // Apply error states if validation errors are provided
169 if (response.data && response.data.errors) {
170 this.applyErrorStates(response.data.errors);
171 }
172 }
173 },
174 error: (xhr, status, error) => {
175
176 // Restore button state
177 $saveBtn.html(originalBtnText);
178 $saveBtn.prop('disabled', false);
179
180 // Show error notification
181 this.showNotification('error', 'Network error occurred while saving quote. Please try again.');
182 }
183 });
184 },
185
186 // Collect all form data
187 collectFormData: function() {
188
189 const formData = {};
190
191 // Get all form inputs
192 $('#quote-form').find('input, select, textarea').each(function() {
193 const $field = $(this);
194 const name = $field.attr('name');
195 const type = $field.attr('type');
196
197 if (name && name !== 'quote_nonce') {
198 let value;
199
200 if (type === 'checkbox') {
201 value = $field.is(':checked') ? '1' : '0';
202 } else if (type === 'radio') {
203 if ($field.is(':checked')) {
204 value = $field.val();
205 }
206 } else {
207 value = $field.val();
208 }
209
210 if (value !== undefined) {
211 formData[name] = value;
212 }
213 }
214 });
215
216 // Collect items data
217 formData.items = this.collectItemsData();
218
219 return formData;
220 },
221
222 // Collect items data
223 collectItemsData: function() {
224 const items = [];
225
226 $('.quote-item').each(function(index) {
227 const $item = $(this);
228
229 const itemData = {
230 title: $item.find('input[name="items[' + index + '][title]"]').val() || '',
231 description: $item.find('textarea[name="items[' + index + '][description]"]').val() || '',
232 quantity: parseFloat($item.find('input[name="items[' + index + '][quantity]"]').val()) || 0,
233 price: parseFloat($item.find('input[name="items[' + index + '][price]"]').val()) || 0,
234 adjust_percentage: parseFloat($item.find('input[name="items[' + index + '][adjust_percentage]"]').val()) || 0,
235 taxable: $item.find('input[name="items[' + index + '][taxable]"]').is(':checked')
236 };
237
238 // Let payment manager calculate totals
239 if (window.EasyInvoicePayment) {
240 const baseTotal = itemData.quantity * itemData.price;
241 itemData.total = baseTotal * (1 + itemData.adjust_percentage / 100);
242
243 // Add tax and discount calculations from payment manager
244 const settings = window.EasyInvoicePayment.settings;
245 if (itemData.taxable && settings.taxRate > 0) {
246 if (settings.calculationMethod === 'before_tax') {
247 // Apply discount first, then tax
248 const discountAmount = settings.discountType === 'percentage'
249 ? itemData.total * (settings.discountValue / 100)
250 : (settings.discountValue / items.length); // Split fixed discount evenly
251 const afterDiscount = itemData.total - discountAmount;
252 itemData.tax_amount = afterDiscount * (settings.taxRate / 100);
253 itemData.total = afterDiscount + itemData.tax_amount;
254 } else {
255 // Calculate tax first, then apply discount
256 itemData.tax_amount = itemData.total * (settings.taxRate / 100);
257 const beforeDiscount = itemData.total + itemData.tax_amount;
258 const discountAmount = settings.discountType === 'percentage'
259 ? beforeDiscount * (settings.discountValue / 100)
260 : (settings.discountValue / items.length); // Split fixed discount evenly
261 itemData.total = beforeDiscount - discountAmount;
262 }
263 }
264 }
265
266 // Only add item if it has a title
267 if (itemData.title.trim()) {
268 items.push(itemData);
269 }
270 });
271
272 return items;
273 },
274
275 // Load quote data for editing
276 loadQuoteData: function() {
277 // This would be implemented if needed for pre-populating form fields
278 },
279
280 // Reload client data
281 reloadClientData: function(clientId) {
282 if (typeof easyInvoice !== 'undefined' && easyInvoice.ajaxUrl) {
283 $.ajax({
284 url: easyInvoice.ajaxUrl,
285 type: 'POST',
286 data: {
287 action: 'easy_invoice_get_client',
288 client_id: clientId,
289 nonce: easyInvoice.nonce
290 },
291 success: function(response) {
292 if (response.success && response.data.client) {
293 // Update client display fields
294 $('#client-name').val(response.data.client.name || '');
295 $('#client-email').val(response.data.client.email || '');
296 $('#client-phone').val(response.data.client.phone || '');
297 $('#client-address').val(response.data.client.address || '');
298
299 // Update global client data
300 if (typeof easyInvoice !== 'undefined') {
301 easyInvoice.clientData = response.data.client;
302 }
303 }
304 }
305 });
306 }
307 },
308
309 // Show notification
310 showNotification: function(type, message) {
311
312 // Create notification element
313 const notification = $(`
314 <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">
315 <div class="flex items-center">
316 <div class="flex-shrink-0">
317 <i class="fas ${type === 'success' ? 'fa-check' : 'fa-exclamation-triangle'}"></i>
318 </div>
319 <div class="ml-3">
320 <p class="text-sm font-medium">${message}</p>
321 </div>
322 <div class="ml-auto pl-3">
323 <button class="text-white hover:text-gray-200" onclick="$(this).parent().parent().parent().remove()">
324 <i class="fas fa-times"></i>
325 </button>
326 </div>
327 </div>
328 </div>
329 `);
330
331 // Add to page
332 $('body').append(notification);
333
334 // Auto-remove after 5 seconds
335 setTimeout(() => {
336 notification.fadeOut(() => notification.remove());
337 }, 5000);
338 },
339
340 // Update preview
341 updatePreview: function() {
342 // This would be implemented to update the live preview
343 if (window.EasyInvoiceBuilder && typeof window.EasyInvoiceBuilder.updatePreview === 'function') {
344 window.EasyInvoiceBuilder.updatePreview();
345 }
346 }
347 };
348
349 // Initialize when document is ready
350 $(document).ready(function() {
351 window.EasyQuoteSave.init();
352 });
353
354 })(jQuery);