Files
FarmaFinder/apps/frontend/src/views/ProductView.jsx
T
Antoni Nuñez Romeu 6155fdcd15 fix: add pharmacy map to ProductView and fix pharmacies endpoint
- Added PharmacyMap component to ProductView
- Updated backend to accept all valid sources for pharmacies endpoint
- Shows map with pharmacy locations and list of pharmacies with prices
2026-07-16 16:48:25 +02:00

195 lines
6.4 KiB
React

import React, { useState, useEffect } from 'react';
import PharmacyMap from '../components/PharmacyMap';
import './ProductView.css';
export default function ProductView({ source, id, onBack }) {
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [pharmacies, setPharmacies] = useState([]);
const [loadingPharmacies, setLoadingPharmacies] = useState(false);
useEffect(() => {
loadProduct();
}, [source, id]);
async function loadProduct() {
setLoading(true);
setError(null);
setPharmacies([]);
try {
// Use parapharmacy endpoint for all non-CIMA sources
const isCima = source === 'cima';
const apiUrl = isCima
? `/api/products/${source}/${id}`
: `/api/products/parapharmacy/${id}`;
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error('Producto no encontrado');
}
const data = await response.json();
setProduct(data);
loadPharmacies(source, data.id || data._id);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
async function loadPharmacies(productSource, productId) {
setLoadingPharmacies(true);
try {
const response = await fetch(`/api/products/${productSource}/${productId}/pharmacies`);
if (response.ok) {
const data = await response.json();
setPharmacies(data);
}
} catch {
// Pharmacies are optional — don't block on failure
} finally {
setLoadingPharmacies(false);
}
}
if (loading) {
return (
<div className="product-view">
<div className="product-loading">Cargando...</div>
</div>
);
}
if (error) {
return (
<div className="product-view">
<button className="back-btn" onClick={onBack}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
Volver
</button>
<div className="product-error">{error}</div>
</div>
);
}
if (!product) return null;
const isCima = product.source === 'cima';
const isParapharmacy = product.source !== 'cima';
const sourceColor = isCima ? '#2563eb' : '#16a34a';
const sourceLabel = isCima ? 'CIMA' : 'Parafarmacia';
return (
<div className="product-view">
<button className="back-btn" onClick={onBack}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
Volver
</button>
<div className="product-header">
{product.image_url && (
<img src={product.image_url} alt={product.name} className="product-image" />
)}
<span className="source-badge" style={{ backgroundColor: sourceColor }}>
{sourceLabel}
</span>
</div>
<h1 className="product-name">{product.name}</h1>
<p className="product-brand">{product.brand}</p>
<div className="product-details">
{isCima ? (
<>
{product.active_ingredient && (
<DetailRow label="Principio activo" value={product.active_ingredient} />
)}
{product.dosage && (
<DetailRow label="Dosis" value={product.dosage} />
)}
{product.form && (
<DetailRow label="Forma farmacéutica" value={product.form} />
)}
{product.prescription && (
<DetailRow label="Prescripción" value={product.prescription} />
)}
{product.commercialized !== undefined && (
<DetailRow label="Comercializado" value={product.commercialized ? 'Sí' : 'No'} />
)}
</>
) : (
<>
{product.price && (
<DetailRow label="Precio" value={`${product.price} €`} />
)}
{product.original_price && product.original_price > product.price && (
<DetailRow label="Precio anterior" value={`${product.original_price} €`} />
)}
{product.category && (
<DetailRow label="Categoría" value={product.category} />
)}
{product.brand && (
<DetailRow label="Marca" value={product.brand} />
)}
{product.source_url && (
<DetailRow label="Fuente" value={product.source} />
)}
</>
)}
</div>
<div className="product-pharmacies">
{loadingPharmacies ? (
<div className="pharmacies-loading">Cargando farmacias...</div>
) : pharmacies.length > 0 ? (
<>
<h3 className="pharmacies-title">
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)} &euro;
</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>
</>
) : null}
</div>
</div>
);
}
function DetailRow({ label, value }) {
return (
<div className="detail-row">
<span className="detail-label">{label}</span>
<span className="detail-value">{value}</span>
</div>
);
}