// /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 = '' + escHtml(it.name) + ''; } else { imgHtml = '' + ''; } 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, {}); })();