feat(frontend): integrate product search in SearchView
Run Tests on Branches / Detect Changes (push) Successful in 14s
Run Tests on Branches / Frontend Tests (push) Has been cancelled
Run Tests on Branches / Frontend Mobile Tests (push) Has been cancelled
Run Tests on Branches / Backend Tests (push) Has been cancelled

- Add ProductResults component import
- Add products and searchMode state
- Add parallel product search to search effect
- Add filter tabs (Todos/Medicamentos/Parafarmacia)
- Render ProductResults when products found
- Add CSS for filter tabs and products section
This commit is contained in:
Antoni Nuñez Romeu
2026-07-13 17:31:42 +02:00
parent 25ebb899e9
commit 31c1a14343
2 changed files with 102 additions and 10 deletions
+41
View File
@@ -356,3 +356,44 @@
color: var(--primary); color: var(--primary);
font-size: 0.85rem; font-size: 0.85rem;
} }
/* Filter tabs */
.filter-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.filter-tab {
padding: 0.5rem 1rem;
border-radius: 9999px;
border: none;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
background: var(--surface-container-lowest, #f3f4f6);
color: var(--on-surface-variant, #6b7280);
transition: all 0.15s;
}
.filter-tab:hover {
background: var(--surface-container-low, #e5e7eb);
}
.filter-tab--active {
background: var(--primary, #2563eb);
color: white;
}
/* Products section */
.products-section {
margin-top: 1.5rem;
}
.section-subtitle {
font-size: 1.125rem;
font-weight: 600;
color: var(--on-surface);
margin-bottom: 0.75rem;
}
+51
View File
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react'; import React, { useState, useEffect, useMemo, useCallback } from 'react';
import SearchBar from '../components/SearchBar'; import SearchBar from '../components/SearchBar';
import MedicineResults from '../components/MedicineResults'; import MedicineResults from '../components/MedicineResults';
import ProductResults from '../components/ProductResults';
import PharmacyList from '../components/PharmacyList'; import PharmacyList from '../components/PharmacyList';
import PharmacyMap from '../components/PharmacyMap'; import PharmacyMap from '../components/PharmacyMap';
import { haversineKm, getUserPosition, hasCachedPosition } from '../utils/geo'; import { haversineKm, getUserPosition, hasCachedPosition } from '../utils/geo';
@@ -16,6 +17,8 @@ const suggestions = [
function SearchView({ currentUser, onLoginRequest, initialQuery = '' }) { function SearchView({ currentUser, onLoginRequest, initialQuery = '' }) {
const [searchQuery, setSearchQuery] = useState(initialQuery); const [searchQuery, setSearchQuery] = useState(initialQuery);
const [medicines, setMedicines] = useState([]); const [medicines, setMedicines] = useState([]);
const [products, setProducts] = useState([]);
const [searchMode, setSearchMode] = useState('all');
const [selectedMedicine, setSelectedMedicine] = useState(null); const [selectedMedicine, setSelectedMedicine] = useState(null);
const [pharmacies, setPharmacies] = useState([]); const [pharmacies, setPharmacies] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -76,6 +79,7 @@ function SearchView({ currentUser, onLoginRequest, initialQuery = '' }) {
const searchMedicines = async () => { const searchMedicines = async () => {
if (searchQuery.trim().length < 2) { if (searchQuery.trim().length < 2) {
setMedicines([]); setMedicines([]);
setProducts([]);
setSelectedMedicine(null); setSelectedMedicine(null);
setPharmacies([]); setPharmacies([]);
return; return;
@@ -90,6 +94,16 @@ function SearchView({ currentUser, onLoginRequest, initialQuery = '' }) {
} finally { } finally {
setLoading(false); setLoading(false);
} }
try {
const productsResponse = await fetch(`/api/products/search?q=${encodeURIComponent(searchQuery)}`);
if (productsResponse.ok) {
const productsData = await productsResponse.json();
setProducts(productsData.results || []);
}
} catch (err) {
console.error('Product search error:', err);
}
}; };
const timeoutId = setTimeout(searchMedicines, 300); const timeoutId = setTimeout(searchMedicines, 300);
return () => clearTimeout(timeoutId); return () => clearTimeout(timeoutId);
@@ -276,6 +290,29 @@ function SearchView({ currentUser, onLoginRequest, initialQuery = '' }) {
)} )}
{searchQuery && !selectedMedicine && ( {searchQuery && !selectedMedicine && (
<>
<div className="filter-tabs">
<button
className={`filter-tab ${searchMode === 'all' ? 'filter-tab--active' : ''}`}
onClick={() => setSearchMode('all')}
>
Todos ({medicines.length + products.length})
</button>
<button
className={`filter-tab ${searchMode === 'medicines' ? 'filter-tab--active' : ''}`}
onClick={() => setSearchMode('medicines')}
>
Medicamentos ({medicines.length})
</button>
<button
className={`filter-tab ${searchMode === 'products' ? 'filter-tab--active' : ''}`}
onClick={() => setSearchMode('products')}
>
Parafarmacia ({products.length})
</button>
</div>
{(searchMode === 'all' || searchMode === 'medicines') && (
<MedicineResults <MedicineResults
medicines={medicines} medicines={medicines}
onSelect={(m) => { onSelect={(m) => {
@@ -288,6 +325,20 @@ function SearchView({ currentUser, onLoginRequest, initialQuery = '' }) {
/> />
)} )}
{(searchMode === 'all' || searchMode === 'products') && products.length > 0 && (
<div className="products-section">
<h3 className="section-subtitle">Parafarmacia y Bebé</h3>
<ProductResults
products={products}
onSelect={(p) => {
window.location.href = `/product/${p.source}/${p.id}`;
}}
/>
</div>
)}
</>
)}
{selectedMedicine && ( {selectedMedicine && (
<div className="selected-medicine-section"> <div className="selected-medicine-section">
<div className="medicine-info"> <div className="medicine-info">