import React, { useState, useEffect, useMemo, useRef } from 'react'; import { useTranslation } from '../../i18n'; import './AdminComponents.css'; const MAX_PHARMACY_RESULTS = 25; function normalize(s) { return (s || '').toString().toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, ''); } function PharmacyProductLink() { const { t } = useTranslation(); const [pharmacies, setPharmacies] = useState([]); const [productSearch, setProductSearch] = useState(''); const [productResults, setProductResults] = useState([]); const [selectedPharmacy, setSelectedPharmacy] = useState(null); const [selectedProduct, setSelectedProduct] = useState(null); const [pharmacyProducts, setPharmacyProducts] = useState([]); const [loading, setLoading] = useState(false); const [searching, setSearching] = useState(false); const [pharmacyQuery, setPharmacyQuery] = useState(''); const [pharmacyDropdownOpen, setPharmacyDropdownOpen] = useState(false); const pharmacyInputRef = useRef(null); const [formData, setFormData] = useState({ pharmacy_id: '', price: '', stock: '' }); const filteredPharmacies = useMemo(() => { const q = normalize(pharmacyQuery).trim(); if (!q) return pharmacies.slice(0, MAX_PHARMACY_RESULTS); const tokens = q.split(/\s+/).filter(Boolean); return pharmacies .filter(p => { const hay = `${normalize(p.name)} ${normalize(p.address)}`; return tokens.every(tok => hay.includes(tok)); }) .slice(0, MAX_PHARMACY_RESULTS); }, [pharmacies, pharmacyQuery]); useEffect(() => { fetchPharmacies(); }, []); useEffect(() => { if (selectedPharmacy) { fetchPharmacyProducts(selectedPharmacy.id); } }, [selectedPharmacy]); // Buscar productos en ambas APIs mientras el usuario escribe useEffect(() => { const q = productSearch.trim(); if (q.length < 2) { setProductResults([]); setSearching(false); return; } const controller = new AbortController(); const timeoutId = setTimeout(async () => { setSearching(true); try { // Search both CIMA and Parapharmacy const [cimaRes, paraRes] = await Promise.allSettled([ fetch(`/api/products/search?q=${encodeURIComponent(q)}`, { credentials: 'include', signal: controller.signal, }), fetch(`/api/products/parapharmacy/search?q=${encodeURIComponent(q)}&limit=10`, { credentials: 'include', signal: controller.signal, }) ]); const cimaData = cimaRes.status === 'fulfilled' ? await cimaRes.value.json() : { results: [] }; const paraData = paraRes.status === 'fulfilled' ? await paraRes.value.json() : { results: [] }; // Handle both array and object responses const cimaResults = Array.isArray(cimaData) ? cimaData : (cimaData.results || []); const paraResults = Array.isArray(paraData) ? paraData : (paraData.results || []); const allResults = [ ...cimaResults, ...paraResults.map(p => ({ ...p, source: p.source || 'parapharmacy' })) ]; setProductResults(allResults); } catch (error) { if (error.name === 'AbortError') return; console.error('Error searching products:', error); } finally { if (!controller.signal.aborted) setSearching(false); } }, 500); return () => { clearTimeout(timeoutId); controller.abort(); }; }, [productSearch]); const fetchPharmacies = async () => { try { const response = await fetch('/api/pharmacies', { credentials: 'include', }); const data = await response.json(); setPharmacies(data); } catch (error) { console.error('Error fetching pharmacies:', error); } }; const fetchPharmacyProducts = async (pharmacyId) => { setLoading(true); try { const response = await fetch(`/api/admin/pharmacies/${pharmacyId}/products`, { credentials: 'include', }); const data = await response.json(); setPharmacyProducts(data); } catch (error) { console.error('Error fetching pharmacy products:', error); } finally { setLoading(false); } }; const handleSubmit = async (e) => { e.preventDefault(); if (!selectedProduct) { alert(t('admin.linkProduct.selectProductFirst')); return; } try { // 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: 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 }; const response = await fetch('/api/admin/pharmacy-products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload) }); if (!response.ok) throw new Error(t('admin.linkProduct.linkError')); resetForm(); if (selectedPharmacy) { fetchPharmacyProducts(selectedPharmacy.id); } alert(t('admin.linkProduct.linkSuccess')); } catch (error) { console.error('Error linking product:', error); alert(t('admin.linkProduct.linkError')); } }; const handleUpdate = async (id, price, stock) => { try { const response = await fetch(`/api/admin/pharmacy-products/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ price, stock }) }); if (!response.ok) throw new Error(t('admin.linkProduct.updateError')); fetchPharmacyProducts(selectedPharmacy.id); alert(t('admin.linkProduct.updateSuccess')); } catch (error) { console.error('Error updating:', error); alert(t('admin.linkProduct.updateError')); } }; const handleDelete = async (id) => { if (!confirm(t('admin.linkProduct.deleteConfirm'))) return; try { const response = await fetch(`/api/admin/pharmacy-products/${id}`, { method: 'DELETE', credentials: 'include' }); if (!response.ok) throw new Error(t('admin.linkProduct.deleteError')); fetchPharmacyProducts(selectedPharmacy.id); alert(t('admin.linkProduct.deleteSuccess')); } catch (error) { console.error('Error deleting:', error); alert(t('admin.linkProduct.deleteError')); } }; const resetForm = () => { setFormData({ pharmacy_id: selectedPharmacy ? selectedPharmacy.id.toString() : '', price: '', stock: '' }); setSelectedProduct(null); setProductSearch(''); setProductResults([]); }; const selectProduct = (product) => { setSelectedProduct(product); setProductSearch(product.product_name || product.name); setProductResults([]); }; const pickPharmacy = (pharmacy) => { setSelectedPharmacy(pharmacy); setFormData(prev => ({ ...prev, pharmacy_id: pharmacy.id.toString() })); setPharmacyQuery(`${pharmacy.name} — ${pharmacy.address}`); setPharmacyDropdownOpen(false); }; const clearPharmacy = () => { setSelectedPharmacy(null); setFormData(prev => ({ ...prev, pharmacy_id: '' })); setPharmacyQuery(''); setPharmacyDropdownOpen(true); setTimeout(() => pharmacyInputRef.current?.focus(), 0); }; const getSourceBadge = (source) => { 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 ( {labels[source] || source} ); }; return (

{t('admin.linkProduct.title')}

{ setPharmacyQuery(e.target.value); if (selectedPharmacy) { setSelectedPharmacy(null); setFormData(prev => ({ ...prev, pharmacy_id: '' })); } setPharmacyDropdownOpen(true); }} onFocus={() => setPharmacyDropdownOpen(true)} onBlur={() => setTimeout(() => setPharmacyDropdownOpen(false), 150)} placeholder={pharmacies.length ? `${t('admin.linkProduct.searchPharmacy')} ${pharmacies.length} ${t('admin.linkProduct.pharmacies')}` : t('admin.linkProduct.loadingPharmacies')} autoComplete="off" required={!selectedPharmacy} /> {!selectedPharmacy && pharmacyDropdownOpen && (
{filteredPharmacies.length === 0 ? (
{t('admin.linkProduct.noPharmacies')} "{pharmacyQuery}"
) : ( filteredPharmacies.map((pharmacy) => (
{ e.preventDefault(); pickPharmacy(pharmacy); }} > {pharmacy.name} {pharmacy.address}
)) )}
)} {selectedPharmacy && (

✅ Selected: {selectedPharmacy.name}

{selectedPharmacy.address}

)}
{ setProductSearch(e.target.value); setSelectedProduct(null); }} placeholder={t('admin.linkProduct.searchProduct')} required /> {searching &&

{t('admin.linkProduct.searching')}

} {productResults.length > 0 && !selectedProduct && (
{productResults.slice(0, 10).map((product) => (
selectProduct(product)} > {product.product_name || product.name} {product.brand && - {product.brand}} {product.brands && - {product.brands}} {product.price != null && - {product.price}€} {getSourceBadge(product.source || 'parapharmacy')}
))}
)} {selectedProduct && (

✅ Selected: {selectedProduct.product_name || selectedProduct.name}

{selectedProduct.brand && `${t('admin.linkProduct.brand')} ${selectedProduct.brand} • `} {selectedProduct.brands && `${t('admin.linkProduct.brand')} ${selectedProduct.brands} • `} {selectedProduct.price != null && `${t('admin.linkProduct.price')} ${selectedProduct.price}€ • `} {getSourceBadge(selectedProduct.source || 'parapharmacy')} {' '} {selectedProduct._id || selectedProduct.id || selectedProduct.nregistro}

)}
setFormData({ ...formData, price: e.target.value })} placeholder="e.g., 3.50" />
setFormData({ ...formData, stock: e.target.value })} placeholder="e.g., 100" />
{selectedPharmacy && (

{t('admin.linkProduct.productsIn')} {selectedPharmacy.name}

{loading ? (
{t('admin.linkProduct.loading')}
) : pharmacyProducts.length === 0 ? (

{t('admin.linkProduct.empty')}

) : (
{pharmacyProducts.map((pp) => (

{pp.product_name} {getSourceBadge(pp.product_source)}

{t('admin.linkProduct.priceLabel')} {pp.price ? `€${parseFloat(pp.price).toFixed(2)}` : t('admin.linkProduct.undefined')} • Stock: {pp.stock || 0}

))}
)}
)}
); } export default PharmacyProductLink;