feat(pos): add catalog UI — browsable inventory with cart and barcode scanner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,3 +55,31 @@ body {
|
|||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Catalog grid */
|
||||||
|
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
|
||||||
|
.catalog-card { cursor: pointer; transition: all 0.2s; }
|
||||||
|
.catalog-card:hover { border-color: var(--color-primary); transform: translateY(-2px); box-shadow: var(--shadow); }
|
||||||
|
.stock-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; }
|
||||||
|
.stock-badge--ok { background: #dcfce7; color: #166534; }
|
||||||
|
.stock-badge--low { background: #fef9c3; color: #854d0e; }
|
||||||
|
.stock-badge--zero { background: #fecaca; color: #991b1b; }
|
||||||
|
|
||||||
|
/* Cart sidebar */
|
||||||
|
.cart-sidebar { position: fixed; right: 0; top: 0; bottom: 0; width: 360px; background: var(--color-surface); border-left: 1px solid var(--color-border); padding: 20px; overflow-y: auto; transform: translateX(100%); transition: transform 0.3s; z-index: 50; }
|
||||||
|
.cart-sidebar.open { transform: translateX(0); }
|
||||||
|
.cart-item { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--color-border); }
|
||||||
|
.cart-total { font-family: var(--font-mono); font-size: 1.3rem; font-weight: 700; }
|
||||||
|
|
||||||
|
/* Search bar */
|
||||||
|
.search-bar { display: flex; gap: 8px; margin-bottom: 20px; }
|
||||||
|
.search-bar input { flex: 1; padding: 10px 16px; border: 1px solid var(--color-border); border-radius: var(--radius); font-size: 1rem; }
|
||||||
|
.search-bar input:focus { outline: none; border-color: var(--color-primary); }
|
||||||
|
|
||||||
|
/* Filter chips */
|
||||||
|
.filter-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
|
||||||
|
.chip { padding: 4px 12px; border-radius: 20px; border: 1px solid var(--color-border); font-size: 0.8rem; cursor: pointer; background: transparent; }
|
||||||
|
.chip.active { background: var(--color-primary); color: white; border-color: var(--color-primary); }
|
||||||
|
|
||||||
|
/* External availability */
|
||||||
|
.external-results { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: var(--radius); padding: 16px; margin-top: 12px; }
|
||||||
|
|||||||
293
pos/static/js/catalog.js
Normal file
293
pos/static/js/catalog.js
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
// /home/Autopartes/pos/static/js/catalog.js
|
||||||
|
// Catalog UI: browsable inventory with cart, barcode scanner, external lookup
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const API = '/pos/api';
|
||||||
|
const token = localStorage.getItem('pos_token');
|
||||||
|
if (!token) { window.location.href = '/pos/login'; return; }
|
||||||
|
|
||||||
|
const headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' };
|
||||||
|
|
||||||
|
// ─── State ───
|
||||||
|
let currentPage = 1;
|
||||||
|
let currentFilters = {};
|
||||||
|
let cartItems = JSON.parse(localStorage.getItem('pos_cart') || '[]');
|
||||||
|
let barcodeBuffer = '';
|
||||||
|
let barcodeTimeout = null;
|
||||||
|
|
||||||
|
// ─── API helpers ───
|
||||||
|
async function apiFetch(url, opts) {
|
||||||
|
const resp = await fetch(url, Object.assign({ headers: headers }, opts || {}));
|
||||||
|
if (resp.status === 401) { localStorage.removeItem('pos_token'); window.location.href = '/pos/login'; return null; }
|
||||||
|
return resp.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Catalog loading ───
|
||||||
|
async function loadCatalog(page, filters) {
|
||||||
|
currentPage = page || 1;
|
||||||
|
currentFilters = filters || currentFilters;
|
||||||
|
const params = new URLSearchParams({ page: currentPage, per_page: 30 });
|
||||||
|
if (currentFilters.q) params.set('q', currentFilters.q);
|
||||||
|
if (currentFilters.category) params.set('category', currentFilters.category);
|
||||||
|
if (currentFilters.brand) params.set('brand', currentFilters.brand);
|
||||||
|
if (currentFilters.vehicle_brand) params.set('vehicle_brand', currentFilters.vehicle_brand);
|
||||||
|
|
||||||
|
const data = await apiFetch(API + '/catalog/search?' + params.toString());
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
renderGrid(data.data || []);
|
||||||
|
renderPagination(data.pagination || {});
|
||||||
|
renderActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGrid(items) {
|
||||||
|
const grid = document.getElementById('catalogGrid');
|
||||||
|
if (!items.length) {
|
||||||
|
grid.innerHTML = '<div class="empty-state"><p>No se encontraron productos</p><button onclick="checkExternalAvailability()" style="margin-top:12px; padding:8px 16px; cursor:pointer;">Buscar en bodegas Nexus</button></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grid.innerHTML = items.map(function (it) {
|
||||||
|
var stockClass = it.stock <= 0 ? 'stock-badge--zero' : (it.low_stock ? 'stock-badge--low' : 'stock-badge--ok');
|
||||||
|
var stockLabel = it.stock <= 0 ? 'Agotado' : it.stock + ' ' + (it.unit || 'PZA');
|
||||||
|
return '<div class="card catalog-card" onclick="window._addToCart(' + it.id + ')" data-id="' + it.id + '">' +
|
||||||
|
(it.image_url ? '<img src="' + it.image_url + '" alt="" style="width:100%;height:120px;object-fit:contain;margin-bottom:8px;">' : '<div style="height:120px;background:#f0f0f0;display:flex;align-items:center;justify-content:center;margin-bottom:8px;border-radius:4px;color:#aaa;">Sin imagen</div>') +
|
||||||
|
'<div style="font-weight:600;font-size:0.9rem;margin-bottom:4px;">' + escHtml(it.name) + '</div>' +
|
||||||
|
'<div style="font-size:0.8rem;color:#666;margin-bottom:4px;">' + escHtml(it.part_number) + (it.brand ? ' · ' + escHtml(it.brand) : '') + '</div>' +
|
||||||
|
'<div style="display:flex;justify-content:space-between;align-items:center;">' +
|
||||||
|
'<span style="font-weight:700;font-size:1rem;">$' + fmt(it.price_1) + '</span>' +
|
||||||
|
'<span class="stock-badge ' + stockClass + '">' + stockLabel + '</span>' +
|
||||||
|
'</div></div>';
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Store items for cart lookup
|
||||||
|
window._catalogItems = {};
|
||||||
|
items.forEach(function (it) { window._catalogItems[it.id] = it; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(pg) {
|
||||||
|
var el = document.getElementById('pagination');
|
||||||
|
if (!pg || pg.total_pages <= 1) { el.innerHTML = ''; return; }
|
||||||
|
var html = '<button ' + (pg.page <= 1 ? 'disabled' : 'onclick="window._loadPage(' + (pg.page - 1) + ')"') + '>« Anterior</button>';
|
||||||
|
html += '<span style="padding:6px 12px; font-size:0.85rem;">' + pg.page + ' / ' + pg.total_pages + '</span>';
|
||||||
|
html += '<button ' + (pg.page >= pg.total_pages ? 'disabled' : 'onclick="window._loadPage(' + (pg.page + 1) + ')"') + '>Siguiente »</button>';
|
||||||
|
el.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderActiveFilters() {
|
||||||
|
var el = document.getElementById('activeFilters');
|
||||||
|
var chips = [];
|
||||||
|
if (currentFilters.category) chips.push('<span class="chip active" onclick="window._removeFilter(\'category\')">Cat: ' + currentFilters.category + ' ×</span>');
|
||||||
|
if (currentFilters.brand) chips.push('<span class="chip active" onclick="window._removeFilter(\'brand\')">' + escHtml(currentFilters.brand) + ' ×</span>');
|
||||||
|
if (currentFilters.vehicle_brand) chips.push('<span class="chip active" onclick="window._removeFilter(\'vehicle_brand\')">Vehiculo: ' + escHtml(currentFilters.vehicle_brand) + ' ×</span>');
|
||||||
|
el.innerHTML = chips.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Sidebar filters ───
|
||||||
|
async function loadCategories() {
|
||||||
|
var data = await apiFetch(API + '/catalog/categories');
|
||||||
|
if (!data) return;
|
||||||
|
var ul = document.getElementById('categoryList');
|
||||||
|
var cats = data.data || [];
|
||||||
|
if (!cats.length) { ul.innerHTML = '<li style="color:#999;">Sin categorias</li>'; return; }
|
||||||
|
ul.innerHTML = '<li onclick="window._filterCat(null)" class="' + (!currentFilters.category ? 'active' : '') + '">Todas</li>' +
|
||||||
|
cats.map(function (c) { return '<li onclick="window._filterCat(' + c.id + ')" class="' + (currentFilters.category == c.id ? 'active' : '') + '">Cat #' + c.id + ' <small>(' + c.count + ')</small></li>'; }).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBrands() {
|
||||||
|
var data = await apiFetch(API + '/catalog/brands');
|
||||||
|
if (!data) return;
|
||||||
|
var ul = document.getElementById('brandList');
|
||||||
|
var brands = data.data || [];
|
||||||
|
if (!brands.length) { ul.innerHTML = '<li style="color:#999;">Sin marcas</li>'; return; }
|
||||||
|
ul.innerHTML = '<li onclick="window._filterBrand(null)" class="' + (!currentFilters.brand ? 'active' : '') + '">Todas</li>' +
|
||||||
|
brands.map(function (b) { return '<li onclick="window._filterBrand(\'' + escHtml(b.name) + '\')" class="' + (currentFilters.brand === b.name ? 'active' : '') + '">' + escHtml(b.name) + ' <small>(' + b.count + ')</small></li>'; }).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Barcode scanner ───
|
||||||
|
async function lookupBarcode(code) {
|
||||||
|
var data = await apiFetch(API + '/catalog/barcode/' + encodeURIComponent(code));
|
||||||
|
if (!data || data.error) { alert('Parte no encontrada: ' + code); return; }
|
||||||
|
addToCart(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for rapid keypress (barcode scanners type fast, then Enter)
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'F1') { e.preventDefault(); document.getElementById('searchInput').focus(); return; }
|
||||||
|
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') return;
|
||||||
|
if (e.key === 'Enter' && barcodeBuffer.length >= 4) {
|
||||||
|
lookupBarcode(barcodeBuffer.trim());
|
||||||
|
barcodeBuffer = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key.length === 1) {
|
||||||
|
barcodeBuffer += e.key;
|
||||||
|
clearTimeout(barcodeTimeout);
|
||||||
|
barcodeTimeout = setTimeout(function () { barcodeBuffer = ''; }, 200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Cart ───
|
||||||
|
function addToCart(item) {
|
||||||
|
var existing = cartItems.find(function (c) { return c.id === item.id; });
|
||||||
|
if (existing) {
|
||||||
|
existing.quantity += 1;
|
||||||
|
} else {
|
||||||
|
cartItems.push({
|
||||||
|
id: item.id, part_number: item.part_number, name: item.name,
|
||||||
|
brand: item.brand, price: item.price_1, tax_rate: item.tax_rate || 0.16,
|
||||||
|
unit: item.unit || 'PZA', stock: item.stock, quantity: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
saveCart();
|
||||||
|
renderCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromCart(index) {
|
||||||
|
cartItems.splice(index, 1);
|
||||||
|
saveCart();
|
||||||
|
renderCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQuantity(index, qty) {
|
||||||
|
qty = parseInt(qty);
|
||||||
|
if (qty <= 0) { removeFromCart(index); return; }
|
||||||
|
cartItems[index].quantity = qty;
|
||||||
|
saveCart();
|
||||||
|
renderCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCartFn() {
|
||||||
|
cartItems = [];
|
||||||
|
saveCart();
|
||||||
|
renderCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCart() {
|
||||||
|
localStorage.setItem('pos_cart', JSON.stringify(cartItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCart() {
|
||||||
|
var badge = document.getElementById('cartBadge');
|
||||||
|
var total = cartItems.reduce(function (s, c) { return s + c.quantity; }, 0);
|
||||||
|
badge.textContent = total;
|
||||||
|
badge.style.display = total > 0 ? 'flex' : 'none';
|
||||||
|
|
||||||
|
var container = document.getElementById('cartItems');
|
||||||
|
var empty = document.getElementById('cartEmpty');
|
||||||
|
var checkoutBtn = document.getElementById('checkoutBtn');
|
||||||
|
|
||||||
|
if (!cartItems.length) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
empty.style.display = 'block';
|
||||||
|
checkoutBtn.disabled = true;
|
||||||
|
document.getElementById('cartSubtotal').textContent = '$0.00';
|
||||||
|
document.getElementById('cartTax').textContent = '$0.00';
|
||||||
|
document.getElementById('cartTotal').textContent = '$0.00';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty.style.display = 'none';
|
||||||
|
checkoutBtn.disabled = false;
|
||||||
|
|
||||||
|
var subtotal = 0;
|
||||||
|
var tax = 0;
|
||||||
|
container.innerHTML = cartItems.map(function (c, i) {
|
||||||
|
var lineTotal = c.price * c.quantity;
|
||||||
|
var lineTax = lineTotal * c.tax_rate;
|
||||||
|
subtotal += lineTotal;
|
||||||
|
tax += lineTax;
|
||||||
|
return '<div class="cart-item">' +
|
||||||
|
'<div style="flex:1;">' +
|
||||||
|
'<div style="font-weight:600;font-size:0.85rem;">' + escHtml(c.name) + '</div>' +
|
||||||
|
'<div style="font-size:0.75rem;color:#666;">' + escHtml(c.part_number) + '</div>' +
|
||||||
|
'<div style="margin-top:4px;display:flex;align-items:center;gap:6px;">' +
|
||||||
|
'<button onclick="window._updateQty(' + i + ',' + (c.quantity - 1) + ')" style="width:24px;height:24px;border:1px solid #ddd;background:#fff;border-radius:4px;cursor:pointer;">-</button>' +
|
||||||
|
'<span style="font-weight:600;">' + c.quantity + '</span>' +
|
||||||
|
'<button onclick="window._updateQty(' + i + ',' + (c.quantity + 1) + ')" style="width:24px;height:24px;border:1px solid #ddd;background:#fff;border-radius:4px;cursor:pointer;">+</button>' +
|
||||||
|
'</div></div>' +
|
||||||
|
'<div style="text-align:right;">' +
|
||||||
|
'<div style="font-weight:600;">$' + fmt(lineTotal) + '</div>' +
|
||||||
|
'<button onclick="window._removeFromCart(' + i + ')" style="font-size:0.75rem;color:#ef4444;background:none;border:none;cursor:pointer;margin-top:4px;">Quitar</button>' +
|
||||||
|
'</div></div>';
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
document.getElementById('cartSubtotal').textContent = '$' + fmt(subtotal);
|
||||||
|
document.getElementById('cartTax').textContent = '$' + fmt(tax);
|
||||||
|
document.getElementById('cartTotal').textContent = '$' + fmt(subtotal + tax);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCart() {
|
||||||
|
document.getElementById('cartSidebar').classList.toggle('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToCheckout() {
|
||||||
|
localStorage.setItem('pos_cart', JSON.stringify(cartItems));
|
||||||
|
window.location.href = '/pos/sale';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── External availability ───
|
||||||
|
async function checkExternalAvailability(partNumber) {
|
||||||
|
var pn = partNumber || currentFilters.q || '';
|
||||||
|
if (!pn) return;
|
||||||
|
var section = document.getElementById('externalSection');
|
||||||
|
var results = document.getElementById('externalResults');
|
||||||
|
section.style.display = 'block';
|
||||||
|
results.innerHTML = '<p>Buscando en bodegas...</p>';
|
||||||
|
|
||||||
|
var data = await apiFetch(API + '/catalog/external-availability/' + encodeURIComponent(pn));
|
||||||
|
if (!data || !data.data || !data.data.length) {
|
||||||
|
results.innerHTML = '<p>No se encontraron resultados externos para "' + escHtml(pn) + '"</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
results.innerHTML = '<ul>' + data.data.map(function (r) {
|
||||||
|
return '<li><strong>' + escHtml(r.name || r.part_number || pn) + '</strong> — Stock: ' + (r.stock || 'N/A') + '</li>';
|
||||||
|
}).join('') + '</ul>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Helpers ───
|
||||||
|
function fmt(n) { return (parseFloat(n) || 0).toFixed(2); }
|
||||||
|
function escHtml(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
||||||
|
|
||||||
|
// ─── Search input ───
|
||||||
|
var searchInput = document.getElementById('searchInput');
|
||||||
|
var searchTimeout = null;
|
||||||
|
searchInput.addEventListener('input', function () {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
searchTimeout = setTimeout(function () {
|
||||||
|
currentFilters.q = searchInput.value.trim();
|
||||||
|
loadCatalog(1, currentFilters);
|
||||||
|
}, 350);
|
||||||
|
});
|
||||||
|
searchInput.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
currentFilters.q = searchInput.value.trim();
|
||||||
|
loadCatalog(1, currentFilters);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Expose globals for inline handlers ───
|
||||||
|
window._addToCart = function (id) {
|
||||||
|
var it = window._catalogItems && window._catalogItems[id];
|
||||||
|
if (it) addToCart(it);
|
||||||
|
};
|
||||||
|
window._loadPage = function (p) { loadCatalog(p); };
|
||||||
|
window._removeFilter = function (key) { delete currentFilters[key]; loadCatalog(1); loadCategories(); loadBrands(); };
|
||||||
|
window._filterCat = function (id) { if (id) currentFilters.category = id; else delete currentFilters.category; loadCatalog(1); loadCategories(); };
|
||||||
|
window._filterBrand = function (name) { if (name) currentFilters.brand = name; else delete currentFilters.brand; loadCatalog(1); loadBrands(); };
|
||||||
|
window._removeFromCart = removeFromCart;
|
||||||
|
window._updateQty = updateQuantity;
|
||||||
|
window.toggleCart = toggleCart;
|
||||||
|
window.goToCheckout = goToCheckout;
|
||||||
|
window.clearCart = clearCartFn;
|
||||||
|
window.checkExternalAvailability = checkExternalAvailability;
|
||||||
|
|
||||||
|
// ─── Init ───
|
||||||
|
renderCart();
|
||||||
|
loadCatalog(1, {});
|
||||||
|
loadCategories();
|
||||||
|
loadBrands();
|
||||||
|
})();
|
||||||
91
pos/templates/catalog.html
Normal file
91
pos/templates/catalog.html
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<!-- /home/Autopartes/pos/templates/catalog.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Catalogo - Nexus POS</title>
|
||||||
|
<link rel="stylesheet" href="/pos/static/css/common.css">
|
||||||
|
<style>
|
||||||
|
body { margin: 0; font-family: var(--font-sans, 'Inter', system-ui, sans-serif); background: var(--color-bg, #f5f5f5); }
|
||||||
|
.top-bar { display: flex; align-items: center; gap: 12px; padding: 12px 20px; background: var(--color-surface, #fff); border-bottom: 1px solid var(--color-border, #e5e5e5); position: sticky; top: 0; z-index: 40; }
|
||||||
|
.top-bar h1 { font-size: 1.1rem; margin: 0; white-space: nowrap; }
|
||||||
|
.cart-btn { position: relative; background: var(--color-primary, #2563eb); color: #fff; border: none; padding: 8px 16px; border-radius: var(--radius, 6px); cursor: pointer; font-size: 0.9rem; }
|
||||||
|
.cart-btn .badge { position: absolute; top: -6px; right: -6px; background: #ef4444; color: #fff; border-radius: 50%; width: 20px; height: 20px; font-size: 0.7rem; display: flex; align-items: center; justify-content: center; }
|
||||||
|
.main-layout { display: flex; gap: 0; min-height: calc(100vh - 60px); }
|
||||||
|
.sidebar-filters { width: 220px; background: var(--color-surface, #fff); border-right: 1px solid var(--color-border, #e5e5e5); padding: 16px; overflow-y: auto; flex-shrink: 0; }
|
||||||
|
.sidebar-filters h3 { font-size: 0.85rem; text-transform: uppercase; color: #666; margin: 16px 0 8px; }
|
||||||
|
.sidebar-filters ul { list-style: none; padding: 0; margin: 0; }
|
||||||
|
.sidebar-filters li { padding: 6px 8px; cursor: pointer; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.sidebar-filters li:hover, .sidebar-filters li.active { background: var(--color-primary-light, #eff6ff); color: var(--color-primary, #2563eb); }
|
||||||
|
.content { flex: 1; padding: 20px; overflow-y: auto; }
|
||||||
|
.pagination { display: flex; justify-content: center; gap: 8px; margin-top: 20px; }
|
||||||
|
.pagination button { padding: 6px 14px; border: 1px solid var(--color-border, #e5e5e5); border-radius: var(--radius, 6px); background: #fff; cursor: pointer; }
|
||||||
|
.pagination button.active { background: var(--color-primary, #2563eb); color: #fff; border-color: var(--color-primary, #2563eb); }
|
||||||
|
.pagination button:disabled { opacity: 0.4; cursor: default; }
|
||||||
|
.external-section { margin-top: 20px; }
|
||||||
|
.empty-state { text-align: center; padding: 60px 20px; color: #888; }
|
||||||
|
.kbd { display: inline-block; padding: 2px 6px; background: #e5e5e5; border-radius: 3px; font-size: 0.75rem; font-family: monospace; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="top-bar">
|
||||||
|
<h1>Catalogo</h1>
|
||||||
|
<div class="search-bar" style="flex:1">
|
||||||
|
<input type="text" id="searchInput" placeholder="Buscar por nombre, numero de parte o codigo de barras... (F1)" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<button class="cart-btn" id="cartToggle" onclick="toggleCart()">
|
||||||
|
Carrito <span class="badge" id="cartBadge" style="display:none">0</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-layout">
|
||||||
|
<aside class="sidebar-filters" id="sidebarFilters">
|
||||||
|
<h3>Categorias</h3>
|
||||||
|
<ul id="categoryList"><li>Cargando...</li></ul>
|
||||||
|
<h3>Marcas (fabricante)</h3>
|
||||||
|
<ul id="brandList"><li>Cargando...</li></ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="content">
|
||||||
|
<div class="filter-chips" id="activeFilters"></div>
|
||||||
|
<div class="catalog-grid" id="catalogGrid"></div>
|
||||||
|
<div class="pagination" id="pagination"></div>
|
||||||
|
|
||||||
|
<div class="external-section" id="externalSection" style="display:none">
|
||||||
|
<h3>Disponibilidad en Bodegas Nexus</h3>
|
||||||
|
<div class="external-results" id="externalResults"></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cart sidebar -->
|
||||||
|
<div class="cart-sidebar" id="cartSidebar">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
|
||||||
|
<h2 style="margin:0; font-size:1.1rem;">Carrito</h2>
|
||||||
|
<button onclick="toggleCart()" style="background:none; border:none; font-size:1.3rem; cursor:pointer;">×</button>
|
||||||
|
</div>
|
||||||
|
<div id="cartItems"></div>
|
||||||
|
<div id="cartEmpty" class="empty-state" style="padding:30px 0;">Carrito vacio</div>
|
||||||
|
<hr style="margin:16px 0;">
|
||||||
|
<div style="display:flex; justify-content:space-between; margin-bottom:4px;">
|
||||||
|
<span>Subtotal</span><span id="cartSubtotal">$0.00</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:space-between; margin-bottom:4px;">
|
||||||
|
<span>IVA</span><span id="cartTax">$0.00</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:space-between; font-weight:700; font-size:1.2rem; margin-bottom:16px;">
|
||||||
|
<span>Total</span><span class="cart-total" id="cartTotal">$0.00</span>
|
||||||
|
</div>
|
||||||
|
<button onclick="goToCheckout()" id="checkoutBtn" disabled
|
||||||
|
style="width:100%; padding:12px; background:var(--color-primary, #2563eb); color:#fff; border:none; border-radius:var(--radius, 6px); font-size:1rem; cursor:pointer;">
|
||||||
|
Ir a cobrar
|
||||||
|
</button>
|
||||||
|
<button onclick="clearCart()" style="width:100%; padding:8px; background:none; border:1px solid #ddd; border-radius:var(--radius, 6px); margin-top:8px; cursor:pointer; font-size:0.85rem;">
|
||||||
|
Vaciar carrito
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/pos/static/js/catalog.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user