Files
apps-registry/workloads/ecommerce/assets/js/app.js
T
devops 27d1ad992d
Build and Push Frontend / build (push) Successful in 1m22s
feat(ecommerce): release Ari Shopping Frontend v2.1 real catalog
2026-07-19 16:21:44 -05:00

25 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
const state = { products: [], store: null, category: 'Todos', collection: '', query: '', sort: 'featured', cart: JSON.parse(localStorage.getItem('ari-shopping-cart-v2.2') || '{}') };
const $ = (sel, root = document) => root.querySelector(sel);
const $$ = (sel, root = document) => [...root.querySelectorAll(sel)];
const els = { productGrid: $('#productGrid'), template: $('#productTemplate'), summary: $('#catalogSummary'), chips: $('#filterChips'), empty: $('#emptyState'), reset: $('#resetFilters'), sort: $('#sortSelect'), searchToggle: $('#searchToggle'), searchClose: $('#searchClose'), searchRow: $('#searchRow'), searchInput: $('#searchInput'), cartCount: $('#cartCount'), cartOpen: $('#cartOpen'), cartClose: $('#cartClose'), cartDrawer: $('#cartDrawer'), cartItems: $('#cartItems'), clearCart: $('#clearCart'), checkoutBtn: $('#checkoutBtn'), backdrop: $('#backdrop'), dialog: $('#productDialog'), dialogClose: $('#dialogClose'), dialogContent: $('#dialogContent') };
const normalize = (v) => String(v || '').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
const persist = () => localStorage.setItem('ari-shopping-cart-v2.2', JSON.stringify(state.cart));
const productById = (id) => state.products.find((p) => p.id === id);
async function loadData() { const [storeRes, productsRes] = await Promise.all([fetch('data/store.json', { cache: 'no-store' }), fetch('data/products.json', { cache: 'no-store' })]); state.store = await storeRes.json(); const catalog = await productsRes.json(); state.products = catalog.products || []; buildChips(); render(); }
function buildChips() { const cats = ['Todos', ...new Set(state.products.map((p) => p.category))]; els.chips.innerHTML = cats.map((cat) => `<button class="chip ${state.category === cat && !state.collection ? 'active' : ''}" data-chip-cat="${cat}">${cat}</button>`).join(''); }
function filtered() { const query = normalize(state.query); return state.products.filter((p) => { if (state.category !== 'Todos' && p.category !== state.category) return false; if (state.collection && p.collection !== state.collection) return false; if (!query) return true; return normalize(`${p.name} ${p.category} ${p.collection} ${p.description}`).includes(query); }).sort((a, b) => { if (state.sort === 'name') return a.name.localeCompare(b.name, 'es'); if (state.sort === 'new') return Number(Boolean(b.new)) - Number(Boolean(a.new)); return Number(Boolean(b.featured)) - Number(Boolean(a.featured)) || a.name.localeCompare(b.name, 'es'); }); }
function renderProducts() { const list = filtered(); els.summary.textContent = `${list.length} productos disponibles para consultar.`; els.empty.hidden = list.length > 0; els.productGrid.innerHTML = ''; list.forEach((product) => { const frag = els.template.content.cloneNode(true); $('.product-card', frag).dataset.id = product.id; $('img', frag).src = product.image; $('img', frag).alt = product.name; $('.category', frag).textContent = product.category; $('.collection', frag).textContent = product.collection; $('.product-title', frag).textContent = product.name; $('.product-description', frag).textContent = product.description; $$('.product-open', frag).forEach((btn) => btn.addEventListener('click', () => openDialog(product.id))); $('.add-product', frag).addEventListener('click', () => addToCart(product.id)); els.productGrid.appendChild(frag); }); }
function renderCart() { const entries = Object.entries(state.cart).map(([id, qty]) => ({ product: productById(id), qty: Number(qty) })).filter((item) => item.product && item.qty > 0); els.cartCount.textContent = String(entries.reduce((sum, item) => sum + item.qty, 0)); if (!entries.length) { els.cartItems.innerHTML = '<div class="cart-empty"><strong>Tu pedido está vacío</strong><p>Agrega productos para consultarlos por WhatsApp.</p></div>'; return; } els.cartItems.innerHTML = entries.map(({ product, qty }) => `<article class="cart-line" data-id="${product.id}"><img src="${product.image}" alt="${product.name}"><div><strong>${product.name}</strong><small>${product.collection} · Consultar disponibilidad</small><div class="qty"><button type="button" data-minus></button><span>${qty}</span><button type="button" data-plus>+</button></div></div><button class="remove-btn" type="button" data-remove>Quitar</button></article>`).join(''); $$('[data-minus]', els.cartItems).forEach((btn) => btn.addEventListener('click', () => changeQty(btn.closest('[data-id]').dataset.id, -1))); $$('[data-plus]', els.cartItems).forEach((btn) => btn.addEventListener('click', () => changeQty(btn.closest('[data-id]').dataset.id, 1))); $$('[data-remove]', els.cartItems).forEach((btn) => btn.addEventListener('click', () => removeFromCart(btn.closest('[data-id]').dataset.id))); }
function render() { buildChips(); renderProducts(); renderCart(); }
function addToCart(id) { state.cart[id] = Number(state.cart[id] || 0) + 1; persist(); renderCart(); openCart(); }
function changeQty(id, delta) { const next = Number(state.cart[id] || 0) + delta; if (next <= 0) delete state.cart[id]; else state.cart[id] = next; persist(); renderCart(); }
function removeFromCart(id) { delete state.cart[id]; persist(); renderCart(); }
function openCart() { els.cartDrawer.classList.add('open'); els.cartDrawer.setAttribute('aria-hidden', 'false'); els.backdrop.hidden = false; }
function closeCart() { els.cartDrawer.classList.remove('open'); els.cartDrawer.setAttribute('aria-hidden', 'true'); els.backdrop.hidden = true; }
function openDialog(id) { const p = productById(id); if (!p) return; els.dialogContent.innerHTML = `<div class="dialog-layout"><img src="${p.image}" alt="${p.name}"><div class="dialog-copy"><span class="eyebrow">${p.category} · ${p.collection}</span><h3>${p.name}</h3><p>${p.description}</p><div class="dialog-details"><div><strong>Precio</strong><span>Consultar disponibilidad</span></div><div><strong>Entrega</strong><span>Coordinar con Ari Shopping</span></div>${p.dimensions ? `<div><strong>Medidas</strong><span>${p.dimensions}</span></div>` : ''}</div><div class="dialog-actions"><button id="dialogAdd" class="primary-btn" type="button">Agregar al pedido</button><a class="ghost-btn" target="_blank" rel="noopener" href="${state.store.whatsappUrl}">Consultar por WhatsApp</a></div></div></div>`; $('#dialogAdd', els.dialogContent).addEventListener('click', () => { addToCart(id); els.dialog.close(); }); els.dialog.showModal(); }
function buildWhatsAppMessage() { const entries = Object.entries(state.cart).map(([id, qty]) => ({ product: productById(id), qty: Number(qty) })).filter((item) => item.product && item.qty > 0); if (!entries.length) return 'Hola Ari Shopping 👋'; return ['Hola Ari Shopping 👋', 'Quiero consultar disponibilidad para los siguientes productos:', '', ...entries.map((item) => `• ${item.qty} x ${item.product.name}`), '', 'Nombre:', 'Ciudad:', 'Forma de entrega:'].join('\n'); }
function checkout() { const text = encodeURIComponent(buildWhatsAppMessage()); window.open(`${state.store.whatsappUrl}?text=${text}`, '_blank', 'noopener'); }
function bind() { document.addEventListener('click', (event) => { const nav = event.target.closest('[data-nav]'); if (nav) { state.category = nav.dataset.nav; state.collection = ''; render(); } const col = event.target.closest('[data-col]'); if (col) { state.category = 'Todos'; state.collection = col.dataset.col; render(); } const cat = event.target.closest('[data-cat]'); if (cat) { state.category = cat.dataset.cat; state.collection = ''; render(); } const chip = event.target.closest('[data-chip-cat]'); if (chip) { state.category = chip.dataset.chipCat; state.collection = ''; render(); } }); els.sort.addEventListener('change', () => { state.sort = els.sort.value; renderProducts(); }); els.searchToggle.addEventListener('click', () => { els.searchRow.hidden = false; els.searchInput.focus(); }); els.searchClose.addEventListener('click', () => { els.searchRow.hidden = true; state.query = ''; els.searchInput.value = ''; renderProducts(); }); els.searchInput.addEventListener('input', () => { state.query = els.searchInput.value; renderProducts(); }); els.cartOpen.addEventListener('click', openCart); els.cartClose.addEventListener('click', closeCart); els.backdrop.addEventListener('click', closeCart); els.clearCart.addEventListener('click', () => { state.cart = {}; persist(); renderCart(); }); els.checkoutBtn.addEventListener('click', checkout); els.reset.addEventListener('click', () => { state.category = 'Todos'; state.collection = ''; state.query = ''; render(); }); els.dialogClose.addEventListener('click', () => els.dialog.close()); }
bind(); loadData().catch((err) => { console.error(err); els.summary.textContent = 'No se pudo cargar el catálogo.'; els.empty.hidden = false; });
})();