import { api } from "../lib/api.js"; class ProductsCrud extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.items = []; this.selectedItems = []; // Array de productos seleccionados this.lastClickedIndex = -1; // Para Shift+Click this.loading = false; this.searchQuery = ""; this.stockFilter = false; this.shadowRoot.innerHTML = `
Productos
Total
En Stock
Cargando productos...
Detalle
Seleccioná un producto para ver detalles
`; } connectedCallback() { this.shadowRoot.getElementById("search").oninput = (e) => { this.searchQuery = e.target.value; clearTimeout(this._searchTimer); this._searchTimer = setTimeout(() => this.load(), 300); }; this.shadowRoot.getElementById("syncBtn").onclick = () => this.syncFromWoo(); // Stats click handlers this.shadowRoot.getElementById("statTotal").onclick = () => { this.stockFilter = false; this.renderList(); this.updateStatStyles(); }; this.shadowRoot.getElementById("statStock").onclick = () => { this.stockFilter = !this.stockFilter; this.renderList(); this.updateStatStyles(); }; this.load(); } updateStatStyles() { const statTotal = this.shadowRoot.getElementById("statTotal"); const statStock = this.shadowRoot.getElementById("statStock"); statTotal.classList.toggle("active", !this.stockFilter); statStock.classList.toggle("active", this.stockFilter); } async load() { this.loading = true; this.renderList(); try { const data = await api.products({ q: this.searchQuery, limit: 2000 }); this.items = data.items || []; this.loading = false; this.renderList(); this.renderStats(); } catch (e) { console.error("Error loading products:", e); this.items = []; this.loading = false; this.renderList(); } } async syncFromWoo() { const btn = this.shadowRoot.getElementById("syncBtn"); btn.disabled = true; btn.textContent = "Sincronizando..."; try { await api.syncProducts(); await this.load(); } catch (e) { console.error("Error syncing products:", e); alert("Error sincronizando: " + (e.message || e)); } finally { btn.disabled = false; btn.textContent = "Sync Woo"; } } renderStats() { const total = this.items.length; const inStock = this.items.filter(p => p.stock_status === "instock" || p.payload?.stock_status === "instock").length; this.shadowRoot.getElementById("totalCount").textContent = total; this.shadowRoot.getElementById("inStockCount").textContent = inStock; } renderList() { const list = this.shadowRoot.getElementById("list"); if (this.loading) { list.innerHTML = `
Cargando productos...
`; return; } // Filter items based on stock filter const filteredItems = this.stockFilter ? this.items.filter(p => p.stock_status === "instock" || p.payload?.stock_status === "instock") : this.items; if (!filteredItems.length) { list.innerHTML = `
No se encontraron productos
`; return; } list.innerHTML = ""; this._filteredItems = filteredItems; // Guardar referencia para Shift+Click for (let i = 0; i < filteredItems.length; i++) { const item = filteredItems[i]; const el = document.createElement("div"); const isSelected = this.selectedItems.some(s => s.woo_product_id === item.woo_product_id); const isSingleSelected = isSelected && this.selectedItems.length === 1; el.className = "item" + (isSelected ? " selected" : "") + (isSingleSelected ? " active" : ""); el.dataset.index = i; const price = item.price != null ? `$${Number(item.price).toLocaleString()}` : "—"; const sku = item.sku || "—"; const stock = item.stock_status || item.payload?.stock_status || "unknown"; const stockBadge = stock === "instock" ? `En stock` : `Sin stock`; // Mostrar unidad actual si está definida const unit = item.sell_unit || item.payload?._sell_unit_override; const unitBadge = unit ? `${unit === 'unit' ? 'Unidad' : 'Kg'}` : ''; el.innerHTML = `
${item.name || "Sin nombre"} ${stockBadge} ${unitBadge}
${price} · SKU: ${sku} · ID: ${item.woo_product_id}
`; el.onclick = (e) => this.handleItemClick(e, item, i); list.appendChild(el); } } handleItemClick(e, item, index) { if (e.shiftKey && this.lastClickedIndex >= 0) { // Shift+Click: seleccionar rango const start = Math.min(this.lastClickedIndex, index); const end = Math.max(this.lastClickedIndex, index); for (let i = start; i <= end; i++) { const rangeItem = this._filteredItems[i]; if (!this.selectedItems.some(s => s.woo_product_id === rangeItem.woo_product_id)) { this.selectedItems.push(rangeItem); } } } else if (e.ctrlKey || e.metaKey) { // Ctrl+Click: toggle individual const idx = this.selectedItems.findIndex(s => s.woo_product_id === item.woo_product_id); if (idx >= 0) { this.selectedItems.splice(idx, 1); } else { this.selectedItems.push(item); } } else { // Click normal: selección única this.selectedItems = [item]; } this.lastClickedIndex = index; this.renderList(); this.renderDetail(); // Scroll detail panel to top const detail = this.shadowRoot.getElementById("detail"); if (detail) detail.scrollTop = 0; } renderDetail() { const detail = this.shadowRoot.getElementById("detail"); if (!this.selectedItems.length) { detail.innerHTML = `
Seleccioná un producto para ver detalles
`; return; } // Si hay múltiples seleccionados, mostrar vista de edición masiva if (this.selectedItems.length > 1) { this.renderMultiDetail(); return; } const p = this.selectedItems[0]; // Categorías: pueden estar en p.categories (string JSON) o p.payload.categories (array) let categoriesArray = []; if (p.categories) { try { const cats = typeof p.categories === 'string' ? JSON.parse(p.categories) : p.categories; categoriesArray = Array.isArray(cats) ? cats.map(c => c.name || c) : [String(cats)]; } catch { categoriesArray = [String(p.categories)]; } } else if (p.payload?.categories) { categoriesArray = p.payload.categories.map(c => c.name || c); } const categoriesText = categoriesArray.join(", "); const attributes = (p.attributes_normalized || p.payload?.attributes || []).map(a => `${a.name}: ${a.options?.join(", ")}`).join("; ") || "—"; // Determinar unidad actual (de payload o inferida) const currentUnit = p.sell_unit || p.payload?._sell_unit_override || this.inferUnit(p); detail.innerHTML = `
${p.name || "—"}
${p.woo_product_id}
${p.sku || "—"}
${p.price != null ? `$${Number(p.price).toLocaleString()} ${p.currency || ""}` : "—"}
Define si este producto se vende por peso o por unidad
Categorías del producto, separadas por coma
${attributes}
${p.refreshed_at ? new Date(p.refreshed_at).toLocaleString() : "—"}
${JSON.stringify(p.payload || {}, null, 2)}
`; // Bind save button this.shadowRoot.getElementById("saveProduct").onclick = () => this.saveProduct(); } async saveProduct() { if (this.selectedItems.length !== 1) return; const p = this.selectedItems[0]; const btn = this.shadowRoot.getElementById("saveProduct"); const sellUnitSelect = this.shadowRoot.getElementById("sellUnit"); const categoriesInput = this.shadowRoot.getElementById("categoriesInput"); const sell_unit = sellUnitSelect.value; const categories = categoriesInput.value.split(",").map(s => s.trim()).filter(Boolean); btn.disabled = true; btn.textContent = "Guardando..."; try { await api.updateProduct(p.woo_product_id, { sell_unit, categories }); // Actualizar localmente p.sell_unit = sell_unit; p.categories = JSON.stringify(categories.map(name => ({ name }))); const idx = this.items.findIndex(i => i.woo_product_id === p.woo_product_id); if (idx >= 0) { this.items[idx].sell_unit = sell_unit; this.items[idx].categories = p.categories; } btn.textContent = "Guardado!"; this.renderList(); setTimeout(() => { btn.textContent = "Guardar cambios"; btn.disabled = false; }, 1500); } catch (e) { console.error("Error saving product:", e); alert("Error guardando: " + (e.message || e)); btn.textContent = "Guardar cambios"; btn.disabled = false; } } renderMultiDetail() { const detail = this.shadowRoot.getElementById("detail"); const count = this.selectedItems.length; const names = this.selectedItems.slice(0, 5).map(p => p.name).join(", "); const moreText = count > 5 ? ` y ${count - 5} más...` : ""; detail.innerHTML = `
${count} productos
${names}${moreText}
Se aplicará a todos los productos seleccionados
`; this.shadowRoot.getElementById("saveUnit").onclick = () => this.saveProductUnit(); this.shadowRoot.getElementById("clearSelection").onclick = () => { this.selectedItems = []; this.lastClickedIndex = -1; this.renderList(); this.renderDetail(); }; } inferUnit(p) { const name = String(p.name || "").toLowerCase(); const cats = (p.payload?.categories || []).map(c => String(c.name || c.slug || "").toLowerCase()); const allText = name + " " + cats.join(" "); // Productos que típicamente se venden por unidad if (/chimichurri|provoleta|queso|pan|salsa|aderezo|condimento|especias?|vino|vinos|bebida|cerveza|gaseosa|whisky|ron|gin|vodka|fernet/i.test(allText)) { return "unit"; } return "kg"; } async saveProductUnit() { if (!this.selectedItems.length) return; const select = this.shadowRoot.getElementById("sellUnit"); const btn = this.shadowRoot.getElementById("saveUnit"); const unit = select.value; const count = this.selectedItems.length; btn.disabled = true; btn.textContent = "Guardando..."; try { // IDs de todos los productos seleccionados const wooProductIds = this.selectedItems.map(p => p.woo_product_id); // Un solo request para todos await api.updateProductsUnit(wooProductIds, { sell_unit: unit }); // Actualizar localmente for (const p of this.selectedItems) { p.sell_unit = unit; const idx = this.items.findIndex(i => i.woo_product_id === p.woo_product_id); if (idx >= 0) this.items[idx].sell_unit = unit; } btn.textContent = `Guardado ${count}!`; this.renderList(); // Actualizar badges en lista setTimeout(() => { btn.textContent = count > 1 ? `Guardar para ${count}` : "Guardar"; btn.disabled = false; }, 1500); } catch (e) { console.error("Error saving product unit:", e); alert("Error guardando: " + (e.message || e)); btn.textContent = count > 1 ? `Guardar para ${count}` : "Guardar"; btn.disabled = false; } } } customElements.define("products-crud", ProductsCrud);