feat(frontend): add product detail view and navigation
- Create ProductView component with product details display - Add ProductView.css with responsive styling - Add product screen to App.jsx screen system - Pass onNavigateToProduct callback from App to SearchView - Update ProductResults onSelect to use callback instead of window.location
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
loadProduct();
|
||||
}, [source, id]);
|
||||
|
||||
async function loadProduct() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch(`/api/products/${source}/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Producto no encontrado');
|
||||
}
|
||||
const data = await response.json();
|
||||
setProduct(data);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(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 sourceColor = isCima ? '#2563eb' : '#16a34a';
|
||||
const sourceLabel = isCima ? 'CIMA' : 'Open Food Facts';
|
||||
|
||||
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.nutriscore && product.nutriscore !== 'not-applicable' && product.nutriscore !== 'unknown' && (
|
||||
<DetailRow label="Nutri-Score" value={product.nutriscore.toUpperCase()} />
|
||||
)}
|
||||
{product.nova_group && (
|
||||
<DetailRow label="NOVA" value={`Grupo ${product.nova_group}`} />
|
||||
)}
|
||||
{product.eco_score && product.eco_score !== 'unknown' && (
|
||||
<DetailRow label="Eco-Score" value={product.eco_score.toUpperCase()} />
|
||||
)}
|
||||
{product.ingredients && (
|
||||
<DetailRow label="Ingredientes" value={product.ingredients} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailRow({ label, value }) {
|
||||
return (
|
||||
<div className="detail-row">
|
||||
<span className="detail-label">{label}</span>
|
||||
<span className="detail-value">{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user