feat(pos): integrate design system for clients, inventory, catalog + offline banner
- Replace customers.html with design system clientes page, add slide panel + wire to customers.js - Replace inventory.html with design system inventario page, load inventory.js - Add empty state component to catalog product grid (hidden, shown when no results) - Add offline banner HTML/CSS to all three pages - Create offline-banner.js: listens online/offline events, auto-dismisses restored banner after 3s Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
46
pos/static/js/offline-banner.js
Normal file
46
pos/static/js/offline-banner.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// /home/Autopartes/pos/static/js/offline-banner.js
|
||||
// Global offline/online banner controller.
|
||||
// Include this script in any page that has an #offlineBanner element.
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var banner = document.getElementById('offlineBanner');
|
||||
var bannerText = document.getElementById('offlineBannerText');
|
||||
if (!banner || !bannerText) return;
|
||||
|
||||
var dismissTimer = null;
|
||||
|
||||
function showOffline() {
|
||||
clearTimeout(dismissTimer);
|
||||
banner.className = 'banner banner--error';
|
||||
banner.style.display = 'flex';
|
||||
banner.style.animation = 'slideDown 0.35s ease-out forwards';
|
||||
bannerText.innerHTML = '<strong>Conexion perdida</strong> — Intentando reconectar...';
|
||||
}
|
||||
|
||||
function showOnline() {
|
||||
clearTimeout(dismissTimer);
|
||||
banner.className = 'banner banner--success';
|
||||
banner.style.display = 'flex';
|
||||
banner.style.animation = 'slideDown 0.35s ease-out forwards';
|
||||
bannerText.innerHTML = '<strong>Conexion restaurada</strong> — Sincronizando datos...';
|
||||
|
||||
// Auto-dismiss after 3 seconds
|
||||
dismissTimer = setTimeout(function () {
|
||||
banner.style.animation = 'slideUp 0.3s ease-in forwards';
|
||||
banner.addEventListener('animationend', function onEnd() {
|
||||
banner.style.display = 'none';
|
||||
banner.removeEventListener('animationend', onEnd);
|
||||
}, { once: true });
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Show warning immediately if already offline
|
||||
if (!navigator.onLine) {
|
||||
showOffline();
|
||||
}
|
||||
|
||||
window.addEventListener('offline', showOffline);
|
||||
window.addEventListener('online', showOnline);
|
||||
})();
|
||||
@@ -1388,6 +1388,101 @@
|
||||
.sidebar-overlay.is-open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
EMPTY STATE COMPONENT
|
||||
========================================================================= */
|
||||
|
||||
.empty-state {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
max-width: 320px;
|
||||
padding: var(--space-8) var(--space-4);
|
||||
margin: var(--space-10) auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-state.is-visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.empty-state__icon {
|
||||
font-size: 48px;
|
||||
line-height: 1;
|
||||
margin-bottom: var(--space-4);
|
||||
opacity: 0.6;
|
||||
filter: grayscale(30%);
|
||||
}
|
||||
|
||||
.empty-state__title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h5);
|
||||
font-weight: var(--heading-weight-secondary);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.empty-state__subtitle {
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.5;
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.empty-state__action {
|
||||
padding: var(--space-2) var(--space-5);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
border: 1px solid var(--btn-secondary-border);
|
||||
background: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
}
|
||||
|
||||
.empty-state__action:hover {
|
||||
background: var(--btn-secondary-bg-hover);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
OFFLINE BANNER
|
||||
========================================================================= */
|
||||
|
||||
@keyframes slideDown { from { transform: translateY(-100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
||||
@keyframes slideUp { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-100%); opacity: 0; } }
|
||||
|
||||
.banner {
|
||||
display: flex; align-items: center; gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4); border-radius: var(--radius-md);
|
||||
font-size: var(--text-body-sm); font-weight: 500; line-height: 1.4;
|
||||
}
|
||||
.banner--warning {
|
||||
background: var(--color-warning-light, #fef9c3); color: var(--color-warning-dark, #854d0e);
|
||||
border: 1px solid var(--color-warning, #eab308);
|
||||
}
|
||||
.banner--success {
|
||||
background: var(--color-success-light, #dcfce7); color: var(--color-success-dark, #166534);
|
||||
border: 1px solid var(--color-success, #22c55e);
|
||||
}
|
||||
.banner--error {
|
||||
background: var(--color-error-light, #fef2f2); color: var(--color-error-dark, #991b1b);
|
||||
border: 1px solid var(--color-error, #ef4444);
|
||||
}
|
||||
.banner--dismissing { animation: slideUp 0.3s ease-in forwards; }
|
||||
.banner__icon { font-size: 18px; flex-shrink: 0; }
|
||||
.banner__text { flex: 1; }
|
||||
.banner__text strong { font-weight: 700; }
|
||||
.banner__dismiss {
|
||||
background: none; border: none; cursor: pointer; font-size: 18px;
|
||||
padding: var(--space-1); opacity: 0.7; color: inherit;
|
||||
}
|
||||
.banner__dismiss:hover { opacity: 1; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -1871,6 +1966,14 @@
|
||||
</div>
|
||||
<!-- /product-grid -->
|
||||
|
||||
<!-- Empty State (shown when no results) -->
|
||||
<div class="empty-state" id="emptyState">
|
||||
<div class="empty-state__icon">🔎</div>
|
||||
<div class="empty-state__title" id="emptyStateTitle">No se encontraron productos</div>
|
||||
<div class="empty-state__subtitle" id="emptyStateSubtitle">Intenta con otro termino de busqueda o verifica el numero de parte</div>
|
||||
<button class="empty-state__action" onclick="document.querySelector('.search-box input').value=''; document.querySelector('.search-box input').focus();">Limpiar filtros</button>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<nav class="pagination" aria-label="Paginación de resultados">
|
||||
<button class="page-item page-item--wide is-disabled" aria-label="Página anterior" disabled>
|
||||
@@ -2069,5 +2172,13 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Offline Banner -->
|
||||
<div id="offlineBanner" class="banner banner--warning" style="display:none;position:fixed;top:0;left:0;right:0;z-index:9999;border-radius:0;animation:none;">
|
||||
<span class="banner__icon"></span>
|
||||
<span class="banner__text" id="offlineBannerText"><strong>Modo offline</strong> — Funciones limitadas. Solo consultas en cache disponibles.</span>
|
||||
<button class="banner__dismiss" onclick="document.getElementById('offlineBanner').style.display='none'" aria-label="Cerrar">×</button>
|
||||
</div>
|
||||
|
||||
<script src="/pos/static/js/offline-banner.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user