Feat/parapharmacy api #38
@@ -34,6 +34,15 @@ function PharmacyMap({ pharmacies }) {
|
||||
<strong>{pharmacy.name}</strong><br />
|
||||
{pharmacy.address}
|
||||
{pharmacy.phone && <><br />{pharmacy.phone}</>}
|
||||
<br />
|
||||
<a
|
||||
href={`https://www.google.com/maps/dir/?api=1&destination=${pharmacy.latitude},${pharmacy.longitude}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: '#2563eb', marginTop: '8px', display: 'inline-block' }}
|
||||
>
|
||||
📍 Cómo llegar
|
||||
</a>
|
||||
</Popup>
|
||||
</Marker>
|
||||
))}
|
||||
|
||||
@@ -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'}
|
||||
</h3>
|
||||
|
||||
<PharmacyMap pharmacies={pharmacies} />
|
||||
|
||||
<div className="pharmacies-grid">
|
||||
{pharmacies.map((pharmacy) => (
|
||||
<div key={pharmacy.id} className="product-pharmacy-card">
|
||||
<h4 className="product-pharmacy-name">{pharmacy.name}</h4>
|
||||
<p className="product-pharmacy-address">{pharmacy.address}</p>
|
||||
{pharmacy.phone && (
|
||||
<p className="product-pharmacy-phone">{pharmacy.phone}</p>
|
||||
)}
|
||||
<div className="product-pharmacy-status">
|
||||
{pharmacy.price && (
|
||||
<span className="product-pharmacy-price">
|
||||
{parseFloat(pharmacy.price).toFixed(2)} €
|
||||
</span>
|
||||
)}
|
||||
{pharmacy.stock !== undefined && (
|
||||
<span className={`product-pharmacy-stock ${pharmacy.stock > 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'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="pharmacy-controls">
|
||||
<button
|
||||
className={`sort-distance-button ${sortByDistance ? 'active' : ''}`}
|
||||
onClick={handleSortByDistance}
|
||||
disabled={locating}
|
||||
>
|
||||
{locating
|
||||
? '📍 Localizando…'
|
||||
: sortByDistance
|
||||
? '📍 Ordenado por distancia · Reset'
|
||||
: '📍 Ordenar por distancia'}
|
||||
</button>
|
||||
{sortByDistance && positionSource && (
|
||||
<span className="location-source">Usando tu ubicación</span>
|
||||
)}
|
||||
{locationError && (
|
||||
<span className="location-error">
|
||||
{locationError}
|
||||
<button className="retry-location-btn" onClick={handleSortByDistance}>
|
||||
Reintentar
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PharmacyMap pharmacies={displayedPharmacies} />
|
||||
|
||||
<PharmacyList
|
||||
pharmacies={displayedPharmacies}
|
||||
loading={loadingPharmacies}
|
||||
userPosition={sortByDistance ? userPosition : null}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user