From bb2dbbab3a3df2be927026f1cc3e331076de92ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Mon, 13 Jul 2026 15:31:39 +0200 Subject: [PATCH] feat(mobile): integrate product search with filter tabs in search screen --- apps/frontend-mobile/app/(tabs)/search.tsx | 132 ++++++++++++++++++++- 1 file changed, 126 insertions(+), 6 deletions(-) diff --git a/apps/frontend-mobile/app/(tabs)/search.tsx b/apps/frontend-mobile/app/(tabs)/search.tsx index 7d5e4d3..dfb11d5 100644 --- a/apps/frontend-mobile/app/(tabs)/search.tsx +++ b/apps/frontend-mobile/app/(tabs)/search.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { View, FlatList, StyleSheet, Text, TouchableOpacity, useWindowDimensions } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; -import { useNavigation } from 'expo-router'; +import { useNavigation, useRouter } from 'expo-router'; import { SearchBar } from '../../components/SearchBar'; import { MedicineCard } from '../../components/MedicineCard'; import { LoadingSpinner } from '../../components/LoadingSpinner'; @@ -9,6 +9,7 @@ import { useDebounce } from '../../hooks/useDebounce'; import { useAuth } from '../../hooks/useAuth'; import { useRecentSearches } from '../../hooks/useRecentSearches'; import { searchMedicines } from '../../services/medicines'; +import { searchProducts, Product } from '../../services/products'; import { useThemeContext } from '../../components/ThemeProvider'; import { spacing, borderRadius } from '../../constants/theme'; import { Medicine } from '../../types'; @@ -31,11 +32,14 @@ export default function SearchScreen() { const { colors } = useThemeContext(); const [query, setQuery] = useState(''); const [results, setResults] = useState([]); + const [products, setProducts] = useState([]); + const [searchMode, setSearchMode] = useState<'all' | 'medicines' | 'products'>('all'); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const debouncedQuery = useDebounce(query, config.SEARCH_DEBOUNCE_MS); const navigation = useNavigation(); + const router = useRouter(); useEffect(() => { const parent = navigation.getParent(); @@ -43,6 +47,8 @@ export default function SearchScreen() { const unsubscribe = parent.addListener('tabPress', () => { setQuery(''); setResults([]); + setProducts([]); + setSearchMode('all'); setIsLoading(false); setError(null); }); @@ -52,6 +58,7 @@ export default function SearchScreen() { useEffect(() => { if (debouncedQuery.length < 2) { setResults([]); + setProducts([]); return; } @@ -69,7 +76,17 @@ export default function SearchScreen() { } }; + const fetchProducts = async () => { + try { + const productResults = await searchProducts(debouncedQuery); + setProducts(productResults); + } catch (err) { + console.error('Product search error:', err); + } + }; + fetchResults(); + fetchProducts(); }, [debouncedQuery]); const handleSearch = (searchQuery: string) => { @@ -77,7 +94,7 @@ export default function SearchScreen() { if (searchQuery.trim()) addSearch(searchQuery); }; - const showSuggestions = !query && !isLoading && results.length === 0; + const showSuggestions = !query && !isLoading && results.length === 0 && products.length === 0; return ( @@ -87,6 +104,20 @@ export default function SearchScreen() { onChangeText={setQuery} /> + {query.length >= 2 && ( + + setSearchMode('all')}> + Todos + + setSearchMode('medicines')}> + Medicamentos + + setSearchMode('products')}> + Parafarmacia + + + )} + {showSuggestions && ( <> @@ -130,7 +161,7 @@ export default function SearchScreen() { )} - {isLoading && } + {isLoading && } {error && ( @@ -138,16 +169,43 @@ export default function SearchScreen() { )} - {!isLoading && !error && results.length === 0 && query.length >= 2 && ( + {!isLoading && !error && results.length === 0 && products.length === 0 && query.length >= 2 && ( - No se encontraron medicamentos + No se encontraron resultados )} item.nregistro} renderItem={({ item }) => } + ListHeaderComponent={ + <> + {(searchMode === 'all' || searchMode === 'products') && products.length > 0 && ( + + Parafarmacia y Bebé + {products.map((product) => ( + router.push(`/product/${product.source}/${product.id}`)} + activeOpacity={0.7} + > + + + + {product.source === 'cima' ? 'CIMA' : 'OFF'} + + + {product.name} + {product.brand} + + + ))} + + )} + + } contentContainerStyle={[styles.list, isTablet && styles.listTablet]} showsVerticalScrollIndicator={false} /> @@ -159,6 +217,34 @@ const styles = StyleSheet.create({ container: { flex: 1, }, + filterContainer: { + flexDirection: 'row', + justifyContent: 'center', + gap: spacing.sm, + paddingVertical: spacing.sm, + paddingHorizontal: spacing.md, + }, + filterTab: { + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm, + borderRadius: borderRadius.full, + backgroundColor: 'rgba(0,0,0,0.05)', + }, + filterTabActive: { + backgroundColor: '#7fbf8f', + }, + filterText: { + fontSize: 14, + fontWeight: '600', + color: '#41493e', + }, + filterTextActive: { + color: '#ffffff', + }, + section: { + paddingHorizontal: spacing.lg, + marginTop: spacing.md, + }, suggestionsSection: { marginTop: spacing.sm, alignSelf: 'center', @@ -245,4 +331,38 @@ const styles = StyleSheet.create({ emptyText: { fontSize: 16, }, + productCard: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: borderRadius.lg, + borderWidth: 1, + padding: spacing.md, + marginBottom: spacing.sm, + }, + productInfo: { + flex: 1, + gap: 4, + }, + badges: { + flexDirection: 'row', + gap: spacing.sm, + marginBottom: 2, + }, + sourceBadge: { + paddingHorizontal: spacing.sm, + paddingVertical: 2, + borderRadius: borderRadius.sm, + }, + badgeText: { + color: '#ffffff', + fontSize: 11, + fontWeight: '700', + }, + productName: { + fontSize: 15, + fontWeight: '600', + }, + productBrand: { + fontSize: 13, + }, });