| @@ -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 | // Check if we're on an invoice page |
| 10 | 19 | const isInvoicePage = window.location.href.includes('easy-invoice-builder') || |
| 11 | 20 | window.location.href.includes('easy-invoice-new') || |
| 12 | 21 | $('#invoice-form').length > 0; |
| @@ -112,10 +121,10 @@ | ||
| 112 | 121 | |
| 113 | 122 | // Show loading state |
| 114 | 123 | const $saveBtn = $('#save-invoice-btn'); |
| 115 | 124 | const originalBtnText = $saveBtn.html(); |
| 116 | - $saveBtn.html('<i class="fas fa-spinner fa-spin mr-2"></i> Saving...'); | |
| 117 | - $saveBtn.prop('disabled', true); | |
| 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'); | |
| 118 | 127 | |
| 119 | 128 | // Collect form data |
| 120 | 129 | const formData = this.collectFormData(); |
| 121 | 130 | |
| @@ -142,9 +151,9 @@ | ||
| 142 | 151 | success: (response) => { |
| 143 | 152 | |
| 144 | 153 | // Restore button state |
| 145 | 154 | $saveBtn.html(originalBtnText); |
| 146 | - $saveBtn.prop('disabled', false); | |
| 155 | + $saveBtn.prop('disabled', false).removeAttr('aria-busy'); | |
| 147 | 156 | |
| 148 | 157 | if (response.success) { |
| 149 | 158 | // Clear any previous error states |
| 150 | 159 | this.clearErrorStates(); |
| @@ -152,20 +161,24 @@ | ||
| 152 | 161 | // If this is a new invoice (no invoice_id in form), update URL without redirect |
| 153 | 162 | |
| 154 | 163 | // Do not show toast here; global handler will do it |
| 155 | 164 | |
| 156 | - // If the button says "Save Invoice", update it to "Update Invoice" | |
| 157 | - if ($saveBtn.text().trim() === 'Save Invoice') { | |
| 158 | - $saveBtn.html('<i class="fas fa-save mr-2"></i> Update Invoice'); | |
| 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; | |
| 159 | 169 | } |
| 170 | + if (invId && String(invId) !== '0') { | |
| 171 | + // Put the id in the address bar so a refresh, a bookmark or a | |
| 172 | + // second Save reopens this invoice instead of a blank builder. | |
| 173 | + if (window.history && window.history.replaceState && !/[?&]invoice_id=/.test(window.location.search)) { | |
| 174 | + var eiUrl = new URL(window.location.href); eiUrl.searchParams.set('invoice_id', String(invId)); window.history.replaceState({}, '', eiUrl.toString()); | |
| 175 | + } | |
| 176 | + $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>'); | |
| 177 | + } | |
| 160 | 178 | |
| 161 | 179 | // Update form with any returned data if needed |
| 162 | 180 | if (response.data.invoice) { |
| 163 | - // Update the invoice ID if it was newly created | |
| 164 | - if (response.data.invoice.id && !$('#invoice-id').val()) { | |
| 165 | - $('#invoice-id').val(response.data.invoice.id); | |
| 166 | - } | |
| 167 | - | |
| 168 | 181 | // Update the invoice status display if available |
| 169 | 182 | if (response.data.invoice.status) { |
| 170 | 183 | $('#payment-status').val(response.data.invoice.status); |
| 171 | 184 | } |
| @@ -185,9 +198,12 @@ | ||
| 185 | 198 | } |
| 186 | 199 | |
| 187 | 200 | // Update any other fields if needed |
| 188 | 201 | } |
| 202 | + | |
| 203 | + $(document).trigger('easy-invoice-saved', [response]); | |
| 189 | 204 | } else { |
| 205 | + $(document).trigger('easy-invoice-save-failed', [response]); | |
| 190 | 206 | // Show error message |
| 191 | 207 | const errorMessage = response.data && response.data.message ? response.data.message : 'Failed to save invoice'; |
| 192 | 208 | if (typeof EasyInvoiceToast !== 'undefined') { |
| 193 | 209 | EasyInvoiceToast.show('error', errorMessage); |
| @@ -199,12 +215,13 @@ | ||
| 199 | 215 | } |
| 200 | 216 | } |
| 201 | 217 | }, |
| 202 | 218 | error: (xhr, status, error) => { |
| 219 | + $(document).trigger('easy-invoice-save-failed', [xhr]); | |
| 203 | 220 | |
| 204 | 221 | // Restore button state |
| 205 | 222 | $saveBtn.html(originalBtnText); |
| 206 | - $saveBtn.prop('disabled', false); | |
| 223 | + $saveBtn.prop('disabled', false).removeAttr('aria-busy'); | |
| 207 | 224 | |
| 208 | 225 | // Try to parse the response text as JSON |
| 209 | 226 | let errorMessage = 'An error occurred while saving the invoice'; |
| 210 | 227 | try { |