708 lines
30 KiB
JavaScript
708 lines
30 KiB
JavaScript
/**
|
|
* 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 = '<div class="loading"><div class="spinner"></div></div>';
|
|
|
|
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 = '<div class="empty-state"><div class="es-icon">📋</div><div class="es-text">No hay vehiculos ' +
|
|
(vehicleStatus === 'pending' ? 'pendientes' : 'en progreso') + '</div></div>';
|
|
document.getElementById('oem-vehicle-pagination').innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = data.map(function (v) {
|
|
return '<div class="vehicle-card" data-mye="' + v.id_mye + '">' +
|
|
'<div class="vc-brand">' + esc(v.brand) + '</div>' +
|
|
'<div class="vc-model">' + esc(v.model) + '</div>' +
|
|
'<div class="vc-details">' + v.year + ' · ' + esc(v.engine) +
|
|
(v.trim_level ? ' · ' + esc(v.trim_level) : '') + '</div>' +
|
|
(v.parts_count ? '<div class="vc-parts-count">' + v.parts_count + ' partes registradas</div>' : '') +
|
|
'</div>';
|
|
}).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 = '<button ' + (pag.page <= 1 ? 'disabled' : '') + ' data-p="' + (pag.page - 1) + '">« Anterior</button>' +
|
|
'<span class="page-info">Pag ' + pag.page + ' de ' + pag.total_pages + ' (' + pag.total + ' total)</span>' +
|
|
'<button ' + (pag.page >= pag.total_pages ? 'disabled' : '') + ' data-p="' + (pag.page + 1) + '">Siguiente »</button>';
|
|
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 = '<div class="vh-info">' +
|
|
'<div><div class="vh-label">Marca</div><div class="vh-value vh-brand">' + esc(currentVehicle.brand) + '</div></div>' +
|
|
'<div><div class="vh-label">Modelo</div><div class="vh-value">' + esc(currentVehicle.model) + '</div></div>' +
|
|
'<div><div class="vh-label">Ano</div><div class="vh-value">' + currentVehicle.year + '</div></div>' +
|
|
'<div><div class="vh-label">Motor</div><div class="vh-value">' + esc(currentVehicle.engine) + '</div></div>' +
|
|
(currentVehicle.trim_level ? '<div><div class="vh-label">Trim</div><div class="vh-value">' + esc(currentVehicle.trim_level) + '</div></div>' : '') +
|
|
'</div>' +
|
|
'<div class="vh-actions">' +
|
|
'<button class="btn btn-secondary" id="btn-back-vehicles">◀ Volver</button>' +
|
|
'<button class="btn btn-primary" id="btn-complete-vehicle">Terminado ✓</button>' +
|
|
'</div>';
|
|
|
|
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 += '<div class="category-section">' +
|
|
'<div class="category-header" data-cat="' + cat.id + '">' +
|
|
'<h3>' + esc(catName) + ' (' + catParts.length + ')</h3>' +
|
|
'<span class="cat-toggle">▼</span></div>' +
|
|
'<div class="category-body" data-cat-body="' + cat.id + '">';
|
|
|
|
cat.groups.forEach(function (g) {
|
|
var groupParts = parts.filter(function (p) { return p.group_id === g.id_part_group; });
|
|
html += '<div class="group-section" data-group="' + g.id_part_group + '">' +
|
|
'<div class="group-name">' + esc(g.group_name) + '</div>' +
|
|
'<div class="part-rows" data-group-parts="' + g.id_part_group + '">';
|
|
|
|
groupParts.forEach(function (p) {
|
|
html += savedPartRow(p);
|
|
});
|
|
|
|
html += '</div>' +
|
|
'<button class="btn-add-part" data-group-id="' + g.id_part_group + '">+ Agregar pieza</button>' +
|
|
'</div>';
|
|
});
|
|
|
|
html += '</div></div>';
|
|
});
|
|
|
|
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 '<div class="part-row saved" data-fitment-id="' + p.id_vehicle_part + '">' +
|
|
'<input class="pr-oem" value="' + esc(p.oem_part_number) + '" readonly>' +
|
|
'<input class="pr-name" value="' + esc(p.name_part || '') + '" readonly>' +
|
|
'<input class="pr-qty" value="' + (p.quantity_required || 1) + '" readonly>' +
|
|
'<button class="pr-btn pr-delete" title="Eliminar">✕</button>' +
|
|
'</div>';
|
|
}
|
|
|
|
function addPartRow(groupId, addBtn) {
|
|
var rowsContainer = document.querySelector('[data-group-parts="' + groupId + '"]');
|
|
var row = document.createElement('div');
|
|
row.className = 'part-row';
|
|
row.innerHTML = '<input class="pr-oem" placeholder="# OEM" data-group="' + groupId + '">' +
|
|
'<input class="pr-name" placeholder="Nombre pieza">' +
|
|
'<input class="pr-qty" value="1" type="number" min="1">' +
|
|
'<button class="pr-btn pr-save" title="Guardar">✓</button>' +
|
|
'<button class="pr-btn pr-delete" title="Quitar">✕</button>';
|
|
|
|
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 = '<div class="loading"><div class="spinner"></div></div>';
|
|
|
|
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 = '<div class="empty-state"><div class="es-icon">✅</div><div class="es-text">No hay piezas sin intercambios</div></div>';
|
|
document.getElementById('aftermarket-pagination').innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = data.map(function (p) {
|
|
return '<div class="part-detail-card" data-part-id="' + p.id_part + '">' +
|
|
'<div class="pdc-header">' +
|
|
'<div><span class="pdc-oem">' + esc(p.oem_part_number) + '</span>' +
|
|
' <span class="pdc-name">' + esc(p.name_part) + '</span></div>' +
|
|
'<span class="pdc-group">' + esc(p.category) + ' › ' + esc(p.group_name) + '</span></div>' +
|
|
'<div class="aftermarket-existing" data-af-list="' + p.id_part + '"></div>' +
|
|
'<div class="aftermarket-form" data-af-form="' + p.id_part + '">' +
|
|
'<div class="af-field"><label>Fabricante</label>' +
|
|
'<select class="af-manufacturer">' + manufacturerOptions() + '</select></div>' +
|
|
'<div class="af-field"><label># Aftermarket</label>' +
|
|
'<input class="af-partnum" placeholder="Ej: MK1234"></div>' +
|
|
'<div class="af-field"><label>Nombre</label>' +
|
|
'<input class="af-name" placeholder="Nombre pieza"></div>' +
|
|
'<div class="af-field"><label>Calidad</label>' +
|
|
'<select class="af-quality">' +
|
|
'<option value="standard">Standard</option>' +
|
|
'<option value="economy">Economy</option>' +
|
|
'<option value="oem">OEM</option>' +
|
|
'<option value="premium">Premium</option></select></div>' +
|
|
'<div class="af-field"><label>Precio USD</label>' +
|
|
'<input class="af-price" type="number" step="0.01" placeholder="0.00" style="width:80px"></div>' +
|
|
'<div class="af-field"><label>Garantia (meses)</label>' +
|
|
'<input class="af-warranty" type="number" placeholder="12" style="width:70px"></div>' +
|
|
'<button class="btn btn-primary af-save-btn" style="padding:0.4rem 1rem">+ Agregar</button>' +
|
|
'</div></div>';
|
|
}).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 '<option value="' + m.id + '">' + esc(m.name) + '</option>';
|
|
}).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 = '<p style="font-size:0.8rem;color:var(--text-secondary);margin-bottom:0.5rem">Sin intercambios registrados</p>';
|
|
return;
|
|
}
|
|
var html = '<table class="aftermarket-table"><thead><tr>' +
|
|
'<th>Fabricante</th><th># Parte</th><th>Nombre</th><th>Calidad</th><th>Precio</th><th>Garantia</th></tr></thead><tbody>';
|
|
items.forEach(function (a) {
|
|
html += '<tr><td>' + esc(a.manufacturer) + '</td><td>' + esc(a.part_number) +
|
|
'</td><td>' + esc(a.name || '') + '</td><td>' + esc(a.quality || '') +
|
|
'</td><td>' + (a.price_usd ? '$' + a.price_usd : '') +
|
|
'</td><td>' + (a.warranty_months || '') + '</td></tr>';
|
|
});
|
|
html += '</tbody></table>';
|
|
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 = '<div class="loading"><div class="spinner"></div></div>';
|
|
|
|
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 = '<div class="empty-state"><div class="es-icon">📷</div><div class="es-text">No hay piezas sin imagen</div></div>';
|
|
document.getElementById('image-pagination').innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = data.map(function (p) {
|
|
return '<div class="image-card" data-part-id="' + p.id_part + '">' +
|
|
'<div class="ic-preview"><span>Sin imagen</span></div>' +
|
|
'<div class="ic-info">' +
|
|
'<div class="ic-oem">' + esc(p.oem_part_number) + '</div>' +
|
|
'<div class="ic-name">' + esc(p.name_part) + ' · ' + esc(p.group_name) + '</div>' +
|
|
'<div class="ic-upload">' +
|
|
'<input type="file" accept="image/jpeg,image/png,image/webp" class="ic-file-input">' +
|
|
'<button class="btn btn-primary ic-upload-btn" style="padding:0.3rem 0.8rem;font-size:0.8rem" disabled>Subir</button>' +
|
|
'</div></div></div>';
|
|
}).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 = '<img src="' + e.target.result + '">';
|
|
};
|
|
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();
|
|
})();
|