feat(ui): marketplace_external skeletons, empty states, toast notifications, Cmd+K

This commit is contained in:
2026-05-26 08:44:09 +00:00
parent 7020890b0e
commit 031c190635
2 changed files with 41 additions and 14 deletions

View File

@@ -105,7 +105,9 @@
window.loadListings = async function() { window.loadListings = async function() {
var container = document.getElementById('listingsContainer'); var container = document.getElementById('listingsContainer');
container.innerHTML = '<p>Cargando...</p>'; container.innerHTML = '<div class="meli-card"><div class="skeleton skeleton--text"></div><div class="skeleton skeleton--text-sm" style="width:70%;"></div></div>'
+ '<div class="meli-card"><div class="skeleton skeleton--text"></div><div class="skeleton skeleton--text-sm" style="width:60%;"></div></div>'
+ '<div class="meli-card"><div class="skeleton skeleton--text"></div><div class="skeleton skeleton--text-sm" style="width:80%;"></div></div>';
try { try {
var res = await fetch(API + '/listings?page=1&per_page=50', { headers: headers() }); var res = await fetch(API + '/listings?page=1&per_page=50', { headers: headers() });
if (!res.ok) throw new Error('Failed to load listings'); if (!res.ok) throw new Error('Failed to load listings');
@@ -113,7 +115,11 @@
listingsData = data.items || []; listingsData = data.items || [];
renderListings(); renderListings();
} catch (e) { } catch (e) {
container.innerHTML = '<p style="color:var(--color-danger);">Error cargando publicaciones</p>'; container.innerHTML = renderEmptyState({
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><path d="M8 21h8M12 17v4"/></svg>',
title: 'Error cargando publicaciones',
subtitle: 'No se pudieron obtener las publicaciones de MercadoLibre. Intenta de nuevo.'
});
} }
}; };
@@ -129,7 +135,12 @@
}); });
if (!filtered.length) { if (!filtered.length) {
container.innerHTML = '<p style="color:var(--color-text-muted);padding:var(--space-4);">No hay publicaciones. Ve a Inventario y publica un producto.</p>'; container.innerHTML = renderEmptyState({
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><path d="M8 21h8M12 17v4"/></svg>',
title: 'Sin publicaciones',
subtitle: 'Aún no hay publicaciones en MercadoLibre. Ve a Inventario y publica un producto.',
action: '<a href="/pos/inventory" class="btn btn--meli btn--sm">Ir a Inventario</a>'
});
return; return;
} }
@@ -159,13 +170,13 @@
var res = await fetch(API + '/listings/' + id + '/sync', { method: 'POST', headers: headers() }); var res = await fetch(API + '/listings/' + id + '/sync', { method: 'POST', headers: headers() });
var data = await res.json(); var data = await res.json();
if (res.ok) { if (res.ok) {
alert('Sincronizado: $' + data.price + ' · Stock: ' + data.stock); showToast('Sincronizado: $' + data.price + ' · Stock: ' + data.stock, 'ok', { title: 'Publicación actualizada' });
loadListings(); loadListings();
} else { } else {
alert('Error: ' + (data.error || 'Unknown')); showToast(data.error || 'Error desconocido', 'error', { title: 'Error de sincronización' });
} }
} catch (e) { } catch (e) {
alert('Error: ' + e.message); showToast(e.message, 'error', { title: 'Error de red' });
} }
}; };
@@ -197,7 +208,7 @@
window.loadOrders = async function() { window.loadOrders = async function() {
var tbody = document.getElementById('ordersTableBody'); var tbody = document.getElementById('ordersTableBody');
tbody.innerHTML = '<tr><td colspan="6">Cargando...</td></tr>'; tbody.innerHTML = renderSkeletonRows(6, 5);
try { try {
var res = await fetch(API + '/orders?page=1&per_page=50', { headers: headers() }); var res = await fetch(API + '/orders?page=1&per_page=50', { headers: headers() });
if (!res.ok) throw new Error('Failed to load orders'); if (!res.ok) throw new Error('Failed to load orders');
@@ -205,7 +216,11 @@
ordersData = data.items || []; ordersData = data.items || [];
renderOrders(); renderOrders();
} catch (e) { } catch (e) {
tbody.innerHTML = '<tr><td colspan="6" style="color:var(--color-danger)">Error cargando órdenes</td></tr>'; tbody.innerHTML = '<tr><td colspan="6">' + renderEmptyState({
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><path d="M8 21h8M12 17v4"/></svg>',
title: 'Error cargando órdenes',
subtitle: 'No se pudieron obtener las órdenes de MercadoLibre.'
}) + '</td></tr>';
} }
}; };
@@ -221,7 +236,11 @@
}); });
if (!filtered.length) { if (!filtered.length) {
tbody.innerHTML = '<tr><td colspan="6" style="color:var(--color-text-muted)">No hay órdenes.</td></tr>'; tbody.innerHTML = '<tr><td colspan="6">' + renderEmptyState({
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><path d="M8 21h8M12 17v4"/></svg>',
title: 'Sin órdenes',
subtitle: 'No hay órdenes de MercadoLibre en este momento.'
}) + '</td></tr>';
return; return;
} }
@@ -296,13 +315,13 @@
}); });
var data = await res.json(); var data = await res.json();
if (res.ok) { if (res.ok) {
alert('Orden convertida a venta #' + data.sale_id); showToast('Orden convertida a venta #' + data.sale_id, 'ok', { title: 'Venta creada' });
loadOrders(); loadOrders();
} else { } else {
alert('Error: ' + (data.error || 'Unknown')); showToast(data.error || 'Error desconocido', 'error', { title: 'Error al convertir' });
} }
} catch (e) { } catch (e) {
alert('Error: ' + e.message); showToast(e.message, 'error', { title: 'Error de red' });
} }
}; };
@@ -361,6 +380,13 @@
})(); })();
} }
// Register Cmd+K items
if (typeof registerCmdKItem === 'function') {
registerCmdKItem({ group: 'MercadoLibre', label: 'Configuración ML', href: '/pos/marketplace-external', icon: '⚙️' });
registerCmdKItem({ group: 'MercadoLibre', label: 'Publicaciones ML', href: '/pos/marketplace-external#listings', icon: '📦' });
registerCmdKItem({ group: 'MercadoLibre', label: 'Órdenes ML', href: '/pos/marketplace-external#orders', icon: '🛒' });
}
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
loadConfig(); loadConfig();
}); });

View File

@@ -193,7 +193,7 @@
<div class="meli-card" style="max-width:600px;"> <div class="meli-card" style="max-width:600px;">
<h3 style="margin:0 0 var(--space-4);font-family:var(--font-heading);">Conexión con MercadoLibre</h3> <h3 style="margin:0 0 var(--space-4);font-family:var(--font-heading);">Conexión con MercadoLibre</h3>
<div id="configStatus"> <div id="configStatus">
<p>Cargando estado...</p> <div class="skeleton skeleton--text" style="width:120px;"></div>
</div> </div>
<div id="configForm" style="display:none;margin-top:var(--space-4);"> <div id="configForm" style="display:none;margin-top:var(--space-4);">
<p style="margin-bottom:var(--space-3);font-size:var(--text-body-sm);color:var(--color-text-secondary);"> <p style="margin-bottom:var(--space-3);font-size:var(--text-body-sm);color:var(--color-text-secondary);">
@@ -316,8 +316,9 @@
<script src="/pos/static/js/i18n.js" defer></script> <script src="/pos/static/js/i18n.js" defer></script>
<script src="/pos/static/js/app-init.js" defer></script> <script src="/pos/static/js/app-init.js" defer></script>
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script> <script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/marketplace_external.js?v=3" defer></script> <script src="/pos/static/js/marketplace_external.js?v=4" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
</body> </body>
</html> </html>