import React, { useState, useEffect } from 'react'; import { Plus, Trash2, Edit3, Package, X, Edit, Image as ImageIcon } from 'lucide-react'; import { ProductVariantFormData, ProductImage } from '../../pages/products/core/_models'; import { Button } from './Button'; import { FileUploader } from './FileUploader'; import { useFileUpload, useFileDelete } from '../../hooks/useFileUpload'; import { persianToEnglish, convertPersianNumbersInObject } from '../../utils/numberUtils'; import { API_GATE_WAY, API_ROUTES } from '@/constant/routes'; const toPublicUrl = (img: any): ProductImage => { const rawUrl: string = img?.url || ''; const serveKey: string | undefined = (img && img.serve_key) || undefined; const url = serveKey ? `${API_GATE_WAY}/${API_ROUTES.DOWNLOAD_FILE(serveKey)}` : rawUrl?.startsWith('http') ? rawUrl : rawUrl ? `${API_GATE_WAY}${rawUrl.startsWith('/') ? '' : '/'}${rawUrl}` : ''; return { id: (img?.id ?? img).toString(), url, alt: img?.alt || '', order: img?.order ?? 0, }; }; interface ProductOption { id: number; title: string; description?: string; } interface VariantManagerProps { variants: ProductVariantFormData[]; onChange: (variants: ProductVariantFormData[]) => void; disabled?: boolean; productOptions?: ProductOption[]; variantAttributeName?: string; } interface VariantFormProps { variant?: ProductVariantFormData; onSave: (variant: ProductVariantFormData) => void; onCancel: () => void; isEdit?: boolean; productOptions?: ProductOption[]; variantAttributeName?: string; } const VariantForm: React.FC = ({ variant, onSave, onCancel, isEdit = false, productOptions = [], variantAttributeName }) => { const [formData, setFormData] = useState( variant || { enabled: true, fee_percentage: 0, profit_percentage: 0, stock_limit: 0, stock_managed: true, stock_number: 0, weight: 0, attributes: {}, meta: {}, file_ids: [] } ); const [uploadedImages, setUploadedImages] = useState( Array.isArray(variant?.file_ids) && variant.file_ids.length > 0 && typeof variant.file_ids[0] === 'object' ? variant.file_ids.map(toPublicUrl) : [] ); const [variantAttributeValue, setVariantAttributeValue] = useState(''); const [meta, setMeta] = useState>(variant?.meta || {}); const [newMetaKey, setNewMetaKey] = useState(''); const [newMetaValue, setNewMetaValue] = useState(''); const [attributeError, setAttributeError] = useState(''); const [weightDisplay, setWeightDisplay] = useState(variant?.weight?.toString() || ''); const [feePercentageDisplay, setFeePercentageDisplay] = useState(variant?.fee_percentage?.toString() || ''); const [profitPercentageDisplay, setProfitPercentageDisplay] = useState(variant?.profit_percentage?.toString() || ''); const { mutateAsync: uploadFile } = useFileUpload(); const { mutate: deleteFile } = useFileDelete(); // Sync formData.file_ids with uploadedImages useEffect(() => { setFormData(prev => ({ ...prev, file_ids: uploadedImages })); }, [uploadedImages]); // Sync display states with formData when editing useEffect(() => { if (variant?.weight !== undefined) { setWeightDisplay(variant.weight.toString()); } if (variant?.fee_percentage !== undefined) { setFeePercentageDisplay(variant.fee_percentage.toString()); } if (variant?.profit_percentage !== undefined) { setProfitPercentageDisplay(variant.profit_percentage.toString()); } // Load variant attribute value if exists if (variantAttributeName && variant?.attributes && variant.attributes[variantAttributeName]) { setVariantAttributeValue(variant.attributes[variantAttributeName].toString()); } }, [variant?.weight, variant?.fee_percentage, variant?.profit_percentage, variant?.attributes, variantAttributeName]); const handleInputChange = (field: keyof ProductVariantFormData, value: any) => { if (typeof value === 'string') { value = persianToEnglish(value); } setFormData(prev => ({ ...prev, [field]: value })); }; const handleFileUpload = async (file: File) => { try { const result = await uploadFile(file); // Use functional update to avoid stale state when multiple files upload concurrently setUploadedImages(prev => { const newImage: ProductImage = { id: result.id, url: result.url, alt: file.name, order: prev.length }; return [...prev, newImage]; }); return result; } catch (error) { console.error('Upload error:', error); throw error; } }; const handleFileRemove = (fileId: string) => { const updatedImages = uploadedImages.filter(img => img.id !== fileId); setUploadedImages(updatedImages); deleteFile(fileId); }; const handleAddMeta = () => { if (newMetaKey.trim() && newMetaValue.trim()) { const updatedMeta = { ...meta, [newMetaKey.trim()]: newMetaValue.trim() }; setMeta(updatedMeta); setNewMetaKey(''); setNewMetaValue(''); } }; const handleRemoveMeta = (key: string) => { const updatedMeta = { ...meta }; delete updatedMeta[key]; setMeta(updatedMeta); }; const handleSave = () => { // Reset previous errors setAttributeError(''); // Validate attribute value when attribute name is defined if (variantAttributeName && !variantAttributeValue.trim()) { setAttributeError(`مقدار ${variantAttributeName} الزامی است.`); return; } // نگه داشتن آبجکت کامل تصویر برای نمایش در لیست و حالت ویرایش const fileObjects = uploadedImages; // Create attributes object with single key-value pair const attributes = variantAttributeName && variantAttributeValue.trim() ? { [variantAttributeName]: variantAttributeValue.trim() } : {}; const convertedData = convertPersianNumbersInObject({ ...formData, attributes, meta, file_ids: fileObjects }); onSave(convertedData); }; return (

{isEdit ? 'ویرایش Variant' : 'افزودن Variant جدید'}

{/* Basic Info */}
{ const converted = persianToEnglish(e.target.value); setFeePercentageDisplay(converted); }} onBlur={(e) => { const converted = persianToEnglish(e.target.value); const numValue = parseFloat(converted) || 0; handleInputChange('fee_percentage', numValue); }} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" placeholder="مثال: ۵.۲" />
{ const converted = persianToEnglish(e.target.value); setProfitPercentageDisplay(converted); }} onBlur={(e) => { const converted = persianToEnglish(e.target.value); const numValue = parseFloat(converted) || 0; handleInputChange('profit_percentage', numValue); }} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" placeholder="مثال: ۱۰.۵" />
{ const converted = persianToEnglish(e.target.value); setWeightDisplay(converted); }} onBlur={(e) => { const converted = persianToEnglish(e.target.value); const numValue = parseFloat(converted) || 0; handleInputChange('weight', numValue); }} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" placeholder="مثال: ۱۲۰۰.۵" />
{/* Stock Management */}
مدیریت موجودی
handleInputChange('stock_managed', e.target.checked)} className="w-4 h-4 text-primary-600 bg-gray-100 border-gray-300 rounded focus:ring-primary-500" />
{ const converted = persianToEnglish(e.target.value); handleInputChange('stock_number', parseInt(converted) || 0); }} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" placeholder="مثال: ۱۰۰" disabled={!formData.stock_managed} />
{ const converted = persianToEnglish(e.target.value); handleInputChange('stock_limit', parseInt(converted) || 0); }} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" placeholder="مثال: ۱۰" disabled={!formData.stock_managed} />
{/* Images */}
تصاویر Variant
{uploadedImages.length > 0 && (
{uploadedImages.map((image, index) => (
{image.alt
))}
)}
{/* Variant Attribute */} {variantAttributeName && (
ویژگی Variant
setVariantAttributeValue(e.target.value)} placeholder={`مقدار ${variantAttributeName} را وارد کنید`} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" /> {attributeError && (

{attributeError}

)}
)} {/* Meta Data */}
Meta Data
setNewMetaKey(e.target.value)} placeholder="کلید Meta" className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" /> setNewMetaValue(e.target.value)} placeholder="مقدار Meta" className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100" />
{Object.keys(meta).length > 0 && (
{Object.entries(meta).map(([key, value]) => (
{key}: {String(value)}
))}
)}
{/* Status */}
handleInputChange('enabled', e.target.checked)} className="w-4 h-4 text-primary-600 bg-gray-100 border-gray-300 rounded focus:ring-primary-500" />
{/* Action Buttons */}
); }; export const VariantManager: React.FC = ({ variants, onChange, disabled = false, productOptions = [], variantAttributeName }) => { const [showForm, setShowForm] = useState(false); const [editingIndex, setEditingIndex] = useState(null); const handleAddVariant = () => { setEditingIndex(null); setShowForm(true); }; const handleEditVariant = (index: number) => { setEditingIndex(index); setShowForm(true); }; const handleDeleteVariant = (index: number) => { const updatedVariants = variants.filter((_, i) => i !== index); onChange(updatedVariants); }; const handleSaveVariant = (variant: ProductVariantFormData) => { if (editingIndex !== null) { // Edit existing variant const updatedVariants = [...variants]; updatedVariants[editingIndex] = variant; onChange(updatedVariants); } else { // Add new variant onChange([...variants, variant]); } setShowForm(false); setEditingIndex(null); }; const handleCancelForm = () => { setShowForm(false); setEditingIndex(null); }; return (

Variants محصول ({variants.length})

{!disabled && !showForm && ( )}
{/* Show Form */} {showForm && ( )} {/* Variants List */} {variants.length > 0 && (
{variants.map((variant, index) => (

Variant {index + 1}

{variant.enabled ? 'فعال' : 'غیرفعال'}
درصد کارمزد: {variant.fee_percentage}%
درصد سود: {variant.profit_percentage}%
موجودی: {variant.stock_managed ? `${variant.stock_number} عدد` : 'بدون محدودیت'}
وزن: {parseFloat(variant.weight.toString()).toLocaleString('fa-IR')} گرم
{variant.file_ids && variant.file_ids.length > 0 && (
{variant.file_ids.slice(0, 3).map((image, imgIndex) => ( {image.alt ))} {variant.file_ids.length > 3 && (
+{variant.file_ids.length - 3}
)}
)} {/* Show Attributes if any */} {Object.keys(variant.attributes).length > 0 && (
ویژگی‌ها:
{Object.entries(variant.attributes).map(([key, value]) => ( {key}: {String(value)} ))}
)}
{!disabled && (
)}
))}
)} {variants.length === 0 && !showForm && (

هنوز هیچ Variant ای اضافه نشده

{!disabled && ( )}
)}
); };