| @@ -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) { |