PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.3
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 / invoice-save.js

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

404 lines 17.8 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 $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>');
172 }
173
174 // Update form with any returned data if needed
175 if (response.data.invoice) {
176 // Update the invoice status display if available
177 if (response.data.invoice.status) {
178 $('#payment-status').val(response.data.invoice.status);
179 }
180
181 // Reload client data if client_id is present
182 if (response.data.invoice.client_id) {
183 // Update the global client data if we have it
184 if (response.data.client && typeof easyInvoice !== 'undefined') {
185 easyInvoice.clientData = response.data.client;
186
187 // Update client display fields directly
188 $('#client-name').val(response.data.client.name || '');
189 $('#client-email').val(response.data.client.email || '');
190 $('#client-phone').val(response.data.client.phone || '');
191 $('#client-address').val(response.data.client.address || '');
192 }
193 }
194
195 // Update any other fields if needed
196 }
197
198 $(document).trigger('easy-invoice-saved', [response]);
199 } else {
200 $(document).trigger('easy-invoice-save-failed', [response]);
201 // Show error message
202 const errorMessage = response.data && response.data.message ? response.data.message : 'Failed to save invoice';
203 if (typeof EasyInvoiceToast !== 'undefined') {
204 EasyInvoiceToast.show('error', errorMessage);
205 }
206
207 // Apply error states if validation errors are provided
208 if (response.data && response.data.errors) {
209 this.applyErrorStates(response.data.errors);
210 }
211 }
212 },
213 error: (xhr, status, error) => {
214 $(document).trigger('easy-invoice-save-failed', [xhr]);
215
216 // Restore button state
217 $saveBtn.html(originalBtnText);
218 $saveBtn.prop('disabled', false).removeAttr('aria-busy');
219
220 // Try to parse the response text as JSON
221 let errorMessage = 'An error occurred while saving the invoice';
222 try {
223 const jsonResponse = JSON.parse(xhr.responseText);
224 if (jsonResponse && jsonResponse.data && jsonResponse.data.message) {
225 errorMessage = jsonResponse.data.message;
226 }
227 } catch (e) {
228 // Could not parse error response as JSON
229 }
230
231 // Show error message
232 if (typeof EasyInvoiceToast !== 'undefined') {
233 EasyInvoiceToast.show('error', errorMessage);
234 }
235 }
236 });
237 },
238
239 // Function to collect form data
240 collectFormData: function() {
241 // Get all form data using jQuery's serializeArray
242 const formArray = $('#invoice-form').serializeArray();
243
244 const data = {};
245
246 // Convert form array to object - exclude individual items fields to prevent duplication
247 $.each(formArray, function(i, field) {
248 // Skip individual items fields (items[0][title], items[0][description], etc.)
249 // These will be collected separately
250 if (!field.name.match(/^items\[\d+\]\[/)) {
251 data[field.name] = field.value;
252 }
253 });
254
255 // Collect items data separately
256 const items = [];
257 const itemRows = $('.invoice-item');
258
259 itemRows.each(function(index) {
260 const itemData = {};
261 const itemFields = $(this).find('input, select, textarea');
262
263 itemFields.each(function() {
264 const fieldName = $(this).attr('name');
265 if (fieldName) {
266 // Extract the field name from items[index][field_name] format
267 const match = fieldName.match(/items\[(\d+)\]\[([^\]]+)\]/);
268 if (match) {
269 const fieldKey = match[2];
270 let fieldValue = $(this).val();
271
272 // Handle checkbox fields
273 if ($(this).attr('type') === 'checkbox') {
274 fieldValue = $(this).is(':checked') ? '1' : '0';
275 }
276
277 itemData[fieldKey] = fieldValue;
278 }
279 }
280 });
281
282
283 // Only add item if it has at least a title or description
284 if (itemData.title || itemData.description) {
285 items.push(itemData);
286 }
287 });
288
289 // Add items to form data
290 data.items = items;
291
292 return data;
293 },
294
295 // Function to initialize page for an existing invoice
296 loadInvoiceData: function() {
297 // Check if we have an invoice ID
298 var invoiceId = parseInt(easyInvoice.invoiceId);
299
300 // If we have an invoice ID, load its data
301 if (invoiceId > 0) {
302
303 // No need to make an AJAX call - data is already loaded in PHP and passed via wp_localize_script
304
305 // Initialize client data if available
306 if ($('#customer-name').val()) {
307 }
308
309 // Update the UI/preview
310 this.updatePreview();
311 } else {
312 // No invoice ID found
313 }
314 },
315
316 // Function to show notification
317 showNotification: function(type, message) {
318 // Deprecated: Use EasyInvoiceToast instead
319 },
320
321 // Update invoice preview
322 updatePreview: function() {
323 // Update invoice details
324 $('#preview-invoice-title').text($('#invoice-title').val() || 'Invoice Title');
325 $('#preview-invoice-description').text($('#invoice-description').val() || 'Invoice description will appear here.');
326 $('#preview-invoice-number').text($('#invoice-number').val() || '-');
327 $('#preview-invoice-date').text($('#invoice-date').val() || '-');
328 $('#preview-due-date').text($('#due-date').val() || '-');
329
330 // Update client details
331 $('#preview-customer-name').text($('#customer-name').val() || 'Client Name');
332 $('#preview-customer-email').text($('#customer-email').val() || 'client@example.com');
333
334 // Update notes & terms
335 $('#preview-notes').text($('#notes').val() || 'Thank you for your business!');
336 $('#preview-terms').text($('#terms').val() || 'Payment is due within 30 days.');
337
338 // Update tax rate display
339 $('#preview-tax-rate').text($('#tax-rate').val() || '0');
340
341 // Update template
342 const selectedTemplate = $('input[name="invoice_template"]:checked').val() || 'standard';
343 $('#preview-live').removeClass('template-standard template-professional template-minimal')
344 .addClass('template-' + selectedTemplate);
345
346 // If payment manager is available, let it update the totals
347 if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
348 window.EasyInvoicePayment.updateTotals();
349 }
350 },
351
352 // Reload client data after save
353 reloadClientData: function(clientId) {
354 if (!clientId) {
355 return;
356 }
357
358 // Update the client ID field
359 $('#client-id').val(clientId);
360
361 // Update the select dropdown
362 $('#select-client').val(clientId);
363
364 // Check if we have client data already loaded from PHP
365 if (typeof easyInvoice !== 'undefined' && easyInvoice.clientData && easyInvoice.clientData.id == clientId) {
366 const client = easyInvoice.clientData;
367
368 // Update client display fields
369 const clientName = client.name || 'N/A';
370
371 $('#selected-client-name').text(clientName);
372 $('#selected-client-email').text(client.email || 'N/A');
373 $('#display-client-name').text(clientName);
374 $('#display-client-company').text(client.company || 'N/A');
375 $('#display-client-email').text(client.email || 'N/A');
376 $('#display-client-phone').text(client.phone || 'N/A');
377 $('#display-client-website').text(client.website || 'N/A');
378 $('#display-client-address').text(client.address || 'N/A');
379
380 // Show client info sections
381 $('#selected-client-display').show().removeClass('hidden');
382 $('#client-info-display').show().removeClass('hidden');
383 $('#no-client-message').hide().addClass('hidden');
384
385 // Update the edit client button URL
386 $('#edit-selected-client').attr('href', easyInvoice.adminUrl + 'admin.php?page=easy-invoice-client-edit&client_id=' + clientId);
387
388 // Update search input
389 $('#client-search-input').val(clientName);
390 } else {
391 // If client data is not available from PHP, we need to reload the page
392 // to get the updated client data from the server
393
394 window.location.reload();
395 }
396 }
397 };
398
399 // Initialize invoice save when document is ready
400 $(document).ready(function() {
401 window.EasyInvoiceSave.init();
402 });
403
404 })(jQuery);