fix(dashboard): fix kebab menu delete/resync and status dot refresh

Kebab dropdown delete/resync used inlineConfirmAction which breaks inside
Bootstrap dropdowns (dropdown closes on click, hiding confirm state).
Replaced with confirm() dialog + direct async action with row feedback.

Detail modal resync/delete/retry now trigger onStatusChange callback to
refresh the orders table, so status dots update without page reload.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-09 15:24:26 +00:00
parent e223128565
commit 84e5d55592
4 changed files with 48 additions and 60 deletions

View File

@@ -572,6 +572,7 @@ function openDashOrderDetail(orderNumber) {
_sharedModalQuickMapFn = openDashQuickMap; _sharedModalQuickMapFn = openDashQuickMap;
renderOrderDetailModal(orderNumber, { renderOrderDetailModal(orderNumber, {
onQuickMap: openDashQuickMap, onQuickMap: openDashQuickMap,
onStatusChange: loadDashOrders,
onAfterRender: function() { /* nothing extra needed */ } onAfterRender: function() { /* nothing extra needed */ }
}); });
} }
@@ -598,67 +599,51 @@ function openDashQuickMap(sku, productName, orderNumber, itemIdx) {
// ── Dashboard row action handlers ──────────────── // ── Dashboard row action handlers ────────────────
function dashResyncOrder(orderNumber, btn) { async function dashResyncOrder(orderNumber, btn) {
inlineConfirmAction(btn, '?', async (b) => { // Close dropdown immediately
try { const dd = btn.closest('.dropdown-menu');
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/resync`, { method: 'POST' }); if (dd) bootstrap.Dropdown.getInstance(dd.previousElementSibling)?.hide();
const data = await res.json(); // Find the table row for visual feedback
if (data.success) { const row = document.querySelector(`tr[data-order="${orderNumber}"]`) ||
b.innerHTML = '<i class="bi bi-check-circle"></i>'; btn.closest('tr');
b.className = 'btn btn-xs btn-success'; try {
setTimeout(() => loadDashOrders(), 1500); if (row) row.style.opacity = '0.5';
} else { const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/resync`, { method: 'POST' });
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>'; const data = await res.json();
b.className = 'btn btn-xs btn-danger'; if (data.success) {
b.title = data.message || 'Eroare'; loadDashOrders();
setTimeout(() => { } else {
b.innerHTML = '<i class="bi bi-arrow-repeat"></i>'; if (row) row.style.opacity = '';
b.className = 'btn btn-xs btn-outline-warning'; alert(data.message || 'Eroare la resync');
b.disabled = false;
b.title = 'Resync';
}, 3000);
}
} catch (err) {
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>';
b.disabled = false;
} }
}, { } catch (err) {
defaultHtml: '<i class="bi bi-arrow-repeat"></i>', if (row) row.style.opacity = '';
loadingText: '', alert('Eroare conexiune la resync');
confirmClass: 'btn-warning', }
defaultBtnClass: 'btn-outline-warning'
});
} }
function dashDeleteOrder(orderNumber, btn) { async function dashDeleteOrder(orderNumber, btn) {
inlineConfirmAction(btn, '?', async (b) => { // Close dropdown immediately
try { const dd = btn.closest('.dropdown-menu');
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/delete`, { method: 'POST' }); if (dd) bootstrap.Dropdown.getInstance(dd.previousElementSibling)?.hide();
const data = await res.json(); // Confirm before delete
if (data.success) { if (!confirm(`Stergi comanda ${orderNumber} din ROA?`)) return;
b.innerHTML = '<i class="bi bi-check-circle"></i>'; // Find the table row for visual feedback
b.className = 'btn btn-xs btn-danger'; const row = document.querySelector(`tr[data-order="${orderNumber}"]`) ||
setTimeout(() => loadDashOrders(), 1500); btn.closest('tr');
} else { try {
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>'; if (row) row.style.opacity = '0.5';
b.className = 'btn btn-xs btn-danger'; const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/delete`, { method: 'POST' });
b.title = data.message || 'Eroare'; const data = await res.json();
setTimeout(() => { if (data.success) {
b.innerHTML = '<i class="bi bi-trash"></i>'; loadDashOrders();
b.className = 'btn btn-xs btn-outline-danger'; } else {
b.disabled = false; if (row) row.style.opacity = '';
b.title = 'Sterge din ROA'; alert(data.message || 'Eroare la stergere');
}, 3000);
}
} catch (err) {
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>';
b.disabled = false;
} }
}, { } catch (err) {
defaultHtml: '<i class="bi bi-trash"></i>', if (row) row.style.opacity = '';
loadingText: '', alert('Eroare conexiune la stergere');
confirmClass: 'btn-danger', }
defaultBtnClass: 'btn-outline-danger'
});
} }

View File

@@ -751,6 +751,7 @@ async function renderOrderDetailModal(orderNumber, opts) {
if (data.success) { if (data.success) {
retryBtn.innerHTML = '<i class="bi bi-check-circle"></i> ' + (data.message || 'Reimportat'); retryBtn.innerHTML = '<i class="bi bi-check-circle"></i> ' + (data.message || 'Reimportat');
retryBtn.className = 'btn btn-sm btn-success'; retryBtn.className = 'btn btn-sm btn-success';
if (opts.onStatusChange) opts.onStatusChange();
// Refresh modal after short delay // Refresh modal after short delay
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500); setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
} else { } else {
@@ -795,6 +796,7 @@ async function renderOrderDetailModal(orderNumber, opts) {
if (data.success) { if (data.success) {
btn.innerHTML = '<i class="bi bi-check-circle"></i> Reimportat'; btn.innerHTML = '<i class="bi bi-check-circle"></i> Reimportat';
btn.className = 'btn btn-sm btn-success'; btn.className = 'btn btn-sm btn-success';
if (opts.onStatusChange) opts.onStatusChange();
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500); setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
} else { } else {
btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare'); btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');
@@ -845,6 +847,7 @@ async function renderOrderDetailModal(orderNumber, opts) {
if (data.success) { if (data.success) {
btn.innerHTML = '<i class="bi bi-check-circle"></i> Sters'; btn.innerHTML = '<i class="bi bi-check-circle"></i> Sters';
btn.className = 'btn btn-sm btn-danger'; btn.className = 'btn btn-sm btn-danger';
if (opts.onStatusChange) opts.onStatusChange();
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500); setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
} else { } else {
btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare'); btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');

View File

@@ -169,7 +169,7 @@
<script>window.ROOT_PATH = "{{ rp }}";</script> <script>window.ROOT_PATH = "{{ rp }}";</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="{{ rp }}/static/js/shared.js?v=44"></script> <script src="{{ rp }}/static/js/shared.js?v=45"></script>
<script> <script>
// Dark mode toggle // Dark mode toggle
function toggleDarkMode() { function toggleDarkMode() {

View File

@@ -115,5 +115,5 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=48"></script> <script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=49"></script>
{% endblock %} {% endblock %}