From 9282bd548092bce312cf25db28af1eec568d1145 Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Tue, 16 Sep 2025 12:07:31 -0600 Subject: [PATCH] feat: Replace product cards with search bar interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace category cards with modern search bar for services/products - Implement real-time search with debouncing (200ms delay) - Add autocomplete functionality for better UX - Maintain anticipo functionality with dedicated form - Add visual feedback notifications instead of alerts - Improve responsive design and accessibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app.js | 238 +++++++++++++++++++++++++++++++++++++++++++++++++++-- index.html | 71 ++++++---------- styles.css | 159 +++++++++++++++++++++++++++++++++++ 3 files changed, 416 insertions(+), 52 deletions(-) diff --git a/app.js b/app.js index 28fadf0..9931c79 100644 --- a/app.js +++ b/app.js @@ -2360,9 +2360,9 @@ function initializeModernSalesInterface() { header.addEventListener('click', toggleCategory); }); - // Load products by categories - loadProductsByCategories(); - + // Initialize search functionality + initializeProductSearch(); + // Update cart display updateCartDisplay(); } @@ -2396,6 +2396,224 @@ function collapseAllCategories() { }); } +// Initialize product search functionality +async function initializeProductSearch() { + try { + // Load all products + const response = await fetch('/api/products'); + if (!response.ok) throw new Error('Failed to load products'); + + const allProducts = await response.json(); + products = allProducts; + console.log('Products loaded for search:', products.length); + + // Setup search event listeners + setupSearchEventListeners(); + + } catch (error) { + console.error('Error loading products for search:', error); + } +} + +function setupSearchEventListeners() { + const searchInput = document.getElementById('service-search-input'); + const searchResults = document.getElementById('search-results'); + const anticipoSection = document.getElementById('anticipo-section'); + + if (!searchInput || !searchResults || !anticipoSection) return; + + let searchTimeout; + + searchInput.addEventListener('input', function() { + clearTimeout(searchTimeout); + const query = this.value.trim().toLowerCase(); + + if (query.length === 0) { + searchResults.style.display = 'none'; + anticipoSection.style.display = 'none'; + return; + } + + // Always hide anticipo section when typing, it will show via search results or click + anticipoSection.style.display = 'none'; + + // Debounce search + searchTimeout = setTimeout(() => { + performProductSearch(query); + }, 200); + }); + + // Hide results when clicking outside + document.addEventListener('click', function(e) { + if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { + searchResults.style.display = 'none'; + } + }); + + // Focus search input for easy access + searchInput.addEventListener('focus', function() { + if (this.value.trim() && !this.value.toLowerCase().includes('anticipo')) { + performProductSearch(this.value.trim().toLowerCase()); + } + }); +} + +function performProductSearch(query) { + const searchResults = document.getElementById('search-results'); + if (!searchResults) return; + + // Filter products based on query + let filteredProducts = products.filter(product => + product.name.toLowerCase().includes(query) || + (product.category && product.category.toLowerCase().includes(query)) + ); + + // Add virtual "anticipo" product if searching for it + if (query.includes('anticipo') || query.includes('advance')) { + const anticipoProduct = { + id: 'virtual_anticipo', + name: 'Anticipo', + category: 'Anticipos', + price: 0, + custom_price: true, + virtual: true + }; + filteredProducts.unshift(anticipoProduct); // Add at the beginning + } + + // Render search results + renderSearchResults(filteredProducts); + searchResults.style.display = 'block'; +} + +function renderSearchResults(filteredProducts) { + const searchResults = document.getElementById('search-results'); + if (!searchResults) return; + + searchResults.innerHTML = ''; + + if (filteredProducts.length === 0) { + searchResults.innerHTML = '
No se encontraron servicios o productos
'; + return; + } + + filteredProducts.forEach(product => { + const resultItem = createSearchResultItem(product); + searchResults.appendChild(resultItem); + }); +} + +function createSearchResultItem(product) { + const item = document.createElement('div'); + item.className = 'search-result-item'; + item.dataset.productId = product.id; + + const priceDisplay = product.custom_price + ? '
Precio personalizado
' + : `
$${parseFloat(product.price || 0).toFixed(2)}
`; + + item.innerHTML = ` +
+
${escapeHTML(product.name)}
+
${escapeHTML(product.category || 'Sin categoría')}
+
+
+ ${priceDisplay} +
+ `; + + // Add click event to add product to cart + item.addEventListener('click', function() { + addProductToCartFromSearch(product.id); + // Clear search and hide results + const searchInput = document.getElementById('service-search-input'); + const searchResults = document.getElementById('search-results'); + if (searchInput) searchInput.value = ''; + if (searchResults) searchResults.style.display = 'none'; + }); + + return item; +} + +function addProductToCartFromSearch(productId) { + // Handle virtual anticipo product + if (productId === 'virtual_anticipo') { + // Show anticipo section instead of adding directly + const anticipoSection = document.getElementById('anticipo-section'); + const searchResults = document.getElementById('search-results'); + if (anticipoSection) anticipoSection.style.display = 'block'; + if (searchResults) searchResults.style.display = 'none'; + + // Focus on the amount input + const amountInput = document.getElementById('anticipo-amount'); + if (amountInput) { + setTimeout(() => amountInput.focus(), 100); + } + return; + } + + // Find the product in the products array + const product = products.find(p => p.id === productId); + if (!product) return; + + // Handle custom price products + let price = product.price; + if (product.custom_price) { + const customPrice = prompt(`Ingresa el precio para "${product.name}":`, '0'); + if (customPrice === null) return; // User cancelled + price = parseFloat(customPrice) || 0; + } + + // Check if product is already in cart + const existingIndex = selectedProducts.findIndex(p => p.id === productId); + + if (existingIndex >= 0) { + // Update quantity + selectedProducts[existingIndex].quantity += 1; + selectedProducts[existingIndex].price = price; // Update price in case it changed + } else { + // Add new product + selectedProducts.push({ + id: product.id, + name: product.name, + price: price, + quantity: 1, + type: product.type, + custom_price: product.custom_price + }); + } + + updateCartDisplay(); + calculateTotals(); + + // Show visual feedback + showAddToCartFeedback(product.name); +} + +function showAddToCartFeedback(productName) { + // Create temporary notification + const notification = document.createElement('div'); + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #28a745; + color: white; + padding: 10px 15px; + border-radius: 5px; + z-index: 10000; + font-weight: bold; + animation: slideIn 0.3s ease; + `; + notification.textContent = `✓ ${productName} agregado al carrito`; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.remove(); + }, 2000); +} + async function loadProductsByCategories() { try { const response = await fetch('/api/products'); @@ -3247,13 +3465,19 @@ function addAnticipo() { // Clear inputs amountInput.value = ''; commentInput.value = ''; - + + // Hide anticipo section and clear search + const anticipoSection = document.getElementById('anticipo-section'); + const searchInput = document.getElementById('service-search-input'); + if (anticipoSection) anticipoSection.style.display = 'none'; + if (searchInput) searchInput.value = ''; + // Update cart display updateCartDisplay(); calculateTotals(); - - // Show confirmation - alert(`✅ ANTICIPO AGREGADO\n\n${anticipoName}: $${amount.toFixed(2)}`); + + // Show visual feedback instead of alert + showAddToCartFeedback(`${anticipoName}: $${amount.toFixed(2)}`); } diff --git a/index.html b/index.html index caec25d..4acfc0f 100644 --- a/index.html +++ b/index.html @@ -115,63 +115,44 @@ - -
-

Selecciona tus servicios

- - -
-
- 👁️ -

Vanity Lashes

- + +
+

Buscar servicios y productos

+ + +
+
+ + 🔍
-
- + + +
- -
-
- ✏️ -

PMU Services

- -
-
- -
-
- - -
-
- 💅 -

Nail Art

- -
-
- -
-
- - -
-
- 💰 -

Anticipos

- -
-
+ + + + +
+

💡 Escribe el nombre del servicio o producto, o escribe "anticipo" para agregar un anticipo

+
diff --git a/styles.css b/styles.css index 58b5945..3271162 100644 --- a/styles.css +++ b/styles.css @@ -8,6 +8,165 @@ - Secundario: #6c757d (gris medio) */ +/* --- Estilos para barra de búsqueda de servicios --- */ +.services-search-container { + margin-bottom: 20px; +} + +.search-bar-container { + position: relative; + margin-bottom: 15px; +} + +.search-input-wrapper { + position: relative; +} + +#service-search-input { + width: 100%; + box-sizing: border-box; + padding: 12px 40px 12px 15px; + border: 2px solid #dee2e6; + border-radius: 8px; + font-size: 16px; + background-color: #fff; + transition: border-color 0.2s, box-shadow 0.2s; +} + +#service-search-input:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); +} + +.search-icon { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + color: #6c757d; + font-size: 18px; +} + +.search-results { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: #fff; + border: 1px solid #dee2e6; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.1); + max-height: 300px; + overflow-y: auto; + z-index: 1000; +} + +.search-result-item { + padding: 12px 15px; + border-bottom: 1px solid #f1f3f4; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + transition: background-color 0.2s; +} + +.search-result-item:hover { + background-color: #f8f9fa; +} + +.search-result-item:last-child { + border-bottom: none; +} + +.search-result-info { + display: flex; + flex-direction: column; +} + +.search-result-name { + font-weight: 600; + color: #212529; + margin-bottom: 2px; +} + +.search-result-category { + font-size: 12px; + color: #6c757d; +} + +.search-result-price { + font-weight: 600; + color: #28a745; +} + +.search-result-custom-price { + font-size: 12px; + color: #ffc107; + font-weight: 500; +} + +.anticipo-section { + margin-top: 15px; +} + +.anticipo-form-card { + background: #fff; + border: 2px solid #28a745; + border-radius: 8px; + padding: 15px; +} + +.anticipo-header { + display: flex; + align-items: center; + margin-bottom: 15px; +} + +.anticipo-header .category-icon { + margin-right: 8px; + font-size: 20px; +} + +.anticipo-header h4 { + margin: 0; + color: #28a745; +} + +.search-help { + background: #e7f3ff; + border: 1px solid #b3d9ff; + border-radius: 6px; + padding: 10px 15px; + margin-top: 15px; +} + +.search-help p { + margin: 0; + font-size: 14px; + color: #0066cc; +} + +.search-empty { + padding: 20px; + text-align: center; + color: #6c757d; + font-style: italic; +} + +/* Animación para notificaciones */ +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + /* Estilos generales y tipografías */ body { font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;