fix(brand-catalog): add JWT auth token to all API requests
brand-catalog.js was missing Authorization header on fetch calls, causing 401 Unauthorized errors. Now reads pos_token from localStorage and includes Bearer token in every request. Also handles 401 responses by redirecting to /pos/login. Bump JS cache bust to v=2.
This commit is contained in:
39
.kimi/plan.md
Normal file
39
.kimi/plan.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Plan: Catálogo por Marca de Vehículo
|
||||||
|
|
||||||
|
## Resumen
|
||||||
|
Reorganizar el catálogo para que la navegación principal sea:
|
||||||
|
**Marca de vehículo → Categoría/Sistema → Partes compatibles**
|
||||||
|
|
||||||
|
Ejemplo: Toyota → Frenos → [balatas Bosch, discos Brembo, pastillas NGK...]
|
||||||
|
|
||||||
|
## Opción recomendada: Materialized View
|
||||||
|
|
||||||
|
No tocamos la tabla masiva `vehicle_parts` (billones de rows). Creamos una materialized view que agregue por marca + categoría.
|
||||||
|
|
||||||
|
### Cambios DB (Master)
|
||||||
|
1. Crear `brand_catalog_parts` MV desde `vehicle_parts → MYE → models → brands`
|
||||||
|
2. Agregar índices: `(brand_id, category_id)`, `(brand_id, part_id)`
|
||||||
|
3. Crear función `refresh_brand_catalog()` para refrescar
|
||||||
|
|
||||||
|
### Cambios Backend
|
||||||
|
1. Nuevos endpoints:
|
||||||
|
- `GET /catalog/vehicle-brands` → lista marcas con conteo de partes
|
||||||
|
- `GET /catalog/brand-categories?brand_id=` → categorías disponibles para esa marca
|
||||||
|
- `GET /catalog/brand-parts?brand_id=&category_id=` → partes compatibles
|
||||||
|
2. Modificar `catalog_service.py` con filtros por marca
|
||||||
|
|
||||||
|
### Cambios Frontend
|
||||||
|
1. Nueva vista inicial: grid de marcas de vehículo (tarjetas con logo/contador)
|
||||||
|
2. Click en marca → lista de categorías/sistemas (frenos, motor, suspensión...)
|
||||||
|
3. Click en categoría → grid de partes compatibles con esa marca
|
||||||
|
4. Filtro opcional: modelo/año/motor para refinar resultados
|
||||||
|
|
||||||
|
### Datos
|
||||||
|
- `vehicle_parts` ya tiene todo. La MV solo agrega/distinct.
|
||||||
|
- Las marcas fabricantes (Bosch, NGK) se muestran como badges en cada parte.
|
||||||
|
|
||||||
|
## Tiempo estimado
|
||||||
|
- DB + Backend: 2-3 horas
|
||||||
|
- Frontend: 2-3 horas
|
||||||
|
- Testing: 1 hora
|
||||||
|
- Total: ~6 horas
|
||||||
@@ -9,9 +9,34 @@
|
|||||||
_limit: 50,
|
_limit: 50,
|
||||||
_total: 0,
|
_total: 0,
|
||||||
|
|
||||||
|
_getToken: function() {
|
||||||
|
return localStorage.getItem('pos_token');
|
||||||
|
},
|
||||||
|
|
||||||
|
_headers: function() {
|
||||||
|
var token = this._getToken();
|
||||||
|
return {
|
||||||
|
'Authorization': 'Bearer ' + (token || ''),
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_checkAuth: function(resp) {
|
||||||
|
if (resp.status === 401) {
|
||||||
|
localStorage.removeItem('pos_token');
|
||||||
|
window.location.href = '/pos/login';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
el: function(id) { return document.getElementById(id); },
|
el: function(id) { return document.getElementById(id); },
|
||||||
|
|
||||||
show: function() {
|
show: function() {
|
||||||
|
if (!this._getToken()) {
|
||||||
|
window.location.href = '/pos/login';
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.el('brandCatalogOverlay').style.display = 'block';
|
this.el('brandCatalogOverlay').style.display = 'block';
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
this.loadBrands();
|
this.loadBrands();
|
||||||
@@ -50,31 +75,36 @@
|
|||||||
this.loading(true);
|
this.loading(true);
|
||||||
this.state = 'brands';
|
this.state = 'brands';
|
||||||
this.setBreadcrumb('<strong>Marcas de vehiculo</strong>');
|
this.setBreadcrumb('<strong>Marcas de vehiculo</strong>');
|
||||||
fetch('/pos/api/catalog/vehicle-brands')
|
var self = this;
|
||||||
.then(r => r.json())
|
fetch('/pos/api/catalog/vehicle-brands', { headers: this._headers() })
|
||||||
.then(data => {
|
.then(function(r) {
|
||||||
this.loading(false);
|
if (!self._checkAuth(r)) return null;
|
||||||
this._allBrands = data.brands || [];
|
return r.json();
|
||||||
if (!this._allBrands.length) {
|
})
|
||||||
this.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-text-muted);">No se encontraron marcas.</p>');
|
.then(function(data) {
|
||||||
|
if (!data) return;
|
||||||
|
self.loading(false);
|
||||||
|
self._allBrands = data.brands || [];
|
||||||
|
if (!self._allBrands.length) {
|
||||||
|
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-text-muted);">No se encontraron marcas.</p>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.renderBrandList(this._allBrands);
|
self.renderBrandList(self._allBrands);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(function(err) {
|
||||||
this.loading(false);
|
self.loading(false);
|
||||||
this.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar marcas: ' + escapeHtml(err.message) + '</p>');
|
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar marcas: ' + escapeHtml(err.message) + '</p>');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderBrandList: function(brands) {
|
renderBrandList: function(brands) {
|
||||||
let html = '<div style="grid-column:1/-1;margin-bottom:var(--space-3);">' +
|
var html = '<div style="grid-column:1/-1;margin-bottom:var(--space-3);">' +
|
||||||
'<input type="text" id="brandSearchInput" placeholder="Buscar marca..." ' +
|
'<input type="text" id="brandSearchInput" placeholder="Buscar marca..." ' +
|
||||||
'style="width:100%;padding:10px 14px;border:1px solid var(--color-border);border-radius:var(--radius-md);' +
|
'style="width:100%;padding:10px 14px;border:1px solid var(--color-border);border-radius:var(--radius-md);' +
|
||||||
'font-size:var(--text-body);background:var(--color-surface);color:var(--color-text-primary);' +
|
'font-size:var(--text-body);background:var(--color-surface);color:var(--color-text-primary);' +
|
||||||
'outline:none;" oninput="BrandCatalog.filterBrands(this.value)">' +
|
'outline:none;" oninput="BrandCatalog.filterBrands(this.value)">' +
|
||||||
'</div>';
|
'</div>';
|
||||||
brands.forEach(b => {
|
brands.forEach(function(b) {
|
||||||
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectBrand(' + JSON.stringify(b.name) + ')">' +
|
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectBrand(' + JSON.stringify(b.name) + ')">' +
|
||||||
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(b.name) + '</div>' +
|
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(b.name) + '</div>' +
|
||||||
'<div style="font-size:var(--text-body-sm);color:var(--color-text-muted);">' + (b.part_count || 0) + ' refacciones</div>' +
|
'<div style="font-size:var(--text-body-sm);color:var(--color-text-muted);">' + (b.part_count || 0) + ' refacciones</div>' +
|
||||||
@@ -84,12 +114,12 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
filterBrands: function(query) {
|
filterBrands: function(query) {
|
||||||
const q = query.toLowerCase().trim();
|
var q = query.toLowerCase().trim();
|
||||||
if (!q) {
|
if (!q) {
|
||||||
this.renderBrandList(this._allBrands);
|
this.renderBrandList(this._allBrands);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const filtered = this._allBrands.filter(function(b) {
|
var filtered = this._allBrands.filter(function(b) {
|
||||||
return b.name.toLowerCase().indexOf(q) !== -1;
|
return b.name.toLowerCase().indexOf(q) !== -1;
|
||||||
});
|
});
|
||||||
this.renderBrandList(filtered);
|
this.renderBrandList(filtered);
|
||||||
@@ -107,12 +137,17 @@
|
|||||||
this.setBreadcrumb(
|
this.setBreadcrumb(
|
||||||
'<a href="javascript:void(0)" onclick="BrandCatalog.loadBrands()" style="color:var(--color-primary);text-decoration:none;">Marcas</a> › <strong>' + escapeHtml(brandName) + '</strong>'
|
'<a href="javascript:void(0)" onclick="BrandCatalog.loadBrands()" style="color:var(--color-primary);text-decoration:none;">Marcas</a> › <strong>' + escapeHtml(brandName) + '</strong>'
|
||||||
);
|
);
|
||||||
fetch('/pos/api/catalog/brand-categories?brand=' + encodeURIComponent(brandName))
|
var self = this;
|
||||||
.then(r => r.json())
|
fetch('/pos/api/catalog/brand-categories?brand=' + encodeURIComponent(brandName), { headers: this._headers() })
|
||||||
.then(data => {
|
.then(function(r) {
|
||||||
this.loading(false);
|
if (!self._checkAuth(r)) return null;
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (!data) return;
|
||||||
|
self.loading(false);
|
||||||
if (!data.categories || !data.categories.length) {
|
if (!data.categories || !data.categories.length) {
|
||||||
this.setContent(
|
self.setContent(
|
||||||
'<div style="grid-column:1/-1;text-align:center;padding:var(--space-8);">' +
|
'<div style="grid-column:1/-1;text-align:center;padding:var(--space-8);">' +
|
||||||
'<p style="color:var(--color-text-muted);font-size:var(--text-body-lg);">No se encontraron categorias para <strong>' + escapeHtml(brandName) + '</strong>.</p>' +
|
'<p style="color:var(--color-text-muted);font-size:var(--text-body-lg);">No se encontraron categorias para <strong>' + escapeHtml(brandName) + '</strong>.</p>' +
|
||||||
'<button class="btn btn--primary" style="margin-top:var(--space-3);" onclick="BrandCatalog.loadBrands()">Volver a marcas</button>' +
|
'<button class="btn btn--primary" style="margin-top:var(--space-3);" onclick="BrandCatalog.loadBrands()">Volver a marcas</button>' +
|
||||||
@@ -120,18 +155,18 @@
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let html = '';
|
var html = '';
|
||||||
data.categories.forEach(c => {
|
data.categories.forEach(function(c) {
|
||||||
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectCategory(' + c.id + ', ' + JSON.stringify(c.name) + ')">' +
|
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectCategory(' + c.id + ', ' + JSON.stringify(c.name) + ')">' +
|
||||||
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(c.name) + '</div>' +
|
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(c.name) + '</div>' +
|
||||||
'<div style="font-size:var(--text-body-sm);color:var(--color-text-muted);">' + c.part_count + ' refacciones</div>' +
|
'<div style="font-size:var(--text-body-sm);color:var(--color-text-muted);">' + c.part_count + ' refacciones</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
});
|
});
|
||||||
this.setContent(html);
|
self.setContent(html);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(function(err) {
|
||||||
this.loading(false);
|
self.loading(false);
|
||||||
this.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar categorias: ' + escapeHtml(err.message) + '</p>');
|
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar categorias: ' + escapeHtml(err.message) + '</p>');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -150,32 +185,37 @@
|
|||||||
'<a href="javascript:void(0)" onclick="BrandCatalog.selectBrand(' + JSON.stringify(brandName) + ')" style="color:var(--color-primary);text-decoration:none;">' + escapeHtml(brandName) + '</a> › ' +
|
'<a href="javascript:void(0)" onclick="BrandCatalog.selectBrand(' + JSON.stringify(brandName) + ')" style="color:var(--color-primary);text-decoration:none;">' + escapeHtml(brandName) + '</a> › ' +
|
||||||
'<strong>' + escapeHtml(this.currentCategory.name) + '</strong>'
|
'<strong>' + escapeHtml(this.currentCategory.name) + '</strong>'
|
||||||
);
|
);
|
||||||
let url = '/pos/api/catalog/brand-parts?brand=' + encodeURIComponent(brandName) + '&category_id=' + encodeURIComponent(categoryId) +
|
var url = '/pos/api/catalog/brand-parts?brand=' + encodeURIComponent(brandName) + '&category_id=' + encodeURIComponent(categoryId) +
|
||||||
'&limit=' + this._limit + '&offset=' + this._offset;
|
'&limit=' + this._limit + '&offset=' + this._offset;
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
url += '&search=' + encodeURIComponent(searchTerm);
|
url += '&search=' + encodeURIComponent(searchTerm);
|
||||||
}
|
}
|
||||||
fetch(url)
|
var self = this;
|
||||||
.then(r => r.json())
|
fetch(url, { headers: this._headers() })
|
||||||
.then(data => {
|
.then(function(r) {
|
||||||
this.loading(false);
|
if (!self._checkAuth(r)) return null;
|
||||||
this._lastItems = data.items || [];
|
return r.json();
|
||||||
this._total = data.total || 0;
|
})
|
||||||
this._offset = data.offset || 0;
|
.then(function(data) {
|
||||||
|
if (!data) return;
|
||||||
|
self.loading(false);
|
||||||
|
self._lastItems = data.items || [];
|
||||||
|
self._total = data.total || 0;
|
||||||
|
self._offset = data.offset || 0;
|
||||||
if (!data.items || !data.items.length) {
|
if (!data.items || !data.items.length) {
|
||||||
this.renderPartsList([], searchTerm);
|
self.renderPartsList([], searchTerm);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.renderPartsList(data.items, searchTerm);
|
self.renderPartsList(data.items, searchTerm);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(function(err) {
|
||||||
this.loading(false);
|
self.loading(false);
|
||||||
this.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar refacciones: ' + escapeHtml(err.message) + '</p>');
|
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar refacciones: ' + escapeHtml(err.message) + '</p>');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPartsList: function(items, searchTerm) {
|
renderPartsList: function(items, searchTerm) {
|
||||||
let html = '<div style="grid-column:1/-1;margin-bottom:var(--space-3);display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center;">' +
|
var html = '<div style="grid-column:1/-1;margin-bottom:var(--space-3);display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center;">' +
|
||||||
'<input type="text" id="partsSearchInput" placeholder="Buscar refaccion..." value="' + escapeHtml(searchTerm || '') + '" ' +
|
'<input type="text" id="partsSearchInput" placeholder="Buscar refaccion..." value="' + escapeHtml(searchTerm || '') + '" ' +
|
||||||
'style="flex:1;min-width:200px;padding:10px 14px;border:1px solid var(--color-border);border-radius:var(--radius-md);' +
|
'style="flex:1;min-width:200px;padding:10px 14px;border:1px solid var(--color-border);border-radius:var(--radius-md);' +
|
||||||
'font-size:var(--text-body);background:var(--color-surface);color:var(--color-text-primary);outline:none;" ' +
|
'font-size:var(--text-body);background:var(--color-surface);color:var(--color-text-primary);outline:none;" ' +
|
||||||
@@ -193,18 +233,17 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats line
|
var startIdx = this._offset + 1;
|
||||||
const startIdx = this._offset + 1;
|
var endIdx = this._offset + items.length;
|
||||||
const endIdx = this._offset + items.length;
|
|
||||||
html += '<div style="grid-column:1/-1;font-size:var(--text-body-sm);color:var(--color-text-muted);margin-bottom:var(--space-2);">' +
|
html += '<div style="grid-column:1/-1;font-size:var(--text-body-sm);color:var(--color-text-muted);margin-bottom:var(--space-2);">' +
|
||||||
'Mostrando ' + startIdx + '-' + endIdx + ' de ' + this._total + ' refacciones' +
|
'Mostrando ' + startIdx + '-' + endIdx + ' de ' + this._total + ' refacciones' +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
html += '<div style="grid-column:1/-1;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-3);">';
|
html += '<div style="grid-column:1/-1;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-3);">';
|
||||||
items.forEach(p => {
|
items.forEach(function(p) {
|
||||||
const price = p.local_price ? '$' + Number(p.local_price).toFixed(2) : 'Consultar precio';
|
var price = p.local_price ? '$' + Number(p.local_price).toFixed(2) : 'Consultar precio';
|
||||||
const img = '/pos/static/images/placeholder-part.png';
|
var img = '/pos/static/images/placeholder-part.png';
|
||||||
const stockBadge = p.local_stock > 0
|
var stockBadge = p.local_stock > 0
|
||||||
? '<span style="display:inline-block;background:var(--color-success);color:#fff;font-size:11px;padding:2px 8px;border-radius:var(--radius-sm);margin-left:6px;">' + p.local_stock + ' en stock</span>'
|
? '<span style="display:inline-block;background:var(--color-success);color:#fff;font-size:11px;padding:2px 8px;border-radius:var(--radius-sm);margin-left:6px;">' + p.local_stock + ' en stock</span>'
|
||||||
: '<span style="display:inline-block;background:var(--color-text-muted);color:#fff;font-size:11px;padding:2px 8px;border-radius:var(--radius-sm);margin-left:6px;">Sin stock local</span>';
|
: '<span style="display:inline-block;background:var(--color-text-muted);color:#fff;font-size:11px;padding:2px 8px;border-radius:var(--radius-sm);margin-left:6px;">Sin stock local</span>';
|
||||||
html += '<div class="catalog-category-card" style="padding:0;overflow:hidden;display:flex;flex-direction:column;">' +
|
html += '<div class="catalog-category-card" style="padding:0;overflow:hidden;display:flex;flex-direction:column;">' +
|
||||||
@@ -221,13 +260,14 @@
|
|||||||
});
|
});
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
// Pagination
|
var hasPrev = this._offset > 0;
|
||||||
const hasPrev = this._offset > 0;
|
var hasNext = (this._offset + this._limit) < this._total;
|
||||||
const hasNext = (this._offset + this._limit) < this._total;
|
var pageNum = Math.floor(this._offset / this._limit) + 1;
|
||||||
|
var totalPages = Math.ceil(this._total / this._limit) || 1;
|
||||||
html += '<div style="grid-column:1/-1;display:flex;justify-content:center;align-items:center;gap:var(--space-3);padding:var(--space-4) 0;">' +
|
html += '<div style="grid-column:1/-1;display:flex;justify-content:center;align-items:center;gap:var(--space-3);padding:var(--space-4) 0;">' +
|
||||||
'<button class="btn btn--secondary" ' + (hasPrev ? '' : 'disabled style="opacity:0.5;cursor:not-allowed;"') +
|
'<button class="btn btn--secondary" ' + (hasPrev ? '' : 'disabled style="opacity:0.5;cursor:not-allowed;"') +
|
||||||
' onclick="BrandCatalog.goToPage(' + (this._offset - this._limit) + ')">← Anterior</button>' +
|
' onclick="BrandCatalog.goToPage(' + (this._offset - this._limit) + ')">← Anterior</button>' +
|
||||||
'<span style="font-size:var(--text-body-sm);color:var(--color-text-muted);">Pagina ' + (Math.floor(this._offset / this._limit) + 1) + ' de ' + (Math.ceil(this._total / this._limit) || 1) + '</span>' +
|
'<span style="font-size:var(--text-body-sm);color:var(--color-text-muted);">Pagina ' + pageNum + ' de ' + totalPages + '</span>' +
|
||||||
'<button class="btn btn--secondary" ' + (hasNext ? '' : 'disabled style="opacity:0.5;cursor:not-allowed;"') +
|
'<button class="btn btn--secondary" ' + (hasNext ? '' : 'disabled style="opacity:0.5;cursor:not-allowed;"') +
|
||||||
' onclick="BrandCatalog.goToPage(' + (this._offset + this._limit) + ')">Siguiente →</button>' +
|
' onclick="BrandCatalog.goToPage(' + (this._offset + this._limit) + ')">Siguiente →</button>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
@@ -248,14 +288,14 @@
|
|||||||
goToPage: function(newOffset) {
|
goToPage: function(newOffset) {
|
||||||
if (newOffset < 0) return;
|
if (newOffset < 0) return;
|
||||||
this._offset = newOffset;
|
this._offset = newOffset;
|
||||||
const searchInput = document.getElementById('partsSearchInput');
|
var searchInput = document.getElementById('partsSearchInput');
|
||||||
const term = searchInput ? searchInput.value : '';
|
var term = searchInput ? searchInput.value : '';
|
||||||
this.loadParts(this.currentBrand, this.currentCategory.id, term);
|
this.loadParts(this.currentBrand, this.currentCategory.id, term);
|
||||||
},
|
},
|
||||||
|
|
||||||
addToCart: function(partId, event) {
|
addToCart: function(partId, event) {
|
||||||
if (event) event.stopPropagation();
|
if (event) event.stopPropagation();
|
||||||
const part = this._lastItems.find(function(p) { return p.id === partId; });
|
var part = this._lastItems.find(function(p) { return p.id === partId; });
|
||||||
if (!part) {
|
if (!part) {
|
||||||
alert('Error: no se encontro la refaccion');
|
alert('Error: no se encontro la refaccion');
|
||||||
return;
|
return;
|
||||||
@@ -273,8 +313,8 @@
|
|||||||
source: 'oem-brand',
|
source: 'oem-brand',
|
||||||
inventory_id: null
|
inventory_id: null
|
||||||
}, 1);
|
}, 1);
|
||||||
const btn = event.target;
|
var btn = event.target;
|
||||||
const oldText = btn.textContent;
|
var oldText = btn.textContent;
|
||||||
btn.textContent = 'Agregado!';
|
btn.textContent = 'Agregado!';
|
||||||
btn.style.background = 'var(--color-success)';
|
btn.style.background = 'var(--color-success)';
|
||||||
setTimeout(function() { btn.textContent = oldText; btn.style.background = ''; }, 1500);
|
setTimeout(function() { btn.textContent = oldText; btn.style.background = ''; }, 1500);
|
||||||
@@ -286,7 +326,7 @@
|
|||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
const div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,6 +294,6 @@
|
|||||||
<script src="/pos/static/js/onboarding.js" defer></script>
|
<script src="/pos/static/js/onboarding.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
<script src="/pos/static/js/brand-catalog.js?v=1" defer></script>
|
<script src="/pos/static/js/brand-catalog.js?v=2" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user