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:
1479
dashboard/customer-landing.html
Normal file
1479
dashboard/customer-landing.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -24,9 +24,21 @@ class VehicleDashboard {
|
|||||||
await this.showBrands();
|
await this.showBrands();
|
||||||
this.bindFilterEvents();
|
this.bindFilterEvents();
|
||||||
this.bindKeyboardShortcuts(); // FASE 5: Keyboard shortcuts
|
this.bindKeyboardShortcuts(); // FASE 5: Keyboard shortcuts
|
||||||
|
this.bindSearchEvents(); // Bind search input events
|
||||||
this.initDarkMode(); // FASE 5: Dark mode
|
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() {
|
async loadStats() {
|
||||||
try {
|
try {
|
||||||
const [brandsRes, vehiclesRes, partsRes, categoriesRes] = await Promise.all([
|
const [brandsRes, vehiclesRes, partsRes, categoriesRes] = await Promise.all([
|
||||||
@@ -47,15 +59,18 @@ class VehicleDashboard {
|
|||||||
this.stats.models = uniqueModels.size;
|
this.stats.models = uniqueModels.size;
|
||||||
this.stats.vehicles = vehicles.length;
|
this.stats.vehicles = vehicles.length;
|
||||||
|
|
||||||
document.getElementById('totalBrands').textContent = this.stats.brands;
|
const brandsEl = document.getElementById('totalBrands');
|
||||||
document.getElementById('totalModels').textContent = this.stats.models;
|
const modelsEl = document.getElementById('totalModels');
|
||||||
document.getElementById('totalVehicles').textContent = this.stats.vehicles;
|
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) {
|
if (partsRes.ok) {
|
||||||
const parts = await partsRes.json();
|
const partsData = await partsRes.json();
|
||||||
this.stats.parts = parts.length || 0;
|
// Handle paginated response
|
||||||
document.getElementById('totalParts').textContent = this.stats.parts;
|
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) {
|
if (categoriesRes.ok) {
|
||||||
@@ -68,115 +83,55 @@ class VehicleDashboard {
|
|||||||
|
|
||||||
updateBreadcrumb() {
|
updateBreadcrumb() {
|
||||||
const breadcrumb = document.getElementById('breadcrumb');
|
const breadcrumb = document.getElementById('breadcrumb');
|
||||||
let html = '';
|
let items = [];
|
||||||
|
|
||||||
// FASE 5: Enhanced breadcrumb with full path: Brand -> Model -> Year -> Category -> Group
|
|
||||||
const yearDisplay = this.selectedYear ? `<span class="breadcrumb-year">${this.selectedYear}</span>` : '';
|
|
||||||
|
|
||||||
|
// Build breadcrumb items based on current view
|
||||||
if (this.currentView === 'brands') {
|
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') {
|
} else if (this.currentView === 'models') {
|
||||||
html = `
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
<li class="breadcrumb-item">
|
items.push({ label: this.selectedBrand, active: true });
|
||||||
<a href="#" onclick="dashboard.goToBrands(); return false;">
|
|
||||||
<i class="fas fa-home"></i> Marcas
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="breadcrumb-item active">${this.selectedBrand}</li>
|
|
||||||
`;
|
|
||||||
} else if (this.currentView === 'vehicles') {
|
} else if (this.currentView === 'vehicles') {
|
||||||
html = `
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
<li class="breadcrumb-item">
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
||||||
<a href="#" onclick="dashboard.goToBrands(); return false;">
|
items.push({ label: this.selectedModel, active: true });
|
||||||
<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>
|
|
||||||
`;
|
|
||||||
} else if (this.currentView === 'categories') {
|
} else if (this.currentView === 'categories') {
|
||||||
html = `
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
<li class="breadcrumb-item">
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
||||||
<a href="#" onclick="dashboard.goToBrands(); return false;">
|
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
|
||||||
<i class="fas fa-home"></i> Marcas
|
if (this.selectedYear) items.push({ label: this.selectedYear });
|
||||||
</a>
|
items.push({ label: 'Categorías', active: true });
|
||||||
</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>
|
|
||||||
`;
|
|
||||||
} else if (this.currentView === 'groups') {
|
} else if (this.currentView === 'groups') {
|
||||||
html = `
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
<li class="breadcrumb-item">
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
||||||
<a href="#" onclick="dashboard.goToBrands(); return false;">
|
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
|
||||||
<i class="fas fa-home"></i> Marcas
|
if (this.selectedYear) items.push({ label: this.selectedYear });
|
||||||
</a>
|
items.push({ label: 'Categorías', action: `dashboard.goToCategories(${this.selectedVehicleId})` });
|
||||||
</li>
|
items.push({ label: this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Grupos', active: true });
|
||||||
<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>
|
|
||||||
`;
|
|
||||||
} 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';
|
||||||
html = `
|
items.push({ label: '<i class="fas fa-home"></i> Marcas', action: 'dashboard.goToBrands()' });
|
||||||
<li class="breadcrumb-item">
|
items.push({ label: this.selectedBrand, action: `dashboard.goToModels('${this.selectedBrand}')` });
|
||||||
<a href="#" onclick="dashboard.goToBrands(); return false;">
|
items.push({ label: this.selectedModel, action: `dashboard.goToVehicles('${this.selectedBrand}', '${this.selectedModel}')` });
|
||||||
<i class="fas fa-home"></i> Marcas
|
if (this.selectedYear) items.push({ label: this.selectedYear });
|
||||||
</a>
|
items.push({ label: 'Categorías', action: `dashboard.goToCategories(${this.selectedVehicleId})` });
|
||||||
</li>
|
items.push({ label: this.selectedCategory ? (this.selectedCategory.name_es || this.selectedCategory.name) : 'Categoría', action: `dashboard.goToGroups(${this.selectedCategory ? this.selectedCategory.id : 0})` });
|
||||||
<li class="breadcrumb-item">
|
items.push({ label: groupName, active: true });
|
||||||
<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>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// FASE 5: Keyboard shortcuts
|
||||||
@@ -199,7 +154,7 @@ class VehicleDashboard {
|
|||||||
// Focus search input with "/" or Ctrl+K
|
// Focus search input with "/" or Ctrl+K
|
||||||
if (e.key === '/' || (e.ctrlKey && e.key === 'k')) {
|
if (e.key === '/' || (e.ctrlKey && e.key === 'k')) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const searchInput = document.getElementById('partNumberSearch');
|
const searchInput = document.getElementById('searchInput');
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.focus();
|
searchInput.focus();
|
||||||
}
|
}
|
||||||
@@ -222,20 +177,39 @@ class VehicleDashboard {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// FASE 5: Close all open modals
|
// FASE 5: Close all open modals (custom modal system)
|
||||||
closeAllModals() {
|
closeAllModals() {
|
||||||
const modals = ['partDetailModal', 'searchResultsModal', 'diagramModal', 'vinDecoderModal'];
|
const modals = ['partDetailModal', 'searchResultsModal', 'diagramModal', 'vinDecoderModal'];
|
||||||
modals.forEach(modalId => {
|
modals.forEach(modalId => {
|
||||||
const modalEl = document.getElementById(modalId);
|
this.closeModal(modalId);
|
||||||
if (modalEl) {
|
|
||||||
const modal = bootstrap.Modal.getInstance(modalEl);
|
|
||||||
if (modal) {
|
|
||||||
modal.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// FASE 5: Go back one level in navigation
|
||||||
goBack() {
|
goBack() {
|
||||||
switch (this.currentView) {
|
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() {
|
initDarkMode() {
|
||||||
const savedTheme = localStorage.getItem('autopartes-theme');
|
// Dark theme is the default and only 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FASE 5: Toggle dark mode
|
|
||||||
toggleDarkMode() {
|
toggleDarkMode() {
|
||||||
const currentTheme = document.documentElement.getAttribute('data-theme');
|
// No-op: single theme design
|
||||||
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);
|
updateDarkModeIcon() {
|
||||||
}
|
// No-op: no toggle button in new 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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FASE 5: Make cards keyboard accessible
|
// 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) {
|
openModalWithFocus(modalId) {
|
||||||
this.lastFocusedElement = document.activeElement;
|
this.openModal(modalId);
|
||||||
const modalEl = document.getElementById(modalId);
|
return { hide: () => this.closeModal(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async showBrands() {
|
async showBrands() {
|
||||||
@@ -733,14 +657,14 @@ class VehicleDashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showFilters() {
|
showFilters() {
|
||||||
document.getElementById('filtersBar').style.display = 'block';
|
document.getElementById('filtersBar').classList.add('visible');
|
||||||
// Reset filters
|
// Reset filters
|
||||||
document.getElementById('yearFilter').value = '';
|
document.getElementById('yearFilter').value = '';
|
||||||
document.getElementById('engineFilter').value = '';
|
document.getElementById('engineFilter').value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
hideFilters() {
|
hideFilters() {
|
||||||
document.getElementById('filtersBar').style.display = 'none';
|
document.getElementById('filtersBar').classList.remove('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
async goToCategories(myeId) {
|
async goToCategories(myeId) {
|
||||||
@@ -956,7 +880,9 @@ class VehicleDashboard {
|
|||||||
throw new Error('Error al cargar partes');
|
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();
|
this.displayParts();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1183,8 +1109,8 @@ class VehicleDashboard {
|
|||||||
|
|
||||||
// FASE 2/4: Search by part number or general search
|
// FASE 2/4: Search by part number or general search
|
||||||
async searchPartNumber() {
|
async searchPartNumber() {
|
||||||
const searchInput = document.getElementById('partNumberSearch');
|
const searchInputEl = document.getElementById('searchInput');
|
||||||
const searchTerm = searchInput.value.trim();
|
const searchTerm = searchInputEl.value.trim();
|
||||||
|
|
||||||
if (!searchTerm) {
|
if (!searchTerm) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
1856
dashboard/index.html
1856
dashboard/index.html
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user