// /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 += '' + esc(parts[i].label) + ''; } } 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 ? '' + esc(p.name) + '' : ''; return '
' + '
' + imgHtml + '
' + '
' + '
' + esc(p.oem_part_number) + '
' + '
' + esc(p.name) + '
' + '
' + '' + '
'; }).join(''); // Wire part card clicks → open detail panel partsGrid.querySelectorAll('.part-card').forEach(function (card) { card.addEventListener('click', function () { openPartDetail(parseInt(this.dataset.partId)); }); }); // Pagination if (data.pagination) renderPagination(data.pagination); }); } // ─── PAGINATION ─── function renderPagination(pg) { if (!pg || pg.total_pages <= 1) { paginationNav.innerHTML = ''; return; } var html = ''; if (pg.page <= 1) { html += ''; } else { html += ''; } var pages = buildPageNumbers(pg.page, pg.total_pages); pages.forEach(function (p) { if (p === '...') { html += '...'; } else if (p === pg.page) { html += ''; } else { html += ''; } }); if (pg.page >= pg.total_pages) { html += ''; } else { html += ''; } paginationNav.innerHTML = html; paginationNav.querySelectorAll('[data-page]').forEach(function (btn) { btn.addEventListener('click', function () { pageBody.scrollTo({ top: 0, behavior: 'smooth' }); loadParts(parseInt(this.dataset.page)); }); }); } function buildPageNumbers(current, total) { if (total <= 7) { var a = []; for (var i = 1; i <= total; i++) a.push(i); return a; } var p = [1]; if (current > 3) p.push('...'); for (var j = Math.max(2, current - 1); j <= Math.min(total - 1, current + 1); j++) p.push(j); if (current < total - 2) p.push('...'); p.push(total); return p; } // ─── DETAIL PANEL ─── function openPartDetail(partId) { detailBody.innerHTML = '
'; detailFooter.style.display = 'none'; detailPanel.classList.add('is-open'); detailOverlay.classList.add('is-visible'); detailQty = 1; qtyDisplay.textContent = '1'; apiFetch(API + '/part/' + partId).then(function (data) { if (!data || data.error) { detailBody.innerHTML = '

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 += '
'; if (p.category_name) html += '
' + esc(p.category_name) + ' > ' + esc(p.group_name) + '
'; html += '
' + esc(p.oem_part_number) + '
'; html += '
' + esc(p.name) + '
'; if (p.description) html += '
' + esc(p.description) + '
'; if (p.image_url) html += '
'; html += '
'; // Local stock html += '
'; html += '
Mi stock
'; if (local && local.stock > 0) { html += '
Cantidad' + local.stock + ' ' + (local.unit || 'PZA') + '
'; html += '
Precio publico$' + fmt(local.price_1) + '
'; if (local.price_2) html += '
Precio mayoreo$' + fmt(local.price_2) + '
'; if (local.price_3) html += '
Precio taller$' + fmt(local.price_3) + '
'; if (local.location) html += '
Ubicacion' + esc(local.location) + '
'; } else { html += '
No tienes esta parte en inventario.
'; } html += '
'; // Bodegas if (bodegas.length) { html += '
'; html += '
Disponible en bodegas
'; html += ''; bodegas.forEach(function (b) { html += ''; }); html += '
BodegaPrecioStock
' + esc(b.business_name) + '' + (b.price ? '$' + fmt(b.price) : '--') + '' + b.stock + '
'; } // Alternatives if (alts.length) { html += '
'; html += '
Alternativas / Cross-references
'; alts.forEach(function (a) { var stockLabel = a.local_stock > 0 ? 'Stock: ' + a.local_stock + '' : (a.bodega_count > 0 ? '' + a.bodega_count + ' bod.' : ''); html += '
' + '
' + esc(a.part_number) + '
' + '
' + esc(a.manufacturer) + (a.name ? ' — ' + esc(a.name) : '') + '
' + '
' + stockLabel + '
' + '
'; }); html += '
'; } detailBody.innerHTML = html; // Show footer only if we have local stock if (local && local.stock > 0) { detailFooter.style.display = ''; } else { detailFooter.style.display = 'none'; } }); } function closeDetail() { detailPanel.classList.remove('is-open'); detailOverlay.classList.remove('is-visible'); currentDetailPart = null; } detailClose.addEventListener('click', closeDetail); detailOverlay.addEventListener('click', closeDetail); qtyMinus.addEventListener('click', function () { if (detailQty > 1) { detailQty--; qtyDisplay.textContent = detailQty; } }); qtyPlus.addEventListener('click', function () { detailQty++; qtyDisplay.textContent = detailQty; }); addToCartBtn.addEventListener('click', function () { if (!currentDetailPart) return; var p = currentDetailPart.part; var local = currentDetailPart.local; if (!local) return; addToCart({ id: p.id_part, part_number: p.oem_part_number, name: p.name, brand: '', price: local.price_1, tax_rate: local.tax_rate || 0.16, unit: local.unit || 'PZA', stock: local.stock, source: 'local', inventory_id: local.inventory_id, }, detailQty); closeDetail(); }); // ─── SMART SEARCH ─── var searchTimeout = null; searchInput.addEventListener('input', function () { clearTimeout(searchTimeout); var q = this.value.trim(); if (q.length < 2) { searchDropdown.classList.remove('is-visible'); return; } searchTimeout = setTimeout(function () { runSearch(q); }, 350); }); searchInput.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); clearTimeout(searchTimeout); var q = this.value.trim(); if (q.length >= 2) runSearch(q); } if (e.key === 'Escape') { searchDropdown.classList.remove('is-visible'); } }); // Close dropdown on outside click document.addEventListener('click', function (e) { if (!searchDropdown.contains(e.target) && e.target !== searchInput) { searchDropdown.classList.remove('is-visible'); } }); function runSearch(q) { apiFetch(API + '/search?q=' + encodeURIComponent(q) + '&limit=20').then(function (data) { if (!data || !data.data || !data.data.length) { searchDropdown.innerHTML = '
Sin resultados para "' + esc(q) + '"
'; searchDropdown.classList.add('is-visible'); return; } searchDropdown.innerHTML = data.data.map(function (r) { var stockLabel = r.local_stock > 0 ? 'Stock: ' + r.local_stock + '' : ''; return '
' + '
' + '
' + esc(r.oem_part_number) + '
' + '
' + esc(r.name) + '
' + (r.vehicle_info ? '
' + esc(r.vehicle_info) + '
' : '') + '
' + stockLabel + '
'; }).join(''); searchDropdown.classList.add('is-visible'); searchDropdown.querySelectorAll('.search-result-item').forEach(function (el) { el.addEventListener('click', function () { searchDropdown.classList.remove('is-visible'); openPartDetail(parseInt(this.dataset.partId)); }); }); }); } // ─── CART ─── function addToCart(item, qty) { qty = qty || 1; var existing = cartItems.find(function (c) { return c.id === item.id; }); if (existing) { existing.quantity += qty; } else { cartItems.push({ id: item.id, part_number: item.part_number, name: item.name, brand: item.brand || '', price: item.price, tax_rate: item.tax_rate || 0.16, unit: item.unit || 'PZA', stock: item.stock, source: item.source || 'local', inventory_id: item.inventory_id, quantity: qty, }); } saveCart(); renderCart(); 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 clearCart() { 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 '
' + '
' + '
' + esc(c.name) + '
' + '
' + esc(c.part_number) + '
' + '
' + '' + '' + c.quantity + '' + '' + '
' + '
' + '
$' + fmt(lineTotal) + '
' + '' + '
'; }).join(''); cartSubtotalEl.textContent = '$' + fmt(subtotal); cartTaxEl.textContent = '$' + fmt(tax); cartTotalEl.textContent = '$' + fmt(subtotal + tax); // Wire cart buttons cartItemsEl.querySelectorAll('[data-cart-action]').forEach(function (btn) { btn.addEventListener('click', function () { var idx = parseInt(this.dataset.idx); var action = this.dataset.cartAction; if (action === 'dec') updateQuantity(idx, cartItems[idx].quantity - 1); else if (action === 'inc') updateQuantity(idx, cartItems[idx].quantity + 1); else if (action === 'remove') removeFromCart(idx); }); }); } function toggleCart() { var isOpen = cartSidebar.classList.toggle('open'); cartOverlay.classList.toggle('open', isOpen); } function goToCheckout() { if (!cartItems.length) return; localStorage.setItem('pos_cart', JSON.stringify(cartItems)); window.location.href = '/pos/sale'; } cartFab.addEventListener('click', toggleCart); cartCloseBtn.addEventListener('click', toggleCart); cartOverlay.addEventListener('click', toggleCart); checkoutBtn.addEventListener('click', goToCheckout); // ─── OFFLINE FALLBACK ─── function enterOfflineMode() { isOffline = true; document.getElementById('offlineBanner').style.display = ''; document.getElementById('offlineBannerText').innerHTML = 'Modo offline — Mostrando solo tu inventario local.'; levelTitle.textContent = 'Inventario local'; setupLevelFilter(false); // TODO: load local inventory via legacy /pos/api/catalog/search endpoint showEmpty('Sin conexion al catalogo', 'Verifica tu conexion. El catalogo TecDoc requiere acceso al servidor central.'); } // ─── BARCODE SCANNER ─── var barcodeBuffer = ''; var barcodeTimeout = null; document.addEventListener('keydown', function (e) { // F1 → focus search if (e.key === 'F1') { e.preventDefault(); searchInput.focus(); return; } // Escape → close panels if (e.key === 'Escape') { closeDetail(); if (cartSidebar.classList.contains('open')) toggleCart(); return; } // Barcode scanner detection if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') return; if (e.key === 'Enter' && barcodeBuffer.length >= 4) { var code = barcodeBuffer.trim(); barcodeBuffer = ''; // Search for the barcode searchInput.value = code; runSearch(code); return; } if (e.key.length === 1) { barcodeBuffer += e.key; clearTimeout(barcodeTimeout); barcodeTimeout = setTimeout(function () { barcodeBuffer = ''; }, 200); } }); // ─── THEME SWITCHER ─── document.querySelectorAll('[data-theme-switch]').forEach(function (btn) { btn.addEventListener('click', function () { var theme = this.dataset.themeSwitch; document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('pos_theme', theme); document.querySelectorAll('[data-theme-switch]').forEach(function (b) { b.classList.remove('is-active'); b.setAttribute('aria-pressed', 'false'); }); this.classList.add('is-active'); this.setAttribute('aria-pressed', 'true'); }); // Set initial active state var current = localStorage.getItem('pos_theme') || 'industrial'; if (btn.dataset.themeSwitch === current) { btn.classList.add('is-active'); btn.setAttribute('aria-pressed', 'true'); } else { btn.classList.remove('is-active'); btn.setAttribute('aria-pressed', 'false'); } }); // ─── VEHICLE SELECTOR (dropdown bar) ─── var vsYear = document.getElementById('vsYear'); var vsBrand = document.getElementById('vsBrand'); var vsModel = document.getElementById('vsModel'); var vsEngine = document.getElementById('vsEngine'); var vsClear = document.getElementById('vsClear'); // Load years on init function vsLoadYears() { apiFetch(API + '/years-all').then(function (data) { if (!data) return; var years = data.data || data; // If endpoint doesn't exist, generate from 1990-2026 if (!years || !years.length) { years = []; for (var y = 2026; y >= 1990; y--) years.push({ id_year: y, year_car: y }); } vsYear.innerHTML = ''; years.forEach(function (y) { vsYear.innerHTML += ''; }); }).catch(function () { // Fallback: generate years statically vsYear.innerHTML = ''; for (var y = 2026; y >= 1990; y--) { vsYear.innerHTML += ''; } }); } function vsYearChanged() { var yearId = vsYear.value; vsBrand.innerHTML = ''; vsModel.innerHTML = ''; vsEngine.innerHTML = ''; vsBrand.disabled = true; vsModel.disabled = true; vsEngine.disabled = true; vsClear.style.display = yearId ? '' : 'none'; if (!yearId) return; // Load brands (reuse existing endpoint) vsBrand.disabled = false; apiFetch(API + '/brands').then(function (data) { var brands = data.data || data; if (!brands) return; vsBrand.innerHTML = ''; brands.forEach(function (b) { vsBrand.innerHTML += ''; }); }); } function vsBrandChanged() { var brandId = vsBrand.value; vsModel.innerHTML = ''; vsEngine.innerHTML = ''; vsModel.disabled = true; vsEngine.disabled = true; if (!brandId) return; vsModel.disabled = false; apiFetch(API + '/models?brand_id=' + brandId).then(function (data) { var models = data.data || data; if (!models) return; vsModel.innerHTML = ''; models.forEach(function (m) { vsModel.innerHTML += ''; }); }); } function vsModelChanged() { var modelId = vsModel.value; var yearVal = vsYear.value; vsEngine.innerHTML = ''; vsEngine.disabled = true; if (!modelId || !yearVal) return; vsEngine.disabled = false; apiFetch(API + '/engines?model_id=' + modelId + '&year_id=' + yearVal).then(function (data) { var engines = data.data || data; if (!engines) return; vsEngine.innerHTML = ''; engines.forEach(function (e) { var label = e.name_engine + (e.trim_level ? ' (' + e.trim_level + ')' : ''); vsEngine.innerHTML += ''; }); // If only 1 engine, auto-select if (engines.length === 1) { vsEngine.value = engines[0].id_mye; vsEngineChanged(); } }); } function vsEngineChanged() { var myeId = vsEngine.value; if (!myeId) return; // Update state and load categories var yearText = vsYear.options[vsYear.selectedIndex].text; var brandText = vsBrand.options[vsBrand.selectedIndex].text; var modelText = vsModel.options[vsModel.selectedIndex].text; var engineText = vsEngine.options[vsEngine.selectedIndex].text; state.brand = { id: parseInt(vsBrand.value), name: brandText }; state.model = { id: parseInt(vsModel.value), name: modelText }; state.year = { id: parseInt(vsYear.value), value: yearText }; state.engine = { id_mye: parseInt(myeId), name: engineText }; state.level = 'categories'; loadCategories(); } function vsClearAll() { vsYear.value = ''; vsBrand.innerHTML = ''; vsModel.innerHTML = ''; vsEngine.innerHTML = ''; vsBrand.disabled = true; vsModel.disabled = true; vsEngine.disabled = true; vsClear.style.display = 'none'; state = { level: 'brands', brand: null, model: null, year: null, engine: null, category: null, group: null, page: 1, totalPages: 1 }; loadBrands(); } // ─── EXPOSE GLOBALS (for backward compat) ─── window.CatalogApp = { toggleCart: toggleCart, goToCheckout: goToCheckout, addToCart: addToCart, removeFromCart: removeFromCart, updateQty: updateQuantity, clearCart: clearCart, loadPage: function (p) { loadParts(p); }, vsYearChanged: vsYearChanged, vsBrandChanged: vsBrandChanged, vsModelChanged: vsModelChanged, vsEngineChanged: vsEngineChanged, vsClear: vsClearAll, }; // ─── INIT ─── renderCart(); vsLoadYears(); loadBrands(); })();