249 lines
7.4 KiB
TypeScript
249 lines
7.4 KiB
TypeScript
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 { SearchBar } from '../../components/SearchBar';
|
|
import { MedicineCard } from '../../components/MedicineCard';
|
|
import { LoadingSpinner } from '../../components/LoadingSpinner';
|
|
import { useDebounce } from '../../hooks/useDebounce';
|
|
import { useAuth } from '../../hooks/useAuth';
|
|
import { useRecentSearches } from '../../hooks/useRecentSearches';
|
|
import { searchMedicines } from '../../services/medicines';
|
|
import { useThemeContext } from '../../components/ThemeProvider';
|
|
import { spacing, borderRadius } from '../../constants/theme';
|
|
import { Medicine } from '../../types';
|
|
import { config } from '../../constants/config';
|
|
|
|
const TABLET_MIN_WIDTH = 768;
|
|
|
|
const suggestions = [
|
|
{ name: 'Paracetamol', icon: 'medical' as const },
|
|
{ name: 'Ibuprofeno', icon: 'fitness' as const },
|
|
{ name: 'Aspirina', icon: 'heart' as const },
|
|
{ name: 'Omeprazol', icon: 'bandage' as const },
|
|
];
|
|
|
|
export default function SearchScreen() {
|
|
const { width } = useWindowDimensions();
|
|
const isTablet = width >= TABLET_MIN_WIDTH;
|
|
const { isAuthenticated } = useAuth();
|
|
const { recentSearches, addSearch, removeSearch } = useRecentSearches();
|
|
const { colors } = useThemeContext();
|
|
const [query, setQuery] = useState('');
|
|
const [results, setResults] = useState<Medicine[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const debouncedQuery = useDebounce(query, config.SEARCH_DEBOUNCE_MS);
|
|
const navigation = useNavigation();
|
|
|
|
useEffect(() => {
|
|
const parent = navigation.getParent();
|
|
if (!parent) return;
|
|
const unsubscribe = parent.addListener('tabPress', () => {
|
|
setQuery('');
|
|
setResults([]);
|
|
setIsLoading(false);
|
|
setError(null);
|
|
});
|
|
return unsubscribe;
|
|
}, [navigation]);
|
|
|
|
useEffect(() => {
|
|
if (debouncedQuery.length < 2) {
|
|
setResults([]);
|
|
return;
|
|
}
|
|
|
|
const fetchResults = async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await searchMedicines(debouncedQuery);
|
|
setResults(data);
|
|
} catch (err) {
|
|
setError('Error al buscar medicamentos');
|
|
console.error(err);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchResults();
|
|
}, [debouncedQuery]);
|
|
|
|
const handleSearch = (searchQuery: string) => {
|
|
setQuery(searchQuery);
|
|
if (searchQuery.trim()) addSearch(searchQuery);
|
|
};
|
|
|
|
const showSuggestions = !query && !isLoading && results.length === 0;
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
|
<SearchBar
|
|
onSearch={handleSearch}
|
|
value={query}
|
|
onChangeText={setQuery}
|
|
/>
|
|
|
|
{showSuggestions && (
|
|
<>
|
|
<View style={[styles.suggestionsSection, isTablet && styles.suggestionsSectionTablet]}>
|
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Sugerencias</Text>
|
|
<View style={styles.suggestionsGrid}>
|
|
{suggestions.map((s) => (
|
|
<TouchableOpacity
|
|
key={s.name}
|
|
style={[styles.suggestionCard, { backgroundColor: colors.card, borderColor: colors.border }]}
|
|
onPress={() => handleSearch(s.name)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<View style={[styles.suggestionIcon, { backgroundColor: colors.surfaceLow }]}>
|
|
<Ionicons name={s.icon} size={22} color={colors.primary} />
|
|
</View>
|
|
<Text style={[styles.suggestionName, { color: colors.text }]}>{s.name}</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{isAuthenticated && recentSearches.length > 0 && (
|
|
<View style={[styles.recentSection, isTablet && styles.recentSectionTablet]}>
|
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Búsquedas recientes</Text>
|
|
{recentSearches.map((term) => (
|
|
<TouchableOpacity
|
|
key={term}
|
|
style={styles.recentItem}
|
|
onPress={() => handleSearch(term)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Ionicons name="time-outline" size={18} color={colors.textSecondary} />
|
|
<Text style={[styles.recentText, { color: colors.text }]}>{term}</Text>
|
|
<TouchableOpacity onPress={() => removeSearch(term)} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
|
|
<Ionicons name="close" size={16} color={colors.textSecondary} />
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{isLoading && <LoadingSpinner message="Buscando medicamentos..." />}
|
|
|
|
{error && (
|
|
<View style={[styles.errorContainer, isTablet && styles.errorContainerTablet, { backgroundColor: colors.dangerContainer }]}>
|
|
<Text style={[styles.errorText, { color: colors.danger }]}>{error}</Text>
|
|
</View>
|
|
)}
|
|
|
|
{!isLoading && !error && results.length === 0 && query.length >= 2 && (
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={[styles.emptyText, { color: colors.textSecondary }]}>No se encontraron medicamentos</Text>
|
|
</View>
|
|
)}
|
|
|
|
<FlatList
|
|
data={results}
|
|
keyExtractor={(item) => item.nregistro}
|
|
renderItem={({ item }) => <MedicineCard medicine={item} />}
|
|
contentContainerStyle={[styles.list, isTablet && styles.listTablet]}
|
|
showsVerticalScrollIndicator={false}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
suggestionsSection: {
|
|
marginTop: spacing.sm,
|
|
alignSelf: 'center',
|
|
width: '80%',
|
|
},
|
|
suggestionsSectionTablet: {
|
|
maxWidth: 700,
|
|
alignSelf: 'center',
|
|
width: '100%',
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 20,
|
|
fontWeight: '700',
|
|
marginBottom: spacing.md,
|
|
},
|
|
suggestionsGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: spacing.sm,
|
|
},
|
|
suggestionCard: {
|
|
width: '48%' as unknown as number,
|
|
borderRadius: borderRadius.lg,
|
|
borderWidth: 1,
|
|
padding: spacing.md,
|
|
gap: spacing.sm,
|
|
},
|
|
suggestionIcon: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: borderRadius.md,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
suggestionName: {
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
},
|
|
recentSection: {
|
|
marginTop: spacing.lg,
|
|
alignSelf: 'center',
|
|
width: '80%',
|
|
},
|
|
recentSectionTablet: {
|
|
maxWidth: 700,
|
|
alignSelf: 'center',
|
|
width: '100%',
|
|
},
|
|
recentItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: spacing.sm,
|
|
gap: spacing.sm,
|
|
},
|
|
recentText: {
|
|
flex: 1,
|
|
fontSize: 15,
|
|
},
|
|
list: {
|
|
paddingBottom: spacing.xl,
|
|
},
|
|
listTablet: {
|
|
maxWidth: 700,
|
|
alignSelf: 'center',
|
|
width: '100%',
|
|
},
|
|
errorContainer: {
|
|
padding: spacing.md,
|
|
marginHorizontal: spacing.lg,
|
|
borderRadius: borderRadius.lg,
|
|
},
|
|
errorContainerTablet: {
|
|
maxWidth: 700,
|
|
alignSelf: 'center',
|
|
},
|
|
errorText: {
|
|
textAlign: 'center',
|
|
fontSize: 14,
|
|
},
|
|
emptyContainer: {
|
|
padding: spacing.xl,
|
|
alignItems: 'center',
|
|
},
|
|
emptyText: {
|
|
fontSize: 16,
|
|
},
|
|
});
|