feat(ui): infinite scroll, saved filters, product timeline, image comparator, customers bulk toolbar, dark mode refinements
This commit is contained in:
@@ -819,3 +819,43 @@ input:disabled, select:disabled, textarea:disabled {
|
||||
background: var(--color-surface-1, #1a1a1a);
|
||||
border-bottom: 2px solid var(--color-primary, #F5A623);
|
||||
}
|
||||
|
||||
/* Enhanced depth layers for both themes */
|
||||
[data-theme="modern"] {
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
|
||||
--shadow-md: 0 4px 12px rgba(0,0,0,0.4);
|
||||
--shadow-lg: 0 8px 24px rgba(0,0,0,0.5);
|
||||
--shadow-xl: 0 16px 48px rgba(0,0,0,0.6);
|
||||
}
|
||||
[data-theme="industrial"] {
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
|
||||
--shadow-md: 0 4px 12px rgba(0,0,0,0.5);
|
||||
--shadow-lg: 0 8px 24px rgba(0,0,0,0.6);
|
||||
--shadow-xl: 0 16px 48px rgba(0,0,0,0.7);
|
||||
}
|
||||
|
||||
/* Smooth theme transition (only on properties that don't cause flash) */
|
||||
.card, .glass-card, .btn, .icon-btn, .kpi-card, .alert-card, .meli-card {
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
/* Improved text rendering in dark */
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Focus visible for accessibility */
|
||||
*:focus-visible {
|
||||
outline: 2px solid var(--color-primary, #F5A623);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Reduced motion preference */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,8 @@ const Customers = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
var selectedCustomers = new Set();
|
||||
|
||||
function renderCustomerRow(c) {
|
||||
const tier = tierMap[c.price_tier] || 'Mostrador';
|
||||
const tClass = tierClass[c.price_tier] || 'mostrador';
|
||||
@@ -104,7 +106,9 @@ const Customers = (() => {
|
||||
const creditClass = usedPct >= 80 ? 'none' : usedPct >= 60 ? 'low' : '';
|
||||
const num = String(c.id).padStart(5, '0');
|
||||
const selClass = (currentCustomer && currentCustomer.id === c.id) ? 'selected' : '';
|
||||
return '<tr class="' + selClass + '" onclick="Customers.selectCustomer(' + c.id + ')">' +
|
||||
const isChecked = selectedCustomers.has(c.id) ? 'checked' : '';
|
||||
return '<tr class="' + selClass + '">' +
|
||||
'<td onclick="event.stopPropagation();"><input type="checkbox" ' + isChecked + ' onchange="Customers.toggleCustomerSelection(' + c.id + ')"></td>' +
|
||||
'<td class="cell-num">' + num + '</td>' +
|
||||
'<td>' +
|
||||
'<div class="cell-name">' + (c.name || '') + '</div>' +
|
||||
@@ -801,6 +805,41 @@ const Customers = (() => {
|
||||
showPaymentModal, closePayment, recordPayment,
|
||||
};
|
||||
|
||||
// Bulk selection
|
||||
publicApi.toggleCustomerSelection = function(id) {
|
||||
if (selectedCustomers.has(id)) selectedCustomers.delete(id);
|
||||
else selectedCustomers.add(id);
|
||||
updateBulkToolbar();
|
||||
};
|
||||
publicApi.toggleSelectAll = function() {
|
||||
var cb = document.getElementById('selectAllCustomers');
|
||||
var allChecked = cb.checked;
|
||||
if (customersVS && customersVS.data) {
|
||||
customersVS.data.forEach(function(c) {
|
||||
if (allChecked) selectedCustomers.add(c.id);
|
||||
else selectedCustomers.delete(c.id);
|
||||
});
|
||||
customersVS.refresh();
|
||||
}
|
||||
updateBulkToolbar();
|
||||
};
|
||||
function updateBulkToolbar() {
|
||||
var container = document.getElementById('customersBulkToolbar');
|
||||
if (!container) return;
|
||||
var count = selectedCustomers.size;
|
||||
if (count === 0) { container.innerHTML = ''; return; }
|
||||
container.innerHTML = renderBulkToolbar(count,
|
||||
'<button class="btn btn--primary btn--sm" onclick="Customers.featureProximamente(\'Exportar seleccionados\')">📥 Exportar</button>' +
|
||||
'<button class="btn btn--ghost btn--sm" onclick="Customers.clearSelection()">Limpiar</button>'
|
||||
);
|
||||
}
|
||||
publicApi.clearSelection = function() {
|
||||
selectedCustomers.clear();
|
||||
document.getElementById('selectAllCustomers').checked = false;
|
||||
if (customersVS) customersVS.refresh();
|
||||
updateBulkToolbar();
|
||||
};
|
||||
|
||||
// Expose globally for inline HTML onclick handlers
|
||||
window.Customers = publicApi;
|
||||
return publicApi;
|
||||
|
||||
@@ -1314,6 +1314,15 @@
|
||||
html += '<span id="imgUploadStatus" style="display:block;margin-top:4px;font-size:var(--text-caption);color:var(--color-text-muted);"></span>';
|
||||
html += '</div>';
|
||||
|
||||
// Action buttons
|
||||
html += '<div style="display:flex;gap:8px;justify-content:center;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--color-border);">';
|
||||
html += '<button class="btn btn--ghost btn--sm" onclick="showProductTimeline(' + data.id + ')">📅 Timeline</button>';
|
||||
if (data.image_url) {
|
||||
html += '<button class="btn btn--ghost btn--sm" onclick="showImageCompare(\'' + esc(data.image_url) + '\')">🖼️ Comparar</button>';
|
||||
}
|
||||
html += '<button class="btn btn--ghost btn--sm" onclick="viewHistory(' + data.id + ')">📜 Historial</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Product info header
|
||||
html += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--color-border);">';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">ID Inventario</span><strong style="font-family:var(--font-mono);">' + data.id + '</strong></div>';
|
||||
@@ -1535,9 +1544,76 @@
|
||||
window.autoMatchCompat = autoMatchCompat;
|
||||
window.removeCompat = removeCompat;
|
||||
|
||||
// ─── Product Timeline ──────────────────────────────────────────
|
||||
window.showProductTimeline = function(itemId) {
|
||||
var modal = document.getElementById('productTimelineModal');
|
||||
var body = document.getElementById('productTimelineBody');
|
||||
body.innerHTML = '<div style="padding:20px;"><div class="skeleton skeleton--text"></div><div class="skeleton skeleton--text-sm"></div></div>';
|
||||
modal.classList.add('is-open');
|
||||
|
||||
apiFetch(API + '/items/' + itemId + '/history').then(function(data) {
|
||||
var history = (data && data.data) ? data.data : [];
|
||||
var html = '<div class="timeline">';
|
||||
html += '<div class="timeline__item"><div class="timeline__dot timeline__dot--green"></div><div class="timeline__content"><div class="timeline__date">Producto creado</div><div class="timeline__title">Registro inicial en inventario</div></div></div>';
|
||||
history.forEach(function(h) {
|
||||
var color = h.quantity > 0 ? 'timeline__dot--green' : (h.quantity < 0 ? 'timeline__dot--red' : 'timeline__dot--blue');
|
||||
var title = (h.type || 'Movimiento') + ' · ' + (h.quantity > 0 ? '+' : '') + h.quantity + ' unidades';
|
||||
html += '<div class="timeline__item"><div class="timeline__dot ' + color + '"></div><div class="timeline__content">' +
|
||||
'<div class="timeline__date">' + esc(h.date) + ' · ' + esc(h.employee) + '</div>' +
|
||||
'<div class="timeline__title">' + esc(title) + '</div>' +
|
||||
(h.notes ? '<div class="timeline__desc">' + esc(h.notes) + '</div>' : '') +
|
||||
'</div></div>';
|
||||
});
|
||||
html += '</div>';
|
||||
body.innerHTML = html;
|
||||
});
|
||||
};
|
||||
|
||||
// ─── Image Comparator ──────────────────────────────────────────
|
||||
window.showImageCompare = function(imageUrl) {
|
||||
var modal = document.getElementById('imageCompareModal');
|
||||
document.getElementById('imgCompareNew').src = imageUrl + '?t=' + Date.now();
|
||||
document.getElementById('imgCompareOld').src = imageUrl + '?t=' + (Date.now() - 1);
|
||||
modal.classList.add('is-open');
|
||||
setTimeout(function() { if (typeof initImageComparator === 'function') initImageComparator('#imgCompareContainer'); }, 100);
|
||||
};
|
||||
|
||||
// ─── Infinite Scroll ───────────────────────────────────────────
|
||||
var _infiniteScrollInstance = null;
|
||||
function setupInfiniteScroll() {
|
||||
if (_infiniteScrollInstance) _infiniteScrollInstance.disconnect();
|
||||
var sentinel = document.createElement('div');
|
||||
sentinel.id = 'inventoryScrollSentinel';
|
||||
sentinel.style.cssText = 'height:1px;';
|
||||
var wrapper = document.querySelector('.table-wrapper');
|
||||
if (wrapper) wrapper.appendChild(sentinel);
|
||||
_infiniteScrollInstance = new InfiniteScroll({
|
||||
sentinelParent: wrapper,
|
||||
onLoad: function(done) {
|
||||
if (!currentSearch && currentPage < (window._inventoryTotalPages || 999)) {
|
||||
loadItems(currentPage + 1);
|
||||
}
|
||||
if (done) done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Saved Filters ─────────────────────────────────────────────
|
||||
function renderSavedFilters() {
|
||||
var container = document.getElementById('savedFiltersContainer');
|
||||
if (!container) return;
|
||||
SavedFilters.renderChips('savedFiltersContainer', function(filters) {
|
||||
if (filters.search) {
|
||||
var el = document.getElementById('productSearch');
|
||||
if (el) { el.value = filters.search; loadItems(1, filters.search); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// INIT — load stock on page load
|
||||
// =====================================================================
|
||||
|
||||
loadItems(1);
|
||||
renderSavedFilters();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user