import { useState, useEffect } from 'react'; import { Search, Filter, Box, Plus, Edit2, Trash2, Image as ImageIcon, X, HelpCircle } from 'lucide-react'; import { toast } from 'react-hot-toast'; import api, { BASE_DOMAIN } from '../services/api'; import Spinner from '../components/ui/Spinner'; import Pagination from '../components/ui/Pagination'; import MediaSelector from '../components/ui/MediaSelector'; export default function Products() { const [products, setProducts] = useState([]); const [categories, setCategories] = useState([]); const [isLoading, setIsLoading] = useState(true); // URL and String Helper Functions const getProductImageUrl = (url: string) => { if (!url) return ''; if (url.startsWith('http')) { const path = url.replace(/^https?:\/\/[^\/]+/, ''); return `${BASE_DOMAIN}${path}`; } return url.startsWith('/') ? `${BASE_DOMAIN}${url}` : `${BASE_DOMAIN}/${url}`; }; const getStoreProductUrl = (slug: string) => { const isDev = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; if (isDev) { return `http://localhost:4000/shop/${slug}`; } // Production fallback based on actual URL const storeOrigin = window.location.origin.replace('admin', 'www'); return `${storeOrigin}/shop/${slug}`; }; const slugify = (text: string) => { return text .toLowerCase() .trim() .replace(/\s+/g, '-') .replace(/[^a-z0-9\-]/g, '') .replace(/-+/g, '-') .replace(/^-|-$/g, ''); }; // Filters const [search, setSearch] = useState(''); const [categoryFilter, setCategoryFilter] = useState(''); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [imageErrors, setImageErrors] = useState>({}); const [mediaImageError, setMediaImageError] = useState(false); const limit = 10; // Modal State const [isModalOpen, setIsModalOpen] = useState(false); const [isMediaSelectorOpen, setIsMediaSelectorOpen] = useState(false); const [editingProduct, setEditingProduct] = useState(null); const [activeTab, setActiveTab] = useState('general'); const [formData, setFormData] = useState({ artNo: '', nameFa: '', nameEn: '', scientificTagline: '', description: '', shortDescription: '', categoryId: '', priceValue: 0, priceDisplay: '', unit: '', packageSize: 0, dosageLogic: '', suitableFor: 'سگ و گربه', imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: '', symptoms: [] as string[] }); const fetchData = async () => { try { setIsLoading(true); // Fetch products first const prodRes = await api.get('/admin/products', { params: { page, limit, search, categoryId: categoryFilter } }); if (prodRes.data?.data) { setProducts(prodRes.data.data); setTotalPages(prodRes.data.meta?.lastPage || 1); } // Fetch categories separately so it doesn't break products try { const catRes = await api.get('/admin/categories'); if (catRes.data?.data) { setCategories(catRes.data.data); } else if (Array.isArray(catRes.data)) { setCategories(catRes.data); } } catch (err) { console.warn('Could not fetch categories', err); } } catch (err: any) { console.error(err); toast.error('خطا در دریافت لیست محصولات: ' + (err.response?.data?.message || err.message)); } finally { setIsLoading(false); } }; useEffect(() => { const timer = setTimeout(() => { fetchData(); }, 500); return () => clearTimeout(timer); }, [search, categoryFilter, page]); const openModal = (product: any = null) => { setMediaImageError(false); if (product) { setEditingProduct(product); setFormData({ artNo: product.artNo, nameFa: product.nameFa || '', nameEn: product.nameEn || '', scientificTagline: product.scientificTagline || '', description: product.description || '', shortDescription: product.shortDescription || '', categoryId: product.categoryId || '', priceValue: Number(product.priceValue), priceDisplay: product.priceDisplay || '', unit: product.unit || '', packageSize: Number(product.packageSize), dosageLogic: product.dosageLogic || '', suitableFor: product.suitableFor || 'سگ و گربه', imageUrl: product.imageUrl || '', metaTitle: product.metaTitle || '', metaDescription: product.metaDescription || '', keywords: product.keywords || '', canonicalUrl: product.canonicalUrl || '', slug: product.slug || '', symptoms: product.symptoms ? product.symptoms.map((s: any) => s.symptom) : [] }); } else { setEditingProduct(null); setFormData({ artNo: '', nameFa: '', nameEn: '', scientificTagline: '', description: '', shortDescription: '', categoryId: '', priceValue: 0, priceDisplay: '', unit: '', packageSize: 0, dosageLogic: '', suitableFor: 'سگ و گربه', imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: '', symptoms: [] }); } setActiveTab('general'); setIsModalOpen(true); }; const handleSave = async (e: React.FormEvent) => { e.preventDefault(); try { const derivedSlug = formData.slug.trim() || slugify(formData.nameEn); const finalPayload = { ...formData, slug: derivedSlug, metaTitle: formData.metaTitle.trim() || `${formData.nameFa || 'نام محصول'} | خرید و قیمت مکمل کانینا`, metaDescription: formData.metaDescription.trim() || formData.shortDescription || 'خرید آنلاین مکمل اصل کانینا آلمان با بالاترین کیفیت بالینی...', canonicalUrl: formData.canonicalUrl.trim() || `/shop/${derivedSlug}` }; if (editingProduct) { await api.put(`/admin/products/${editingProduct.id}`, finalPayload); } else { await api.post('/admin/products', finalPayload); } setIsModalOpen(false); fetchData(); } catch (error) { console.error('Save failed', error); alert('خطا در ذخیره محصول'); } }; const handleDelete = async (id: string) => { if (!window.confirm('آیا از حذف این محصول مطمئن هستید؟')) return; try { await api.delete(`/admin/products/${id}`); fetchData(); } catch (error) { console.error('Delete failed', error); } }; return (

مدیریت محصولات

افزودن و ویرایش پیشرفته محصولات با تنظیمات سئو

{ setSearch(e.target.value); setPage(1); }} className="w-full pl-4 pr-12 py-3 rounded-xl border border-gray-200 focus:border-purple-500 focus:ring-2 focus:ring-purple-200 outline-none transition-all" />
{isLoading ? ( ) : products.length === 0 ? ( ) : ( products.map((product) => ( )) )}
تصویر کد (Art No) نام محصول دسته‌بندی موجودی / قیمت عملیات
محصولی یافت نشد
{product.imageUrl && !imageErrors[product.id] ? ( setImageErrors(prev => ({ ...prev, [product.id]: true }))} className="w-12 h-12 rounded-xl object-cover border border-gray-200" /> ) : (
)}
{product.artNo}
{product.nameFa || product.name || '-'} {product.nameEn && {product.nameEn}}
{product.category?.name || 'بدون دسته'}
{Number(product.priceValue).toLocaleString()} تومان {product.packageSize} موجودی
{!isLoading && totalPages > 1 && (
)}
{isModalOpen && (

{editingProduct ? 'ویرایش پیشرفته محصول' : 'افزودن محصول جدید'}

{editingProduct && formData.slug && ( مشاهده آنلاین در سایت 🔗 )}
{[ { id: 'general', label: 'اطلاعات پایه' }, { id: 'pricing', label: 'موجودی و قیمت' }, { id: 'seo', label: 'سئو (SEO)' }, { id: 'media', label: 'تصاویر' } ].map(tab => ( ))}
{/* General Tab */}
setFormData({...formData, artNo: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono" dir="ltr" />
setFormData({...formData, nameFa: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="rtl" placeholder="مثال: ژل دندان میکروسیلور" />
setFormData({...formData, nameEn: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono" dir="ltr" placeholder="e.g. Canina Mikrosilber Zahngel - 50ml" />
setFormData({...formData, scientificTagline: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
{formData.symptoms && formData.symptoms.map((symptom, idx) => ( {symptom} ))} { if (e.key === 'Enter') { e.preventDefault(); const input = e.currentTarget; const value = input.value.trim(); if (value && !formData.symptoms.includes(value)) { setFormData({ ...formData, symptoms: [...(formData.symptoms || []), value] }); input.value = ''; } } }} />

پس از نوشتن هر علامت درمانی (مثلاً «درد مفاصل»)، کلید Enter را فشار دهید.

{/* Pricing Tab */}
setFormData({...formData, priceValue: Number(e.target.value)})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="ltr" />
setFormData({...formData, priceDisplay: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
setFormData({...formData, packageSize: Number(e.target.value)})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="ltr" />
setFormData({...formData, unit: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
{/* SEO Tab */}
{(() => { const derivedSlug = formData.slug || slugify(formData.nameEn); const derivedMetaTitle = formData.metaTitle || `${formData.nameFa || 'نام محصول'} | خرید و قیمت مکمل کانینا`; const derivedMetaDescription = formData.metaDescription || (formData.shortDescription || 'خرید آنلاین مکمل اصل کانینا آلمان با بالاترین کیفیت بالینی و ضمانت اصالت فیزیکی...'); const derivedCanonicalUrl = formData.canonicalUrl || `/shop/${derivedSlug}`; return (
{/* Right Column: SEO Inputs */}
setFormData({...formData, slug: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="ltr" placeholder={derivedSlug} />
setFormData({...formData, metaTitle: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" placeholder={derivedMetaTitle} />
setFormData({...formData, keywords: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" placeholder="مثال: مکمل سگ, تقویت مفاصل, کنهیدروکس" />
setFormData({...formData, canonicalUrl: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-sm" dir="ltr" placeholder={derivedCanonicalUrl} />
{/* Left Column: Sticky Google SERP Preview */}
پیش‌نمایش نتایج در گوگل (Google SERP Preview)
{/* Domain / Breadcrumb */}
caninoiran.ir › shop › {derivedSlug}
{/* Title */}

{derivedMetaTitle}

{/* Description */}

{derivedMetaDescription}

تغییرات فیلدهای سمت راست، بلافاصله در پیش‌نمایش بالا منعکس می‌شود.

); })()}
{/* Media Tab */}
{formData.imageUrl && !mediaImageError ? (
setMediaImageError(true)} className="max-w-xs h-48 object-contain rounded-xl shadow-md border border-gray-100" />

{formData.imageUrl.split('/').pop()}

برای تغییر عکس، روی تصویر کلیک کنید یا دکمه زیر را بزنید

) : ( <>

تصویر محصول یافت نشد یا انتخاب نشده است

از گالری رسانه تصویر انتخاب کنید یا با Ctrl+V از کلیپ‌بورد بچسبانید

)}
)} {/* Media Selector Modal */} setIsMediaSelectorOpen(false)} onSelect={(url) => { setFormData({...formData, imageUrl: url}); setMediaImageError(false); }} selectedUrl={formData.imageUrl.startsWith('http') ? formData.imageUrl : formData.imageUrl ? `${BASE_DOMAIN}${formData.imageUrl}` : undefined} />
); }