Add admin panel, enhanced search, Gonher import and expand API

- Add admin interface (admin.html, admin.js) for managing catalog data
- Add enhanced search module with advanced filtering capabilities
- Expand server.py with new API endpoints and admin functionality
- Add Gonher catalog import scripts (import_gonher_catalog.py, import_gonher_complete.py)
- Add demo data population script and sample CSV data
- Update customer landing page and dashboard with UI improvements
- Update database with enriched vehicle and parts data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 00:35:05 +00:00
parent 6fb2a52f86
commit e66b18f6ae
15 changed files with 7308 additions and 141 deletions

View File

@@ -503,16 +503,16 @@ class VehicleDashboard {
const myeRecords = await myeRes.json();
// Merge mye_id into vehicles based on matching fields
// Only keep vehicles that have a matching mye_id (i.e., have parts)
this.allVehicles = vehicles.map(v => {
const mye = myeRecords.find(m =>
m.brand === v.brand &&
m.model === v.model &&
m.year === v.year &&
m.engine === v.engine &&
(m.trim_level === v.trim_level || (!m.trim_level && !v.trim_level) || (m.trim_level === 'unknown' && v.trim_level === 'unknown'))
m.engine === v.engine
);
return { ...v, mye_id: mye ? mye.id : null };
});
}).filter(v => v.mye_id !== null); // Only show vehicles with parts
this.filteredVehicles = [...this.allVehicles];
// Poblar filtros
@@ -714,7 +714,79 @@ class VehicleDashboard {
document.getElementById('filtersBar').classList.remove('visible');
}
// Navigate to vehicle from search results
async navigateToVehicle(myeId, brand, model, year) {
// Set the state for breadcrumb navigation
this.selectedBrand = brand;
this.selectedModel = model;
this.selectedYear = year;
// Add vehicle to allVehicles if not already there (for breadcrumb)
if (!this.allVehicles.find(v => v.mye_id === myeId)) {
this.allVehicles.push({
mye_id: myeId,
brand: brand,
model: model,
year: year
});
}
// Navigate to categories
await this.goToCategories(myeId);
}
// Navigate to vehicle and directly to a specific category
async navigateToVehicleCategory(myeId, brand, model, year, categoryId) {
// Set the state for breadcrumb navigation
this.selectedBrand = brand;
this.selectedModel = model;
this.selectedYear = year;
// Add vehicle to allVehicles if not already there (for breadcrumb)
if (!this.allVehicles.find(v => v.mye_id === myeId)) {
this.allVehicles.push({
mye_id: myeId,
brand: brand,
model: model,
year: year
});
}
this.selectedVehicleId = myeId;
// Load categories if not available (needed for breadcrumb)
if (this.allCategories.length === 0) {
try {
const response = await fetch('/api/categories');
if (response.ok) {
this.allCategories = await response.json();
}
} catch (e) {
console.error('Error loading categories:', e);
}
}
// Navigate directly to the category's groups
await this.goToGroups(categoryId);
}
async goToCategories(myeId) {
// Validate myeId before proceeding
if (!myeId || myeId === 'null' || myeId === 'undefined') {
const container = document.getElementById('mainContent');
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h4>Vehículo sin partes disponibles</h4>
<p>Este vehículo no tiene partes registradas en el catálogo.</p>
<button class="btn btn-back mt-3" onclick="dashboard.goToModels('${this.selectedBrand}')">
<i class="fas fa-arrow-left"></i> Volver a modelos
</button>
</div>
`;
return;
}
this.currentView = 'categories';
this.selectedVehicleId = myeId;
this.selectedCategory = null;
@@ -737,8 +809,8 @@ class VehicleDashboard {
`;
try {
// Get all categories (since we don't have vehicle-specific parts yet, show all categories)
const response = await fetch('/api/categories');
// Get vehicle-specific categories (only categories with parts for this vehicle)
const response = await fetch(`/api/vehicles/${myeId}/categories`);
if (!response.ok) {
throw new Error('Error al cargar categorías');
@@ -824,7 +896,15 @@ class VehicleDashboard {
`;
try {
const response = await fetch(`/api/categories/${categoryId}/groups`);
// Use vehicle-specific endpoint when a vehicle is selected
let url;
if (this.selectedVehicleId) {
url = `/api/vehicles/${this.selectedVehicleId}/groups?category_id=${categoryId}`;
} else {
url = `/api/categories/${categoryId}/groups`;
}
const response = await fetch(url);
if (!response.ok) {
throw new Error('Error al cargar grupos');
@@ -921,15 +1001,23 @@ class VehicleDashboard {
`;
try {
const response = await fetch(`/api/parts?group_id=${groupId}`);
// Use vehicle-specific endpoint when a vehicle is selected
let url;
if (this.selectedVehicleId) {
url = `/api/vehicles/${this.selectedVehicleId}/parts?group_id=${groupId}`;
} else {
url = `/api/parts?group_id=${groupId}`;
}
const response = await fetch(url);
if (!response.ok) {
throw new Error('Error al cargar partes');
}
const partsData = await response.json();
// Handle paginated response
this.allParts = partsData.data || partsData;
// Handle both array response (vehicle parts) and paginated response
this.allParts = Array.isArray(partsData) ? partsData : (partsData.data || partsData);
this.displayParts();
} catch (error) {