PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.4
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.4
2.4.3 2.4.4 2.4.2 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 All 60 releases
easy-invoice / assets / js / invoice-save.js

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

409 lines 18.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Invoice Save Functionality
3 *
4 * Handles saving and updating invoices in the invoice builder page.
5 */
6 (function($) {
7 'use strict';
8
9 function eiBuilderI18n(key, fallback) {
10 if (typeof easyInvoice !== 'undefined' && easyInvoice.i18n && easyInvoice.i18n[key]) {
11 return easyInvoice.i18n[key];
12 }
13 return fallback || '';
14 }
15
16 var spinnerSvg = '<svg class="ei-btn-spinner h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
17
18 // Check if we're on an invoice page
19 const isInvoicePage = window.location.href.includes('easy-invoice-builder') ||
20 window.location.href.includes('easy-invoice-new') ||
21 $('#invoice-form').length > 0;
22
23 if (!isInvoicePage) {
24
25 return;
26 }
27
28 // Create a global object for invoice save functionality
29 window.EasyInvoiceSave = {
30 init: function() {
31
32 this.bindEvents();
33
34 // Load invoice data if in edit mode
35 if ($('#invoice-id').val()) {
36 this.loadInvoiceData();
37 } else {
38 // No invoice ID - this is a new invoice
39 }
40 },
41
42 // Clear all error states
43 clearErrorStates: function() {
44 $('.form-input, .form-select, .form-textarea').removeClass('error');
45 $('.field-error').remove();
46 },
47
48 // Apply error states to specific fields
49 applyErrorStates: function(errors) {
50 this.clearErrorStates();
51
52 if (errors && typeof errors === 'object') {
53 Object.keys(errors).forEach(fieldName => {
54 const field = $(`[name="${fieldName}"]`);
55 if (field.length) {
56 field.addClass('error');
57
58 // Add error message below the field
59 const errorMessage = errors[fieldName];
60 if (errorMessage) {
61 const errorHtml = `<div class="field-error text-red-500 text-sm mt-1">${errorMessage}</div>`;
62 field.closest('div').append(errorHtml);
63 }
64 }
65 });
66 }
67 },
68
69 bindEvents: function() {
70
71
72 // Count save buttons to make sure it exists
73 const saveButtonCount = $('#save-invoice-btn').length;
74
75 // Only bind the click event if the button exists
76 if (saveButtonCount > 0) {
77 // Save invoice button click handler - jQuery method
78 $('#save-invoice-btn').on('click', this.handleSaveButtonClick.bind(this));
79 } else {
80 // Save button not found in the DOM
81 }
82
83 // Add form submit handler to trigger save button
84 $('#invoice-form').on('submit', function(e) {
85 e.preventDefault();
86
87 // Ensure all item totals are calculated before submission
88 $('.invoice-item').each(function() {
89 const $item = $(this);
90 if (window.EasyInvoiceBuilder && typeof window.EasyInvoiceBuilder.calculateItemTotal === 'function') {
91 window.EasyInvoiceBuilder.calculateItemTotal($item);
92 }
93 });
94
95 // Update overall totals
96 if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
97 window.EasyInvoicePayment.updateTotals();
98 }
99
100 $('#save-invoice-btn').click();
101 });
102
103 // Clear error states when user starts typing
104 $('.form-input, .form-select, .form-textarea').on('input change', function() {
105 const field = $(this);
106 if (field.hasClass('error')) {
107 field.removeClass('error');
108 field.siblings('.field-error').remove();
109 }
110 });
111 },
112
113 // Handle save button click event
114 handleSaveButtonClick: function(e) {
115 e.preventDefault();
116 this.saveInvoice();
117 },
118
119 // Function to save or update invoice
120 saveInvoice: function() {
121
122 // Show loading state
123 const $saveBtn = $('#save-invoice-btn');
124 const originalBtnText = $saveBtn.html();
125 $saveBtn.html('<span class="inline-flex items-center gap-2">' + spinnerSvg + '<span>' + eiBuilderI18n('saving', 'Saving…') + '</span></span>');
126 $saveBtn.prop('disabled', true).attr('aria-busy', 'true');
127
128 // Collect form data
129 const formData = this.collectFormData();
130
131
132
133 // Backend validation will handle all field validation
134 // No need for frontend validation since we have comprehensive backend validation
135
136 // Prepare the request data
137 const requestData = {
138 action: 'easy_invoice_save_invoice',
139 nonce: $('#invoice_nonce').val(),
140 invoice_data: formData
141 };
142
143
144
145 // Send AJAX request
146 $.ajax({
147 url: easyInvoice.ajaxUrl,
148 type: 'POST',
149 data: requestData,
150
151 success: (response) => {
152
153 // Restore button state
154 $saveBtn.html(originalBtnText);
155 $saveBtn.prop('disabled', false).removeAttr('aria-busy');
156
157 if (response.success) {
158 // Clear any previous error states
159 this.clearErrorStates();
160
161 // If this is a new invoice (no invoice_id in form), update URL without redirect
162
163 // Do not show toast here; global handler will do it
164
165 var invId = $('#invoice-id').val();
166 if (response.data.invoice && response.data.invoice.id) {
167 $('#invoice-id').val(response.data.invoice.id);
168 invId = response.data.invoice.id;
169 }
170 if (invId && String(invId) !== '0') {
171 // Put the id in the address bar so a refresh, a bookmark or a
172 // second Save reopens this invoice instead of a blank builder.
173 if (window.history && window.history.replaceState && !/[?&]invoice_id=/.test(window.location.search)) {
174 var eiUrl = new URL(window.location.href); eiUrl.searchParams.set('invoice_id', String(invId)); window.history.replaceState({}, '', eiUrl.toString());
175 }
176 $saveBtn.html('<span class="inline-flex items-center gap-2"><svg class="h-4 w-4 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" /></svg><span>' + eiBuilderI18n('update_invoice', 'Update Invoice') + '</span></span>');
177 }
178
179 // Update form with any returned data if needed
180 if (response.data.invoice) {
181 // Update the invoice status display if available
182 if (response.data.invoice.status) {
183 $('#payment-status').val(response.data.invoice.status);
184 }
185
186 // Reload client data if client_id is present
187 if (response.data.invoice.client_id) {
188 // Update the global client data if we have it
189 if (response.data.client && typeof easyInvoice !== 'undefined') {
190 easyInvoice.clientData = response.data.client;
191
192 // Update client display fields directly
193 $('#client-name').val(response.data.client.name || '');
194 $('#client-email').val(response.data.client.email || '');
195 $('#client-phone').val(response.data.client.phone || '');
196 $('#client-address').val(response.data.client.address || '');
197 }
198 }
199
200 // Update any other fields if needed
201 }
202
203 $(document).trigger('easy-invoice-saved', [response]);
204 } else {
205 $(document).trigger('easy-invoice-save-failed', [response]);
206 // Show error message
207 const errorMessage = response.data && response.data.message ? response.data.message : 'Failed to save invoice';
208 if (typeof EasyInvoiceToast !== 'undefined') {
209 EasyInvoiceToast.show('error', errorMessage);
210 }
211
212 // Apply error states if validation errors are provided
213 if (response.data && response.data.errors) {
214 this.applyErrorStates(response.data.errors);
215 }
216 }
217 },
218 error: (xhr, status, error) => {
219 $(document).trigger('easy-invoice-save-failed', [xhr]);
220
221 // Restore button state
222 $saveBtn.html(originalBtnText);
223 $saveBtn.prop('disabled', false).removeAttr('aria-busy');
224
225 // Try to parse the response text as JSON
226 let errorMessage = 'An error occurred while saving the invoice';
227 try {
228 const jsonResponse = JSON.parse(xhr.responseText);
229 if (jsonResponse && jsonResponse.data && jsonResponse.data.message) {
230 errorMessage = jsonResponse.data.message;
231 }
232 } catch (e) {
233 // Could not parse error response as JSON
234 }
235
236 // Show error message
237 if (typeof EasyInvoiceToast !== 'undefined') {
238 EasyInvoiceToast.show('error', errorMessage);
239 }
240 }
241 });
242 },
243
244 // Function to collect form data
245 collectFormData: function() {
246 // Get all form data using jQuery's serializeArray
247 const formArray = $('#invoice-form').serializeArray();
248
249 const data = {};
250
251 // Convert form array to object - exclude individual items fields to prevent duplication
252 $.each(formArray, function(i, field) {
253 // Skip individual items fields (items[0][title], items[0][description], etc.)
254 // These will be collected separately
255 if (!field.name.match(/^items\[\d+\]\[/)) {
256 data[field.name] = field.value;
257 }
258 });
259
260 // Collect items data separately
261 const items = [];
262 const itemRows = $('.invoice-item');
263
264 itemRows.each(function(index) {
265 const itemData = {};
266 const itemFields = $(this).find('input, select, textarea');
267
268 itemFields.each(function() {
269 const fieldName = $(this).attr('name');
270 if (fieldName) {
271 // Extract the field name from items[index][field_name] format
272 const match = fieldName.match(/items\[(\d+)\]\[([^\]]+)\]/);
273 if (match) {
274 const fieldKey = match[2];
275 let fieldValue = $(this).val();
276
277 // Handle checkbox fields
278 if ($(this).attr('type') === 'checkbox') {
279 fieldValue = $(this).is(':checked') ? '1' : '0';
280 }
281
282 itemData[fieldKey] = fieldValue;
283 }
284 }
285 });
286
287
288 // Only add item if it has at least a title or description
289 if (itemData.title || itemData.description) {
290 items.push(itemData);
291 }
292 });
293
294 // Add items to form data
295 data.items = items;
296
297 return data;
298 },
299
300 // Function to initialize page for an existing invoice
301 loadInvoiceData: function() {
302 // Check if we have an invoice ID
303 var invoiceId = parseInt(easyInvoice.invoiceId);
304
305 // If we have an invoice ID, load its data
306 if (invoiceId > 0) {
307
308 // No need to make an AJAX call - data is already loaded in PHP and passed via wp_localize_script
309
310 // Initialize client data if available
311 if ($('#customer-name').val()) {
312 }
313
314 // Update the UI/preview
315 this.updatePreview();
316 } else {
317 // No invoice ID found
318 }
319 },
320
321 // Function to show notification
322 showNotification: function(type, message) {
323 // Deprecated: Use EasyInvoiceToast instead
324 },
325
326 // Update invoice preview
327 updatePreview: function() {
328 // Update invoice details
329 $('#preview-invoice-title').text($('#invoice-title').val() || 'Invoice Title');
330 $('#preview-invoice-description').text($('#invoice-description').val() || 'Invoice description will appear here.');
331 $('#preview-invoice-number').text($('#invoice-number').val() || '-');
332 $('#preview-invoice-date').text($('#invoice-date').val() || '-');
333 $('#preview-due-date').text($('#due-date').val() || '-');
334
335 // Update client details
336 $('#preview-customer-name').text($('#customer-name').val() || 'Client Name');
337 $('#preview-customer-email').text($('#customer-email').val() || '[email protected]');
338
339 // Update notes & terms
340 $('#preview-notes').text($('#notes').val() || 'Thank you for your business!');
341 $('#preview-terms').text($('#terms').val() || 'Payment is due within 30 days.');
342
343 // Update tax rate display
344 $('#preview-tax-rate').text($('#tax-rate').val() || '0');
345
346 // Update template
347 const selectedTemplate = $('input[name="invoice_template"]:checked').val() || 'standard';
348 $('#preview-live').removeClass('template-standard template-professional template-minimal')
349 .addClass('template-' + selectedTemplate);
350
351 // If payment manager is available, let it update the totals
352 if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
353 window.EasyInvoicePayment.updateTotals();
354 }
355 },
356
357 // Reload client data after save
358 reloadClientData: function(clientId) {
359 if (!clientId) {
360 return;
361 }
362
363 // Update the client ID field
364 $('#client-id').val(clientId);
365
366 // Update the select dropdown
367 $('#select-client').val(clientId);
368
369 // Check if we have client data already loaded from PHP
370 if (typeof easyInvoice !== 'undefined' && easyInvoice.clientData && easyInvoice.clientData.id == clientId) {
371 const client = easyInvoice.clientData;
372
373 // Update client display fields
374 const clientName = client.name || 'N/A';
375
376 $('#selected-client-name').text(clientName);
377 $('#selected-client-email').text(client.email || 'N/A');
378 $('#display-client-name').text(clientName);
379 $('#display-client-company').text(client.company || 'N/A');
380 $('#display-client-email').text(client.email || 'N/A');
381 $('#display-client-phone').text(client.phone || 'N/A');
382 $('#display-client-website').text(client.website || 'N/A');
383 $('#display-client-address').text(client.address || 'N/A');
384
385 // Show client info sections
386 $('#selected-client-display').show().removeClass('hidden');
387 $('#client-info-display').show().removeClass('hidden');
388 $('#no-client-message').hide().addClass('hidden');
389
390 // Update the edit client button URL
391 $('#edit-selected-client').attr('href', easyInvoice.adminUrl + 'admin.php?page=easy-invoice-client-edit&client_id=' + clientId);
392
393 // Update search input
394 $('#client-search-input').val(clientName);
395 } else {
396 // If client data is not available from PHP, we need to reload the page
397 // to get the updated client data from the server
398
399 window.location.reload();
400 }
401 }
402 };
403
404 // Initialize invoice save when document is ready
405 $(document).ready(function() {
406 window.EasyInvoiceSave.init();
407 });
408
409 })(jQuery);