PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.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
← All changes | assets/js/quote-save.js +34 -14 2.1.12.4.0 View file →
@@ -5,8 +5,17 @@
5 5 */
6 6 (function($) {
7 7 'use strict';
8 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 +
9 18 // Create a global object for quote save functionality
10 19 window.EasyQuoteSave = {
11 20 init: function() {
12 21
@@ -86,10 +95,10 @@
86 95
87 96 // Show loading state
88 97 const $saveBtn = $('#save-quote-btn');
89 98 const originalBtnText = $saveBtn.html();
90 - $saveBtn.html('<i class="fas fa-spinner fa-spin mr-2"></i> Saving...');
91 - $saveBtn.prop('disabled', true);
99 + $saveBtn.html('<span class="inline-flex items-center gap-2">' + spinnerSvg + '<span>' + eiBuilderI18n('saving', 'Saving…') + '</span></span>');
100 + $saveBtn.prop('disabled', true).attr('aria-busy', 'true');
92 101
93 102 // Collect form data
94 103 const formData = this.collectFormData();
95 104
@@ -116,9 +125,9 @@
116 125 success: (response) => {
117 126
118 127 // Restore button state
119 128 $saveBtn.html(originalBtnText);
120 - $saveBtn.prop('disabled', false);
129 + $saveBtn.prop('disabled', false).removeAttr('aria-busy');
121 130
122 131 if (response.success) {
123 132 // Clear any previous error states
124 133 this.clearErrorStates();
@@ -124,20 +133,23 @@
124 133 this.clearErrorStates();
125 134
126 135 // Do not show toast here; global handler will do it
127 136
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');
137 + var qId = $('#quote-id').val();
138 + if (response.data.quote && response.data.quote.id) {
139 + $('#quote-id').val(response.data.quote.id);
140 + qId = response.data.quote.id;
131 141 }
142 + // Same as the invoice builder: a refresh must reopen this quote, not a blank form.
143 + if (qId && String(qId) !== '0' && window.history && window.history.replaceState && !/[?&]id=/.test(window.location.search)) {
144 + var eiUrl = new URL(window.location.href); eiUrl.searchParams.set('id', String(qId)); window.history.replaceState({}, '', eiUrl.toString());
145 + }
146 + if (qId && String(qId) !== '0') {
147 + $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_quote', 'Update Quote') + '</span></span>');
148 + }
132 149
133 150 // Update form with any returned data if needed
134 151 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 152 // Update the quote status display if available
141 153 if (response.data.quote.status) {
142 154 $('#quote-status').val(response.data.quote.status);
143 155 }
@@ -157,11 +169,13 @@
157 169 }
158 170
159 171 // Update any other fields if needed
160 172 }
161 -
162 173
174 + $(document).trigger('easy-quote-saved', [response]);
175 +
163 176 } else {
177 + $(document).trigger('easy-quote-save-failed', [response]);
164 178 // Show error message
165 179 const errorMessage = response.data && response.data.message ? response.data.message : 'Failed to save quote';
166 180 this.showNotification('error', errorMessage);
167 181
@@ -171,12 +185,13 @@
171 185 }
172 186 }
173 187 },
174 188 error: (xhr, status, error) => {
189 + $(document).trigger('easy-quote-save-failed', [xhr]);
175 190
176 191 // Restore button state
177 192 $saveBtn.html(originalBtnText);
178 - $saveBtn.prop('disabled', false);
193 + $saveBtn.prop('disabled', false).removeAttr('aria-busy');
179 194
180 195 // Show error notification
181 196 this.showNotification('error', 'Network error occurred while saving quote. Please try again.');
182 197 }
@@ -237,9 +252,14 @@
237 252
238 253 // Let payment manager calculate totals
239 254 if (window.EasyInvoicePayment) {
240 255 const baseTotal = itemData.quantity * itemData.price;
241 - itemData.total = baseTotal * (1 + itemData.adjust_percentage / 100);
256 + let adjustPercentage = 0;
257 + // Only apply adjust percentage if the adjust field is enabled
258 + if (window.easyInvoice && window.easyInvoice.showAdjustField) {
259 + adjustPercentage = itemData.adjust_percentage;
260 + }
261 + itemData.total = baseTotal * (1 + adjustPercentage / 100);
242 262
243 263 // Add tax and discount calculations from payment manager
244 264 const settings = window.EasyInvoicePayment.settings;
245 265 if (itemData.taxable && settings.taxRate > 0) {