Add landing page and redesign catalog with dark theme

Landing page (customer-landing.html):
- Modern dark theme with orange accent (#ff6b35)
- Dynamic stats, categories, products from API
- Integrated search modal with FTS
- VIN decoder section
- Responsive design with mobile support

Catalog redesign (index.html):
- Unified design matching landing page
- Custom modal system (removed Bootstrap dependency)
- Simplified breadcrumb navigation
- Updated card styles with hover effects
- Consistent color scheme and typography

JavaScript updates (dashboard.js):
- Custom modal open/close methods
- Updated element IDs for new HTML
- Paginated response handling
- Search input event binding

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-05 08:33:15 +00:00
parent d4d1c9b7ba
commit 834d7d2d96
3 changed files with 2658 additions and 1003 deletions

View File

@@ -24,9 +24,21 @@ class VehicleDashboard {
await this.showBrands();
this.bindFilterEvents();
this.bindKeyboardShortcuts(); // FASE 5: Keyboard shortcuts
this.bindSearchEvents(); // Bind search input events
this.initDarkMode(); // FASE 5: Dark mode
}
bindSearchEvents() {
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.searchPartNumber();
}
});
}
}
async loadStats() {
try {
const [brandsRes, vehiclesRes, partsRes, categoriesRes] = await Promise.all([
@@ -47,15 +59,18 @@ class VehicleDashboard {
this.stats.models = uniqueModels.size;
this.stats.vehicles = vehicles.length;
document.getElementById('totalBrands').textContent = this.stats.brands;
document.getElementById('totalModels').textContent = this.stats.models;
document.getElementById('totalVehicles').textContent = this.stats.vehicles;
const brandsEl = document.getElementById('totalBrands');
const modelsEl = document.getElementById('totalModels');
if (brandsEl) brandsEl.textContent = this.stats.brands;
if (modelsEl) modelsEl.textContent = this.stats.models > 1000 ? Math.floor(this.stats.models/1000) + 'K+' : this.stats.models;
}
if (partsRes.ok) {
const parts = await partsRes.json();
this.stats.parts = parts.length || 0;
document.getElementById('totalParts').textContent = this.stats.parts;
const partsData = await partsRes.json();
// Handle paginated response
this.stats.parts = partsData.pagination ? partsData.pagination.total : (partsData.data ? partsData.data.length : partsData.length || 0);
const partsEl = document.getElementById('totalParts');
if (partsEl) partsEl.textContent = this.stats.parts;
}
if (categoriesRes.ok) {
@@ -68,115 +83,55 @@ class VehicleDashboard {
updateBreadcrumb() {
const breadcrumb = document.getElementById('breadcrumb');
let html = '';
// FASE 5: Enhanced breadcrumb with full path: Brand -> Model -> Year -> Category -> Group
const yearDisplay = this.selectedYear ? `<span class="breadcrumb-year">${this.selectedYear}</span>` : '';
let items = [];
// Build breadcrumb items based on current view
if (this.currentView === 'brands') {
html = `<li class="breadcrumb-item active"><i class="fas fa-home"></i> Marcas</li>`;
items.push({ label: '<i class="fas fa-home"></i> Marcas', active: true });
} else if (this.currentView === 'models') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item active">${this.selectedBrand}</li>
`;
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
items.push({ label: this.selectedBrand, active: true });
} else if (this.currentView === 'vehicles') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToModels('${this.selectedBrand}'); return false;">
${this.selectedBrand}
</a>
</li>
<li class="breadcrumb-item active">${this.selectedModel}</li>
`;
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.selectedModel, active: true });
} else if (this.currentView === 'categories') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToModels('${this.selectedBrand}'); return false;">
${this.selectedBrand}
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}'); return false;">
${this.selectedModel}
</a>
</li>
${this.selectedYear ? `<li class="breadcrumb-item">${yearDisplay}</li>` : ''}
<li class="breadcrumb-item active">Categorias</li>
`;
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.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
if (this.selectedYear) items.push({ label: this.selectedYear });
items.push({ label: 'Categorías', active: true });
} else if (this.currentView === 'groups') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToModels('${this.selectedBrand}'); return false;">
${this.selectedBrand}
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}'); return false;">
${this.selectedModel}
</a>
</li>
${this.selectedYear ? `<li class="breadcrumb-item">${yearDisplay}</li>` : ''}
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToCategories(${this.selectedVehicleId}); return false;">
Categorias
</a>
</li>
<li class="breadcrumb-item active">${this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Grupos'}</li>
`;
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.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
if (this.selectedYear) items.push({ label: this.selectedYear });
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 });
} else if (this.currentView === 'parts') {
const groupName = this.selectedGroup ? (this.selectedGroup.name_es || this.selectedGroup.name) : 'Grupo';
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToModels('${this.selectedBrand}'); return false;">
${this.selectedBrand}
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}'); return false;">
${this.selectedModel}
</a>
</li>
${this.selectedYear ? `<li class="breadcrumb-item">${yearDisplay}</li>` : ''}
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToCategories(${this.selectedVehicleId}); return false;">
Categorias
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToGroups(${this.selectedCategory ? this.selectedCategory.id : 0}); return false;">
${this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Categoria'}
</a>
</li>
<li class="breadcrumb-item active">${groupName}</li>
`;
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.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
if (this.selectedYear) items.push({ label: this.selectedYear });
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: groupName, active: true });
}
breadcrumb.innerHTML = html;
// Generate HTML
breadcrumb.innerHTML = items.map((item, i) => {
const isLast = i === items.length - 1;
const separator = !isLast ? '<span class="breadcrumb-separator">/</span>' : '';
if (item.action) {
return `<span class="breadcrumb-item"><a href="#" onclick="${item.action}; return false;">${item.label}</a></span>${separator}`;
} else if (item.active) {
return `<span class="breadcrumb-item active">${item.label}</span>`;
} else {
return `<span class="breadcrumb-item">${item.label}</span>${separator}`;
}
}).join('');
}
// FASE 5: Keyboard shortcuts
@@ -199,7 +154,7 @@ class VehicleDashboard {
// Focus search input with "/" or Ctrl+K
if (e.key === '/' || (e.ctrlKey && e.key === 'k')) {
e.preventDefault();
const searchInput = document.getElementById('partNumberSearch');
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.focus();
}
@@ -222,20 +177,39 @@ class VehicleDashboard {
});
}
// FASE 5: Close all open modals
// FASE 5: Close all open modals (custom modal system)
closeAllModals() {
const modals = ['partDetailModal', 'searchResultsModal', 'diagramModal', 'vinDecoderModal'];
modals.forEach(modalId => {
const modalEl = document.getElementById(modalId);
if (modalEl) {
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) {
modal.hide();
}
}
this.closeModal(modalId);
});
}
// Custom modal open
openModal(modalId) {
this.lastFocusedElement = document.activeElement;
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add('active');
// Focus first focusable element
setTimeout(() => {
const focusable = modal.querySelector('input, button, [tabindex]:not([tabindex="-1"])');
if (focusable) focusable.focus();
}, 100);
}
}
// Custom modal close
closeModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.remove('active');
if (this.lastFocusedElement) {
this.lastFocusedElement.focus();
}
}
}
// FASE 5: Go back one level in navigation
goBack() {
switch (this.currentView) {
@@ -264,46 +238,17 @@ class VehicleDashboard {
}
}
// FASE 5: Initialize dark mode from localStorage
// Theme is now always dark, no toggle needed
initDarkMode() {
const savedTheme = localStorage.getItem('autopartes-theme');
if (savedTheme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
this.updateDarkModeIcon(true);
}
// Bind toggle button
const toggleBtn = document.getElementById('darkModeToggle');
if (toggleBtn) {
toggleBtn.addEventListener('click', () => this.toggleDarkMode());
}
// Dark theme is the default and only theme
}
// FASE 5: Toggle dark mode
toggleDarkMode() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const isDark = currentTheme === 'dark';
if (isDark) {
document.documentElement.removeAttribute('data-theme');
localStorage.setItem('autopartes-theme', 'light');
} else {
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem('autopartes-theme', 'dark');
}
this.updateDarkModeIcon(!isDark);
// No-op: single theme design
}
// FASE 5: Update dark mode toggle icon
updateDarkModeIcon(isDark) {
const toggleBtn = document.getElementById('darkModeToggle');
if (toggleBtn) {
const icon = toggleBtn.querySelector('i');
if (icon) {
icon.className = isDark ? 'fas fa-sun' : 'fas fa-moon';
}
}
updateDarkModeIcon() {
// No-op: no toggle button in new design
}
// FASE 5: Make cards keyboard accessible
@@ -331,31 +276,10 @@ class VehicleDashboard {
});
}
// FASE 5: Open modal with focus management
// FASE 5: Open modal with focus management (custom modal system)
openModalWithFocus(modalId) {
this.lastFocusedElement = document.activeElement;
const modalEl = document.getElementById(modalId);
if (!modalEl) return null;
const modal = new bootstrap.Modal(modalEl);
// Focus first focusable element when modal is shown
modalEl.addEventListener('shown.bs.modal', () => {
const focusable = modalEl.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (focusable) {
focusable.focus();
}
}, { once: true });
// Return focus when modal is hidden
modalEl.addEventListener('hidden.bs.modal', () => {
if (this.lastFocusedElement && document.body.contains(this.lastFocusedElement)) {
this.lastFocusedElement.focus();
}
}, { once: true });
modal.show();
return modal;
this.openModal(modalId);
return { hide: () => this.closeModal(modalId) };
}
async showBrands() {
@@ -733,14 +657,14 @@ class VehicleDashboard {
}
showFilters() {
document.getElementById('filtersBar').style.display = 'block';
document.getElementById('filtersBar').classList.add('visible');
// Reset filters
document.getElementById('yearFilter').value = '';
document.getElementById('engineFilter').value = '';
}
hideFilters() {
document.getElementById('filtersBar').style.display = 'none';
document.getElementById('filtersBar').classList.remove('visible');
}
async goToCategories(myeId) {
@@ -956,7 +880,9 @@ class VehicleDashboard {
throw new Error('Error al cargar partes');
}
this.allParts = await response.json();
const partsData = await response.json();
// Handle paginated response
this.allParts = partsData.data || partsData;
this.displayParts();
} catch (error) {
@@ -1183,8 +1109,8 @@ class VehicleDashboard {
// FASE 2/4: Search by part number or general search
async searchPartNumber() {
const searchInput = document.getElementById('partNumberSearch');
const searchTerm = searchInput.value.trim();
const searchInputEl = document.getElementById('searchInput');
const searchTerm = searchInputEl.value.trim();
if (!searchTerm) {
return;