/** * captura.js — Data entry logic for Nexus Autoparts * 3 sections: OEM Parts, Aftermarket/Interchange, Images */ (function () { 'use strict'; var API = ''; var currentMye = null; // selected vehicle MYE id var currentVehicle = null; // vehicle info object var vehicleParts = []; // existing parts for current vehicle var manufacturers = []; // cached manufacturer list var vehicleStatus = 'pending'; var vehiclePage = 1; // ================================================================ // Utility // ================================================================ function toast(msg, type) { var el = document.createElement('div'); el.className = 'toast ' + (type || 'success'); el.textContent = msg; document.body.appendChild(el); setTimeout(function () { el.remove(); }, 3000); } function api(path, opts) { opts = opts || {}; return fetch(API + path, opts).then(function (r) { if (!r.ok) return r.json().then(function (d) { throw new Error(d.error || 'Error'); }); return r.json(); }); } function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; } // ================================================================ // Tab Switching // ================================================================ document.querySelectorAll('.captura-tab').forEach(function (tab) { tab.addEventListener('click', function () { document.querySelectorAll('.captura-tab').forEach(function (t) { t.classList.remove('active'); }); document.querySelectorAll('.captura-section').forEach(function (s) { s.classList.remove('active'); }); tab.classList.add('active'); var target = tab.getAttribute('data-tab'); document.getElementById('section-' + target).classList.add('active'); if (target === 'aftermarket') loadPartsWithoutAftermarket(); if (target === 'images') loadPartsWithoutImage(); }); }); // ================================================================ // SECTION 1: OEM Parts // ================================================================ // --- Status tabs --- document.querySelectorAll('.status-tab').forEach(function (tab) { tab.addEventListener('click', function () { document.querySelectorAll('.status-tab').forEach(function (t) { t.classList.remove('active'); }); tab.classList.add('active'); vehicleStatus = tab.getAttribute('data-status'); vehiclePage = 1; loadVehicles(); }); }); // --- Brand filter --- function loadBrands() { api('/api/brands').then(function (brands) { var sel = document.getElementById('oem-brand-filter'); brands.forEach(function (b) { var opt = document.createElement('option'); opt.value = b; opt.textContent = b; sel.appendChild(opt); }); }); } document.getElementById('oem-brand-filter').addEventListener('change', function () { vehiclePage = 1; loadVehicles(); }); var modelTimer = null; document.getElementById('oem-model-filter').addEventListener('input', function () { clearTimeout(modelTimer); modelTimer = setTimeout(function () { vehiclePage = 1; loadVehicles(); }, 400); }); // --- Load vehicles --- function loadVehicles() { var brand = document.getElementById('oem-brand-filter').value; var model = document.getElementById('oem-model-filter').value; var list = document.getElementById('oem-vehicle-list'); list.innerHTML = '
'; var endpoint = vehicleStatus === 'pending' ? '/api/captura/vehicles/pending' : '/api/captura/vehicles/in-progress'; var params = '?page=' + vehiclePage + '&per_page=30'; if (brand) params += '&brand=' + encodeURIComponent(brand); if (model) params += '&model=' + encodeURIComponent(model); api(endpoint + params).then(function (res) { var data = res.data || []; if (data.length === 0) { list.innerHTML = '
📋
No hay vehiculos ' + (vehicleStatus === 'pending' ? 'pendientes' : 'en progreso') + '
'; document.getElementById('oem-vehicle-pagination').innerHTML = ''; return; } list.innerHTML = data.map(function (v) { return '
' + '
' + esc(v.brand) + '
' + '
' + esc(v.model) + '
' + '
' + v.year + ' · ' + esc(v.engine) + (v.trim_level ? ' · ' + esc(v.trim_level) : '') + '
' + (v.parts_count ? '
' + v.parts_count + ' partes registradas
' : '') + '
'; }).join(''); // Click handler for vehicle cards list.querySelectorAll('.vehicle-card').forEach(function (card) { card.addEventListener('click', function () { selectVehicle(parseInt(card.getAttribute('data-mye'))); }); }); // Pagination renderPagination('oem-vehicle-pagination', res.pagination, function (p) { vehiclePage = p; loadVehicles(); }); }); } function renderPagination(containerId, pag, onPage) { var c = document.getElementById(containerId); if (!pag || pag.total_pages <= 1) { c.innerHTML = ''; return; } c.innerHTML = '' + 'Pag ' + pag.page + ' de ' + pag.total_pages + ' (' + pag.total + ' total)' + ''; c.querySelectorAll('button').forEach(function (btn) { btn.addEventListener('click', function () { onPage(parseInt(btn.getAttribute('data-p'))); }); }); } // --- Select vehicle and show part entry --- function selectVehicle(myeId) { currentMye = myeId; document.getElementById('oem-vehicle-select').style.display = 'none'; document.getElementById('oem-part-entry').style.display = 'block'; // Mark as in_progress api('/api/captura/vehicles/' + myeId + '/status', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'in_progress' }) }); loadVehicleParts(myeId); } function loadVehicleParts(myeId) { api('/api/captura/vehicles/' + myeId + '/parts').then(function (res) { currentVehicle = res.vehicle; vehicleParts = res.parts || []; // Render vehicle header var hdr = document.getElementById('oem-vehicle-header'); hdr.innerHTML = '
' + '
Marca
' + esc(currentVehicle.brand) + '
' + '
Modelo
' + esc(currentVehicle.model) + '
' + '
Ano
' + currentVehicle.year + '
' + '
Motor
' + esc(currentVehicle.engine) + '
' + (currentVehicle.trim_level ? '
Trim
' + esc(currentVehicle.trim_level) + '
' : '') + '
' + '
' + '' + '' + '
'; document.getElementById('btn-back-vehicles').addEventListener('click', backToVehicles); document.getElementById('btn-complete-vehicle').addEventListener('click', completeVehicle); // Build groups by category renderGroups(res.groups, vehicleParts); updateProgress(); }); } function backToVehicles() { document.getElementById('oem-vehicle-select').style.display = 'block'; document.getElementById('oem-part-entry').style.display = 'none'; currentMye = null; loadVehicles(); } function completeVehicle() { if (vehicleParts.length === 0) { toast('Registra al menos una parte antes de marcar como terminado', 'error'); return; } api('/api/captura/vehicles/' + currentMye + '/status', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'completed' }) }).then(function () { toast('Vehiculo completado'); backToVehicles(); }); } // --- Render groups/categories --- function renderGroups(groups, parts) { var container = document.getElementById('oem-groups-container'); // Group by category var categories = {}; groups.forEach(function (g) { if (!categories[g.category]) { categories[g.category] = { id: g.id_part_category, groups: [] }; } categories[g.category].groups.push(g); }); var html = ''; Object.keys(categories).forEach(function (catName) { var cat = categories[catName]; var catParts = parts.filter(function (p) { return cat.groups.some(function (g) { return g.id_part_group === p.group_id; }); }); html += '
' + '
' + '

' + esc(catName) + ' (' + catParts.length + ')

' + '
' + '
'; cat.groups.forEach(function (g) { var groupParts = parts.filter(function (p) { return p.group_id === g.id_part_group; }); html += '
' + '
' + esc(g.group_name) + '
' + '
'; groupParts.forEach(function (p) { html += savedPartRow(p); }); html += '
' + '' + '
'; }); html += '
'; }); container.innerHTML = html; // Category toggle container.querySelectorAll('.category-header').forEach(function (ch) { ch.addEventListener('click', function () { var catId = ch.getAttribute('data-cat'); var body = container.querySelector('[data-cat-body="' + catId + '"]'); ch.classList.toggle('collapsed'); body.classList.toggle('collapsed'); }); }); // Add part buttons container.querySelectorAll('.btn-add-part').forEach(function (btn) { btn.addEventListener('click', function () { addPartRow(parseInt(btn.getAttribute('data-group-id')), btn); }); }); } function savedPartRow(p) { return '
' + '' + '' + '' + '' + '
'; } function addPartRow(groupId, addBtn) { var rowsContainer = document.querySelector('[data-group-parts="' + groupId + '"]'); var row = document.createElement('div'); row.className = 'part-row'; row.innerHTML = '' + '' + '' + '' + ''; rowsContainer.appendChild(row); // Focus OEM field row.querySelector('.pr-oem').focus(); // OEM blur: check if exists row.querySelector('.pr-oem').addEventListener('blur', function () { var oem = this.value.trim(); if (!oem) return; api('/api/captura/parts/check-oem?oem=' + encodeURIComponent(oem)).then(function (res) { if (res.exists) { row.querySelector('.pr-name').value = res.part.name_part || ''; row.querySelector('.pr-name').style.borderColor = 'var(--success)'; row.dataset.existingPartId = res.part.id_part; } }); }); // Save row.querySelector('.pr-save').addEventListener('click', function () { savePart(row, groupId); }); // Delete (unsaved) row.querySelector('.pr-delete').addEventListener('click', function () { row.remove(); }); } function savePart(row, groupId) { var oem = row.querySelector('.pr-oem').value.trim(); var name = row.querySelector('.pr-name').value.trim(); var qty = parseInt(row.querySelector('.pr-qty').value) || 1; if (!oem) { toast('Ingresa el numero OEM', 'error'); row.querySelector('.pr-oem').focus(); return; } var saveBtn = row.querySelector('.pr-save'); saveBtn.disabled = true; saveBtn.textContent = '...'; // Check if part already exists var existingId = row.dataset.existingPartId; function createFitment(partId) { api('/api/admin/fitment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model_year_engine_id: currentMye, part_id: partId, quantity_required: qty }) }).then(function (res) { // Replace row with saved version var newPart = { id_vehicle_part: res.id, part_id: partId, oem_part_number: oem, name_part: name, quantity_required: qty, group_id: groupId }; vehicleParts.push(newPart); row.outerHTML = savedPartRow(newPart); updateProgress(); toast('Parte guardada: ' + oem); // Re-attach delete handlers attachDeleteHandlers(); }).catch(function (err) { toast(err.message, 'error'); saveBtn.disabled = false; saveBtn.textContent = '\u2713'; }); } if (existingId) { createFitment(parseInt(existingId)); } else { // Create part first api('/api/admin/parts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ oem_part_number: oem, name: name || oem, group_id: groupId }) }).then(function (res) { createFitment(res.id); }).catch(function (err) { toast(err.message, 'error'); saveBtn.disabled = false; saveBtn.textContent = '\u2713'; }); } } function attachDeleteHandlers() { document.querySelectorAll('.part-row.saved .pr-delete').forEach(function (btn) { btn.onclick = function () { var row = btn.closest('.part-row'); var fitmentId = row.getAttribute('data-fitment-id'); if (!fitmentId) { row.remove(); return; } api('/api/admin/fitment/' + fitmentId, { method: 'DELETE' }).then(function () { vehicleParts = vehicleParts.filter(function (p) { return p.id_vehicle_part !== parseInt(fitmentId); }); row.remove(); updateProgress(); toast('Parte eliminada'); }).catch(function (err) { toast(err.message, 'error'); }); }; }); } function updateProgress() { var count = vehicleParts.length; var totalGroups = 63; var pct = Math.min(100, Math.round((count / totalGroups) * 100)); document.getElementById('oem-progress-fill').style.width = pct + '%'; document.getElementById('oem-progress-text').textContent = count + ' partes registradas'; // Update category counts document.querySelectorAll('.category-header h3').forEach(function (h3) { var catSection = h3.closest('.category-section'); var rows = catSection.querySelectorAll('.part-row.saved'); var catName = h3.textContent.replace(/\s*\(\d+\)$/, ''); h3.textContent = catName + ' (' + rows.length + ')'; }); } // ================================================================ // SECTION 2: Aftermarket / Interchange // ================================================================ var aftermarketPage = 1; function loadPartsWithoutAftermarket(page) { page = page || 1; aftermarketPage = page; var search = document.getElementById('aftermarket-search').value; var list = document.getElementById('aftermarket-list'); list.innerHTML = '
'; var params = '?page=' + page + '&per_page=20'; if (search) params += '&search=' + encodeURIComponent(search); api('/api/captura/parts/without-aftermarket' + params).then(function (res) { var data = res.data || []; if (data.length === 0) { list.innerHTML = '
No hay piezas sin intercambios
'; document.getElementById('aftermarket-pagination').innerHTML = ''; return; } list.innerHTML = data.map(function (p) { return '
' + '
' + '
' + esc(p.oem_part_number) + '' + ' ' + esc(p.name_part) + '
' + '' + esc(p.category) + ' › ' + esc(p.group_name) + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '
'; }).join(''); // Load existing aftermarket for each part data.forEach(function (p) { loadPartAftermarket(p.id_part); }); // Save handlers list.querySelectorAll('.af-save-btn').forEach(function (btn) { btn.addEventListener('click', function () { var card = btn.closest('.part-detail-card'); saveAftermarket(card); }); }); renderPagination('aftermarket-pagination', res.pagination, function (p) { loadPartsWithoutAftermarket(p); }); }); } function manufacturerOptions() { return manufacturers.map(function (m) { return ''; }).join(''); } function loadPartAftermarket(partId) { api('/api/captura/parts/' + partId + '/aftermarket').then(function (items) { var container = document.querySelector('[data-af-list="' + partId + '"]'); if (items.length === 0) { container.innerHTML = '

Sin intercambios registrados

'; return; } var html = '' + ''; items.forEach(function (a) { html += ''; }); html += '
Fabricante# ParteNombreCalidadPrecioGarantia
' + esc(a.manufacturer) + '' + esc(a.part_number) + '' + esc(a.name || '') + '' + esc(a.quality || '') + '' + (a.price_usd ? '$' + a.price_usd : '') + '' + (a.warranty_months || '') + '
'; container.innerHTML = html; }); } function saveAftermarket(card) { var partId = card.getAttribute('data-part-id'); var manufacturer = card.querySelector('.af-manufacturer').value; var partNumber = card.querySelector('.af-partnum').value.trim(); var name = card.querySelector('.af-name').value.trim(); var quality = card.querySelector('.af-quality').value; var price = card.querySelector('.af-price').value; var warranty = card.querySelector('.af-warranty').value; if (!partNumber) { toast('Ingresa el numero de parte aftermarket', 'error'); return; } api('/api/admin/aftermarket', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ oem_part_id: parseInt(partId), manufacturer_id: parseInt(manufacturer), part_number: partNumber, name: name, quality_tier: quality, price_usd: price ? parseFloat(price) : null, warranty_months: warranty ? parseInt(warranty) : null }) }).then(function () { toast('Intercambio guardado: ' + partNumber); // Clear form card.querySelector('.af-partnum').value = ''; card.querySelector('.af-name').value = ''; card.querySelector('.af-price').value = ''; card.querySelector('.af-warranty').value = ''; // Reload aftermarket list loadPartAftermarket(parseInt(partId)); }).catch(function (err) { toast(err.message, 'error'); }); } // ================================================================ // SECTION 3: Images // ================================================================ var imagePage = 1; function loadPartsWithoutImage(page) { page = page || 1; imagePage = page; var search = document.getElementById('image-search').value; var list = document.getElementById('image-list'); list.innerHTML = '
'; var params = '?page=' + page + '&per_page=20'; if (search) params += '&search=' + encodeURIComponent(search); api('/api/captura/parts/without-image' + params).then(function (res) { var data = res.data || []; if (data.length === 0) { list.innerHTML = '
📷
No hay piezas sin imagen
'; document.getElementById('image-pagination').innerHTML = ''; return; } list.innerHTML = data.map(function (p) { return '
' + '
Sin imagen
' + '
' + '
' + esc(p.oem_part_number) + '
' + '
' + esc(p.name_part) + ' · ' + esc(p.group_name) + '
' + '
' + '' + '' + '
'; }).join(''); // File input change → enable upload button and show preview list.querySelectorAll('.ic-file-input').forEach(function (input) { input.addEventListener('change', function () { var card = input.closest('.image-card'); var btn = card.querySelector('.ic-upload-btn'); var preview = card.querySelector('.ic-preview'); if (input.files && input.files[0]) { btn.disabled = false; // Show preview var reader = new FileReader(); reader.onload = function (e) { preview.innerHTML = ''; }; reader.readAsDataURL(input.files[0]); } }); }); // Upload button list.querySelectorAll('.ic-upload-btn').forEach(function (btn) { btn.addEventListener('click', function () { var card = btn.closest('.image-card'); uploadImage(card); }); }); renderPagination('image-pagination', res.pagination, function (p) { loadPartsWithoutImage(p); }); }); } function uploadImage(card) { var partId = card.getAttribute('data-part-id'); var fileInput = card.querySelector('.ic-file-input'); var btn = card.querySelector('.ic-upload-btn'); if (!fileInput.files || !fileInput.files[0]) return; btn.disabled = true; btn.textContent = 'Subiendo...'; var formData = new FormData(); formData.append('image', fileInput.files[0]); fetch(API + '/api/captura/parts/' + partId + '/image', { method: 'POST', body: formData }).then(function (r) { return r.json(); }) .then(function (res) { if (res.error) throw new Error(res.error); toast('Imagen subida correctamente'); // Remove card from list card.style.opacity = '0.3'; setTimeout(function () { card.remove(); }, 500); }).catch(function (err) { toast(err.message, 'error'); btn.disabled = false; btn.textContent = 'Subir'; }); } // ================================================================ // Init // ================================================================ function init() { loadBrands(); loadVehicles(); // Pre-load manufacturers for Section 2 api('/api/captura/manufacturers').then(function (data) { manufacturers = data; }); } // Make functions globally accessible for inline onclick handlers window.loadPartsWithoutAftermarket = loadPartsWithoutAftermarket; window.loadPartsWithoutImage = loadPartsWithoutImage; init(); })();