Phase 44 complete: - Clickable status badge opens dropdown with grouped statuses - AJAX POST changes status without page reload (optimistic update) - Fixed-position dropdown escapes table overflow:hidden - updateStatus() returns JSON for AJAX, redirect for standard POST Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
201 lines
6.4 KiB
JavaScript
201 lines
6.4 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var activeDropdown = null;
|
|
var activeWrap = null;
|
|
|
|
function closeDropdown() {
|
|
if (activeDropdown) {
|
|
activeDropdown.remove();
|
|
activeDropdown = null;
|
|
activeWrap = null;
|
|
}
|
|
}
|
|
|
|
function buildBadgeHtml(statusCode, statusLabel, statusColor) {
|
|
var label = statusLabel || '-';
|
|
if (statusColor) {
|
|
return '<span class="order-tag" style="background-color:' + escapeAttr(statusColor) + ';color:#fff">' + escapeHtml(label) + '</span>';
|
|
}
|
|
var code = statusCode.toLowerCase();
|
|
var cls = 'is-neutral';
|
|
if (code === 'shipped' || code === 'delivered') cls = 'is-success';
|
|
else if (code === 'cancelled' || code === 'returned') cls = 'is-danger';
|
|
else if (code === 'new' || code === 'confirmed') cls = 'is-info';
|
|
else if (code === 'processing' || code === 'packed' || code === 'paid') cls = 'is-warn';
|
|
return '<span class="order-tag ' + cls + '">' + escapeHtml(label) + '</span>';
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
var div = document.createElement('div');
|
|
div.appendChild(document.createTextNode(str));
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function escapeAttr(str) {
|
|
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
|
|
function createDropdown(wrap, allStatuses, statusColorMap, csrfToken) {
|
|
closeDropdown();
|
|
|
|
var currentStatus = (wrap.getAttribute('data-current-status') || '').toLowerCase();
|
|
var orderId = wrap.getAttribute('data-order-id');
|
|
|
|
var dropdown = document.createElement('div');
|
|
dropdown.className = 'orders-status-dropdown';
|
|
|
|
var lastGroup = null;
|
|
for (var i = 0; i < allStatuses.length; i++) {
|
|
var s = allStatuses[i];
|
|
if (s.group && s.group !== lastGroup) {
|
|
var header = document.createElement('div');
|
|
header.className = 'orders-status-dropdown__group-header';
|
|
header.textContent = s.group;
|
|
dropdown.appendChild(header);
|
|
lastGroup = s.group;
|
|
}
|
|
|
|
var item = document.createElement('div');
|
|
item.className = 'orders-status-dropdown__item';
|
|
if (s.code.toLowerCase() === currentStatus) {
|
|
item.classList.add('is-current');
|
|
}
|
|
item.setAttribute('data-status-code', s.code);
|
|
|
|
var color = statusColorMap[s.code.toLowerCase()] || '#94a3b8';
|
|
var dot = document.createElement('span');
|
|
dot.className = 'orders-status-dropdown__color-dot';
|
|
dot.style.backgroundColor = color;
|
|
item.appendChild(dot);
|
|
|
|
var label = document.createElement('span');
|
|
label.textContent = s.name;
|
|
item.appendChild(label);
|
|
|
|
item.addEventListener('click', (function (statusCode) {
|
|
return function (e) {
|
|
e.stopPropagation();
|
|
changeStatus(wrap, orderId, statusCode, allStatuses, statusColorMap, csrfToken);
|
|
};
|
|
})(s.code));
|
|
|
|
dropdown.appendChild(item);
|
|
}
|
|
|
|
dropdown.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
document.body.appendChild(dropdown);
|
|
activeDropdown = dropdown;
|
|
activeWrap = wrap;
|
|
|
|
var wrapRect = wrap.getBoundingClientRect();
|
|
var dropdownHeight = dropdown.offsetHeight;
|
|
var spaceBelow = window.innerHeight - wrapRect.bottom - 8;
|
|
var spaceAbove = wrapRect.top - 8;
|
|
|
|
if (spaceBelow >= dropdownHeight || spaceBelow >= spaceAbove) {
|
|
dropdown.style.top = (wrapRect.bottom + 4) + 'px';
|
|
} else {
|
|
dropdown.style.top = (wrapRect.top - dropdownHeight - 4) + 'px';
|
|
}
|
|
dropdown.style.left = wrapRect.left + 'px';
|
|
}
|
|
|
|
function changeStatus(wrap, orderId, newStatusCode, allStatuses, statusColorMap, csrfToken) {
|
|
var prevHtml = wrap.innerHTML;
|
|
var prevStatus = wrap.getAttribute('data-current-status');
|
|
|
|
closeDropdown();
|
|
|
|
var statusInfo = null;
|
|
for (var i = 0; i < allStatuses.length; i++) {
|
|
if (allStatuses[i].code === newStatusCode) {
|
|
statusInfo = allStatuses[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (statusInfo) {
|
|
var color = statusColorMap[newStatusCode.toLowerCase()] || '';
|
|
wrap.innerHTML = buildBadgeHtml(newStatusCode, statusInfo.name, color);
|
|
wrap.setAttribute('data-current-status', newStatusCode.toLowerCase());
|
|
}
|
|
|
|
var body = new FormData();
|
|
body.append('new_status', newStatusCode);
|
|
body.append('_token', csrfToken);
|
|
|
|
fetch('/orders/' + orderId + '/status', {
|
|
method: 'POST',
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
body: body
|
|
})
|
|
.then(function (resp) {
|
|
return resp.json().then(function (data) {
|
|
return { ok: resp.ok, data: data };
|
|
});
|
|
})
|
|
.then(function (result) {
|
|
if (!result.ok || !result.data.success) {
|
|
wrap.innerHTML = prevHtml;
|
|
wrap.setAttribute('data-current-status', prevStatus || '');
|
|
var msg = (result.data && result.data.error) ? result.data.error : 'Nie udalo sie zmienic statusu';
|
|
if (window.OrderProAlerts && typeof window.OrderProAlerts.alert === 'function') {
|
|
window.OrderProAlerts.alert({ title: 'Blad', message: msg });
|
|
}
|
|
return;
|
|
}
|
|
|
|
var d = result.data;
|
|
wrap.innerHTML = buildBadgeHtml(d.status_code, d.status_label, d.status_color);
|
|
wrap.setAttribute('data-current-status', d.status_code);
|
|
})
|
|
.catch(function () {
|
|
wrap.innerHTML = prevHtml;
|
|
wrap.setAttribute('data-current-status', prevStatus || '');
|
|
if (window.OrderProAlerts && typeof window.OrderProAlerts.alert === 'function') {
|
|
window.OrderProAlerts.alert({ title: 'Blad', message: 'Blad polaczenia z serwerem' });
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('click', function (e) {
|
|
if (!e.target || !e.target.closest) return;
|
|
|
|
var wrap = e.target.closest('.orders-status-wrap');
|
|
|
|
if (!wrap || !wrap.hasAttribute('data-order-id')) {
|
|
closeDropdown();
|
|
return;
|
|
}
|
|
|
|
if (activeDropdown && activeWrap === wrap) {
|
|
closeDropdown();
|
|
return;
|
|
}
|
|
|
|
var configEl = document.getElementById('js-inline-status-config');
|
|
if (!configEl) return;
|
|
|
|
var allStatuses, statusColorMap, csrfToken;
|
|
try {
|
|
var config = JSON.parse(configEl.textContent);
|
|
allStatuses = config.allStatuses || [];
|
|
statusColorMap = config.statusColorMap || {};
|
|
csrfToken = config.csrfToken || '';
|
|
} catch (ex) {
|
|
return;
|
|
}
|
|
|
|
e.stopPropagation();
|
|
createDropdown(wrap, allStatuses, statusColorMap, csrfToken);
|
|
});
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape') closeDropdown();
|
|
});
|
|
})();
|