feat/parapharmacy-baby-products #31

Open
Ichitux wants to merge 24 commits from feat/parapharmacy-baby-products into main
2 changed files with 277 additions and 0 deletions
Showing only changes of commit a709deb893 - Show all commits
@@ -0,0 +1,163 @@
.product-results {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
margin-top: 0.5rem;
max-height: 70vh;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
animation: fadeInUp 0.5s ease-out;
}
@media (min-width: 1024px) {
.product-results {
max-height: 76vh;
}
}
.product-card {
background: var(--surface-container-lowest);
border-radius: var(--radius-md);
padding: 1.25rem;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
border: 1px solid var(--outline-variant);
display: flex;
flex-direction: column;
justify-content: space-between;
box-shadow: var(--shadow-soft);
}
.product-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
border-color: var(--primary);
}
.product-card-image {
width: 100%;
height: 120px;
margin-bottom: 1rem;
overflow: hidden;
border-radius: var(--radius-sm);
background: var(--surface-container-low);
display: flex;
align-items: center;
justify-content: center;
}
.product-card-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.product-image-placeholder {
color: var(--outline-variant);
display: flex;
align-items: center;
justify-content: center;
}
.product-card-content {
display: flex;
flex-direction: column;
flex: 1;
}
.product-card-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.source-badge {
color: white;
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.category-badge {
background: var(--surface-container-high);
color: var(--on-surface-variant);
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 0.7rem;
font-weight: 600;
}
.nutri-score-badge {
color: white;
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 0.7rem;
font-weight: 600;
}
.product-card-header {
margin-bottom: 0.5rem;
}
.product-card-header h3 {
color: var(--on-surface);
font-size: 1.15rem;
font-weight: 700;
letter-spacing: -0.01em;
flex: 1;
margin: 0;
}
.product-card-body {
margin-bottom: 1rem;
flex: 1;
}
.product-card-body p {
font-size: 0.9rem;
color: var(--on-surface-variant);
line-height: 1.55;
margin-bottom: 0.25rem;
}
.product-card-body strong {
color: var(--on-surface);
font-weight: 600;
}
.product-card-footer {
padding-top: 0.75rem;
border-top: 1px solid var(--outline-variant);
}
.view-details {
color: var(--primary);
font-weight: 600;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.view-details::after {
content: "→";
transition: transform 0.2s;
}
.product-card:hover .view-details::after {
transform: translateX(4px);
}
.no-results {
text-align: center;
padding: 2.5rem 1.5rem;
background: var(--surface-container-low);
border-radius: var(--radius-md);
color: var(--on-surface-variant);
border: 1px dashed var(--outline-variant);
}
@@ -0,0 +1,114 @@
import React from 'react';
import './ProductResults.css';
const categoryLabels = {
otc: 'Sin Receta',
baby_food: 'Alimentación Infantil',
baby_milk: 'Leche de Fórmula',
baby_cereal: 'Cereales Bebé'
};
const sourceColors = {
cima: '#2563eb',
openfoodfacts: '#16a34a'
};
const sourceLabels = {
cima: 'CIMA',
openfoodfacts: 'Open Food Facts'
};
function ProductResults({ products, onSelect }) {
if (!products || products.length === 0) {
return (
<div className="no-results">
<p>No se encontraron productos</p>
</div>
);
}
return (
<div className="product-results">
{products.map((product) => (
<ProductCard
key={product.id}
product={product}
onSelect={onSelect}
/>
))}
</div>
);
}
function ProductCard({ product, onSelect }) {
const nutriScore = product.nutri_score;
const nutriScoreColors = {
a: '#16a34a',
b: '#65a30d',
c: '#eab308',
d: '#f97316',
e: '#dc2626'
};
return (
<div className="product-card" onClick={() => onSelect(product)}>
<div className="product-card-image">
{product.image_url ? (
<img src={product.image_url} alt={product.name} loading="lazy" />
) : (
<div className="product-image-placeholder">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
<circle cx="8.5" cy="8.5" r="1.5" />
<polyline points="21 15 16 10 5 21" />
</svg>
</div>
)}
</div>
<div className="product-card-content">
<div className="product-card-badges">
<span
className="source-badge"
style={{ backgroundColor: sourceColors[product.source] }}
>
{sourceLabels[product.source]}
</span>
<span className="category-badge">
{categoryLabels[product.category] || product.category}
</span>
{product.source === 'openfoodfacts' && nutriScore && (
<span
className="nutri-score-badge"
style={{ backgroundColor: nutriScoreColors[nutriScore.toLowerCase()] || '#9ca3af' }}
>
Nutri-Score {nutriScore.toUpperCase()}
</span>
)}
</div>
<div className="product-card-header">
<h3>{product.name}</h3>
</div>
<div className="product-card-body">
{product.brand && (
<p><strong>Marca:</strong> {product.brand}</p>
)}
{product.source === 'cima' && product.active_ingredient && (
<p><strong>Principio Activo:</strong> {product.active_ingredient}</p>
)}
{product.source === 'cima' && product.dosage && (
<p><strong>Dosis:</strong> {product.dosage}</p>
)}
</div>
<div className="product-card-footer">
<span className="view-details">Ver detalles </span>
</div>
</div>
</div>
);
}
export default ProductResults;