diff --git a/apps/frontend/src/components/PharmacyMap.jsx b/apps/frontend/src/components/PharmacyMap.jsx index ca9876c..7182157 100644 --- a/apps/frontend/src/components/PharmacyMap.jsx +++ b/apps/frontend/src/components/PharmacyMap.jsx @@ -34,6 +34,15 @@ function PharmacyMap({ pharmacies }) { {pharmacy.name}
{pharmacy.address} {pharmacy.phone && <>
{pharmacy.phone}} +
+ + 📍 Cómo llegar + ))} diff --git a/apps/frontend/src/views/ProductView.jsx b/apps/frontend/src/views/ProductView.jsx index 344533d..c6493ad 100644 --- a/apps/frontend/src/views/ProductView.jsx +++ b/apps/frontend/src/views/ProductView.jsx @@ -1,5 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import PharmacyMap from '../components/PharmacyMap'; +import PharmacyList from '../components/PharmacyList'; +import { haversineKm, getUserPosition, hasCachedPosition } from '../utils/geo'; import './ProductView.css'; export default function ProductView({ source, id, onBack }) { @@ -8,6 +10,50 @@ export default function ProductView({ source, id, onBack }) { const [error, setError] = useState(null); const [pharmacies, setPharmacies] = useState([]); const [loadingPharmacies, setLoadingPharmacies] = useState(false); + const [sortByDistance, setSortByDistance] = useState(false); + const [userPosition, setUserPosition] = useState(null); + const [positionSource, setPositionSource] = useState(null); + const [locating, setLocating] = useState(false); + const [locationError, setLocationError] = useState(''); + + const hasSavedCoords = product?.latitude != null && product?.longitude != null; + + 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]); + + const handleSortByDistance = async () => { + if (sortByDistance) { + setSortByDistance(false); + return; + } + setLocationError(''); + setLocating(true); + try { + const pos = await getUserPosition(); + setUserPosition(pos); + setPositionSource('browser'); + setSortByDistance(true); + } 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.'; + else if (err.code === 2) msg = 'Ubicación no disponible.'; + else if (err.code === 3) msg = 'La ubicación tardó demasiado.'; + } + setLocationError(msg); + } finally { + setLocating(false); + } + }; useEffect(() => { loadProduct(); @@ -152,31 +198,38 @@ export default function ProductView({ source, id, onBack }) { Disponible en {pharmacies.length} {pharmacies.length === 1 ? 'farmacia' : 'farmacias'} - - -
- {pharmacies.map((pharmacy) => ( -
-

{pharmacy.name}

-

{pharmacy.address}

- {pharmacy.phone && ( -

{pharmacy.phone}

- )} -
- {pharmacy.price && ( - - {parseFloat(pharmacy.price).toFixed(2)} € - - )} - {pharmacy.stock !== undefined && ( - 20 ? 'in-stock' : pharmacy.stock > 0 ? 'low-stock' : 'out-of-stock'}`}> - {pharmacy.stock > 20 ? 'En Stock' : pharmacy.stock > 0 ? `Stock Bajo (${pharmacy.stock})` : 'Sin Stock'} - - )} -
-
- ))} +
+ + {sortByDistance && positionSource && ( + Usando tu ubicación + )} + {locationError && ( + + {locationError} + + + )}
+ + + + ) : null}