Fix 7 frontend bugs found during audit
- Fix breadcrumb group name fetch using existing /api/categories/<id>/groups
- Fix diagramModalLabel → diagramModalTitle DOM ID mismatch
- Replace bootstrap.Modal.getInstance() with classList.remove('active') for modal close
- Escape single quotes in brand/model names in breadcrumb onclick handlers
- Implement editAftermarket() form population in admin panel
- Handle VIN decoder response wrapper in landing page
- Fetch models count from API instead of hardcoded '13K+'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -849,9 +849,28 @@ async function openAftermarketModal(id = null) {
|
|||||||
openModal('aftermarketModal');
|
openModal('aftermarketModal');
|
||||||
}
|
}
|
||||||
|
|
||||||
function editAftermarket(id) {
|
async function editAftermarket(id) {
|
||||||
// TODO: Fetch and populate for edit
|
await openAftermarketModal(id);
|
||||||
openAftermarketModal(id);
|
try {
|
||||||
|
const res = await fetch(`/api/aftermarket?search=&per_page=200`);
|
||||||
|
const data = await res.json();
|
||||||
|
const items = data.data || data;
|
||||||
|
const item = items.find(a => a.id === id);
|
||||||
|
if (item) {
|
||||||
|
document.getElementById('aftermarketId').value = item.id;
|
||||||
|
document.getElementById('aftermarketPartNumber').value = item.part_number || '';
|
||||||
|
document.getElementById('aftermarketName').value = item.name || '';
|
||||||
|
document.getElementById('aftermarketNameEs').value = item.name_es || '';
|
||||||
|
document.getElementById('aftermarketQuality').value = item.quality_tier || 'standard';
|
||||||
|
document.getElementById('aftermarketPrice').value = item.price_usd || '';
|
||||||
|
document.getElementById('aftermarketWarranty').value = item.warranty_months || '';
|
||||||
|
if (item.oem_part_id) document.getElementById('aftermarketOemPart').value = item.oem_part_id;
|
||||||
|
if (item.manufacturer_id) document.getElementById('aftermarketManufacturer').value = item.manufacturer_id;
|
||||||
|
document.getElementById('aftermarketModalTitle').textContent = 'Editar Parte Aftermarket';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading aftermarket for edit:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveAftermarket() {
|
async function saveAftermarket() {
|
||||||
|
|||||||
@@ -1277,12 +1277,13 @@
|
|||||||
// Load stats
|
// Load stats
|
||||||
async function loadStats() {
|
async function loadStats() {
|
||||||
try {
|
try {
|
||||||
const [partsRes, brandsRes, categoriesRes, manufacturersRes, aftermarketRes] = await Promise.all([
|
const [partsRes, brandsRes, categoriesRes, manufacturersRes, aftermarketRes, modelsRes] = await Promise.all([
|
||||||
fetch(`${API_BASE}/api/parts?per_page=1`),
|
fetch(`${API_BASE}/api/parts?per_page=1`),
|
||||||
fetch(`${API_BASE}/api/brands`),
|
fetch(`${API_BASE}/api/brands`),
|
||||||
fetch(`${API_BASE}/api/categories`),
|
fetch(`${API_BASE}/api/categories`),
|
||||||
fetch(`${API_BASE}/api/manufacturers`),
|
fetch(`${API_BASE}/api/manufacturers`),
|
||||||
fetch(`${API_BASE}/api/aftermarket?per_page=1`)
|
fetch(`${API_BASE}/api/aftermarket?per_page=1`),
|
||||||
|
fetch(`${API_BASE}/api/models`)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const parts = await partsRes.json();
|
const parts = await partsRes.json();
|
||||||
@@ -1290,11 +1291,13 @@
|
|||||||
const categories = await categoriesRes.json();
|
const categories = await categoriesRes.json();
|
||||||
const manufacturers = await manufacturersRes.json();
|
const manufacturers = await manufacturersRes.json();
|
||||||
const aftermarket = await aftermarketRes.json();
|
const aftermarket = await aftermarketRes.json();
|
||||||
|
const models = await modelsRes.json();
|
||||||
|
|
||||||
document.getElementById('stat-parts').textContent = (parts.pagination?.total || parts.length || 0) + '+';
|
document.getElementById('stat-parts').textContent = (parts.pagination?.total || parts.length || 0) + '+';
|
||||||
document.getElementById('stat-brands').textContent = brands.length || 0;
|
document.getElementById('stat-brands').textContent = brands.length || 0;
|
||||||
document.getElementById('stat-categories').textContent = categories.length || 0;
|
document.getElementById('stat-categories').textContent = categories.length || 0;
|
||||||
document.getElementById('stat-models').textContent = '13K+';
|
const modelCount = models.length || 0;
|
||||||
|
document.getElementById('stat-models').textContent = modelCount >= 1000 ? Math.floor(modelCount / 1000) + 'K+' : modelCount;
|
||||||
document.getElementById('stat-manufacturers').textContent = manufacturers.length || 0;
|
document.getElementById('stat-manufacturers').textContent = manufacturers.length || 0;
|
||||||
document.getElementById('stat-aftermarket').textContent = (aftermarket.pagination?.total || aftermarket.length || 0) + '+';
|
document.getElementById('stat-aftermarket').textContent = (aftermarket.pagination?.total || aftermarket.length || 0) + '+';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -1444,13 +1447,15 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE}/api/vin/decode/${vin}`);
|
const res = await fetch(`${API_BASE}/api/vin/decode/${vin}`);
|
||||||
const data = await res.json();
|
const raw = await res.json();
|
||||||
|
const data = raw.vehicle || raw;
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
resultDiv.innerHTML = `<p style="color: #ef4444;">${data.error}</p>`;
|
resultDiv.innerHTML = `<p style="color: #ef4444;">${data.error}</p>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const engineInfo = data.engine_info || {};
|
||||||
resultDiv.innerHTML = `
|
resultDiv.innerHTML = `
|
||||||
<div style="background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; text-align: left; max-width: 500px; margin: 0 auto;">
|
<div style="background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; text-align: left; max-width: 500px; margin: 0 auto;">
|
||||||
<h3 style="color: var(--accent); margin-bottom: 1rem;">Vehículo Identificado</h3>
|
<h3 style="color: var(--accent); margin-bottom: 1rem;">Vehículo Identificado</h3>
|
||||||
@@ -1459,7 +1464,7 @@
|
|||||||
<p><strong>Año:</strong> ${data.year || 'N/A'}</p>
|
<p><strong>Año:</strong> ${data.year || 'N/A'}</p>
|
||||||
<p><strong>Tipo:</strong> ${data.body_class || 'N/A'}</p>
|
<p><strong>Tipo:</strong> ${data.body_class || 'N/A'}</p>
|
||||||
<p><strong>Tracción:</strong> ${data.drive_type || 'N/A'}</p>
|
<p><strong>Tracción:</strong> ${data.drive_type || 'N/A'}</p>
|
||||||
${data.engine_info ? `<p><strong>Motor:</strong> ${data.engine_info.displacement_l || ''}L ${data.engine_info.cylinders || ''} cil.</p>` : ''}
|
${engineInfo.raw || engineInfo.displacement_l ? `<p><strong>Motor:</strong> ${engineInfo.raw || (engineInfo.displacement_l + 'L ' + (engineInfo.cylinders || '') + ' cil.')}</p>` : ''}
|
||||||
<a href="index.html" class="btn btn-primary" style="margin-top: 1rem; display: inline-block;">Ver partes compatibles</a>
|
<a href="index.html" class="btn btn-primary" style="margin-top: 1rem; display: inline-block;">Ver partes compatibles</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -93,26 +93,26 @@ class VehicleDashboard {
|
|||||||
items.push({ label: this.selectedBrand, active: true });
|
items.push({ label: this.selectedBrand, active: true });
|
||||||
} else if (this.currentView === 'vehicles') {
|
} else if (this.currentView === 'vehicles') {
|
||||||
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand.replace(/'/g, "\\'")}')` });
|
||||||
items.push({ label: this.selectedModel, active: true });
|
items.push({ label: this.selectedModel, active: true });
|
||||||
} else if (this.currentView === 'categories') {
|
} else if (this.currentView === 'categories') {
|
||||||
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand.replace(/'/g, "\\'")}')` });
|
||||||
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
|
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand.replace(/'/g, "\\'")}', '${this.selectedModel.replace(/'/g, "\\'")}')` });
|
||||||
if (this.selectedYear) items.push({ label: this.selectedYear });
|
if (this.selectedYear) items.push({ label: this.selectedYear });
|
||||||
items.push({ label: 'Categorías', active: true });
|
items.push({ label: 'Categorías', active: true });
|
||||||
} else if (this.currentView === 'groups') {
|
} else if (this.currentView === 'groups') {
|
||||||
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand.replace(/'/g, "\\'")}')` });
|
||||||
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
|
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand.replace(/'/g, "\\'")}', '${this.selectedModel.replace(/'/g, "\\'")}')` });
|
||||||
if (this.selectedYear) items.push({ label: this.selectedYear });
|
if (this.selectedYear) items.push({ label: this.selectedYear });
|
||||||
items.push({ label: 'Categorías', action: `dashboard.goToCategories(${this.selectedVehicleId})` });
|
items.push({ label: 'Categorías', action: `dashboard.goToCategories(${this.selectedVehicleId})` });
|
||||||
items.push({ label: this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Grupos', active: true });
|
items.push({ label: this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Grupos', active: true });
|
||||||
} else if (this.currentView === 'parts') {
|
} else if (this.currentView === 'parts') {
|
||||||
const groupName = this.selectedGroup ? (this.selectedGroup.name_es || this.selectedGroup.name) : 'Grupo';
|
const groupName = this.selectedGroup ? (this.selectedGroup.name_es || this.selectedGroup.name) : 'Grupo';
|
||||||
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand.replace(/'/g, "\\'")}')` });
|
||||||
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
|
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand.replace(/'/g, "\\'")}', '${this.selectedModel.replace(/'/g, "\\'")}')` });
|
||||||
if (this.selectedYear) items.push({ label: this.selectedYear });
|
if (this.selectedYear) items.push({ label: this.selectedYear });
|
||||||
items.push({ label: 'Categorías', action: `dashboard.goToCategories(${this.selectedVehicleId})` });
|
items.push({ label: 'Categorías', action: `dashboard.goToCategories(${this.selectedVehicleId})` });
|
||||||
items.push({ label: this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Categoría', action: `dashboard.goToGroups(${this.selectedCategory ? this.selectedCategory.id : 0})` });
|
items.push({ label: this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Categoría', action: `dashboard.goToGroups(${this.selectedCategory ? this.selectedCategory.id : 0})` });
|
||||||
@@ -980,9 +980,10 @@ class VehicleDashboard {
|
|||||||
|
|
||||||
// FASE 5: Fetch group details for breadcrumb
|
// FASE 5: Fetch group details for breadcrumb
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/groups/${groupId}`);
|
const response = await fetch(`/api/categories/${this.selectedCategory ? this.selectedCategory.id : 0}/groups`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
this.selectedGroup = await response.json();
|
const groups = await response.json();
|
||||||
|
this.selectedGroup = groups.find(g => g.id === groupId) || { id: groupId, name: 'Grupo' };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching group details:', error);
|
console.error('Error fetching group details:', error);
|
||||||
@@ -1354,9 +1355,9 @@ class VehicleDashboard {
|
|||||||
// FASE 2: Show part detail from search results (closes search modal first)
|
// FASE 2: Show part detail from search results (closes search modal first)
|
||||||
showPartDetailFromSearch(partId) {
|
showPartDetailFromSearch(partId) {
|
||||||
// Close search results modal
|
// Close search results modal
|
||||||
const searchModal = bootstrap.Modal.getInstance(document.getElementById('searchResultsModal'));
|
const searchModalEl = document.getElementById('searchResultsModal');
|
||||||
if (searchModal) {
|
if (searchModalEl) {
|
||||||
searchModal.hide();
|
searchModalEl.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small delay to allow modal transition, then show part detail
|
// Small delay to allow modal transition, then show part detail
|
||||||
@@ -1453,7 +1454,7 @@ class VehicleDashboard {
|
|||||||
async showDiagram(diagramId) {
|
async showDiagram(diagramId) {
|
||||||
// FASE 5: Use focus management for modal
|
// FASE 5: Use focus management for modal
|
||||||
const contentContainer = document.getElementById('diagramModalContent');
|
const contentContainer = document.getElementById('diagramModalContent');
|
||||||
const modalTitle = document.getElementById('diagramModalLabel');
|
const modalTitle = document.getElementById('diagramModalTitle');
|
||||||
|
|
||||||
contentContainer.innerHTML = `
|
contentContainer.innerHTML = `
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-5">
|
||||||
@@ -1567,9 +1568,9 @@ class VehicleDashboard {
|
|||||||
onHotspotClick(hotspot) {
|
onHotspotClick(hotspot) {
|
||||||
if (hotspot.part_id) {
|
if (hotspot.part_id) {
|
||||||
// Close diagram modal first
|
// Close diagram modal first
|
||||||
const diagramModal = bootstrap.Modal.getInstance(document.getElementById('diagramModal'));
|
const diagramModalEl = document.getElementById('diagramModal');
|
||||||
if (diagramModal) {
|
if (diagramModalEl) {
|
||||||
diagramModal.hide();
|
diagramModalEl.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small delay to allow modal transition, then show part detail
|
// Small delay to allow modal transition, then show part detail
|
||||||
@@ -1790,9 +1791,9 @@ class VehicleDashboard {
|
|||||||
// FASE 4: View parts for a VIN
|
// FASE 4: View parts for a VIN
|
||||||
async viewVinParts(vin, myeId) {
|
async viewVinParts(vin, myeId) {
|
||||||
// Close VIN modal
|
// Close VIN modal
|
||||||
const vinModal = bootstrap.Modal.getInstance(document.getElementById('vinDecoderModal'));
|
const vinModalEl = document.getElementById('vinDecoderModal');
|
||||||
if (vinModal) {
|
if (vinModalEl) {
|
||||||
vinModal.hide();
|
vinModalEl.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// FASE 5: Use focus management for modal
|
// FASE 5: Use focus management for modal
|
||||||
|
|||||||
Reference in New Issue
Block a user