import React, { useState, useEffect, useMemo } from 'react'; import '../App.css'; import SearchBar from '../components/SearchBar'; import MedicineResults from '../components/MedicineResults'; import ProductResults from '../components/ProductResults'; import PharmacyList from '../components/PharmacyList'; import PharmacyMap from '../components/PharmacyMap'; import HomeView from './HomeView'; import ScannerView from './ScannerView'; import { haversineKm, getUserPosition } from '../utils/geo'; function PublicView({ currentUser, onLoginRequest, screen: screenProp, onScreenChange, }) { // ponytail: uncontrolled by default (own state); controlled when App.jsx passes props // so BottomNav can drive the inner screen directly. No-op fallback breaks tests. const [internalScreen, setInternalScreen] = useState('home'); const screen = screenProp ?? internalScreen; const setScreen = onScreenChange ?? setInternalScreen; const [searchQuery, setSearchQuery] = useState(''); const [medicines, setMedicines] = useState([]); const [products, setProducts] = useState([]); const [searchMode, setSearchMode] = useState('all'); // 'all' | 'medicines' | 'products' const [selectedMedicine, setSelectedMedicine] = useState(null); const [pharmacies, setPharmacies] = useState([]); const [loading, setLoading] = useState(false); const [userPosition, setUserPosition] = useState(null); const [positionSource, setPositionSource] = useState(null); // 'profile' | 'browser' const [sortByDistance, setSortByDistance] = useState(false); const [locating, setLocating] = useState(false); const [locationError, setLocationError] = useState(''); useEffect(() => { const searchMedicines = async () => { if (searchQuery.trim().length < 2) { setMedicines([]); setProducts([]); 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); } 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); 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); 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) { console.error('[location] error', err); let msg; if (err && typeof err.code === 'number') { if (err.code === 1) msg = 'Permiso de ubicación denegado — actívalo en la configuración del navegador'; else if (err.code === 2) msg = 'Ubicación no disponible — comprueba los servicios de ubicación/WiFi'; else if (err.code === 3) msg = 'La solicitud de ubicación expiró — inténtalo de nuevo'; else msg = `Error de geolocalización (código ${err.code})`; } else { msg = err && err.message ? err.message : 'No se pudo obtener tu ubicació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]); /* ── Scanner → Search handoff ──────────────────────────── */ function handleScanSelectMedicine(medicineName) { setSearchQuery(medicineName); setSelectedMedicine(null); setPharmacies([]); setScreen('search'); } /* ── Screens ─────────────────────────────────────────────── */ if (screen === 'home') { return ( setScreen('scan')} onSearchClick={() => setScreen('search')} /> ); } if (screen === 'scan') { return ( setScreen('home')} onSelectMedicine={handleScanSelectMedicine} /> ); } // screen === 'search' return ( <>

💊 FarmaClic

Encuentra tu medicamento en farmacias cercanas

{loading &&
Buscando...
} {searchQuery && (
)} {searchQuery && !selectedMedicine && (searchMode === 'all' || searchMode === 'medicines') && ( )} {(searchMode === 'all' || searchMode === 'products') && products.length > 0 && (

Parafarmacia y Bebé

{ window.location.href = `/product/${product.source}/${product.id}`; }} />
)} {selectedMedicine && (

{selectedMedicine.name}

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