636 lines
29 KiB
TypeScript
636 lines
29 KiB
TypeScript
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<VariantFormProps> = ({ variant, onSave, onCancel, isEdit = false, productOptions = [], variantAttributeName }) => {
|
||
const [formData, setFormData] = useState<ProductVariantFormData>(
|
||
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<ProductImage[]>(
|
||
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<Record<string, any>>(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 (
|
||
<div className="space-y-6 bg-gray-50 dark:bg-gray-700 p-6 rounded-lg border">
|
||
<div>
|
||
<h4 className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||
{isEdit ? 'ویرایش Variant' : 'افزودن Variant جدید'}
|
||
</h4>
|
||
</div>
|
||
|
||
{/* Basic Info */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
درصد کارمزد
|
||
</label>
|
||
<input
|
||
type="text"
|
||
inputMode="decimal"
|
||
value={feePercentageDisplay}
|
||
onChange={(e) => {
|
||
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="مثال: ۵.۲"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
درصد سود
|
||
</label>
|
||
<input
|
||
type="text"
|
||
inputMode="decimal"
|
||
value={profitPercentageDisplay}
|
||
onChange={(e) => {
|
||
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="مثال: ۱۰.۵"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
وزن (گرم)
|
||
</label>
|
||
<input
|
||
type="text"
|
||
inputMode="decimal"
|
||
value={weightDisplay}
|
||
onChange={(e) => {
|
||
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="مثال: ۱۲۰۰.۵"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
{/* Stock Management */}
|
||
<div>
|
||
<h5 className="text-md font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||
مدیریت موجودی
|
||
</h5>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<div className="flex items-center space-x-3 space-x-reverse">
|
||
<input
|
||
type="checkbox"
|
||
checked={formData.stock_managed}
|
||
onChange={(e) => 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"
|
||
/>
|
||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||
مدیریت موجودی فعال باشد
|
||
</label>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
تعداد موجودی
|
||
</label>
|
||
<input
|
||
type="text"
|
||
inputMode="numeric"
|
||
value={formData.stock_number || ''}
|
||
onChange={(e) => {
|
||
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}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
حد موجودی
|
||
</label>
|
||
<input
|
||
type="text"
|
||
inputMode="numeric"
|
||
value={formData.stock_limit || ''}
|
||
onChange={(e) => {
|
||
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}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Images */}
|
||
<div>
|
||
<h5 className="text-md font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||
تصاویر Variant
|
||
</h5>
|
||
<FileUploader
|
||
onUpload={handleFileUpload}
|
||
onRemove={handleFileRemove}
|
||
acceptedTypes={['image/*']}
|
||
maxFileSize={5 * 1024 * 1024}
|
||
maxFiles={5}
|
||
label=""
|
||
description="تصاویر مخصوص این variant را آپلود کنید"
|
||
/>
|
||
|
||
{uploadedImages.length > 0 && (
|
||
<div className="mt-4">
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||
{uploadedImages.map((image, index) => (
|
||
<div key={image.id} className="relative group">
|
||
<img
|
||
src={image.url}
|
||
alt={image.alt || `تصویر ${index + 1}`}
|
||
className="w-full h-20 object-cover rounded-lg border"
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleFileRemove(image.id)}
|
||
className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Variant Attribute */}
|
||
{variantAttributeName && (
|
||
<div>
|
||
<h5 className="text-md font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||
ویژگی Variant
|
||
</h5>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
{variantAttributeName}
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={variantAttributeValue}
|
||
onChange={(e) => 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 && (
|
||
<p className="text-red-500 text-xs mt-1">{attributeError}</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Meta Data */}
|
||
<div>
|
||
<h5 className="text-md font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||
Meta Data
|
||
</h5>
|
||
|
||
<div className="flex gap-3 mb-3">
|
||
<input
|
||
type="text"
|
||
value={newMetaKey}
|
||
onChange={(e) => 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"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={newMetaValue}
|
||
onChange={(e) => 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"
|
||
/>
|
||
<Button
|
||
type="button"
|
||
variant="secondary"
|
||
onClick={handleAddMeta}
|
||
className="flex items-center gap-2"
|
||
>
|
||
<Plus className="h-4 w-4" />
|
||
افزودن
|
||
</Button>
|
||
</div>
|
||
|
||
{Object.keys(meta).length > 0 && (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||
{Object.entries(meta).map(([key, value]) => (
|
||
<div key={key} className="flex items-center justify-between bg-white dark:bg-gray-600 px-3 py-2 rounded-md border">
|
||
<span className="text-sm">
|
||
<strong>{key}:</strong> {String(value)}
|
||
</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleRemoveMeta(key)}
|
||
className="text-red-500 hover:text-red-700"
|
||
>
|
||
<Trash2 className="h-3 w-3" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Status */}
|
||
<div className="flex items-center space-x-3 space-x-reverse">
|
||
<input
|
||
type="checkbox"
|
||
checked={formData.enabled}
|
||
onChange={(e) => handleInputChange('enabled', e.target.checked)}
|
||
className="w-4 h-4 text-primary-600 bg-gray-100 border-gray-300 rounded focus:ring-primary-500"
|
||
/>
|
||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||
Variant فعال باشد
|
||
</label>
|
||
</div>
|
||
|
||
{/* Action Buttons */}
|
||
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||
<Button variant="secondary" onClick={onCancel}>
|
||
انصراف
|
||
</Button>
|
||
<Button onClick={handleSave}>
|
||
{isEdit ? 'بهروزرسانی' : 'افزودن'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const VariantManager: React.FC<VariantManagerProps> = ({ variants, onChange, disabled = false, productOptions = [], variantAttributeName }) => {
|
||
const [showForm, setShowForm] = useState(false);
|
||
const [editingIndex, setEditingIndex] = useState<number | null>(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 (
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||
Variants محصول ({variants.length})
|
||
</h3>
|
||
{!disabled && !showForm && (
|
||
<Button onClick={handleAddVariant} className="flex items-center gap-2">
|
||
<Plus className="h-4 w-4" />
|
||
افزودن Variant
|
||
</Button>
|
||
)}
|
||
</div>
|
||
|
||
{/* Show Form */}
|
||
{showForm && (
|
||
<VariantForm
|
||
variant={editingIndex !== null ? variants[editingIndex] : undefined}
|
||
onSave={handleSaveVariant}
|
||
onCancel={handleCancelForm}
|
||
isEdit={editingIndex !== null}
|
||
productOptions={productOptions}
|
||
variantAttributeName={variantAttributeName}
|
||
/>
|
||
)}
|
||
|
||
{/* Variants List */}
|
||
{variants.length > 0 && (
|
||
<div className="space-y-3">
|
||
{variants.map((variant, index) => (
|
||
<div key={index} className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-4 mb-2">
|
||
<h4 className="font-medium text-gray-900 dark:text-gray-100">
|
||
Variant {index + 1}
|
||
</h4>
|
||
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${variant.enabled ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||
}`}>
|
||
{variant.enabled ? 'فعال' : 'غیرفعال'}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm text-gray-600 dark:text-gray-400">
|
||
<div>
|
||
<strong>درصد کارمزد:</strong> {variant.fee_percentage}%
|
||
</div>
|
||
<div>
|
||
<strong>درصد سود:</strong> {variant.profit_percentage}%
|
||
</div>
|
||
<div>
|
||
<strong>موجودی:</strong> {variant.stock_managed ? `${variant.stock_number} عدد` : 'بدون محدودیت'}
|
||
</div>
|
||
<div>
|
||
<strong>وزن:</strong> {parseFloat(variant.weight.toString()).toLocaleString('fa-IR')} گرم
|
||
</div>
|
||
</div>
|
||
|
||
{variant.file_ids && variant.file_ids.length > 0 && (
|
||
<div className="flex gap-2 mt-3">
|
||
{variant.file_ids.slice(0, 3).map((image, imgIndex) => (
|
||
<img
|
||
key={image.id}
|
||
src={image.url}
|
||
alt={image.alt || `تصویر ${imgIndex + 1}`}
|
||
className="w-12 h-12 object-cover rounded border"
|
||
/>
|
||
))}
|
||
{variant.file_ids.length > 3 && (
|
||
<div className="w-12 h-12 bg-gray-100 dark:bg-gray-600 rounded border flex items-center justify-center text-xs">
|
||
+{variant.file_ids.length - 3}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Show Attributes if any */}
|
||
{Object.keys(variant.attributes).length > 0 && (
|
||
<div className="mt-2">
|
||
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">ویژگیها:</div>
|
||
<div className="flex flex-wrap gap-1">
|
||
{Object.entries(variant.attributes).map(([key, value]) => (
|
||
<span key={key} className="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800">
|
||
{key}: {String(value)}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{!disabled && (
|
||
<div className="flex gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => handleEditVariant(index)}
|
||
className="p-2 text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md"
|
||
title="ویرایش"
|
||
>
|
||
<Edit3 className="h-4 w-4" />
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleDeleteVariant(index)}
|
||
className="p-2 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md"
|
||
title="حذف"
|
||
>
|
||
<Trash2 className="h-4 w-4" />
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{variants.length === 0 && !showForm && (
|
||
<div className="text-center py-8 bg-gray-50 dark:bg-gray-700 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600">
|
||
<Package className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
||
<p className="text-gray-500 dark:text-gray-400 mb-4">
|
||
هنوز هیچ Variant ای اضافه نشده
|
||
</p>
|
||
{!disabled && (
|
||
<Button onClick={handleAddVariant} className="flex items-center gap-2 mx-auto">
|
||
<Plus className="h-4 w-4" />
|
||
افزودن اولین Variant
|
||
</Button>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|