PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.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 / settings.js

settings.js in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.3.1, at assets/js/settings.js

631 lines 25.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(function($) {
2 // Add CSS for drag and drop functionality
3 $('<style>')
4 .prop('type', 'text/css')
5 .html(`
6 .gateway-handle {
7 cursor: move;
8 transition: color 0.2s ease;
9 }
10 .gateway-handle:hover {
11 color: #6b7280 !important;
12 }
13 .gateway-item.dragging {
14 opacity: 0.8;
15 transform: rotate(2deg);
16 box-shadow: 0 10px 25px rgba(0,0,0,0.15);
17 z-index: 1000;
18 }
19 .gateway-item-placeholder {
20 background: #f3f4f6;
21 border: 2px dashed #d1d5db;
22 border-radius: 0.5rem;
23 height: 6rem;
24 margin: 1.25rem 0;
25 }
26 body.dragging-gateway .gateway-item:not(.dragging) {
27 transition: transform 0.2s ease;
28 }
29 body.dragging-gateway .gateway-item:not(.dragging):hover {
30 transform: translateY(-2px);
31 }
32 `)
33 .appendTo('head');
34
35
36 // Initialize Select2 for multiselect fields if Select2 is available
37 function initializeSelect2() {
38 if ($.fn.select2) {
39 $('select[multiple="multiple"]').each(function() {
40 if (!$(this).hasClass('select2-hidden-accessible')) {
41
42 $(this).select2({
43 width: '100%',
44 placeholder: $(this).attr('placeholder') || 'Select options',
45 closeOnSelect: false,
46 allowClear: true
47 });
48 }
49 });
50 }
51 }
52
53 // Initialize Select2 on page load
54 initializeSelect2();
55
56 // Reinitialize Select2 after settings save
57 $(document).on('settingsSaved', function() {
58 setTimeout(function() {
59 initializeSelect2();
60 }, 500);
61 });
62
63 // Make gateways sortable
64 if ($("#sortable-gateways").length) {
65 $("#sortable-gateways").sortable({
66 handle: ".gateway-handle",
67 axis: "y",
68 placeholder: "gateway-item-placeholder bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg h-24",
69 tolerance: "pointer",
70 opacity: 0.8,
71 update: function(event, ui) {
72 // Update the order inputs when items are reordered
73 $('#sortable-gateways .gateway-item').each(function(index) {
74 $(this).find('input.gateway-order-input').val($(this).data('gateway-id'));
75 });
76
77 // Show a subtle indication that order was updated
78 if (typeof EasyInvoiceToast !== 'undefined') {
79 EasyInvoiceToast.info('Payment method order updated. Save settings to apply changes.', { duration: 3000 });
80 }
81 },
82 start: function(event, ui) {
83 ui.item.addClass('dragging');
84 $('body').addClass('dragging-gateway');
85 },
86 stop: function(event, ui) {
87 ui.item.removeClass('dragging');
88 $('body').removeClass('dragging-gateway');
89 }
90 }).disableSelection();
91 }
92
93 // Toggle gateway settings visibility based on checkbox
94 $('#settings-form').on('change', '.gateway-enable-checkbox', function() {
95 var settingsDiv = $(this).closest('.gateway-item').find('.gateway-settings');
96 if ($(this).is(':checked')) {
97 settingsDiv.removeClass('hidden');
98 } else {
99 settingsDiv.addClass('hidden');
100 }
101 });
102
103 // Initialize gateway settings visibility on page load
104 $('.gateway-enable-checkbox').each(function() {
105 var settingsDiv = $(this).closest('.gateway-item').find('.gateway-settings');
106 if ($(this).is(':checked')) {
107 settingsDiv.removeClass('hidden');
108 } else {
109 settingsDiv.addClass('hidden');
110 }
111 });
112
113 // Generic function to handle all conditional fields
114 function updateConditionalFields() {
115 $('.conditional-field').each(function() {
116 const field = $(this);
117 let shouldShow = true;
118
119 // Check all dependencies
120 field.find('.depends-on-*').each(function() {
121 const className = $(this).attr('class');
122 const matches = className.match(/depends-on-([^-]+)-([^-]+)/);
123 if (matches) {
124 const dependsKey = matches[1];
125 const dependsValue = matches[2];
126 const dependsField = $('select[name="settings[' + dependsKey + ']"], input[name="settings[' + dependsKey + ']"]');
127
128 if (dependsField.length && dependsField.val() !== dependsValue) {
129 shouldShow = false;
130 }
131 }
132 });
133
134 if (shouldShow) {
135 field.removeClass('hidden');
136 } else {
137 field.addClass('hidden');
138 }
139 });
140 }
141
142 // Initialize conditional field visibility on page load
143 updateConditionalFields();
144
145 // Update fields when any dependency field changes
146 $(document).on('change', 'select[name*="settings["], input[name*="settings["]', function() {
147 updateConditionalFields();
148 });
149
150 // Handle logo upload
151 $('.upload-logo-button').on('click', function(e) {
152 e.preventDefault();
153
154
155 const button = $(this);
156 const fieldId = button.attr('id').replace('upload_image_button_', '');
157 const previewId = '#' + fieldId + '-preview';
158 const inputId = '#' + fieldId;
159
160
161
162 if (typeof wp === 'undefined' || typeof wp.media === 'undefined') {
163 console.error('WordPress media library not loaded');
164 if (typeof EasyInvoiceToast !== 'undefined') {
165 EasyInvoiceToast.show('error', 'Media library not available. Please refresh the page and try again.');
166 }
167 return;
168 }
169
170 const frame = wp.media({
171 title: 'Select Company Logo',
172 button: { text: 'Use this logo' },
173 multiple: false
174 });
175
176 frame.on('select', function() {
177 const attachment = frame.state().get('selection').first().toJSON();
178 $(previewId).attr('src', attachment.url).removeClass('hidden');
179 $(inputId).val(attachment.url);
180 });
181
182 frame.open();
183 });
184
185 // Handle form submission
186 $('#save-settings, #save-settings-bottom').on('click', function(e) {
187 e.preventDefault();
188 const button = $(this);
189 const originalText = button.html();
190
191 // Sync wp_editor content before submission
192 if (typeof tinyMCE !== 'undefined') {
193 tinyMCE.triggerSave();
194 }
195
196 button.prop('disabled', true)
197 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Saving...');
198 const formData = new FormData($('#settings-form')[0]);
199 formData.append('action', 'easy_invoice_save_settings');
200 formData.append('nonce', easyInvoiceSettings.nonce);
201 if (!$('input[name="settings[easy_invoice_payment_methods][]"]:checked').length) {
202 formData.append('settings[easy_invoice_payment_methods]', '');
203 }
204
205
206 $.ajax({
207 url: easyInvoiceSettings.ajaxurl,
208 type: 'POST',
209 data: formData,
210 processData: false,
211 contentType: false,
212 success: function(response) {
213
214 if (response.success) {
215 if (typeof EasyInvoiceToast !== 'undefined') {
216 EasyInvoiceToast.success('Settings saved successfully');
217 } else {
218 // Simple fallback if toast system is not available
219 alert('Settings saved successfully');
220 }
221
222 // Trigger settingsSaved event to reinitialize Select2
223 $(document).trigger('settingsSaved');
224 } else {
225 // Check for error message in different possible locations
226 let errorMessage = 'Error saving settings';
227 if (response.message) {
228 errorMessage = response.message;
229 } else if (response.data && response.data.message) {
230 errorMessage = response.data.message;
231 }
232
233 if (typeof EasyInvoiceToast !== 'undefined') {
234 EasyInvoiceToast.error(errorMessage);
235 } else {
236 alert(errorMessage);
237 }
238 }
239 button.prop('disabled', false).html(originalText);
240 },
241 error: function(xhr, status, error) {
242 console.error('Settings save AJAX error:', {xhr, status, error});
243
244 let errorMessage = 'Error saving settings';
245 if (xhr.responseJSON && xhr.responseJSON.message) {
246 errorMessage = xhr.responseJSON.message;
247 } else if (xhr.responseText) {
248 try {
249 const response = JSON.parse(xhr.responseText);
250 if (response.message) {
251 errorMessage = response.message;
252 }
253 } catch (e) {
254 console.error('Failed to parse error response:', e);
255 }
256 }
257
258 if (typeof EasyInvoiceToast !== 'undefined') {
259 EasyInvoiceToast.error(errorMessage);
260 } else {
261 alert(errorMessage);
262 }
263 button.prop('disabled', false).html(originalText);
264 }
265 });
266 });
267
268 // Handle regenerate quote numbers functionality
269 $(document).on('click', '.regenerate-quote-numbers-button', function(e) {
270 e.preventDefault();
271 const button = $(this);
272 const originalText = button.html();
273
274 // Check if EasyInvoiceConfirmation is available
275 if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.confirmAction) {
276
277 try {
278 EasyInvoiceConfirmation.confirmAction(
279 'regenerate',
280 'all quote numbers',
281 function() {
282 // User confirmed, proceed with regeneration
283 performQuoteRegeneration(button, originalText);
284 }
285 );
286 } catch (error) {
287 console.error('Error calling EasyInvoiceConfirmation.confirmAction:', error);
288 }
289 } else {
290 // Fallback to browser confirm
291 if (confirm('This will regenerate all quote numbers starting from the Next Quote Number. This action cannot be undone. Are you sure you want to continue?')) {
292 performQuoteRegeneration(button, originalText);
293 }
294 }
295 });
296
297 function performQuoteRegeneration(button, originalText) {
298 button.prop('disabled', true).html('Regenerating...');
299
300 $.ajax({
301 url: easyInvoiceSettings.ajaxurl,
302 type: 'POST',
303 data: {
304 action: 'regenerate_quote_numbers',
305 nonce: easyInvoiceSettings.nonce,
306 easy_invoice_settings_nonce: $('input[name="easy_invoice_settings_nonce"]').val()
307 },
308 success: function(response) {
309 if (response.success) {
310 if (typeof EasyInvoiceToast !== 'undefined') {
311 EasyInvoiceToast.success(response.data.message || 'Quote numbers regenerated successfully');
312 } else {
313 alert(response.data.message || 'Quote numbers regenerated successfully');
314 }
315
316 // Update the Next Quote Number field if provided
317 if (response.data.next_number) {
318 $('input[name="settings[easy_invoice_next_quote_number]"]').val(response.data.next_number);
319 }
320
321 // Reload the page after a short delay to show the updated numbers
322 setTimeout(function() {
323 location.reload();
324 }, 2000);
325 } else {
326 if (typeof EasyInvoiceToast !== 'undefined') {
327 EasyInvoiceToast.error(response.data.message || 'Failed to regenerate quote numbers');
328 } else {
329 alert(response.data.message || 'Failed to regenerate quote numbers');
330 }
331 }
332 button.prop('disabled', false).html(originalText);
333 },
334 error: function(xhr, status, error) {
335 console.error('AJAX Error:', error);
336 console.error('Status:', status);
337 console.error('Response:', xhr.responseText);
338 if (typeof EasyInvoiceToast !== 'undefined') {
339 EasyInvoiceToast.error('Failed to regenerate quote numbers. Please try again.');
340 } else {
341 alert('Failed to regenerate quote numbers. Please try again.');
342 }
343 button.prop('disabled', false).html(originalText);
344 }
345 });
346 }
347
348 // Handle test email functionality
349 $(document).on('click', '.test-email-button', function(e) {
350 e.preventDefault();
351 const button = $(this);
352 const originalText = button.html();
353 const emailInput = button.closest('.mt-1').find('input[type="email"]');
354 const resultDiv = button.closest('.mt-1').find('[id$="_result"]');
355 const testEmail = emailInput.val().trim();
356
357 if (!testEmail) {
358 if (typeof EasyInvoiceToast !== 'undefined') {
359 EasyInvoiceToast.show('error', 'Please enter a valid email address');
360 }
361 return;
362 }
363
364 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(testEmail)) {
365 if (typeof EasyInvoiceToast !== 'undefined') {
366 EasyInvoiceToast.show('error', 'Please enter a valid email address');
367 }
368 return;
369 }
370
371 button.prop('disabled', true)
372 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Sending...');
373
374 $.ajax({
375 url: easyInvoiceSettings.ajaxurl,
376 type: 'POST',
377 data: {
378 action: 'easy_invoice_test_email',
379 test_email: testEmail,
380 nonce: easyInvoiceSettings.nonce
381 },
382 success: function(response) {
383 if (response.success) {
384 resultDiv.html('<div class="text-green-600 text-sm"><i class="fas fa-check-circle mr-1"></i>' + response.data.message + '</div>')
385 .show();
386 } else {
387 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>' + (response.data && response.data.message ? response.data.message : 'Failed to send test email') + '</div>')
388 .show();
389 }
390 button.prop('disabled', false).html(originalText);
391
392 // Hide result after 5 seconds
393 setTimeout(function() {
394 resultDiv.fadeOut();
395 }, 5000);
396 },
397 error: function() {
398 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>Error sending test email</div>')
399 .show();
400 button.prop('disabled', false).html(originalText);
401
402 // Hide result after 5 seconds
403 setTimeout(function() {
404 resultDiv.fadeOut();
405 }, 5000);
406 }
407 });
408 });
409
410 // Handle test template email functionality
411 $(document).on('click', '.test-template-email-button', function(e) {
412 e.preventDefault();
413 const button = $(this);
414 const originalText = button.html();
415 const emailInput = button.closest('.mt-1').find('input[type="email"]');
416 const resultDiv = button.closest('.mt-1').find('[id$="_result"]');
417 const testEmail = emailInput.val().trim();
418 const templateType = button.data('template-type');
419
420 if (!testEmail) {
421 if (typeof EasyInvoiceToast !== 'undefined') {
422 EasyInvoiceToast.show('error', 'Please enter a valid email address');
423 }
424 return;
425 }
426
427 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(testEmail)) {
428 if (typeof EasyInvoiceToast !== 'undefined') {
429 EasyInvoiceToast.show('error', 'Please enter a valid email address');
430 }
431 return;
432 }
433
434 button.prop('disabled', true)
435 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Testing...');
436
437 $.ajax({
438 url: easyInvoiceSettings.ajaxurl,
439 type: 'POST',
440 data: {
441 action: 'easy_invoice_test_template_email',
442 test_email: testEmail,
443 template_type: templateType,
444 nonce: easyInvoiceSettings.nonce
445 },
446 success: function(response) {
447 if (response.success) {
448 resultDiv.html('<div class="text-green-600 text-sm"><i class="fas fa-check-circle mr-1"></i>' + response.data.message + '</div>')
449 .show();
450 } else {
451 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>' + (response.data && response.data.message ? response.data.message : 'Failed to send template test email') + '</div>')
452 .show();
453 }
454 button.prop('disabled', false).html(originalText);
455
456 // Hide result after 5 seconds
457 setTimeout(function() {
458 resultDiv.fadeOut();
459 }, 5000);
460 },
461 error: function() {
462 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>Error sending template test email</div>')
463 .show();
464 button.prop('disabled', false).html(originalText);
465
466 // Hide result after 5 seconds
467 setTimeout(function() {
468 resultDiv.fadeOut();
469 }, 5000);
470 }
471 });
472 });
473
474 // Handle test payment reminder email functionality
475 $(document).on('click', '.test-payment-reminder-email-button', function(e) {
476 e.preventDefault();
477 const button = $(this);
478 const originalText = button.html();
479 const emailInput = button.closest('.mt-1').find('input[type="email"]');
480 const resultDiv = button.closest('.mt-1').find('[id$="_result"]');
481 const testEmail = emailInput.val().trim();
482
483 if (!testEmail) {
484 if (typeof EasyInvoiceToast !== 'undefined') {
485 EasyInvoiceToast.show('error', 'Please enter a valid email address');
486 }
487 return;
488 }
489
490 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(testEmail)) {
491 if (typeof EasyInvoiceToast !== 'undefined') {
492 EasyInvoiceToast.show('error', 'Please enter a valid email address');
493 }
494 return;
495 }
496
497 button.prop('disabled', true)
498 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Testing...');
499
500 $.ajax({
501 url: easyInvoiceSettings.ajaxurl,
502 type: 'POST',
503 data: {
504 action: 'easy_invoice_test_payment_reminder_email',
505 test_email: testEmail,
506 nonce: easyInvoiceSettings.nonce
507 },
508 success: function(response) {
509 if (response.success) {
510 resultDiv.html('<div class="text-green-600 text-sm"><i class="fas fa-check-circle mr-1"></i>' + response.data.message + '</div>')
511 .show();
512 } else {
513 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>' + (response.data && response.data.message ? response.data.message : 'Failed to send test payment reminder email') + '</div>')
514 .show();
515 }
516 button.prop('disabled', false).html(originalText);
517
518 // Hide result after 5 seconds
519 setTimeout(function() {
520 resultDiv.fadeOut();
521 }, 5000);
522 },
523 error: function() {
524 resultDiv.html('<div class="text-red-600 text-sm"><i class="fas fa-exclamation-circle mr-1"></i>Error sending test payment reminder email</div>')
525 .show();
526 button.prop('disabled', false).html(originalText);
527
528 // Hide result after 5 seconds
529 setTimeout(function() {
530 resultDiv.fadeOut();
531 }, 5000);
532 }
533 });
534 });
535
536 // Handle regenerate invoice numbers functionality
537 $(document).on('click', '.regenerate-invoice-numbers-button', function(e) {
538 e.preventDefault();
539 const button = $(this);
540 const originalText = button.html();
541
542
543 // Show confirmation dialog
544 if (typeof EasyInvoiceConfirmation !== 'undefined') {
545 try {
546 EasyInvoiceConfirmation.confirmAction(
547 'regenerate',
548 'all invoice numbers',
549 function() {
550 // User confirmed, proceed with regeneration
551 performInvoiceRegeneration(button, originalText);
552 }
553 );
554 } catch (error) {
555 console.error('Error calling EasyInvoiceConfirmation.confirmAction:', error);
556 }
557 } else {
558 // Fallback to browser confirm
559 if (confirm('This will regenerate all invoice numbers starting from the Last Invoice Number. This action cannot be undone. Are you sure you want to continue?')) {
560 performInvoiceRegeneration(button, originalText);
561 }
562 }
563 });
564
565 // Function to perform the actual regeneration
566 function performInvoiceRegeneration(button, originalText) {
567 button.prop('disabled', true)
568 .html('<i class="fas fa-spinner fa-spin mr-2"></i>Regenerating...');
569
570
571 $.ajax({
572 url: easyInvoiceSettings.ajaxurl,
573 type: 'POST',
574 data: {
575 action: 'regenerate_invoice_numbers',
576 nonce: easyInvoiceSettings.nonce,
577 easy_invoice_settings_nonce: $('input[name="easy_invoice_settings_nonce"]').val()
578 },
579 success: function(response) {
580 if (response.success) {
581 if (typeof EasyInvoiceToast !== 'undefined') {
582 EasyInvoiceToast.success(response.data.message);
583 } else {
584 alert(response.data.message);
585 }
586
587 // Update the Next Invoice Number field if provided
588 if (response.data.next_number) {
589 $('input[name="settings[easy_invoice_next_invoice_number]"]').val(response.data.next_number);
590 }
591
592 // Refresh the page to show updated values
593 setTimeout(function() {
594 location.reload();
595 }, 2000);
596 } else {
597 if (typeof EasyInvoiceToast !== 'undefined') {
598 EasyInvoiceToast.error(response.data.message || 'Failed to regenerate invoice numbers');
599 } else {
600 alert(response.data.message || 'Failed to regenerate invoice numbers');
601 }
602 }
603 button.prop('disabled', false).html(originalText);
604 },
605 error: function(xhr, status, error) {
606 console.error('Invoice regeneration AJAX error:', {xhr, status, error});
607
608 let errorMessage = 'Error regenerating invoice numbers';
609 if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
610 errorMessage = xhr.responseJSON.data.message;
611 } else if (xhr.responseText) {
612 try {
613 const response = JSON.parse(xhr.responseText);
614 if (response.data && response.data.message) {
615 errorMessage = response.data.message;
616 }
617 } catch (e) {
618 console.error('Failed to parse error response:', e);
619 }
620 }
621
622 if (typeof EasyInvoiceToast !== 'undefined') {
623 EasyInvoiceToast.error(errorMessage);
624 } else {
625 alert(errorMessage);
626 }
627 button.prop('disabled', false).html(originalText);
628 }
629 });
630 }
631 });