- Migrate from SQLite to PostgreSQL with normalized schema - Add 11 lookup tables (fuel_type, body_type, drivetrain, transmission, materials, position_part, manufacture_type, quality_tier, countries, reference_type, shapes) - Rewrite dashboard/server.py (76 routes) using SQLAlchemy text() queries - Rewrite console/db.py (27 methods) using SQLAlchemy ORM - Add models.py with 27 SQLAlchemy model definitions - Add config.py for centralized DB_URL configuration - Add migrate_to_postgres.py migration script - Add docs/METABASE_GUIDE.md with complete data entry guide - Rebrand from "AUTOPARTS DB" to "NEXUS AUTOPARTS" - Fill vehicle data gaps via NHTSA API + heuristics: engines (cylinders, power, torque), brands (country, founded_year), models (body_type, production years), MYE (drivetrain, transmission, trim) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1570 lines
63 KiB
HTML
1570 lines
63 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Admin Panel - Nexus Autoparts</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="/shared.css">
|
|
<style>
|
|
/* Admin-specific variable overrides */
|
|
:root {
|
|
--text-secondary: #8888aa;
|
|
--success: #00d68f;
|
|
--warning: #ffaa00;
|
|
}
|
|
|
|
/* Layout */
|
|
.container {
|
|
display: flex;
|
|
min-height: calc(100vh - 60px);
|
|
padding-top: 60px;
|
|
}
|
|
|
|
/* Sidebar */
|
|
.sidebar {
|
|
width: 250px;
|
|
background: var(--bg-secondary);
|
|
border-right: 1px solid var(--border);
|
|
padding: 1rem 0;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.sidebar-section {
|
|
padding: 0.5rem 1rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.sidebar-section h3 {
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 0.5rem;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.sidebar-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.75rem 1rem;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.sidebar-item:hover {
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.sidebar-item.active {
|
|
background: var(--bg-tertiary);
|
|
color: var(--accent);
|
|
border-left-color: var(--accent);
|
|
}
|
|
|
|
.sidebar-item .icon {
|
|
width: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Main Content */
|
|
.main-content {
|
|
flex: 1;
|
|
padding: 2rem;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.page-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 1.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.page-actions {
|
|
display: flex;
|
|
gap: 1rem;
|
|
}
|
|
|
|
/* Buttons */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.75rem 1.5rem;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--accent-hover);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--border);
|
|
}
|
|
|
|
.btn-success {
|
|
background: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: var(--danger);
|
|
color: white;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 0.5rem 0.75rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
/* Cards */
|
|
.card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
padding-bottom: 1rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Tables */
|
|
.table-wrapper {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th, td {
|
|
text-align: left;
|
|
padding: 1rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
th {
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
font-size: 0.85rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
tr:hover td {
|
|
background: rgba(255, 107, 53, 0.05);
|
|
}
|
|
|
|
.actions-cell {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* Forms - admin-specific */
|
|
select.form-input {
|
|
cursor: pointer;
|
|
}
|
|
|
|
textarea.form-input {
|
|
min-height: 100px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
/* Modal */
|
|
.modal-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
z-index: 1000;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
padding: 2rem;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.modal-overlay.active {
|
|
display: flex;
|
|
}
|
|
|
|
.modal {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 16px;
|
|
width: 100%;
|
|
max-width: 600px;
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 1.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.modal-close {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
padding: 0.5rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
.modal-close:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 1rem;
|
|
padding: 1.5rem;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
/* File Upload */
|
|
.file-upload {
|
|
border: 2px dashed var(--border);
|
|
border-radius: 12px;
|
|
padding: 2rem;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.file-upload:hover {
|
|
border-color: var(--accent);
|
|
background: rgba(255, 107, 53, 0.05);
|
|
}
|
|
|
|
.file-upload.dragover {
|
|
border-color: var(--accent);
|
|
background: rgba(255, 107, 53, 0.1);
|
|
}
|
|
|
|
.file-upload input {
|
|
display: none;
|
|
}
|
|
|
|
.file-upload-icon {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.file-upload-text {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.file-upload-text strong {
|
|
color: var(--accent);
|
|
}
|
|
|
|
/* Stats Grid */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.stat-label {
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
/* Badge */
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.badge-economy { background: #444; color: #ccc; }
|
|
.badge-standard { background: #2a5a2a; color: #7fff7f; }
|
|
.badge-premium { background: #5a5a2a; color: #ffff7f; }
|
|
.badge-oem { background: #2a2a5a; color: #7f7fff; }
|
|
|
|
/* Pagination */
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
.pagination button {
|
|
padding: 0.5rem 1rem;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pagination button:hover:not(:disabled) {
|
|
background: var(--accent);
|
|
}
|
|
|
|
.pagination button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.pagination button.active {
|
|
background: var(--accent);
|
|
}
|
|
|
|
/* Search */
|
|
.search-box {
|
|
position: relative;
|
|
max-width: 300px;
|
|
}
|
|
|
|
.search-box input {
|
|
width: 100%;
|
|
padding: 0.75rem 1rem 0.75rem 2.5rem;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.search-box::before {
|
|
content: "🔍";
|
|
position: absolute;
|
|
left: 0.75rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Tab navigation */
|
|
.tab-nav {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.tab-btn {
|
|
padding: 0.75rem 1.5rem;
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
border-radius: 8px 8px 0 0;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.tab-btn:hover {
|
|
color: var(--text-primary);
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.tab-btn.active {
|
|
color: var(--accent);
|
|
background: var(--bg-tertiary);
|
|
border-bottom: 2px solid var(--accent);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
/* Hidden sections */
|
|
.admin-section {
|
|
display: none;
|
|
}
|
|
|
|
.admin-section.active {
|
|
display: block;
|
|
}
|
|
|
|
/* Loading */
|
|
.loading {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid var(--border);
|
|
border-top-color: var(--accent);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
/* Image Upload */
|
|
.image-upload-container {
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.image-preview {
|
|
width: 120px;
|
|
height: 120px;
|
|
background: var(--bg-tertiary);
|
|
border: 2px dashed var(--border);
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.image-preview img {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.image-placeholder {
|
|
color: var(--text-secondary);
|
|
font-size: 0.85rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.image-upload-actions {
|
|
flex: 1;
|
|
}
|
|
|
|
.image-upload-actions input[type="file"] {
|
|
display: none;
|
|
}
|
|
|
|
.part-thumbnail {
|
|
width: 50px;
|
|
height: 50px;
|
|
object-fit: contain;
|
|
border-radius: 4px;
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
/* Bulk Parts Selector */
|
|
.bulk-parts-container {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.bulk-part-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 0.75rem;
|
|
border-bottom: 1px solid var(--border);
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.bulk-part-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.bulk-part-item:hover {
|
|
background: var(--bg-hover, rgba(255, 107, 53, 0.1));
|
|
}
|
|
|
|
.bulk-part-item.selected {
|
|
background: rgba(0, 214, 143, 0.15);
|
|
border-color: var(--success);
|
|
}
|
|
|
|
.bulk-part-item input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
accent-color: var(--accent);
|
|
}
|
|
|
|
.bulk-part-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.bulk-part-number {
|
|
font-family: monospace;
|
|
color: var(--accent);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.bulk-part-name {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.bulk-part-group {
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.bulk-part-qty {
|
|
width: 60px;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Shared Navigation -->
|
|
<div id="shared-nav"></div>
|
|
<script src="/nav.js"></script>
|
|
|
|
<div class="container">
|
|
<!-- Sidebar -->
|
|
<aside class="sidebar">
|
|
<div class="sidebar-section">
|
|
<h3>Dashboard</h3>
|
|
<div class="sidebar-item active" data-section="dashboard">
|
|
<span class="icon">📊</span>
|
|
<span>Resumen</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Catálogo</h3>
|
|
<div class="sidebar-item" data-section="categories">
|
|
<span class="icon">📁</span>
|
|
<span>Categorías</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="groups">
|
|
<span class="icon">📂</span>
|
|
<span>Grupos</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="parts">
|
|
<span class="icon">🔧</span>
|
|
<span>Partes OEM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Aftermarket</h3>
|
|
<div class="sidebar-item" data-section="manufacturers">
|
|
<span class="icon">🏭</span>
|
|
<span>Fabricantes</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="aftermarket">
|
|
<span class="icon">🔩</span>
|
|
<span>Partes Aftermarket</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="crossref">
|
|
<span class="icon">🔗</span>
|
|
<span>Cross-References</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Fitment</h3>
|
|
<div class="sidebar-item" data-section="fitment">
|
|
<span class="icon">🚗</span>
|
|
<span>Vehículo-Partes</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Diagramas</h3>
|
|
<div class="sidebar-item" data-section="diagrams">
|
|
<span class="icon">📐</span>
|
|
<span>Hotspot Editor</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3>Importar/Exportar</h3>
|
|
<div class="sidebar-item" data-section="import">
|
|
<span class="icon">📥</span>
|
|
<span>Importar CSV</span>
|
|
</div>
|
|
<div class="sidebar-item" data-section="export">
|
|
<span class="icon">📤</span>
|
|
<span>Exportar CSV</span>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main class="main-content">
|
|
<!-- Alert container -->
|
|
<div id="alertContainer"></div>
|
|
|
|
<!-- Dashboard Section -->
|
|
<section id="section-dashboard" class="admin-section active">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Panel de Administración</h1>
|
|
</div>
|
|
|
|
<div class="stats-grid" id="dashboardStats">
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statCategories">-</div>
|
|
<div class="stat-label">Categorías</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statGroups">-</div>
|
|
<div class="stat-label">Grupos</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statParts">-</div>
|
|
<div class="stat-label">Partes OEM</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statAftermarket">-</div>
|
|
<div class="stat-label">Partes Aftermarket</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statManufacturers">-</div>
|
|
<div class="stat-label">Fabricantes</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="statFitment">-</div>
|
|
<div class="stat-label">Fitments</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">Acciones Rápidas</h2>
|
|
</div>
|
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
<button class="btn btn-primary" onclick="showSection('import')">📥 Importar CSV</button>
|
|
<button class="btn btn-secondary" onclick="showSection('parts')">🔧 Agregar Parte</button>
|
|
<button class="btn btn-secondary" onclick="showSection('manufacturers')">🏭 Agregar Fabricante</button>
|
|
<button class="btn btn-secondary" onclick="showSection('export')">📤 Exportar Datos</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Categories Section -->
|
|
<section id="section-categories" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Categorías</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openCategoryModal()">+ Nueva Categoría</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nombre</th>
|
|
<th>Nombre (ES)</th>
|
|
<th>Slug</th>
|
|
<th>Icono</th>
|
|
<th>Orden</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="categoriesTable">
|
|
<tr><td colspan="7" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Groups Section -->
|
|
<section id="section-groups" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Grupos</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openGroupModal()">+ Nuevo Grupo</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-group" style="max-width: 300px; margin-bottom: 1rem;">
|
|
<label class="form-label">Filtrar por Categoría</label>
|
|
<select class="form-input" id="groupCategoryFilter" onchange="loadGroups()">
|
|
<option value="">Todas las categorías</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nombre</th>
|
|
<th>Nombre (ES)</th>
|
|
<th>Categoría</th>
|
|
<th>Orden</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="groupsTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Parts Section -->
|
|
<section id="section-parts" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Partes OEM</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openPartModal()">+ Nueva Parte</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-row" style="margin-bottom: 1rem;">
|
|
<div class="search-box">
|
|
<input type="text" id="partSearch" placeholder="Buscar por nombre o número..." onkeyup="debounceSearch(loadParts)">
|
|
</div>
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="partGroupFilter" onchange="loadParts()">
|
|
<option value="">Todos los grupos</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Img</th>
|
|
<th>Número OEM</th>
|
|
<th>Nombre</th>
|
|
<th>Grupo</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="partsTable">
|
|
<tr><td colspan="5" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="partsPagination"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Manufacturers Section -->
|
|
<section id="section-manufacturers" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Fabricantes</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openManufacturerModal()">+ Nuevo Fabricante</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nombre</th>
|
|
<th>Tipo</th>
|
|
<th>Calidad</th>
|
|
<th>País</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="manufacturersTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Aftermarket Section -->
|
|
<section id="section-aftermarket" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Partes Aftermarket</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openAftermarketModal()">+ Nueva Parte</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-row" style="margin-bottom: 1rem;">
|
|
<div class="search-box">
|
|
<input type="text" id="aftermarketSearch" placeholder="Buscar..." onkeyup="debounceSearch(loadAftermarket)">
|
|
</div>
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="aftermarketManufacturerFilter" onchange="loadAftermarket()">
|
|
<option value="">Todos los fabricantes</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Número</th>
|
|
<th>Nombre</th>
|
|
<th>OEM Ref</th>
|
|
<th>Fabricante</th>
|
|
<th>Calidad</th>
|
|
<th>Precio</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="aftermarketTable">
|
|
<tr><td colspan="8" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="aftermarketPagination"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Cross-References Section -->
|
|
<section id="section-crossref" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Cross-References</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openCrossRefModal()">+ Nueva Referencia</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Parte OEM</th>
|
|
<th>Número Referencia</th>
|
|
<th>Tipo</th>
|
|
<th>Fuente</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="crossrefTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="crossrefPagination"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Fitment Section -->
|
|
<section id="section-fitment" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Fitment (Vehículo-Partes)</h1>
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openFitmentModal()">+ Nuevo Fitment</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-row" style="margin-bottom: 1rem;">
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="fitmentBrandFilter" onchange="loadFitmentModels()">
|
|
<option value="">Selecciona marca...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
<select class="form-input" id="fitmentModelFilter" onchange="loadFitment()">
|
|
<option value="">Todos los modelos</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Vehículo</th>
|
|
<th>Parte</th>
|
|
<th>Cantidad</th>
|
|
<th>Posición</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="fitmentTable">
|
|
<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="fitmentPagination"></div>
|
|
</div>
|
|
|
|
<!-- Bulk Fitment Editor -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">⚡ Editor Masivo de Fitment</h2>
|
|
</div>
|
|
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
|
|
Selecciona un vehículo y agrega múltiples partes de una vez.
|
|
</p>
|
|
|
|
<div class="form-row" style="margin-bottom: 1.5rem;">
|
|
<div class="form-group">
|
|
<label class="form-label">1. Selecciona Marca</label>
|
|
<select class="form-input" id="bulkBrand" onchange="loadBulkModels()">
|
|
<option value="">Selecciona marca...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">2. Selecciona Modelo</label>
|
|
<select class="form-input" id="bulkModel" onchange="loadBulkYears()">
|
|
<option value="">Selecciona modelo...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">3. Selecciona Año</label>
|
|
<select class="form-input" id="bulkYear" onchange="loadBulkEngines()">
|
|
<option value="">Selecciona año...</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">4. Selecciona Motor</label>
|
|
<select class="form-input" id="bulkEngine" onchange="selectBulkVehicle()">
|
|
<option value="">Selecciona motor...</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="bulkVehicleSelected" style="display: none;">
|
|
<div class="alert alert-success" style="margin-bottom: 1rem;">
|
|
<strong>Vehículo seleccionado:</strong> <span id="bulkVehicleName"></span>
|
|
(MYE ID: <span id="bulkMYEId"></span>)
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">5. Selecciona Categoría de Partes</label>
|
|
<select class="form-input" id="bulkCategory" onchange="loadBulkParts()" style="max-width: 300px;">
|
|
<option value="">Todas las categorías</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">6. Selecciona Partes a Vincular</label>
|
|
<div class="bulk-parts-container" id="bulkPartsContainer">
|
|
<p style="color: var(--text-secondary);">Cargando partes disponibles...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top: 1rem;">
|
|
<button class="btn btn-success" onclick="saveBulkFitments()">
|
|
✓ Guardar Fitments Seleccionados
|
|
</button>
|
|
<span id="bulkSelectedCount" style="margin-left: 1rem; color: var(--text-secondary);">
|
|
0 partes seleccionadas
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Import Section -->
|
|
<section id="section-import" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Importar Datos (CSV)</h1>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">Selecciona tipo de datos</h2>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Tipo de importación</label>
|
|
<select class="form-input" id="importType" style="max-width: 300px;">
|
|
<option value="parts">Partes OEM</option>
|
|
<option value="aftermarket">Partes Aftermarket</option>
|
|
<option value="manufacturers">Fabricantes</option>
|
|
<option value="categories">Categorías</option>
|
|
<option value="groups">Grupos</option>
|
|
<option value="crossref">Cross-References</option>
|
|
<option value="fitment">Fitment (Vehículo-Partes)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="file-upload" id="dropZone">
|
|
<input type="file" id="csvFile" accept=".csv">
|
|
<div class="file-upload-icon">📄</div>
|
|
<div class="file-upload-text">
|
|
Arrastra un archivo CSV aquí o <strong>haz clic para seleccionar</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="importPreview" style="margin-top: 1.5rem; display: none;">
|
|
<h3 style="margin-bottom: 1rem;">Vista previa (<span id="previewCount">0</span> registros)</h3>
|
|
<div class="table-wrapper" style="max-height: 300px; overflow-y: auto;">
|
|
<table id="previewTable">
|
|
<thead id="previewHead"></thead>
|
|
<tbody id="previewBody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div style="margin-top: 1rem;">
|
|
<button class="btn btn-success" onclick="executeImport()">✓ Importar Datos</button>
|
|
<button class="btn btn-secondary" onclick="cancelImport()">✕ Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">Formato de CSV</h2>
|
|
</div>
|
|
<div id="csvFormatHelp">
|
|
<p style="color: var(--text-secondary); margin-bottom: 1rem;">Selecciona un tipo de importación para ver el formato requerido.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Export Section -->
|
|
<section id="section-export" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Exportar Datos (CSV)</h1>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
|
|
Descarga los datos en formato CSV para respaldo o importación en otros sistemas.
|
|
</p>
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
|
|
<button class="btn btn-secondary" onclick="exportData('categories')">📁 Categorías</button>
|
|
<button class="btn btn-secondary" onclick="exportData('groups')">📂 Grupos</button>
|
|
<button class="btn btn-secondary" onclick="exportData('parts')">🔧 Partes OEM</button>
|
|
<button class="btn btn-secondary" onclick="exportData('manufacturers')">🏭 Fabricantes</button>
|
|
<button class="btn btn-secondary" onclick="exportData('aftermarket')">🔩 Aftermarket</button>
|
|
<button class="btn btn-secondary" onclick="exportData('crossref')">🔗 Cross-References</button>
|
|
<button class="btn btn-secondary" onclick="exportData('fitment')">🚗 Fitment</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Diagrams / Hotspot Editor Section -->
|
|
<section id="section-diagrams" class="admin-section">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Editor de Hotspots</h1>
|
|
</div>
|
|
|
|
<div class="card" style="margin-bottom: 1.5rem;">
|
|
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
|
Busca un diagrama por código y haz clic en la imagen para agregar hotspots vinculados a partes.
|
|
</p>
|
|
<div style="display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;">
|
|
<input type="text" class="form-input" id="diagramSearchInput"
|
|
placeholder="Buscar diagrama (ej: F200, S341...)"
|
|
style="max-width: 300px;"
|
|
onkeypress="if(event.key==='Enter') searchDiagramsAdmin()">
|
|
<button class="btn btn-primary" onclick="searchDiagramsAdmin()">
|
|
Buscar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Diagram search results grid -->
|
|
<div id="diagramSearchResults" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 1.5rem;"></div>
|
|
|
|
<!-- Hotspot Editor Area (shown when a diagram is selected) -->
|
|
<div id="hotspotEditorArea" style="display: none;">
|
|
<div class="card">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
|
<h2 id="hotspotEditorTitle" style="margin: 0; font-size: 1.1rem;">Diagrama</h2>
|
|
<button class="btn btn-secondary" onclick="closeHotspotEditor()">Cerrar Editor</button>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 1.5rem; flex-wrap: wrap;">
|
|
<!-- Image with click-to-place -->
|
|
<div style="flex: 1; min-width: 400px; position: relative; background: #f0f0f0; border-radius: 8px; overflow: hidden; cursor: crosshair;" id="hotspotImageContainer">
|
|
<img id="hotspotEditorImg" src="" alt="Diagram"
|
|
style="width: 100%; display: block;"
|
|
onclick="onHotspotImageClick(event)">
|
|
<div id="hotspotMarkersContainer" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;"></div>
|
|
</div>
|
|
|
|
<!-- Hotspot form + list -->
|
|
<div style="width: 320px; flex-shrink: 0;">
|
|
<h3 style="font-size: 0.95rem; margin-bottom: 0.75rem;">Agregar / Editar Hotspot</h3>
|
|
<form id="hotspotForm" style="margin-bottom: 1rem;">
|
|
<input type="hidden" id="hsEditId">
|
|
<div class="form-group">
|
|
<label class="form-label">Posición (x%, y%)</label>
|
|
<input type="text" class="form-input" id="hsCoords" placeholder="Clic en imagen..." readonly>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label"># Callout</label>
|
|
<input type="number" class="form-input" id="hsCallout" min="1" value="1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM (buscar)</label>
|
|
<input type="text" class="form-input" id="hsPartSearch"
|
|
placeholder="Buscar parte por nombre o #..."
|
|
oninput="searchPartsForHotspot(this.value)">
|
|
<select class="form-input" id="hsPartSelect" size="4" style="margin-top: 0.25rem; display: none;">
|
|
</select>
|
|
<input type="hidden" id="hsPartId">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Etiqueta</label>
|
|
<input type="text" class="form-input" id="hsLabel" placeholder="Opcional">
|
|
</div>
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<button type="button" class="btn btn-primary" onclick="saveHotspot()">Guardar</button>
|
|
<button type="button" class="btn btn-secondary" onclick="clearHotspotForm()">Limpiar</button>
|
|
</div>
|
|
</form>
|
|
|
|
<h3 style="font-size: 0.95rem; margin-bottom: 0.5rem;">Hotspots Existentes</h3>
|
|
<div id="hotspotsList" style="max-height: 300px; overflow-y: auto;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Category Modal -->
|
|
<div class="modal-overlay" id="categoryModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="categoryModalTitle">Nueva Categoría</h2>
|
|
<button class="modal-close" onclick="closeModal('categoryModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="categoryForm">
|
|
<input type="hidden" id="categoryId">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="categoryName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="categoryNameEs">
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Slug</label>
|
|
<input type="text" class="form-input" id="categorySlug" placeholder="auto-generado">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Icono</label>
|
|
<input type="text" class="form-input" id="categoryIcon" placeholder="ej: engine">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Orden de visualización</label>
|
|
<input type="number" class="form-input" id="categoryOrder" value="0">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('categoryModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveCategory()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Group Modal -->
|
|
<div class="modal-overlay" id="groupModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="groupModalTitle">Nuevo Grupo</h2>
|
|
<button class="modal-close" onclick="closeModal('groupModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="groupForm">
|
|
<input type="hidden" id="groupId">
|
|
<div class="form-group">
|
|
<label class="form-label">Categoría</label>
|
|
<select class="form-input" id="groupCategory" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="groupName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="groupNameEs">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Orden de visualización</label>
|
|
<input type="number" class="form-input" id="groupOrder" value="0">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('groupModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveGroup()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Part Modal -->
|
|
<div class="modal-overlay" id="partModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="partModalTitle">Nueva Parte OEM</h2>
|
|
<button class="modal-close" onclick="closeModal('partModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="partForm">
|
|
<input type="hidden" id="partId">
|
|
<div class="form-group">
|
|
<label class="form-label">Número de Parte OEM</label>
|
|
<input type="text" class="form-input" id="partOemNumber" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="partName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="partNameEs">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Grupo</label>
|
|
<select class="form-input" id="partGroup" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Descripción (EN)</label>
|
|
<textarea class="form-input" id="partDescription"></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Descripción (ES)</label>
|
|
<textarea class="form-input" id="partDescriptionEs"></textarea>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Peso (kg)</label>
|
|
<input type="number" step="0.01" class="form-input" id="partWeight">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Material</label>
|
|
<input type="text" class="form-input" id="partMaterial">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Imagen de la parte</label>
|
|
<div class="image-upload-container">
|
|
<div class="image-preview" id="partImagePreview">
|
|
<span class="image-placeholder">📷 Sin imagen</span>
|
|
</div>
|
|
<div class="image-upload-actions">
|
|
<input type="file" id="partImageFile" accept="image/*" onchange="previewPartImage(this)">
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="document.getElementById('partImageFile').click()">
|
|
📤 Subir imagen
|
|
</button>
|
|
<input type="text" class="form-input" id="partImageUrl" placeholder="O pegar URL de imagen..." style="margin-top: 0.5rem;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('partModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="savePart()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Manufacturer Modal -->
|
|
<div class="modal-overlay" id="manufacturerModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="manufacturerModalTitle">Nuevo Fabricante</h2>
|
|
<button class="modal-close" onclick="closeModal('manufacturerModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="manufacturerForm">
|
|
<input type="hidden" id="manufacturerId">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre</label>
|
|
<input type="text" class="form-input" id="manufacturerName" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Tipo</label>
|
|
<select class="form-input" id="manufacturerType" required>
|
|
<option value="aftermarket">Aftermarket</option>
|
|
<option value="oem">OEM</option>
|
|
<option value="remanufactured">Remanufacturado</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Tier de Calidad</label>
|
|
<select class="form-input" id="manufacturerQuality">
|
|
<option value="economy">Economy</option>
|
|
<option value="standard">Standard</option>
|
|
<option value="premium">Premium</option>
|
|
<option value="oem">OEM</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">País</label>
|
|
<input type="text" class="form-input" id="manufacturerCountry">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Website</label>
|
|
<input type="url" class="form-input" id="manufacturerWebsite">
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('manufacturerModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveManufacturer()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Aftermarket Part Modal -->
|
|
<div class="modal-overlay" id="aftermarketModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="aftermarketModalTitle">Nueva Parte Aftermarket</h2>
|
|
<button class="modal-close" onclick="closeModal('aftermarketModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="aftermarketForm">
|
|
<input type="hidden" id="aftermarketId">
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM de referencia</label>
|
|
<select class="form-input" id="aftermarketOemPart" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Fabricante</label>
|
|
<select class="form-input" id="aftermarketManufacturer" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Número de Parte</label>
|
|
<input type="text" class="form-input" id="aftermarketPartNumber" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (EN)</label>
|
|
<input type="text" class="form-input" id="aftermarketName">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Nombre (ES)</label>
|
|
<input type="text" class="form-input" id="aftermarketNameEs">
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Calidad</label>
|
|
<select class="form-input" id="aftermarketQuality">
|
|
<option value="economy">Economy</option>
|
|
<option value="standard">Standard</option>
|
|
<option value="premium">Premium</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Precio (USD)</label>
|
|
<input type="number" step="0.01" class="form-input" id="aftermarketPrice">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Garantía (meses)</label>
|
|
<input type="number" class="form-input" id="aftermarketWarranty">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('aftermarketModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveAftermarket()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cross-Reference Modal -->
|
|
<div class="modal-overlay" id="crossrefModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="crossrefModalTitle">Nueva Cross-Reference</h2>
|
|
<button class="modal-close" onclick="closeModal('crossrefModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="crossrefForm">
|
|
<input type="hidden" id="crossrefId">
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM</label>
|
|
<select class="form-input" id="crossrefPart" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Número de Referencia</label>
|
|
<input type="text" class="form-input" id="crossrefNumber" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Tipo</label>
|
|
<select class="form-input" id="crossrefType" required>
|
|
<option value="oem_alternate">OEM Alternativo</option>
|
|
<option value="supersession">Supersesión</option>
|
|
<option value="interchange">Intercambio</option>
|
|
<option value="competitor">Competidor</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Fuente</label>
|
|
<input type="text" class="form-input" id="crossrefSource">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Notas</label>
|
|
<textarea class="form-input" id="crossrefNotes"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('crossrefModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveCrossRef()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fitment Modal -->
|
|
<div class="modal-overlay" id="fitmentModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="fitmentModalTitle">Nuevo Fitment</h2>
|
|
<button class="modal-close" onclick="closeModal('fitmentModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="fitmentForm">
|
|
<input type="hidden" id="fitmentId">
|
|
<div class="form-group">
|
|
<label class="form-label">Vehículo (Marca/Modelo/Año)</label>
|
|
<select class="form-input" id="fitmentVehicle" required></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Parte OEM</label>
|
|
<select class="form-input" id="fitmentPart" required></select>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Cantidad requerida</label>
|
|
<input type="number" class="form-input" id="fitmentQuantity" value="1" min="1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Posición</label>
|
|
<select class="form-input" id="fitmentPosition">
|
|
<option value="">N/A</option>
|
|
<option value="front">Frontal</option>
|
|
<option value="rear">Trasero</option>
|
|
<option value="left">Izquierdo</option>
|
|
<option value="right">Derecho</option>
|
|
<option value="front-left">Frontal Izq.</option>
|
|
<option value="front-right">Frontal Der.</option>
|
|
<option value="rear-left">Trasero Izq.</option>
|
|
<option value="rear-right">Trasero Der.</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Notas de fitment</label>
|
|
<textarea class="form-input" id="fitmentNotes"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('fitmentModal')">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="saveFitment()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="admin.js"></script>
|
|
</body>
|
|
</html>
|