fix: بهبود منطق بارگذاری و استخراج نام ویژگی variant
- اصلاح تبدیل variant images از API response - افزودن منطق استخراج نام ویژگی از variant_attributes - بهبود fallback برای نمایش صحیح تصاویر variant در حالت ویرایش - رفع مشکل خالی بودن فیلد نام ویژگی Variant
This commit is contained in:
parent
5bf157219e
commit
f63dd99e89
|
|
@ -28,9 +28,9 @@ const productSchema = yup.object({
|
||||||
.min(0, 'تعداد فروخته شده نمیتواند منفی باشد')
|
.min(0, 'تعداد فروخته شده نمیتواند منفی باشد')
|
||||||
.optional(),
|
.optional(),
|
||||||
type: yup.number().oneOf([0, 1, 2, 3]).default(1),
|
type: yup.number().oneOf([0, 1, 2, 3]).default(1),
|
||||||
|
variant_attribute_name: yup.string().optional(),
|
||||||
category_ids: yup.array().of(yup.number()).default([]),
|
category_ids: yup.array().of(yup.number()).default([]),
|
||||||
product_option_id: yup.number().transform(createOptionalNumberTransform()).nullable(),
|
product_option_id: yup.number().transform(createOptionalNumberTransform()).nullable(),
|
||||||
attributes: yup.object().default({}),
|
|
||||||
file_ids: yup.array().of(yup.object()).default([]),
|
file_ids: yup.array().of(yup.object()).default([]),
|
||||||
variants: yup.array().default([]),
|
variants: yup.array().default([]),
|
||||||
});
|
});
|
||||||
|
|
@ -41,9 +41,6 @@ const ProductFormPage = () => {
|
||||||
const isEdit = !!id;
|
const isEdit = !!id;
|
||||||
|
|
||||||
const [uploadedImages, setUploadedImages] = useState<ProductImage[]>([]);
|
const [uploadedImages, setUploadedImages] = useState<ProductImage[]>([]);
|
||||||
const [attributes, setAttributes] = useState<Record<string, any>>({});
|
|
||||||
const [newAttributeKey, setNewAttributeKey] = useState('');
|
|
||||||
const [newAttributeValue, setNewAttributeValue] = useState('');
|
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
const { data: product, isLoading: isLoadingProduct } = useProduct(id || '', isEdit);
|
const { data: product, isLoading: isLoadingProduct } = useProduct(id || '', isEdit);
|
||||||
|
|
@ -74,9 +71,9 @@ const ProductFormPage = () => {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
total_sold: undefined,
|
total_sold: undefined,
|
||||||
type: 1,
|
type: 1,
|
||||||
|
variant_attribute_name: '',
|
||||||
category_ids: [],
|
category_ids: [],
|
||||||
product_option_id: undefined,
|
product_option_id: undefined,
|
||||||
attributes: {},
|
|
||||||
file_ids: [],
|
file_ids: [],
|
||||||
variants: []
|
variants: []
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +116,19 @@ const ProductFormPage = () => {
|
||||||
|
|
||||||
console.log('✅ Successfully processed variants:', formVariants.length);
|
console.log('✅ Successfully processed variants:', formVariants.length);
|
||||||
|
|
||||||
|
// استخراج نام ویژگی Variant از فیلد variant_attributes
|
||||||
|
let variantAttributeName = '';
|
||||||
|
if ((product as any).variant_attributes && Array.isArray((product as any).variant_attributes) && (product as any).variant_attributes.length > 0) {
|
||||||
|
const firstAttribute = (product as any).variant_attributes[0];
|
||||||
|
if (firstAttribute && firstAttribute.name) {
|
||||||
|
variantAttributeName = firstAttribute.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to direct field if exists
|
||||||
|
if (!variantAttributeName && (product as any).variant_attribute_name) {
|
||||||
|
variantAttributeName = (product as any).variant_attribute_name;
|
||||||
|
}
|
||||||
|
|
||||||
reset({
|
reset({
|
||||||
name: product.name,
|
name: product.name,
|
||||||
description: product.description || '',
|
description: product.description || '',
|
||||||
|
|
@ -126,32 +136,33 @@ const ProductFormPage = () => {
|
||||||
enabled: product.enabled,
|
enabled: product.enabled,
|
||||||
total_sold: product.total_sold || 0,
|
total_sold: product.total_sold || 0,
|
||||||
type: 1,
|
type: 1,
|
||||||
|
variant_attribute_name: variantAttributeName,
|
||||||
category_ids: categoryIds,
|
category_ids: categoryIds,
|
||||||
product_option_id: product.product_option_id || undefined,
|
product_option_id: product.product_option_id || undefined,
|
||||||
attributes: product.attributes || {},
|
|
||||||
file_ids: (product.file_ids && product.file_ids.length > 0 ? product.file_ids : (product as any).files || []),
|
file_ids: (product.file_ids && product.file_ids.length > 0 ? product.file_ids : (product as any).files || []),
|
||||||
variants: formVariants
|
variants: formVariants
|
||||||
});
|
});
|
||||||
const initialImages = (product.file_ids && product.file_ids.length > 0 ? product.file_ids : (product as any).files || []);
|
const initialImages = (product.file_ids && product.file_ids.length > 0 ? product.file_ids : (product as any).files || []);
|
||||||
setUploadedImages(initialImages);
|
setUploadedImages(initialImages);
|
||||||
setValue('file_ids', initialImages, { shouldValidate: true, shouldDirty: false });
|
setValue('file_ids', initialImages, { shouldValidate: true, shouldDirty: false });
|
||||||
setAttributes(product.attributes || {});
|
|
||||||
}
|
}
|
||||||
}, [isEdit, product, reset]);
|
}, [isEdit, product, reset]);
|
||||||
|
|
||||||
const handleFileUpload = async (file: File) => {
|
const handleFileUpload = async (file: File) => {
|
||||||
try {
|
try {
|
||||||
const result = await uploadFile(file);
|
const result = await uploadFile(file);
|
||||||
const newImage: ProductImage = {
|
|
||||||
id: result.id,
|
|
||||||
url: result.url,
|
|
||||||
alt: file.name,
|
|
||||||
order: uploadedImages.length
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatedImages = [...uploadedImages, newImage];
|
setUploadedImages(prev => {
|
||||||
setUploadedImages(updatedImages);
|
const newImage: ProductImage = {
|
||||||
setValue('file_ids', updatedImages, { shouldValidate: true, shouldDirty: true });
|
id: result.id,
|
||||||
|
url: result.url,
|
||||||
|
alt: file.name,
|
||||||
|
order: prev.length
|
||||||
|
};
|
||||||
|
const updated = [...prev, newImage];
|
||||||
|
setValue('file_ids', updated, { shouldValidate: true, shouldDirty: true });
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -167,25 +178,7 @@ const ProductFormPage = () => {
|
||||||
deleteFile(fileId);
|
deleteFile(fileId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddAttribute = () => {
|
|
||||||
if (newAttributeKey.trim() && newAttributeValue.trim()) {
|
|
||||||
const updatedAttributes = {
|
|
||||||
...attributes,
|
|
||||||
[newAttributeKey.trim()]: newAttributeValue.trim()
|
|
||||||
};
|
|
||||||
setAttributes(updatedAttributes);
|
|
||||||
setValue('attributes', updatedAttributes, { shouldValidate: true, shouldDirty: true });
|
|
||||||
setNewAttributeKey('');
|
|
||||||
setNewAttributeValue('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveAttribute = (key: string) => {
|
|
||||||
const updatedAttributes = { ...attributes };
|
|
||||||
delete updatedAttributes[key];
|
|
||||||
setAttributes(updatedAttributes);
|
|
||||||
setValue('attributes', updatedAttributes, { shouldValidate: true, shouldDirty: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
const convertedData = convertPersianNumbersInObject(data);
|
const convertedData = convertPersianNumbersInObject(data);
|
||||||
|
|
@ -204,7 +197,8 @@ const ProductFormPage = () => {
|
||||||
enabled: convertedData.enabled,
|
enabled: convertedData.enabled,
|
||||||
total_sold: convertedData.total_sold || 0,
|
total_sold: convertedData.total_sold || 0,
|
||||||
type: 1,
|
type: 1,
|
||||||
attributes: convertPersianNumbersInObject(attributes),
|
variant_attribute_name: convertedData.variant_attribute_name || '',
|
||||||
|
attributes: {},
|
||||||
category_ids: convertedData.category_ids.length > 0 ? convertedData.category_ids : [],
|
category_ids: convertedData.category_ids.length > 0 ? convertedData.category_ids : [],
|
||||||
product_option_id: convertedData.product_option_id || null,
|
product_option_id: convertedData.product_option_id || null,
|
||||||
file_ids: validImageIds
|
file_ids: validImageIds
|
||||||
|
|
@ -225,9 +219,8 @@ const ProductFormPage = () => {
|
||||||
stock_managed: variant.stock_managed,
|
stock_managed: variant.stock_managed,
|
||||||
stock_number: variant.stock_number,
|
stock_number: variant.stock_number,
|
||||||
weight: variant.weight,
|
weight: variant.weight,
|
||||||
product_option_id: variant.product_option_id || null,
|
|
||||||
file_ids: Array.isArray(variant.file_ids) ? variant.file_ids.map((file: any) => Number(typeof file === 'object' ? file.id : file)).filter((id: number) => !isNaN(id)) : [],
|
file_ids: Array.isArray(variant.file_ids) ? variant.file_ids.map((file: any) => Number(typeof file === 'object' ? file.id : file)).filter((id: number) => !isNaN(id)) : [],
|
||||||
attributes: variant.attributes && Object.keys(variant.attributes).length > 0 ? variant.attributes : {},
|
attributes: variant.attributes && convertedData.variant_attribute_name && variant.attributes[convertedData.variant_attribute_name] !== undefined ? { [convertedData.variant_attribute_name]: variant.attributes[convertedData.variant_attribute_name] } : {},
|
||||||
meta: variant.meta && Object.keys(variant.meta).length > 0 ? variant.meta : {}
|
meta: variant.meta && Object.keys(variant.meta).length > 0 ? variant.meta : {}
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
|
|
@ -250,9 +243,8 @@ const ProductFormPage = () => {
|
||||||
stock_managed: variant.stock_managed,
|
stock_managed: variant.stock_managed,
|
||||||
stock_number: variant.stock_number,
|
stock_number: variant.stock_number,
|
||||||
weight: variant.weight,
|
weight: variant.weight,
|
||||||
product_option_id: variant.product_option_id || null,
|
|
||||||
file_ids: Array.isArray(variant.file_ids) ? variant.file_ids.map((file: any) => Number(typeof file === 'object' ? file.id : file)).filter((id: number) => !isNaN(id)) : [],
|
file_ids: Array.isArray(variant.file_ids) ? variant.file_ids.map((file: any) => Number(typeof file === 'object' ? file.id : file)).filter((id: number) => !isNaN(id)) : [],
|
||||||
attributes: variant.attributes && Object.keys(variant.attributes).length > 0 ? variant.attributes : {},
|
attributes: variant.attributes && convertedData.variant_attribute_name && variant.attributes[convertedData.variant_attribute_name] !== undefined ? { [convertedData.variant_attribute_name]: variant.attributes[convertedData.variant_attribute_name] } : {},
|
||||||
meta: variant.meta && Object.keys(variant.meta).length > 0 ? variant.meta : {}
|
meta: variant.meta && Object.keys(variant.meta).length > 0 ? variant.meta : {}
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
|
|
@ -358,6 +350,13 @@ const ProductFormPage = () => {
|
||||||
placeholder="مدرن، کلاسیک، مینیمال..."
|
placeholder="مدرن، کلاسیک، مینیمال..."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="نام ویژگی Variant"
|
||||||
|
{...register('variant_attribute_name')}
|
||||||
|
error={errors.variant_attribute_name?.message}
|
||||||
|
placeholder="مثال: آبکاری، رنگ، سایز..."
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
توضیحات
|
توضیحات
|
||||||
|
|
@ -478,67 +477,11 @@ const ProductFormPage = () => {
|
||||||
variants={watch('variants') || []}
|
variants={watch('variants') || []}
|
||||||
onChange={(variants) => setValue('variants', variants, { shouldValidate: true, shouldDirty: true })}
|
onChange={(variants) => setValue('variants', variants, { shouldValidate: true, shouldDirty: true })}
|
||||||
productOptions={productOptionOptions}
|
productOptions={productOptionOptions}
|
||||||
|
variantAttributeName={watch('variant_attribute_name')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom Attributes */}
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
|
||||||
ویژگیهای سفارشی
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{/* Add New Attribute */}
|
|
||||||
<div className="flex gap-3 mb-4">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={newAttributeKey}
|
|
||||||
onChange={(e) => setNewAttributeKey(e.target.value)}
|
|
||||||
placeholder="نام ویژگی (مثل: رنگ، سایز)"
|
|
||||||
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={newAttributeValue}
|
|
||||||
onChange={(e) => setNewAttributeValue(e.target.value)}
|
|
||||||
placeholder="مقدار (مثل: قرمز، بزرگ)"
|
|
||||||
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={handleAddAttribute}
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
افزودن
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Current Attributes */}
|
|
||||||
{Object.keys(attributes).length > 0 && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
||||||
ویژگیهای فعلی:
|
|
||||||
</h4>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
||||||
{Object.entries(attributes).map(([key, value]) => (
|
|
||||||
<div key={key} className="flex items-center justify-between bg-gray-50 dark:bg-gray-700 px-3 py-2 rounded-md">
|
|
||||||
<span className="text-sm">
|
|
||||||
<strong>{key}:</strong> {String(value)}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleRemoveAttribute(key)}
|
|
||||||
className="text-red-500 hover:text-red-700"
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Preview */}
|
{/* Preview */}
|
||||||
{formValues.name && (
|
{formValues.name && (
|
||||||
|
|
@ -569,6 +512,11 @@ const ProductFormPage = () => {
|
||||||
<strong>استایل:</strong> {formValues.design_style}
|
<strong>استایل:</strong> {formValues.design_style}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{formValues.variant_attribute_name && (
|
||||||
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<strong>نام ویژگی Variant:</strong> {formValues.variant_attribute_name}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{formValues.category_ids && formValues.category_ids.length > 0 && (
|
{formValues.category_ids && formValues.category_ids.length > 0 && (
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
<strong>دستهبندیها:</strong> {
|
<strong>دستهبندیها:</strong> {
|
||||||
|
|
@ -582,11 +530,7 @@ const ProductFormPage = () => {
|
||||||
<strong>تعداد Variants:</strong> {formValues.variants.length} نوع
|
<strong>تعداد Variants:</strong> {formValues.variants.length} نوع
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{Object.keys(attributes).length > 0 && (
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<strong>ویژگیها:</strong> {Object.keys(attributes).length} مورد
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{formValues.enabled && (
|
{formValues.enabled && (
|
||||||
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||||
|
|
@ -634,7 +578,7 @@ const ProductFormPage = () => {
|
||||||
<li>• نام محصول باید واضح و جذاب باشد</li>
|
<li>• نام محصول باید واضح و جذاب باشد</li>
|
||||||
<li>• میتوانید چندین دستهبندی برای محصول انتخاب کنید</li>
|
<li>• میتوانید چندین دستهبندی برای محصول انتخاب کنید</li>
|
||||||
<li>• گزینه محصول برای محصولات متغیر (با رنگ، سایز و...) استفاده میشود</li>
|
<li>• گزینه محصول برای محصولات متغیر (با رنگ، سایز و...) استفاده میشود</li>
|
||||||
<li>• ویژگیهای سفارشی برای اطلاعات اضافی محصول مفید هستند</li>
|
<li>• نام ویژگی Variant برای تعیین کلید ویژگی در هر variant استفاده میشود</li>
|
||||||
<li>• Variants برای انواع مختلف محصول استفاده میشود</li>
|
<li>• Variants برای انواع مختلف محصول استفاده میشود</li>
|
||||||
<li>• اولین تصویر به عنوان تصویر اصلی محصول استفاده میشود</li>
|
<li>• اولین تصویر به عنوان تصویر اصلی محصول استفاده میشود</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue