// /home/Autopartes/pos/static/js/catalog.js
// Catalog UI: browsable inventory with cart, barcode scanner, external lookup
// Aligned with design-system catalog.html IDs and class names.
(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' };
// ─── DOM refs (design-system IDs) ───
const productGrid = document.getElementById('productGrid');
const emptyState = document.getElementById('emptyState');
const emptyTitle = document.getElementById('emptyStateTitle');
const emptySubtitle = document.getElementById('emptyStateSubtitle');
const searchInput = document.getElementById('inp-part');
const partSearchBtn = document.getElementById('partSearchBtn');
const cartSidebar = document.getElementById('cartSidebar');
const cartOverlay = document.getElementById('cartOverlay');
const cartItemsEl = document.getElementById('cartItems');
const cartEmptyEl = document.getElementById('cartEmpty');
const cartSubtotalEl = document.getElementById('cartSubtotal');
const cartTaxEl = document.getElementById('cartTax');
const cartTotalEl = document.getElementById('cartTotal');
const cartBadge = document.getElementById('cartBadge');
const checkoutBtn = document.getElementById('checkoutBtn');
const paginationNav = document.querySelector('.pagination');
// ─── State ───
let currentPage = 1;
let currentFilters = {};
let cartItems = JSON.parse(localStorage.getItem('pos_cart') || '[]');
let barcodeBuffer = '';
let barcodeTimeout = null;
let catalogItemsMap = {}; // id -> item for cart lookup
// ─── API helpers ───
async function apiFetch(url, opts) {
try {
var 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();
} catch (e) {
console.error('API fetch error:', e);
return null;
}
}
// ─── Catalog loading ───
async function loadCatalog(page, filters) {
currentPage = page || 1;
currentFilters = filters || currentFilters;
var 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);
var data = await apiFetch(API + '/catalog/search?' + params.toString());
if (!data) return;
var items = data.data || [];
renderGrid(items);
renderPagination(data.pagination || {});
updateResultsCount(data.pagination || {});
}
function renderGrid(items) {
// Clear demo/hardcoded cards
productGrid.innerHTML = '';
if (!items.length) {
productGrid.style.display = 'none';
emptyState.classList.add('is-visible');
emptyTitle.textContent = 'No se encontraron productos';
emptySubtitle.textContent = currentFilters.q
? 'No hay resultados para "' + currentFilters.q + '". Intenta con otro termino.'
: 'Intenta con otro termino de busqueda o verifica el numero de parte';
return;
}
emptyState.classList.remove('is-visible');
productGrid.style.display = '';
catalogItemsMap = {};
items.forEach(function (it) { catalogItemsMap[it.id] = it; });
productGrid.innerHTML = items.map(function (it) {
var stockClass, stockLabel;
if (it.stock <= 0) {
stockClass = 'stock-out';
stockLabel = 'Agotado';
} else if (it.low_stock) {
stockClass = 'stock-low';
stockLabel = 'Ultimas ' + it.stock;
} else {
stockClass = 'stock-ok';
stockLabel = 'En stock';
}
var isOut = it.stock <= 0;
var imgHtml;
if (it.image_url) {
imgHtml = '
';
} else {
imgHtml = '' +
'IMG';
}
return '' +
'' +
imgHtml +
'
' + stockLabel + '
' +
'
' +
'' +
(it.category_name ? '
' + escHtml(it.category_name) + '
' : '') +
'
' + escHtml(it.name) + '
' +
'
' + escHtml(it.part_number || '') + '
' +
'
' +
'' + escHtml(it.brand || '') + '' +
'
' +
'
' +
'' +
'';
}).join('');
}
function renderPagination(pg) {
if (!paginationNav) return;
if (!pg || pg.total_pages <= 1) {
paginationNav.innerHTML = '';
return;
}
var html = '';
// Previous button
if (pg.page <= 1) {
html += '';
} else {
html += '';
}
// Page numbers
var pages = buildPageNumbers(pg.page, pg.total_pages);
pages.forEach(function (p) {
if (p === '...') {
html += '…';
} else if (p === pg.page) {
html += '';
} else {
html += '';
}
});
// Next button
if (pg.page >= pg.total_pages) {
html += '';
} else {
html += '';
}
paginationNav.innerHTML = html;
}
function buildPageNumbers(current, total) {
if (total <= 7) {
var arr = [];
for (var i = 1; i <= total; i++) arr.push(i);
return arr;
}
var pages = [1];
if (current > 3) pages.push('...');
for (var j = Math.max(2, current - 1); j <= Math.min(total - 1, current + 1); j++) {
pages.push(j);
}
if (current < total - 2) pages.push('...');
pages.push(total);
return pages;
}
function updateResultsCount(pg) {
var el = document.querySelector('.results-count');
if (!el || !pg) return;
var total = pg.total || 0;
el.innerHTML = '' + total.toLocaleString('es-MX') + ' partes encontradas';
}
// ─── 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);
}
document.addEventListener('keydown', function (e) {
if (e.key === 'F1') {
e.preventDefault();
if (searchInput) searchInput.focus();
return;
}
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.tagName === 'SELECT') 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 addToCartById(id) {
var it = catalogItemsMap[id];
if (it) addToCart(it);
}
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 || item.price, tax_rate: item.tax_rate || 0.16,
unit: item.unit || 'PZA', stock: item.stock, quantity: 1
});
}
saveCart();
renderCart();
// Brief open to show item was added
if (!cartSidebar.classList.contains('open')) {
toggleCart();
}
}
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 total = cartItems.reduce(function (s, c) { return s + c.quantity; }, 0);
if (cartBadge) {
cartBadge.textContent = total;
cartBadge.style.display = total > 0 ? 'flex' : 'none';
}
if (!cartItems.length) {
cartItemsEl.innerHTML = '';
cartEmptyEl.style.display = 'block';
if (checkoutBtn) checkoutBtn.disabled = true;
cartSubtotalEl.textContent = '$0.00';
cartTaxEl.textContent = '$0.00';
cartTotalEl.textContent = '$0.00';
return;
}
cartEmptyEl.style.display = 'none';
if (checkoutBtn) checkoutBtn.disabled = false;
var subtotal = 0;
var tax = 0;
cartItemsEl.innerHTML = cartItems.map(function (c, i) {
var lineTotal = c.price * c.quantity;
var lineTax = lineTotal * c.tax_rate;
subtotal += lineTotal;
tax += lineTax;
return '
' +
'
' +
'
' + escHtml(c.name) + '
' +
'
' + escHtml(c.part_number) + '
' +
'
' +
'' +
'' + c.quantity + '' +
'' +
'
' +
'
' +
'
$' + fmt(lineTotal) + '
' +
'
' +
'
';
}).join('');
cartSubtotalEl.textContent = '$' + fmt(subtotal);
cartTaxEl.textContent = '$' + fmt(tax);
cartTotalEl.textContent = '$' + fmt(subtotal + tax);
}
function toggleCart() {
var isOpen = cartSidebar.classList.toggle('open');
if (cartOverlay) cartOverlay.classList.toggle('open', isOpen);
}
function goToCheckout() {
if (!cartItems.length) return;
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;
alert('Buscando "' + pn + '" en bodegas Nexus...');
var data = await apiFetch(API + '/catalog/external-availability/' + encodeURIComponent(pn));
if (!data || !data.data || !data.data.length) {
alert('No se encontraron resultados externos para "' + pn + '"');
return;
}
var msg = data.data.map(function (r) {
return (r.name || r.part_number || pn) + ' - Stock: ' + (r.stock || 'N/A');
}).join('\n');
alert('Resultados externos:\n' + msg);
}
// ─── 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 searchTimeout = null;
if (searchInput) {
searchInput.addEventListener('input', function () {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function () {
currentFilters.q = searchInput.value.trim();
loadCatalog(1, currentFilters);
}, 300);
});
searchInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
clearTimeout(searchTimeout);
currentFilters.q = searchInput.value.trim();
loadCatalog(1, currentFilters);
}
});
}
// ─── Part search button ───
if (partSearchBtn) {
partSearchBtn.addEventListener('click', function () {
if (searchInput) {
currentFilters.q = searchInput.value.trim();
loadCatalog(1, currentFilters);
}
});
}
// ─── Filter chips (dynamic from inventory brands) ───
async function loadBrandChips() {
var data = await apiFetch(API + '/catalog/brands');
if (!data || !data.data) return;
var container = document.getElementById('brandChips');
if (!container) return;
container.innerHTML = '';
data.data.forEach(function(b) {
var btn = document.createElement('button');
btn.className = 'chip';
btn.setAttribute('data-chip', b.name);
btn.setAttribute('aria-pressed', 'false');
btn.textContent = b.name + ' (' + b.count + ')';
container.appendChild(btn);
});
// Re-wire all chip clicks (including the "Todos" chip)
wireChipClicks();
}
function wireChipClicks() {
document.querySelectorAll('[data-chip]').forEach(function (chip) {
chip.addEventListener('click', function () {
var isActive = chip.classList.contains('is-active');
document.querySelectorAll('[data-chip]').forEach(function (c) {
c.classList.remove('is-active');
c.setAttribute('aria-pressed', 'false');
});
if (!isActive && chip.dataset.chip) {
chip.classList.add('is-active');
chip.setAttribute('aria-pressed', 'true');
currentFilters.brand = chip.dataset.chip;
delete currentFilters.chip;
} else {
// "Todos" or deselect
document.querySelector('[data-chip=""]').classList.add('is-active');
document.querySelector('[data-chip=""]').setAttribute('aria-pressed', 'true');
delete currentFilters.brand;
delete currentFilters.chip;
}
loadCatalog(1, currentFilters);
});
});
}
loadBrandChips();
wireChipClicks();
// ─── Expose globals for inline onclick handlers ───
window.CatalogApp = {
toggleCart: toggleCart,
goToCheckout: goToCheckout,
addToCart: addToCartById,
removeFromCart: removeFromCart,
updateQty: updateQuantity,
clearCart: clearCartFn,
loadPage: function (p) { loadCatalog(p); },
checkExternal: checkExternalAvailability
};
// Also keep legacy window._ handlers for backward compat
window.toggleCart = toggleCart;
window.goToCheckout = goToCheckout;
window.checkExternalAvailability = checkExternalAvailability;
// ─── Init ───
renderCart();
loadCatalog(1, {});
})();