diff --git a/resources/views/certificate-backend/index.blade.php b/resources/views/certificate-backend/index.blade.php index 7237e35e8..94c528cf3 100644 --- a/resources/views/certificate-backend/index.blade.php +++ b/resources/views/certificate-backend/index.blade.php @@ -5,11 +5,11 @@

Certificate Backend – Excellence & Super Organiser

- Refresh - View errors + Refresh + View errors
- +
{{-- Tabs --}}
{{-- Table --}}
Loading…
@@ -97,15 +97,12 @@ (function() { const editionSelect = document.getElementById('edition-select'); const typeSlug = '{{ $typeSlug }}'; + const basePath = '{{ url("/admin/certificate-backend") }}'.replace(/\/$/, ''); let currentPage = 1; let searchQuery = ''; - function baseUrl() { - return '{{ route("certificate_backend.index", ["edition" => $edition, "type" => $typeSlug]) }}'.replace(/\d{4}$/, editionSelect.value).replace(/excellence|super-organiser/, typeSlug); - } - function apiUrl(path, params = {}) { - const u = new URL('{{ url("/admin/certificate-backend") }}' + path, window.location.origin); + const u = new URL(basePath + path.replace(/^\//, ''), window.location.origin); u.searchParams.set('edition', editionSelect.value); u.searchParams.set('type', typeSlug); Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== '') u.searchParams.set(k, v); }); @@ -116,23 +113,41 @@ function csrf() { return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''; } + function handleResponse(r) { + const contentType = r.headers.get('content-type') || ''; + const isJson = contentType.indexOf('application/json') !== -1; + if (!r.ok) { + return (isJson ? r.json() : r.text()).then(function(data) { + const msg = (data && data.message) ? data.message : (typeof data === 'string' ? data.substring(0, 200) : 'Request failed (' + r.status + ')'); + throw new Error(r.status === 419 ? 'Session expired. Please refresh the page and try again.' : (r.status === 403 ? 'Access denied.' : msg)); + }); + } + return isJson ? r.json() : r.text().then(function() { return {}; }); + } + function fetchJson(url, options = {}) { - const opts = { headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } }; + const opts = { method: 'GET', headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }; if (options.method === 'POST') { opts.method = 'POST'; opts.headers['Content-Type'] = 'application/json'; opts.headers['X-CSRF-TOKEN'] = csrf(); opts.body = JSON.stringify(options.body || {}); } - return fetch(url, opts).then(r => r.json()); + return fetch(url, opts).then(handleResponse); } function postJson(url, body = {}) { return fetch(url, { method: 'POST', - headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf(), 'X-Requested-With': 'XMLHttpRequest' }, + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': csrf(), + 'X-Requested-With': 'XMLHttpRequest' + }, body: JSON.stringify(body) - }).then(r => r.json()); + }).then(handleResponse); } function loadStatus() { @@ -163,12 +178,12 @@ function loadList(page = 1) { '' + (row.certificate_generated ? 'Yes' : 'No') + (row.certificate_generation_error ? ' (error)' : '') + '' + '' + (row.certificate_sent ? (row.notified_at || 'Yes') : 'No') + (row.certificate_sent_error ? ' (error)' : '') + '' + '' + (row.certificate_url ? 'Open' : '–') + '' + - '' + (row.certificate_url ? '' : '–') + ''; + '' + (row.certificate_url ? '' : '–') + ''; tbody.appendChild(tr); }); pagination(data.current_page, data.last_page, data.total); - }).catch(() => { - document.getElementById('table-loading').textContent = 'Error loading list.'; + }).catch(function(err) { + document.getElementById('table-loading').innerHTML = 'Error loading list. ' + (err.message || ''); }); } @@ -185,9 +200,9 @@ function pagination(current, last, total) { const el = document.getElementById('pagination'); if (last <= 1) { el.innerHTML = ''; return; } let html = ''; - if (current > 1) html += ' '; + if (current > 1) html += ' '; html += ' Page ' + current + ' of ' + last + ' (' + total + ' total) '; - if (current < last) html += ''; + if (current < last) html += ''; el.innerHTML = html; el.querySelectorAll('.page-btn').forEach(btn => btn.addEventListener('click', function() { currentPage = parseInt(this.dataset.page, 10); loadList(currentPage); })); } @@ -197,24 +212,26 @@ function pagination(current, last, total) { }); document.getElementById('btn-generate').addEventListener('click', function() { - this.disabled = true; + const btn = this; + btn.disabled = true; postJson(apiUrl('/generate/start')).then(r => { alert(r.message || (r.ok ? 'Started.' : 'Error')); - this.disabled = false; loadStatus(); - }).catch(() => { this.disabled = false; }); + }).catch(function(err) { + alert(err.message || 'Request failed.'); + }).finally(function() { btn.disabled = false; }); }); document.getElementById('btn-cancel').addEventListener('click', function() { - postJson(apiUrl('/generate/cancel')).then(r => { alert(r.message); loadStatus(); }); + postJson(apiUrl('/generate/cancel')).then(r => { alert(r.message); loadStatus(); }).catch(function(err) { alert(err.message || 'Request failed.'); }); }); document.getElementById('btn-send').addEventListener('click', function() { - postJson(apiUrl('/send/start')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); }); + postJson(apiUrl('/send/start')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); }).catch(function(err) { alert(err.message || 'Request failed.'); }); }); document.getElementById('btn-resend-all-failed').addEventListener('click', function() { - postJson(apiUrl('/resend-all-failed')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); }); + postJson(apiUrl('/resend-all-failed')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); }).catch(function(err) { alert(err.message || 'Request failed.'); }); }); document.getElementById('btn-manual-generate').addEventListener('click', function() { @@ -225,7 +242,7 @@ function pagination(current, last, total) { postJson(apiUrl('/manual-create-send'), { user_email: email, generate_only: true }).then(r => { resultEl.textContent = r.ok ? ('Generated. ' + (r.certificate_url ? 'URL: ' + r.certificate_url : '')) : r.message; if (r.ok) { loadStatus(); loadList(currentPage); } - }).catch(() => { resultEl.textContent = 'Request failed.'; }); + }).catch(function(err) { resultEl.textContent = err.message || 'Request failed.'; }); }); document.getElementById('btn-manual-send').addEventListener('click', function() { @@ -234,9 +251,9 @@ function pagination(current, last, total) { if (!email) { resultEl.textContent = 'Enter email.'; return; } resultEl.textContent = 'Sending…'; postJson(apiUrl('/manual-create-send'), { user_email: email, send_only: true }).then(r => { - resultEl.textContent = r.ok ? 'Email queued.' : r.message; + resultEl.textContent = r.ok ? (r.message || 'Email sent.') : r.message; if (r.ok) { loadStatus(); loadList(currentPage); } - }).catch(() => { resultEl.textContent = 'Request failed.'; }); + }).catch(function(err) { resultEl.textContent = err.message || 'Request failed.'; }); }); document.getElementById('btn-search').addEventListener('click', function() { @@ -251,12 +268,12 @@ function pagination(current, last, total) { if (!btn) return; const id = btn.dataset.id; btn.disabled = true; - postJson('{{ url("/admin/certificate-backend/resend") }}/' + id, {}).then(r => { + const resendUrl = '{{ url("/admin/certificate-backend/resend") }}'.replace(/\/$/, '') + '/' + id; + postJson(resendUrl, {}).then(r => { alert(r.message); - btn.disabled = false; loadStatus(); loadList(currentPage); - }).catch(() => { btn.disabled = false; }); + }).catch(function(err) { alert(err.message || 'Request failed.'); }).finally(function() { btn.disabled = false; }); }); loadStatus();