import React, { useState, useEffect, useMemo, useCallback } from 'react'; import SearchBar from '../components/SearchBar'; import MedicineResults from '../components/MedicineResults'; import PharmacyList from '../components/PharmacyList'; import PharmacyMap from '../components/PharmacyMap'; import { haversineKm, getUserPosition, hasCachedPosition } from '../utils/geo'; import './SearchView.css'; const suggestions = [ { name: 'Paracetamol', icon: 'medication', color: 'neutral-1' }, { name: 'Ibuprofeno', icon: 'pill', color: 'neutral-2' }, { name: 'Aspirina', icon: 'vaccines', color: 'neutral-3' }, { name: 'Omeprazol', icon: 'emergency_home', color: 'neutral-4' }, ]; function SearchView({ currentUser, onLoginRequest, initialQuery = '' }) { const [searchQuery, setSearchQuery] = useState(initialQuery); const [medicines, setMedicines] = useState([]); const [selectedMedicine, setSelectedMedicine] = useState(null); const [pharmacies, setPharmacies] = useState([]); const [loading, setLoading] = useState(false); const [userPosition, setUserPosition] = useState(null); const [positionSource, setPositionSource] = useState(null); const [sortByDistance, setSortByDistance] = useState(false); const [locating, setLocating] = useState(false); const [locationError, setLocationError] = useState(''); const [recentSearches, setRecentSearches] = useState([]); // Precache position on mount — makes first "sort by distance" nearly instant useEffect(() => { if (hasCachedPosition()) { getUserPosition().then(pos => { setUserPosition(pos); }).catch(() => {}); } }, []); // Fetch recent searches when user is logged in useEffect(() => { if (!currentUser) { setRecentSearches([]); return; } fetch('/api/search/recent', { credentials: 'include' }) .then((r) => r.json()) .then((data) => setRecentSearches(Array.isArray(data) ? data : [])) .catch(() => setRecentSearches([])); }, [currentUser]); const saveToRecent = useCallback((medicine) => { if (!currentUser) return; fetch('/api/search/recent', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ medicine }), }).catch(() => {}); // Update local state immediately setRecentSearches((prev) => { const filtered = prev.filter((m) => m.id !== medicine.id); return [ { id: medicine.id, name: medicine.name, active_ingredient: medicine.active_ingredient, dosage: medicine.dosage, form: medicine.form, timestamp: Date.now(), }, ...filtered, ].slice(0, 5); }); }, [currentUser]); useEffect(() => { const searchMedicines = async () => { if (searchQuery.trim().length < 2) { setMedicines([]); setSelectedMedicine(null); setPharmacies([]); return; } setLoading(true); try { const response = await fetch(`/api/medicines/search?q=${encodeURIComponent(searchQuery)}`); const data = await response.json(); setMedicines(data); } catch (error) { console.error('Error searching medicines:', error); } finally { setLoading(false); } }; const timeoutId = setTimeout(searchMedicines, 300); return () => clearTimeout(timeoutId); }, [searchQuery]); useEffect(() => { const fetchPharmacies = async () => { if (!selectedMedicine) { setPharmacies([]); return; } setLoading(true); try { const response = await fetch(`/api/medicines/${selectedMedicine.id}/pharmacies`); const data = await response.json(); setPharmacies(data); } catch (error) { console.error('Error fetching pharmacies:', error); } finally { setLoading(false); } }; fetchPharmacies(); }, [selectedMedicine]); const savedLat = currentUser?.latitude; const savedLon = currentUser?.longitude; const hasSavedCoords = savedLat != null && savedLon != null; const handleSortByDistance = async () => { if (sortByDistance) { setSortByDistance(false); return; } setLocationError(''); if (hasSavedCoords) { setUserPosition({ lat: savedLat, lon: savedLon }); setPositionSource('profile'); setSortByDistance(true); // Save to search history try { fetch('/api/search-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ address: currentUser?.address, latitude: savedLat, longitude: savedLon, }), }); } catch (err) { console.error('Error saving search history:', err); } return; } if (userPosition) { setSortByDistance(true); setPositionSource('cached'); return; } setLocating(true); try { const pos = await getUserPosition(); setUserPosition(pos); setPositionSource('browser'); setSortByDistance(true); // Save to search history try { fetch('/api/search-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ address: 'Ubicación actual', latitude: pos.lat, longitude: pos.lon, }), }); } catch (err) { console.error('Error saving search history:', err); } } catch (err) { let msg = 'No se pudo obtener tu ubicación'; if (err && typeof err.code === 'number') { if (err.code === 1) msg = 'Permiso de ubicación denegado. Permite el acceso a la ubicación en tu navegador.'; else if (err.code === 2) msg = 'Ubicación no disponible. Verifica que el GPS esté activado.'; else if (err.code === 3) msg = 'La ubicación tardó demasiado. Intenta de nuevo o verifica tu conexión.'; } setLocationError(msg); } finally { setLocating(false); } }; const displayedPharmacies = useMemo(() => { if (!sortByDistance || !userPosition) return pharmacies; return [...pharmacies].sort((a, b) => { if (a.latitude == null || a.longitude == null) return 1; if (b.latitude == null || b.longitude == null) return -1; return ( haversineKm(userPosition.lat, userPosition.lon, a.latitude, a.longitude) - haversineKm(userPosition.lat, userPosition.lon, b.latitude, b.longitude) ); }); }, [pharmacies, sortByDistance, userPosition]); return (
{loading &&
Buscando...
} {!searchQuery && !selectedMedicine && ( <>

Sugerencias

{suggestions.map((s, i) => ( ))}
{currentUser && recentSearches.length > 0 && (

Resultados Recientes

{recentSearches.map((r) => (
setSelectedMedicine(r)} style={{ cursor: 'pointer' }} >

{r.name}

{r.dosage && `${r.dosage}`} {r.dosage && r.form && ' \u2022 '} {r.form}

{r.active_ingredient && ( {r.active_ingredient} )}
))}
)} )} {searchQuery && !selectedMedicine && ( { saveToRecent(m); setSelectedMedicine(m); }} query={searchQuery} currentUser={currentUser} onLoginRequest={onLoginRequest} /> )} {selectedMedicine && (

{selectedMedicine.name}

Ingrediente Activo: {selectedMedicine.active_ingredient} Dosis: {selectedMedicine.dosage} Forma: {selectedMedicine.form}
{pharmacies.length > 0 && (
{sortByDistance && positionSource === 'profile' && ( Usando tu dirección guardada )} {sortByDistance && positionSource === 'cached' && ( Usando ubicación reciente )} {locationError && ( {locationError} )}
)}
)}
); } export default SearchView;