Feat/parapharmacy api #38
@@ -130,6 +130,12 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.product-card-footer {
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--outline-variant);
|
||||
|
||||
@@ -3,19 +3,27 @@ import './ProductResults.css';
|
||||
|
||||
const categoryLabels = {
|
||||
otc: 'Sin Receta',
|
||||
baby_food: 'Alimentación Infantil',
|
||||
baby_milk: 'Leche de Fórmula',
|
||||
baby_cereal: 'Cereales Bebé'
|
||||
parapharmacy: 'Parafarmacia',
|
||||
dermocosmética: 'Dermocosmética',
|
||||
'Fórmulas lácteas': 'Fórmulas lácteas',
|
||||
vitaminas: 'Vitaminas',
|
||||
analgésicos: 'Analgésicos'
|
||||
};
|
||||
|
||||
const sourceColors = {
|
||||
cima: '#2563eb',
|
||||
openfoodfacts: '#16a34a'
|
||||
promofarma: '#16a34a',
|
||||
docmorris: '#e11d48',
|
||||
primor: '#7c3aed',
|
||||
parapharmacy: '#16a34a'
|
||||
};
|
||||
|
||||
const sourceLabels = {
|
||||
cima: 'CIMA',
|
||||
openfoodfacts: 'Open Food Facts'
|
||||
promofarma: 'Promofarma',
|
||||
docmorris: 'DocMorris',
|
||||
primor: 'Primor',
|
||||
parapharmacy: 'Parafarmacia'
|
||||
};
|
||||
|
||||
function ProductResults({ products, onSelect }) {
|
||||
@@ -95,6 +103,9 @@ function ProductCard({ product, onSelect }) {
|
||||
{product.brand && (
|
||||
<p><strong>Marca:</strong> {product.brand}</p>
|
||||
)}
|
||||
{product.price != null && (
|
||||
<p className="product-price"><strong>Precio:</strong> {product.price} €</p>
|
||||
)}
|
||||
{product.source === 'cima' && product.active_ingredient && (
|
||||
<p><strong>Principio Activo:</strong> {product.active_ingredient}</p>
|
||||
)}
|
||||
|
||||
@@ -47,7 +47,7 @@ function PharmacyProductLink() {
|
||||
}
|
||||
}, [selectedPharmacy]);
|
||||
|
||||
// Buscar productos en la API mientras el usuario escribe
|
||||
// Buscar productos en ambas APIs mientras el usuario escribe
|
||||
useEffect(() => {
|
||||
const q = productSearch.trim();
|
||||
if (q.length < 2) {
|
||||
@@ -60,12 +60,27 @@ function PharmacyProductLink() {
|
||||
const timeoutId = setTimeout(async () => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const response = await fetch(`/api/products/search?q=${encodeURIComponent(q)}`, {
|
||||
// Search both CIMA and Parapharmacy
|
||||
const [cimaRes, paraRes] = await Promise.allSettled([
|
||||
fetch(`/api/products/search?q=${encodeURIComponent(q)}`, {
|
||||
credentials: 'include',
|
||||
signal: controller.signal,
|
||||
});
|
||||
const data = await response.json();
|
||||
setProductResults(Array.isArray(data) ? data : []);
|
||||
}),
|
||||
fetch(`/api/products/parapharmacy/search?q=${encodeURIComponent(q)}&limit=10`, {
|
||||
credentials: 'include',
|
||||
signal: controller.signal,
|
||||
})
|
||||
]);
|
||||
|
||||
const cimaData = cimaRes.status === 'fulfilled' ? await cimaRes.value.json() : [];
|
||||
const paraData = paraRes.status === 'fulfilled' ? await paraRes.value.json() : { results: [] };
|
||||
|
||||
const allResults = [
|
||||
...(Array.isArray(cimaData) ? cimaData : []),
|
||||
...(paraData.results || []).map(p => ({ ...p, source: p.source || 'parapharmacy' }))
|
||||
];
|
||||
|
||||
setProductResults(allResults);
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return;
|
||||
console.error('Error searching products:', error);
|
||||
@@ -116,15 +131,15 @@ function PharmacyProductLink() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Build identifier: source:id (e.g., openfoodfacts:9421025231209)
|
||||
const identifier = selectedProduct._id
|
||||
? `${selectedProduct.source || 'openfoodfacts'}:${selectedProduct._id}`
|
||||
: `${selectedProduct.source || 'openfoodfacts'}:${selectedProduct.id}`;
|
||||
// Build identifier: source:id
|
||||
const productId = selectedProduct._id || selectedProduct.id;
|
||||
const source = selectedProduct.source || 'parapharmacy';
|
||||
const identifier = `${source}:${productId}`;
|
||||
|
||||
const payload = {
|
||||
pharmacy_id: parseInt(formData.pharmacy_id),
|
||||
product_source: selectedProduct.source || 'openfoodfacts',
|
||||
product_off_id: selectedProduct._id || selectedProduct.id,
|
||||
product_source: source,
|
||||
product_off_id: productId,
|
||||
product_name: selectedProduct.product_name || selectedProduct.name,
|
||||
price: formData.price ? parseFloat(formData.price) : null,
|
||||
stock: formData.stock ? parseInt(formData.stock) : 0
|
||||
@@ -221,10 +236,28 @@ function PharmacyProductLink() {
|
||||
};
|
||||
|
||||
const getSourceBadge = (source) => {
|
||||
if (source === 'cima') {
|
||||
return <span className="source-badge source-badge--cima">CIMA</span>;
|
||||
}
|
||||
return <span className="source-badge source-badge--off">OFF</span>;
|
||||
const colors = {
|
||||
cima: '#2563eb',
|
||||
promofarma: '#16a34a',
|
||||
docmorris: '#e11d48',
|
||||
primor: '#7c3aed',
|
||||
parapharmacy: '#16a34a'
|
||||
};
|
||||
const labels = {
|
||||
cima: 'CIMA',
|
||||
promofarma: 'Promofarma',
|
||||
docmorris: 'DocMorris',
|
||||
primor: 'Primor',
|
||||
parapharmacy: 'Parafarmacia'
|
||||
};
|
||||
return (
|
||||
<span
|
||||
className="source-badge"
|
||||
style={{ backgroundColor: colors[source] || '#6b7280' }}
|
||||
>
|
||||
{labels[source] || source}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -284,7 +317,7 @@ function PharmacyProductLink() {
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Buscar Producto (Open Food Facts) *</label>
|
||||
<label>Buscar Producto (CIMA / Parafarmacia) *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={productSearch}
|
||||
@@ -292,7 +325,7 @@ function PharmacyProductLink() {
|
||||
setProductSearch(e.target.value);
|
||||
setSelectedProduct(null);
|
||||
}}
|
||||
placeholder="Escribe para buscar productos..."
|
||||
placeholder="Escribe para buscar medicamentos o productos de parafarmacia..."
|
||||
required
|
||||
/>
|
||||
{searching && <p className="loading-text">Buscando...</p>}
|
||||
@@ -301,13 +334,15 @@ function PharmacyProductLink() {
|
||||
<div className="medicine-search-results">
|
||||
{productResults.slice(0, 10).map((product) => (
|
||||
<div
|
||||
key={product._id || product.id}
|
||||
key={product._id || product.id || product.nregistro}
|
||||
className="search-result-item"
|
||||
onClick={() => selectProduct(product)}
|
||||
>
|
||||
<strong>{product.product_name || product.name}</strong>
|
||||
{product.brand && <span> - {product.brand}</span>}
|
||||
{product.brands && <span> - {product.brands}</span>}
|
||||
{product.quantity && <span> ({product.quantity})</span>}
|
||||
{product.price != null && <span> - {product.price}€</span>}
|
||||
{getSourceBadge(product.source || 'parapharmacy')}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -317,11 +352,12 @@ function PharmacyProductLink() {
|
||||
<div className="selected-medicine-info">
|
||||
<p>✅ Selected: <strong>{selectedProduct.product_name || selectedProduct.name}</strong></p>
|
||||
<p className="medicine-details">
|
||||
{selectedProduct.brand && `Marca: ${selectedProduct.brand} • `}
|
||||
{selectedProduct.brands && `Marca: ${selectedProduct.brands} • `}
|
||||
{selectedProduct.quantity && `Cantidad: ${selectedProduct.quantity} • `}
|
||||
{getSourceBadge(selectedProduct.source || 'openfoodfacts')}
|
||||
{selectedProduct.price != null && `Precio: ${selectedProduct.price}€ • `}
|
||||
{getSourceBadge(selectedProduct.source || 'parapharmacy')}
|
||||
{' '}
|
||||
{selectedProduct._id || selectedProduct.id}
|
||||
{selectedProduct._id || selectedProduct.id || selectedProduct.nregistro}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user