// /home/Autopartes/pos/static/js/catalog.js // Catalog UI: TecDoc vehicle navigation with local stock enrichment, cart, detail panel. (function () { 'use strict'; var API = '/pos/api/catalog'; var token = localStorage.getItem('pos_token'); if (!token) { window.location.href = '/pos/login'; return; } var headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }; // ─── DOM refs ─── var breadcrumb = document.getElementById('breadcrumb'); var searchInput = document.getElementById('searchInput'); var searchDropdown = document.getElementById('searchDropdown'); var levelTitle = document.getElementById('levelTitle'); var levelFilter = document.getElementById('levelFilter'); var loading = document.getElementById('loading'); var emptyState = document.getElementById('emptyState'); var emptyTitle = document.getElementById('emptyTitle'); var emptySubtitle = document.getElementById('emptySubtitle'); var navGrid = document.getElementById('navGrid'); var partsGrid = document.getElementById('partsGrid'); var paginationNav = document.getElementById('pagination'); var pageBody = document.getElementById('pageBody'); // Detail panel var detailPanel = document.getElementById('detailPanel'); var detailOverlay = document.getElementById('detailOverlay'); var detailBody = document.getElementById('detailBody'); var detailFooter = document.getElementById('detailFooter'); var detailClose = document.getElementById('detailClose'); var qtyMinus = document.getElementById('qtyMinus'); var qtyPlus = document.getElementById('qtyPlus'); var qtyDisplay = document.getElementById('qtyDisplay'); var addToCartBtn = document.getElementById('addToCartBtn'); // Cart var cartSidebar = document.getElementById('cartSidebar'); var cartOverlay = document.getElementById('cartOverlay'); var cartItemsEl = document.getElementById('cartItems'); var cartEmptyEl = document.getElementById('cartEmpty'); var cartSubtotalEl= document.getElementById('cartSubtotal'); var cartTaxEl = document.getElementById('cartTax'); var cartTotalEl = document.getElementById('cartTotal'); var cartBadge = document.getElementById('cartBadge'); var checkoutBtn = document.getElementById('checkoutBtn'); var cartFab = document.getElementById('cartFab'); var cartCloseBtn = document.getElementById('cartCloseBtn'); // ─── Navigation State ─── var nav = { level: 'brands', // brands|models|years|engines|categories|groups|parts brand: null, // {id, name} model: null, // {id, name} year: null, // {id, year} engine: null, // {id_mye, name} category: null, // {id, name} group: null, // {id, name} }; var currentPage = 1; var currentDetailPart = null; var detailQty = 1; var isOffline = false; // ─── Cart State ─── var cartItems = JSON.parse(localStorage.getItem('pos_cart') || '[]'); // ─── API helper ─── function apiFetch(url) { return fetch(url, { headers: headers }) .then(function (resp) { if (resp.status === 401) { localStorage.removeItem('pos_token'); window.location.href = '/pos/login'; return null; } return resp.json(); }) .catch(function (e) { console.error('API error:', e); return null; }); } // ─── UI helpers ─── function showLoading() { loading.classList.add('is-visible'); navGrid.innerHTML = ''; partsGrid.style.display = 'none'; partsGrid.innerHTML = ''; emptyState.classList.remove('is-visible'); paginationNav.innerHTML = ''; } function hideLoading() { loading.classList.remove('is-visible'); } function showEmpty(title, subtitle) { emptyTitle.textContent = title; emptySubtitle.textContent = subtitle || ''; emptyState.classList.add('is-visible'); navGrid.innerHTML = ''; partsGrid.style.display = 'none'; } function fmt(n) { return (parseFloat(n) || 0).toFixed(2); } function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; } // ─── Breadcrumb ─── function updateBreadcrumb() { var parts = []; parts.push({ label: 'Catalogo', action: 'loadBrands' }); if (nav.brand) parts.push({ label: nav.brand.name, action: 'loadModels' }); if (nav.model) parts.push({ label: nav.model.name, action: 'loadYears' }); if (nav.year) parts.push({ label: String(nav.year.year), action: 'loadEngines' }); if (nav.engine) parts.push({ label: nav.engine.name, action: 'loadCategories' }); if (nav.category) parts.push({ label: nav.category.name, action: 'loadGroups' }); if (nav.group) parts.push({ label: nav.group.name, action: null }); var html = ''; for (var i = 0; i < parts.length; i++) { if (i > 0) html += ''; if (i < parts.length - 1 && parts[i].action) { html += '' + esc(parts[i].label) + ''; } else { html += ''; } } breadcrumb.innerHTML = html; // Wire breadcrumb clicks breadcrumb.querySelectorAll('[data-bc-action]').forEach(function (el) { el.addEventListener('click', function () { var action = this.dataset.bcAction; if (action === 'loadBrands') { resetNav(); loadBrands(); } else if (action === 'loadModels') { resetNavFrom('models'); loadModels(); } else if (action === 'loadYears') { resetNavFrom('years'); loadYears(); } else if (action === 'loadEngines') { resetNavFrom('engines'); loadEngines(); } else if (action === 'loadCategories') { resetNavFrom('categories'); loadCategories(); } else if (action === 'loadGroups') { resetNavFrom('groups'); loadGroups(); } }); }); } function resetNav() { nav.level = 'brands'; nav.brand = nav.model = nav.year = nav.engine = nav.category = nav.group = null; } function resetNavFrom(level) { var levels = ['brands', 'models', 'years', 'engines', 'categories', 'groups', 'parts']; var idx = levels.indexOf(level); if (idx <= 0) { resetNav(); return; } nav.level = level; var keys = [null, 'model', 'year', 'engine', 'category', 'group', null]; for (var i = idx; i < keys.length; i++) { if (keys[i]) nav[keys[i]] = null; } } // ─── Level filter ─── function setupLevelFilter(show) { if (!show) { levelFilter.style.display = 'none'; levelFilter.value = ''; return; } levelFilter.style.display = ''; levelFilter.value = ''; levelFilter.oninput = function () { var q = this.value.toLowerCase(); var cards = navGrid.querySelectorAll('.nav-card'); cards.forEach(function (card) { var text = card.textContent.toLowerCase(); card.style.display = text.indexOf(q) >= 0 ? '' : 'none'; }); }; } // ─── LEVEL LOADERS ─── function loadBrands() { nav.level = 'brands'; updateBreadcrumb(); levelTitle.textContent = 'Selecciona una marca'; setupLevelFilter(true); showLoading(); apiFetch(API + '/brands').then(function (data) { hideLoading(); if (!data || !data.data || !data.data.length) { if (!data) { enterOfflineMode(); return; } showEmpty('Sin marcas', 'El catalogo no tiene marcas con partes disponibles.'); return; } navGrid.className = 'nav-grid'; navGrid.innerHTML = data.data.map(function (b) { return '
'; }).join(''); navGrid.querySelectorAll('.nav-card').forEach(function (card) { card.addEventListener('click', function () { nav.brand = { id: parseInt(this.dataset.brandId), name: this.dataset.name }; loadModels(); }); }); }); } function loadModels() { nav.level = 'models'; updateBreadcrumb(); levelTitle.textContent = 'Modelos de ' + nav.brand.name; setupLevelFilter(true); showLoading(); apiFetch(API + '/models?brand_id=' + nav.brand.id).then(function (data) { hideLoading(); if (!data || !data.data || !data.data.length) { showEmpty('Sin modelos', 'No hay modelos con partes para ' + nav.brand.name); return; } navGrid.className = 'nav-grid'; navGrid.innerHTML = data.data.map(function (m) { return ''; }).join(''); navGrid.querySelectorAll('.nav-card').forEach(function (card) { card.addEventListener('click', function () { nav.model = { id: parseInt(this.dataset.modelId), name: this.dataset.name }; loadYears(); }); }); }); } function loadYears() { nav.level = 'years'; updateBreadcrumb(); levelTitle.textContent = nav.brand.name + ' ' + nav.model.name + ' — Anios'; setupLevelFilter(false); showLoading(); apiFetch(API + '/years?model_id=' + nav.model.id).then(function (data) { hideLoading(); if (!data || !data.data || !data.data.length) { showEmpty('Sin anios', 'No hay anios con partes para este modelo.'); return; } navGrid.className = 'nav-grid nav-grid--years'; navGrid.innerHTML = data.data.map(function (y) { return ''; }).join(''); navGrid.querySelectorAll('.nav-card').forEach(function (card) { card.addEventListener('click', function () { nav.year = { id: parseInt(this.dataset.yearId), year: parseInt(this.dataset.year) }; loadEngines(); }); }); }); } function loadEngines() { nav.level = 'engines'; updateBreadcrumb(); levelTitle.textContent = nav.brand.name + ' ' + nav.model.name + ' ' + nav.year.year + ' — Motor'; setupLevelFilter(false); showLoading(); apiFetch(API + '/engines?model_id=' + nav.model.id + '&year_id=' + nav.year.id).then(function (data) { hideLoading(); if (!data || !data.data || !data.data.length) { showEmpty('Sin motores', 'No hay configuraciones de motor para esta combinacion.'); return; } // If only one engine, auto-select it if (data.data.length === 1) { var e = data.data[0]; nav.engine = { id_mye: e.id_mye, name: e.name_engine + (e.trim_level ? ' ' + e.trim_level : '') }; loadCategories(); return; } navGrid.className = 'nav-grid'; navGrid.innerHTML = data.data.map(function (e) { var label = e.name_engine + (e.trim_level ? ' — ' + e.trim_level : ''); return ''; }).join(''); navGrid.querySelectorAll('.nav-card').forEach(function (card) { card.addEventListener('click', function () { nav.engine = { id_mye: parseInt(this.dataset.myeId), name: this.dataset.name }; loadCategories(); }); }); }); } function loadCategories() { nav.level = 'categories'; updateBreadcrumb(); levelTitle.textContent = 'Categorias de partes'; setupLevelFilter(true); showLoading(); apiFetch(API + '/categories?mye_id=' + nav.engine.id_mye).then(function (data) { hideLoading(); if (!data || !data.data || !data.data.length) { showEmpty('Sin categorias', 'No hay partes catalogadas para este vehiculo.'); return; } navGrid.className = 'nav-grid'; navGrid.innerHTML = data.data.map(function (c) { return ''; }).join(''); navGrid.querySelectorAll('.nav-card').forEach(function (card) { card.addEventListener('click', function () { nav.category = { id: parseInt(this.dataset.catId), name: this.dataset.name }; loadGroups(); }); }); }); } function loadGroups() { nav.level = 'groups'; updateBreadcrumb(); levelTitle.textContent = nav.category.name; setupLevelFilter(true); showLoading(); apiFetch(API + '/groups?mye_id=' + nav.engine.id_mye + '&category_id=' + nav.category.id).then(function (data) { hideLoading(); if (!data || !data.data || !data.data.length) { showEmpty('Sin subcategorias', 'No hay subcategorias para ' + nav.category.name); return; } navGrid.className = 'nav-grid'; navGrid.innerHTML = data.data.map(function (g) { return ''; }).join(''); navGrid.querySelectorAll('.nav-card').forEach(function (card) { card.addEventListener('click', function () { nav.group = { id: parseInt(this.dataset.groupId), name: this.dataset.name }; loadParts(1); }); }); }); } function loadParts(page) { nav.level = 'parts'; currentPage = page || 1; updateBreadcrumb(); levelTitle.textContent = nav.group.name; setupLevelFilter(false); showLoading(); navGrid.innerHTML = ''; apiFetch(API + '/parts?mye_id=' + nav.engine.id_mye + '&group_id=' + nav.group.id + '&page=' + currentPage + '&per_page=30').then(function (data) { hideLoading(); if (!data || !data.data || !data.data.length) { showEmpty('Sin partes', 'No hay partes en este grupo.'); return; } partsGrid.style.display = ''; partsGrid.innerHTML = data.data.map(function (p) { var stockBadge; if (p.local_stock > 0) { stockBadge = 'En stock: ' + p.local_stock + ''; } else if (p.bodega_count > 0) { stockBadge = '' + p.bodega_count + ' bodega' + (p.bodega_count > 1 ? 's' : '') + ''; } else { stockBadge = 'Sin stock'; } var imgHtml = p.image_url ? 'Error al cargar detalle.
'; return; } currentDetailPart = data; var p = data.part; var local = data.local; var bodegas = data.bodegas || []; var alts = data.alternatives || []; var html = ''; // Part info html += '| Bodega | Precio | Stock |
|---|---|---|
| ' + esc(b.business_name) + ' | ' + (b.price ? '$' + fmt(b.price) : '--') + ' | ' + b.stock + ' |