Corregeix imports duplicats a app.js
This commit is contained in:
@@ -1,10 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
API_URL,
|
API_URL,
|
||||||
AUTO_REFRESH_MS,
|
AUTO_REFRESH_MS,
|
||||||
BLOODBROS_LOGO_URL,
|
BLOODBROS_LOGO_URL,
|
||||||
WHATSAPP_PHONE,
|
CREATE_CHECKOUT_URL
|
||||||
CREATE_CHECKOUT_URL
|
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
fmtPrice,
|
||||||
|
parseDescription,
|
||||||
|
getStockState,
|
||||||
|
getSalesHook,
|
||||||
|
buildWhatsappLink,
|
||||||
|
getFilteredProducts
|
||||||
|
} from './catalog.js';
|
||||||
|
|
||||||
const els = {
|
const els = {
|
||||||
cards: document.getElementById('cards'),
|
cards: document.getElementById('cards'),
|
||||||
@@ -39,14 +47,6 @@ import {
|
|||||||
.replaceAll("'", ''');
|
.replaceAll("'", ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtPrice(product) {
|
|
||||||
if (product.europe_price_text) return product.europe_price_text;
|
|
||||||
const n = Number(product.europe_price_number);
|
|
||||||
if (Number.isFinite(n)) {
|
|
||||||
return new Intl.NumberFormat('ca-ES', { style: 'currency', currency: 'EUR' }).format(n);
|
|
||||||
}
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
function fmtTime(value) {
|
function fmtTime(value) {
|
||||||
if (!value) return '--:--';
|
if (!value) return '--:--';
|
||||||
@@ -55,130 +55,6 @@ import {
|
|||||||
return d.toLocaleTimeString('ca-ES', { hour: '2-digit', minute: '2-digit' });
|
return d.toLocaleTimeString('ca-ES', { hour: '2-digit', minute: '2-digit' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeText(v) {
|
|
||||||
return String(v ?? '').toLowerCase().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseDescription(description) {
|
|
||||||
const raw = String(description ?? '').trim();
|
|
||||||
if (!raw) return [];
|
|
||||||
const lines = raw
|
|
||||||
.split(/\n|\r|\||•|;/g)
|
|
||||||
.map(s => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStockState(stock) {
|
|
||||||
const n = Number(stock || 0);
|
|
||||||
if (n <= 1) {
|
|
||||||
return {
|
|
||||||
cls: 'stock-last',
|
|
||||||
label: '🔥 Última unitat'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (n <= 3) {
|
|
||||||
return {
|
|
||||||
cls: 'stock-low',
|
|
||||||
label: `⚠️ Queden poques unitats · ${n} disponibles`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
cls: 'stock-ok',
|
|
||||||
label: `Stock disponible · ${n} unitats`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSalesHook(product) {
|
|
||||||
if (product.top_vendes) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const stock = Number(product.stock || 0);
|
|
||||||
if (stock <= 1) {
|
|
||||||
return {
|
|
||||||
badge: 'Última oportunitat',
|
|
||||||
text: 'Aquest model està a punt d’esgotar-se.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (stock <= 3) {
|
|
||||||
return {
|
|
||||||
badge: 'Alta demanda',
|
|
||||||
text: 'Model amb poques unitats disponibles.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
badge: 'Selecció premium',
|
|
||||||
text: 'Una aposta segura per estil i rendiment.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildWhatsappLink(product) {
|
|
||||||
const text = [
|
|
||||||
'Hola Blood Bros Sports,',
|
|
||||||
'voldria encomanar aquest model:',
|
|
||||||
`${product.product_code || ''}`,
|
|
||||||
product.model ? `Model: ${product.model}` : '',
|
|
||||||
product.category ? `Família: ${product.category}` : '',
|
|
||||||
product.colors ? `Colors: ${product.colors}` : '',
|
|
||||||
Number(product.europe_price_number) ? `Preu: ${fmtPrice(product)}` : '',
|
|
||||||
`Stock visible: ${product.stock ?? ''}`
|
|
||||||
].filter(Boolean).join('\n');
|
|
||||||
|
|
||||||
if (WHATSAPP_PHONE) {
|
|
||||||
return `https://wa.me/${encodeURIComponent(WHATSAPP_PHONE)}?text=${encodeURIComponent(text)}`;
|
|
||||||
}
|
|
||||||
return `https://api.whatsapp.com/send?text=${encodeURIComponent(text)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilteredProducts() {
|
|
||||||
const q = normalizeText(els.searchInput.value);
|
|
||||||
const top = els.topFilter.value;
|
|
||||||
const sort = els.sortFilter.value;
|
|
||||||
const price = els.priceFilter.value;
|
|
||||||
|
|
||||||
let data = products.filter(p => Number(p.stock) >= 1);
|
|
||||||
|
|
||||||
if (q) {
|
|
||||||
data = data.filter(p => {
|
|
||||||
const hay = [p.product_code, p.model, p.category, p.colors, p.description]
|
|
||||||
.map(normalizeText).join(' ');
|
|
||||||
return hay.includes(q);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (top === 'top') data = data.filter(p => !!p.top_vendes);
|
|
||||||
if (top === 'normal') data = data.filter(p => !p.top_vendes);
|
|
||||||
|
|
||||||
if (price !== 'all') {
|
|
||||||
data = data.filter(p => {
|
|
||||||
const n = Number(p.europe_price_number);
|
|
||||||
if (!Number.isFinite(n)) return false;
|
|
||||||
if (price === '0-60') return n <= 60;
|
|
||||||
if (price === '60-80') return n > 60 && n <= 80;
|
|
||||||
if (price === '80+') return n > 80;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
data.sort((a, b) => {
|
|
||||||
const codeA = String(a.product_code || '');
|
|
||||||
const codeB = String(b.product_code || '');
|
|
||||||
const priceA = Number.isFinite(Number(a.europe_price_number)) ? Number(a.europe_price_number) : -Infinity;
|
|
||||||
const priceB = Number.isFinite(Number(b.europe_price_number)) ? Number(b.europe_price_number) : -Infinity;
|
|
||||||
const stockA = Number(a.stock || 0);
|
|
||||||
const stockB = Number(b.stock || 0);
|
|
||||||
|
|
||||||
switch (sort) {
|
|
||||||
case 'code-desc': return codeB.localeCompare(codeA, 'ca');
|
|
||||||
case 'price-asc': return priceA - priceB;
|
|
||||||
case 'price-desc': return priceB - priceA;
|
|
||||||
case 'stock-desc': return stockB - stockA;
|
|
||||||
default: return codeA.localeCompare(codeB, 'ca');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDescription(product) {
|
function renderDescription(product) {
|
||||||
const items = parseDescription(product.description);
|
const items = parseDescription(product.description);
|
||||||
@@ -199,7 +75,12 @@ import {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const data = getFilteredProducts();
|
const data = getFilteredProducts(products, {
|
||||||
|
search: els.searchInput.value,
|
||||||
|
top: els.topFilter.value,
|
||||||
|
sort: els.sortFilter.value,
|
||||||
|
price: els.priceFilter.value
|
||||||
|
});
|
||||||
|
|
||||||
els.cards.innerHTML = '';
|
els.cards.innerHTML = '';
|
||||||
els.errorBox.style.display = 'none';
|
els.errorBox.style.display = 'none';
|
||||||
|
|||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
import { WHATSAPP_PHONE } from './config.js';
|
||||||
|
|
||||||
|
export function fmtPrice(product) {
|
||||||
|
if (product.europe_price_text) return product.europe_price_text;
|
||||||
|
const n = Number(product.europe_price_number);
|
||||||
|
if (Number.isFinite(n)) {
|
||||||
|
return new Intl.NumberFormat('ca-ES', { style: 'currency', currency: 'EUR' }).format(n);
|
||||||
|
}
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeText(v) {
|
||||||
|
return String(v ?? '').toLowerCase().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseDescription(description) {
|
||||||
|
const raw = String(description ?? '').trim();
|
||||||
|
if (!raw) return [];
|
||||||
|
return raw
|
||||||
|
.split(/\n|\r|\||•|;/g)
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStockState(stock) {
|
||||||
|
const n = Number(stock || 0);
|
||||||
|
|
||||||
|
if (n <= 1) {
|
||||||
|
return {
|
||||||
|
cls: 'stock-last',
|
||||||
|
label: '🔥 Última unitat'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n <= 3) {
|
||||||
|
return {
|
||||||
|
cls: 'stock-low',
|
||||||
|
label: `⚠️ Queden poques unitats · ${n} disponibles`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cls: 'stock-ok',
|
||||||
|
label: `Stock disponible · ${n} unitats`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSalesHook(product) {
|
||||||
|
if (product.top_vendes) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stock = Number(product.stock || 0);
|
||||||
|
|
||||||
|
if (stock <= 1) {
|
||||||
|
return {
|
||||||
|
badge: 'Última oportunitat',
|
||||||
|
text: 'Aquest model està a punt d’esgotar-se.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stock <= 3) {
|
||||||
|
return {
|
||||||
|
badge: 'Alta demanda',
|
||||||
|
text: 'Model amb poques unitats disponibles.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
badge: 'Selecció premium',
|
||||||
|
text: 'Una aposta segura per estil i rendiment.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildWhatsappLink(product) {
|
||||||
|
const text = [
|
||||||
|
'Hola Blood Bros Sports,',
|
||||||
|
'voldria encomanar aquest model:',
|
||||||
|
`${product.product_code || ''}`,
|
||||||
|
product.model ? `Model: ${product.model}` : '',
|
||||||
|
product.category ? `Família: ${product.category}` : '',
|
||||||
|
product.colors ? `Colors: ${product.colors}` : '',
|
||||||
|
Number(product.europe_price_number) ? `Preu: ${fmtPrice(product)}` : '',
|
||||||
|
`Stock visible: ${product.stock ?? ''}`
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
if (WHATSAPP_PHONE) {
|
||||||
|
return `https://wa.me/${encodeURIComponent(WHATSAPP_PHONE)}?text=${encodeURIComponent(text)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `https://api.whatsapp.com/send?text=${encodeURIComponent(text)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFilteredProducts(products, filters) {
|
||||||
|
const q = normalizeText(filters.search);
|
||||||
|
const top = filters.top;
|
||||||
|
const sort = filters.sort;
|
||||||
|
const price = filters.price;
|
||||||
|
|
||||||
|
let data = products.filter(p => Number(p.stock) >= 1);
|
||||||
|
|
||||||
|
if (q) {
|
||||||
|
data = data.filter(p => {
|
||||||
|
const hay = [p.product_code, p.model, p.category, p.colors, p.description]
|
||||||
|
.map(normalizeText)
|
||||||
|
.join(' ');
|
||||||
|
return hay.includes(q);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top === 'top') data = data.filter(p => !!p.top_vendes);
|
||||||
|
if (top === 'normal') data = data.filter(p => !p.top_vendes);
|
||||||
|
|
||||||
|
if (price !== 'all') {
|
||||||
|
data = data.filter(p => {
|
||||||
|
const n = Number(p.europe_price_number);
|
||||||
|
if (!Number.isFinite(n)) return false;
|
||||||
|
if (price === '0-60') return n <= 60;
|
||||||
|
if (price === '60-80') return n > 60 && n <= 80;
|
||||||
|
if (price === '80+') return n > 80;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
data.sort((a, b) => {
|
||||||
|
const codeA = String(a.product_code || '');
|
||||||
|
const codeB = String(b.product_code || '');
|
||||||
|
const priceA = Number.isFinite(Number(a.europe_price_number)) ? Number(a.europe_price_number) : -Infinity;
|
||||||
|
const priceB = Number.isFinite(Number(b.europe_price_number)) ? Number(b.europe_price_number) : -Infinity;
|
||||||
|
const stockA = Number(a.stock || 0);
|
||||||
|
const stockB = Number(b.stock || 0);
|
||||||
|
|
||||||
|
switch (sort) {
|
||||||
|
case 'code-desc':
|
||||||
|
return codeB.localeCompare(codeA, 'ca');
|
||||||
|
case 'price-asc':
|
||||||
|
return priceA - priceB;
|
||||||
|
case 'price-desc':
|
||||||
|
return priceB - priceA;
|
||||||
|
case 'stock-desc':
|
||||||
|
return stockB - stockA;
|
||||||
|
default:
|
||||||
|
return codeA.localeCompare(codeB, 'ca');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user