PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.2.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.2.0
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-form.js

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

498 lines 21.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(document).ready(function($) {
2 // Determine if this is a quote form
3 const isQuoteForm = window.isQuoteForm || false;
4 const formType = isQuoteForm ? 'quote' : 'invoice';
5 const templateFieldName = isQuoteForm ? 'quote_template' : 'invoice_template';
6 const previewClass = isQuoteForm ? 'quote-preview' : 'invoice-preview';
7
8 // Function to get URL parameter
9 function getUrlParameter(name) {
10 name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
11 var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
12 var results = regex.exec(location.search);
13 return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
14 }
15
16 // Function to switch to a specific tab
17 function switchToTab(tabId) {
18 if (tabId && $('#' + tabId).length) {
19 // Remove active state from all tabs
20 $('.tab-button').removeClass('border-indigo-500 text-indigo-600').addClass('border-transparent text-gray-500');
21 $('.tab-content').addClass('hidden');
22
23 // Activate the specified tab
24 $('.tab-button[data-tab="' + tabId + '"]').addClass('border-indigo-500 text-indigo-600');
25 $('#' + tabId).removeClass('hidden');
26
27 // Update URL without page reload
28 var url = new URL(window.location);
29 url.searchParams.set('tab', tabId);
30 window.history.replaceState({}, '', url);
31 }
32 }
33
34 // Make switchToTab globally available
35 window.switchToTab = switchToTab;
36
37 // Check for tab parameter on page load
38 var initialTab = getUrlParameter('tab');
39 if (initialTab) {
40 switchToTab(initialTab);
41 }
42
43 // Tab switching
44 $('.tab-button').on('click', function() {
45 var tabId = $(this).data('tab');
46 switchToTab(tabId);
47 });
48
49 // Remove the updateTemplatePreview function and all quote-specific template preview logic
50 // (No need to handle quote template preview here anymore)
51
52 // Handle template selection after page reload
53 const selectedTemplate = sessionStorage.getItem('selectedTemplate');
54 if (selectedTemplate) {
55
56
57 // Update the hidden input field
58 $('input[name="' + templateFieldName + '"][type="hidden"]').val(selectedTemplate);
59
60 // Update the radio button if it exists
61 let radioInput = $('input[name="' + templateFieldName + '"][type="radio"][value="' + selectedTemplate + '"]');
62 if (radioInput.length > 0) {
63 $('input[name="' + templateFieldName + '"][type="radio"]').prop('checked', false);
64 radioInput.prop('checked', true);
65 }
66
67 // Update the visual state and trigger click event
68 $('.template-card').removeClass('selected');
69 const selectedCard = $('.template-card[data-template-id="' + selectedTemplate + '"]');
70 if (selectedCard.length > 0) {
71 selectedCard.addClass('selected');
72 // Trigger click event to ensure all handlers are executed
73 selectedCard.trigger('click');
74 }
75
76 // Clear the sessionStorage
77 sessionStorage.removeItem('selectedTemplate');
78 } else {
79 // Handle initial template state for existing invoices/quotes
80 const currentTemplate = $('input[name="' + templateFieldName + '"][type="hidden"]').val();
81
82 if (currentTemplate) {
83
84
85 // Update the radio button if it exists
86 let radioInput = $('input[name="' + templateFieldName + '"][type="radio"][value="' + currentTemplate + '"]');
87 if (radioInput.length > 0) {
88 $('input[name="' + templateFieldName + '"][type="radio"]').prop('checked', false);
89 radioInput.prop('checked', true);
90 }
91
92 // Update the visual state
93 $('.template-card').removeClass('selected');
94 const selectedCard = $('.template-card[data-template-id="' + currentTemplate + '"]');
95 if (selectedCard.length > 0) {
96 selectedCard.addClass('selected');
97 }
98
99 // Update the preview
100 //updateTemplatePreview(currentTemplate);
101 }
102 }
103
104
105
106 // Initialize item counter
107 let itemCounter = $('.invoice-item, .quote-item').length;
108
109 // Auto-calculate totals when quantity or price changes
110 $(document).on('input', 'input[name="item-quantity[]"], input[name="item-price[]"]', function() {
111 // This will be handled by invoice-builder.js
112 if (window.EasyInvoiceBuilder && typeof window.EasyInvoiceBuilder.calculateItemTotal === 'function') {
113 window.EasyInvoiceBuilder.calculateItemTotal($(this).closest('.invoice-item, .quote-item'));
114 }
115 });
116
117 // Both buttons should open the template gallery - using event delegation for better reliability
118 $(document).on('click', '#browse-all-templates, #browse-templates-btn', function(e) {
119 e.preventDefault();
120 e.stopPropagation();
121
122 // Check if this is the free version
123 const isFreeVersion = !window.easyInvoice || !window.easyInvoice.isPro;
124
125 if (isFreeVersion) {
126 // Show premium upgrade modal for free users
127 if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) {
128 const title = window.easyInvoice && window.easyInvoice.translations ? window.easyInvoice.translations.premiumTemplates : 'Premium Templates';
129 const description = window.easyInvoice && window.easyInvoice.translations ? window.easyInvoice.translations.premiumTemplatesDescription : 'Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.';
130 EasyInvoiceConfirmation.showFeatureUpgrade(title, description);
131 } else {
132 const message = window.easyInvoice && window.easyInvoice.translations ? window.easyInvoice.translations.premiumFeatureMessage : 'This is a premium feature. Please upgrade to Pro to access all templates.';
133 if (typeof EasyInvoiceToast !== 'undefined') {
134 EasyInvoiceToast.show('info', message);
135 }
136 }
137 } else {
138 // Pro users can browse all templates
139 $('#template-gallery-modal').fadeIn();
140
141 // Highlight the currently selected template in the gallery
142 const currentTemplate = $('input[name="' + templateFieldName + '"]:checked').val();
143 if (currentTemplate) {
144 $('.gallery-template-card').removeClass('selected');
145 $('.gallery-template-card[data-template-id="' + currentTemplate + '"]').addClass('selected');
146 }
147 }
148 });
149
150
151
152 // Template card click handler in main grid
153 $('.template-card').on('click', function(e) {
154 // If clicking on premium upgrade button, handle separately
155 if ($(e.target).hasClass('premium-upgrade-btn') || $(e.target).closest('.premium-upgrade-btn').length) {
156 return;
157 }
158
159 const templateId = $(this).data('template-id');
160 const radioInput = $(this).find('input[type="radio"]');
161
162 // Check if this is a premium template in free version
163 const isPremium = $(this).attr('data-is-premium') === 'true';
164 const isFreeVersion = !window.easyInvoice || !window.easyInvoice.isPro;
165
166 if (isPremium && isFreeVersion) {
167 // Show premium upgrade modal instead of selecting the template
168 if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) {
169 const title = window.easyInvoice && window.easyInvoice.translations ? window.easyInvoice.translations.premiumTemplates : 'Premium Templates';
170 const description = window.easyInvoice && window.easyInvoice.translations ? window.easyInvoice.translations.premiumTemplatesDescription : 'Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.';
171 EasyInvoiceConfirmation.showFeatureUpgrade(title, description);
172 } else {
173 const message = window.easyInvoice && window.easyInvoice.translations ? window.easyInvoice.translations.premiumFeatureMessage : 'This is a premium feature. Please upgrade to Pro to access all templates.';
174 if (typeof EasyInvoiceToast !== 'undefined') {
175 EasyInvoiceToast.show('info', message);
176 }
177 }
178 return;
179 }
180
181 // Check the radio input within this card
182 radioInput.prop('checked', true);
183
184 // Update the hidden input field value
185 $('input[name="' + templateFieldName + '"][type="hidden"]').val(templateId);
186
187 // Update the visual state
188 $('.template-card').removeClass('selected');
189 $(this).addClass('selected');
190
191 // No need to call updateTemplatePreview for quotes
192 });
193
194
195
196 // Apply button click handler
197 $('#apply-selected-template').on('click', function() {
198 // Get the selected template from the gallery
199 const selectedGalleryCard = $('.gallery-template-card.selected');
200
201 if (selectedGalleryCard.length === 0) {
202 // If no template is selected, show an error message and return
203 const message = window.easyInvoice && window.easyInvoice.translations ? window.easyInvoice.translations.selectTemplateFirst : 'Please select a template first';
204 if (typeof EasyInvoiceToast !== 'undefined') {
205 EasyInvoiceToast.show('error', message);
206 }
207 return;
208 }
209
210 const templateId = selectedGalleryCard.data('template-id');
211 const templateName = selectedGalleryCard.find('.font-medium').text();
212 const templateDescription = selectedGalleryCard.find('.text-sm.text-gray-600').text();
213 const templateIcon = selectedGalleryCard.find('i').attr('class');
214 const isPremium = selectedGalleryCard.data('is-premium') === 'true';
215
216 // Store the selected template in sessionStorage for after page reload
217 sessionStorage.setItem('selectedTemplate', templateId);
218
219 // Update the hidden input field value
220 $('input[name="' + templateFieldName + '"][type="hidden"]').val(templateId);
221
222 // Update the main template grid
223 updateMainTemplateGrid(templateId, templateName, templateDescription, templateIcon, isPremium);
224
225 // Close the modal
226 $('#template-gallery-modal').fadeOut();
227 });
228
229 // Close gallery modal
230 $('.close-gallery-modal, .gallery-close-btn').on('click', function() {
231 $('#template-gallery-modal').fadeOut();
232 });
233
234 // Close gallery when clicking outside of it
235 $(window).on('click', function(e) {
236 if ($(e.target).is('#template-gallery-modal')) {
237 $('#template-gallery-modal').fadeOut();
238 }
239 });
240
241 // Gallery template card click handler - just selects the template visually
242 $('.gallery-template-card').on('click', function(e) {
243 // If clicking on the favorite icon, handle separately
244 if ($(e.target).hasClass('template-favorite') || $(e.target).closest('.template-favorite').length) {
245 return;
246 }
247
248 // Update the visual state in the gallery
249 $('.gallery-template-card').removeClass('selected');
250 $(this).addClass('selected');
251
252
253 });
254
255
256
257
258
259 // Client selection functionality - only for invoice forms, not quote forms
260 if (!isQuoteForm) {
261 $('#select-client').on('change', function() {
262 const selectedClientId = $(this).val();
263 const $clientInfoDisplay = $('#client-info-display');
264 const $noClientMessage = $('#no-client-message');
265 const $clientIdField = $('#client-id');
266
267 if (selectedClientId) {
268 // Set the hidden client ID field
269 $clientIdField.val(selectedClientId);
270
271 // Get client information via AJAX
272 loadClientInformation(selectedClientId);
273
274 // Show client info display, hide no client message
275 $clientInfoDisplay.removeClass('hidden');
276 $noClientMessage.addClass('hidden');
277 } else {
278 // Clear the hidden client ID field
279 $clientIdField.val('');
280
281 // Hide client info display, show no client message
282 $clientInfoDisplay.addClass('hidden');
283 $noClientMessage.removeClass('hidden');
284
285 // Clear all display fields
286 clearClientDisplay();
287 }
288 });
289 }
290
291 // Function to load client information
292 function loadClientInformation(clientId) {
293 // First check if we have client data already loaded from PHP
294 if (typeof easyInvoice !== 'undefined' && easyInvoice.clientData && easyInvoice.clientData.id == clientId) {
295 const clientData = easyInvoice.clientData;
296 populateClientDisplay(clientData);
297 return;
298 }
299
300 // If we don't have the client data pre-loaded, we should load it via PHP
301 // This should not happen in normal circumstances since client data is loaded on page load
302 // Client data not found in pre-loaded data
303 clearClientDisplay();
304 }
305
306 // Function to populate client display
307 function populateClientDisplay(clientData) {
308 $('#display-client-name').text(clientData.name || '-');
309 $('#display-client-email').text(clientData.email || '-');
310 $('#display-client-phone').text(clientData.phone || '-');
311 $('#display-client-company').text(clientData.company || '-');
312 $('#display-client-address').text(clientData.address || '-');
313 $('#display-client-website').text(clientData.website || '-');
314 }
315
316 // Function to clear client display
317 function clearClientDisplay() {
318 $('#display-client-name').text('-');
319 $('#display-client-email').text('-');
320 $('#display-client-phone').text('-');
321 $('#display-client-company').text('-');
322 $('#display-client-address').text('-');
323 $('#display-client-website').text('-');
324 }
325
326 // Function to update main template grid when template is selected from popup
327 function updateMainTemplateGrid(templateId, templateName, templateDescription, templateIcon, isPremium) {
328 const $mainGrid = $('#main-templates-grid');
329 const $existingCards = $mainGrid.find('.template-card');
330
331 // Check if the selected template already exists in the main grid
332 const $existingCard = $mainGrid.find('.template-card[data-template-id="' + templateId + '"]');
333
334 if ($existingCard.length > 0) {
335 // Template already exists, just select it and don't add duplicates
336 $existingCard.addClass('selected');
337 $existingCard.find('input[type="radio"]').prop('checked', true);
338 $mainGrid.find('.template-card').not($existingCard).removeClass('selected');
339 return;
340 }
341
342 // If we have 3 templates and the new one doesn't exist, replace the currently selected one
343 if ($existingCards.length >= 3) {
344 const $selectedCard = $mainGrid.find('.template-card.selected');
345 if ($selectedCard.length > 0) {
346 $selectedCard.remove();
347 } else {
348 // If no template is selected, remove the first one
349 $existingCards.first().remove();
350 }
351 }
352
353 // Create new template card HTML
354 const isFreeVersion = !window.easyInvoice || !window.easyInvoice.isPro;
355 const premiumClasses = (isPremium && isFreeVersion) ? 'premium-template-blurred' : '';
356 const disabledAttr = (isPremium && isFreeVersion) ? 'disabled' : '';
357
358 const newCardHtml = `
359 <div class="template-card selected ${premiumClasses}" data-template-id="${templateId}" data-is-premium="${isPremium}">
360 <input type="radio" id="template-${templateId}" name="${templateFieldName}" value="${templateId}" class="template-radio" checked ${disabledAttr}>
361 <div class="p-4 border rounded-lg">
362 <div class="flex items-center mb-2">
363 <i class="${templateIcon} text-indigo-600 mr-2"></i>
364 <span class="font-medium">${templateName}</span>
365 </div>
366 <p class="text-sm text-gray-600">${templateDescription}</p>
367 <div class="mt-2 flex-grow">
368 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center ${templateId}">
369 </div>
370 </div>
371 </div>
372 </div>
373 `;
374
375 // Add the new template card to the main grid
376 $mainGrid.append(newCardHtml);
377
378 // Update the visual state - ensure only the new template is selected
379 $mainGrid.find('.template-card').removeClass('selected');
380 $mainGrid.find('.template-card[data-template-id="' + templateId + '"]').addClass('selected');
381 }
382
383 // Save to Item Library functionality
384 $(document).on('click', '.save-to-library-btn', function(e) {
385 e.preventDefault();
386
387 var $btn = $(this);
388 var itemIndex = $btn.data('item-index');
389 // Find the closest item container (either invoice-item or quote-item)
390 var $itemContainer = $btn.closest('.invoice-item, .quote-item');
391
392 console.log('Save to Library - Item container found:', $itemContainer.length);
393 console.log('Save to Library - Item index:', itemIndex);
394
395 if (!$itemContainer.length) {
396 console.error('Save to Library - Could not find item container');
397 return;
398 }
399
400 // Get item data from the form
401 var itemData = {
402 title: '',
403 description: '',
404 price: 0,
405 quantity: 0,
406 taxable: 0
407 };
408
409 // Extract data from the item fields
410 $itemContainer.find('input, textarea, select').each(function() {
411 var $field = $(this);
412 var name = $field.attr('name');
413 var value = $field.val();
414 var type = $field.attr('type');
415
416 if (name) {
417 // Handle array names like items[0][title] or quote_items[0][title]
418 var matches = name.match(/(items|quote_items)\[\d+\]\[(.+)\]/);
419 if (matches && matches[2]) {
420 if (type === 'checkbox' || type === 'radio') {
421 itemData[matches[2]] = $field.is(':checked') ? 1 : 0;
422 } else {
423 itemData[matches[2]] = value;
424 }
425 }
426 }
427 });
428
429 console.log('Save to Library - Extracted raw data:', itemData);
430
431 // Convert numeric fields
432 itemData.price = parseFloat(itemData.price) || 0;
433 itemData.quantity = parseInt(itemData.quantity) || 0;
434 itemData.taxable = parseInt(itemData.taxable) || 0;
435
436 console.log('Save to Library - Processed data:', itemData);
437
438 // Validate required fields
439 if (!itemData.title || !itemData.title.trim()) {
440 if (typeof EasyInvoiceToast !== 'undefined') {
441 EasyInvoiceToast.error('Please fill in item title before saving to library');
442 } else {
443 alert('Please fill in item title before saving to library');
444 }
445 return;
446 }
447
448 if (!itemData.price || itemData.price <= 0) {
449 if (typeof EasyInvoiceToast !== 'undefined') {
450 EasyInvoiceToast.error('Please fill in item price before saving to library');
451 } else {
452 alert('Please fill in item price before saving to library');
453 }
454 return;
455 }
456
457 // Show loading state
458 var originalHtml = $btn.html();
459 $btn.html('<i class="fas fa-spinner fa-spin"></i>').prop('disabled', true);
460
461 // AJAX call to save to item library
462 $.ajax({
463 url: ajaxurl,
464 type: 'POST',
465 data: {
466 action: 'easy_invoice_pro_add_item',
467 nonce: $btn.data('nonce'),
468 item_data: JSON.stringify(itemData)
469 },
470 success: function(response) {
471 if (response.success) {
472 if (typeof EasyInvoiceToast !== 'undefined') {
473 EasyInvoiceToast.success('Item saved to library successfully!');
474 } else {
475 alert('Item saved to library successfully!');
476 }
477 } else {
478 if (typeof EasyInvoiceToast !== 'undefined') {
479 EasyInvoiceToast.error(response.data.message || 'Failed to save item');
480 } else {
481 alert('Failed to save item: ' + (response.data.message || 'Unknown error'));
482 }
483 }
484 },
485 error: function() {
486 if (typeof EasyInvoiceToast !== 'undefined') {
487 EasyInvoiceToast.error('Error saving item to library');
488 } else {
489 alert('Error saving item to library');
490 }
491 },
492 complete: function() {
493 // Restore button
494 $btn.html(originalHtml).prop('disabled', false);
495 }
496 });
497 });
498 });