From afab715b56e1f162ef0fe8b079b7a3f564eb9206 Mon Sep 17 00:00:00 2001 From: hossein taromi Date: Wed, 30 Jul 2025 17:18:22 +0330 Subject: [PATCH] feat: add image preview functionality and improve product detail layout in ProductDetailPage --- .../product-detail/ProductDetailPage.tsx | 845 +++++++++--------- .../products/product-form/ProductFormPage.tsx | 12 +- 2 files changed, 435 insertions(+), 422 deletions(-) diff --git a/src/pages/products/product-detail/ProductDetailPage.tsx b/src/pages/products/product-detail/ProductDetailPage.tsx index 02781ce..361fcd5 100644 --- a/src/pages/products/product-detail/ProductDetailPage.tsx +++ b/src/pages/products/product-detail/ProductDetailPage.tsx @@ -1,4 +1,6 @@ import { useParams, useNavigate } from 'react-router-dom'; +import { useState } from 'react'; +import { Modal } from '../../../components/ui/Modal'; import { ArrowRight, Edit, Package, Tag, Image, Calendar, FileText, Eye, DollarSign, Hash, Layers, Settings } from 'lucide-react'; import { Button } from '../../../components/ui/Button'; import { LoadingSpinner } from '../../../components/ui/LoadingSpinner'; @@ -20,6 +22,8 @@ const ProductDetailPage = () => { return new Intl.NumberFormat('fa-IR').format(price) + ' تومان'; }; + const [previewImage, setPreviewImage] = useState(null); + const formatNumber = (num: number) => { return new Intl.NumberFormat('fa-IR').format(num); }; @@ -30,486 +34,493 @@ const ProductDetailPage = () => { const variants = (product.variants && product.variants.length > 0) ? product.variants : ((product as any).product_variants || []); return ( - -
-
- -
- جزئیات محصول -

{product.name}

+ <> + +
+
+ +
+ جزئیات محصول +

{product.name}

+
+
+ +
+
-
- -
-
+
+ {/* اطلاعات اصلی */} +
+ {/* اطلاعات پایه */} +
+ + + اطلاعات محصول + -
- {/* اطلاعات اصلی */} -
- {/* اطلاعات پایه */} -
- - - اطلاعات محصول - - -
-
-
- -
-

- {product.name} -

-
-
- - {product.sku && ( +
+
-
-

- {product.sku} -

-
-
- )} -
- - {product.description && ( -
- -
-

- {product.description} -

-
-
- )} - - {product.design_style && ( -
- -
-

- {product.design_style} -

-
-
- )} - -
-
- -
-

- {PRODUCT_TYPE_LABELS[product.type as keyof typeof PRODUCT_TYPE_LABELS] || 'نامشخص'} -

-
-
- -
- -
- - {product.enabled ? 'فعال' : 'غیرفعال'} - -
-
- - {product.price && ( -
-

- {formatPrice(product.price)} + {product.name} +

+
+
+ + {product.sku && ( +
+ +
+

+ {product.sku} +

+
+
+ )} +
+ + {product.description && ( +
+ +
+

+ {product.description}

)} -
-
-
- {/* ویژگی‌های محصول */} - {product.attributes && Object.keys(product.attributes).length > 0 && ( -
- - - ویژگی‌های محصول - -
- {Object.entries(product.attributes).map(([key, value]) => ( -
-
- - {key} - - - {typeof value === 'object' ? JSON.stringify(value) : value} - + {product.design_style && ( +
+ +
+

+ {product.design_style} +

- ))} -
-
- )} + )} - {/* تصاویر محصول */} - {images.length > 0 && ( -
- - - تصاویر محصول - -
- {images.map((image: any, index: number) => ( -
- {image.alt -
- +
+
+ +
+

+ {PRODUCT_TYPE_LABELS[product.type as keyof typeof PRODUCT_TYPE_LABELS] || 'نامشخص'} +

- ))} -
-
- )} - {/* نسخه‌های محصول */} - {(variants.length > 0) && ( -
- - - نسخه‌های محصول - -
- {variants.map((variant, index) => ( -
-
-

- نسخه {index + 1} -

- + +
+ - {variant.enabled ? 'فعال' : 'غیرفعال'} + {product.enabled ? 'فعال' : 'غیرفعال'}
+
-
-
- درصد کارمزد - - {formatNumber(variant.fee_percentage)}% - + {product.price && ( +
+ +
+

+ {formatPrice(product.price)} +

-
- درصد سود - - {formatNumber(variant.profit_percentage)}% +
+ )} +
+
+
+ + {/* ویژگی‌های محصول */} + {product.attributes && Object.keys(product.attributes).length > 0 && ( +
+ + + ویژگی‌های محصول + +
+ {Object.entries(product.attributes).map(([key, value]) => ( +
+
+ + {key} -
-
- وزن - - {formatNumber(variant.weight)} گرم - -
-
- موجودی - - {formatNumber(variant.stock_number)} + + {typeof value === 'object' ? JSON.stringify(value) : value}
+ ))} +
+
+ )} -
-
- حد موجودی - - {formatNumber(variant.stock_limit)} - + {/* تصاویر محصول */} + {images.length > 0 && ( +
+ + + تصاویر محصول + +
+ {images.map((image: any, index: number) => ( +
+ {image.alt setPreviewImage(image.url)} + /> +
+
-
- مدیریت موجودی - + ))} +
+
+ )} + + {/* نسخه‌های محصول */} + {(variants.length > 0) && ( +
+ + + نسخه‌های محصول + +
+ {variants.map((variant, index) => ( +
+
+

+ نسخه {index + 1} +

+ - {variant.stock_managed ? 'فعال' : 'غیرفعال'} + {variant.enabled ? 'فعال' : 'غیرفعال'}
- {variant.product_option_id && ( -
- گزینه محصول - - شناسه: {variant.product_option_id} + +
+
+ درصد کارمزد + + {formatNumber(variant.fee_percentage)}%
+
+ درصد سود + + {formatNumber(variant.profit_percentage)}% + +
+
+ وزن + + {formatNumber(variant.weight)} گرم + +
+
+ موجودی + + {formatNumber(variant.stock_number)} + +
+
+ +
+
+ حد موجودی + + {formatNumber(variant.stock_limit)} + +
+
+ مدیریت موجودی + + {variant.stock_managed ? 'فعال' : 'غیرفعال'} + +
+ {variant.product_option_id && ( +
+ گزینه محصول + + شناسه: {variant.product_option_id} + +
+ )} +
+ + {/* ویژگی‌های نسخه */} + {variant.attributes && Object.keys(variant.attributes).length > 0 && ( +
+
+ ویژگی‌های نسخه +
+
+ {Object.entries(variant.attributes).map(([key, value]) => ( +
+ {key}: + + {typeof value === 'object' ? JSON.stringify(value) : value} + +
+ ))} +
+
+ )} + + {/* متاداده نسخه */} + {variant.meta && Object.keys(variant.meta).length > 0 && ( +
+
+ اطلاعات تکمیلی +
+
+ {Object.entries(variant.meta).map(([key, value]) => ( +
+ {key}: + + {typeof value === 'object' ? JSON.stringify(value) : value} + +
+ ))} +
+
+ )} + + {/* تصاویر نسخه */} + {(variant.file_ids && variant.file_ids.length > 0 || (variant as any).files?.length > 0) && ( +
+
+ تصاویر نسخه +
+
+ {((variant.file_ids && variant.file_ids.length > 0 ? variant.file_ids : (variant as any).files) as any[]).map((image: any, imgIndex: number) => ( + {image.alt setPreviewImage(image.url)} + /> + ))} +
+
)}
- - {/* ویژگی‌های نسخه */} - {variant.attributes && Object.keys(variant.attributes).length > 0 && ( -
-
- ویژگی‌های نسخه -
-
- {Object.entries(variant.attributes).map(([key, value]) => ( -
- {key}: - - {typeof value === 'object' ? JSON.stringify(value) : value} - -
- ))} -
-
- )} - - {/* متاداده نسخه */} - {variant.meta && Object.keys(variant.meta).length > 0 && ( -
-
- اطلاعات تکمیلی -
-
- {Object.entries(variant.meta).map(([key, value]) => ( -
- {key}: - - {typeof value === 'object' ? JSON.stringify(value) : value} - -
- ))} -
-
- )} - - {/* تصاویر نسخه */} - {variant.file_ids && variant.file_ids.length > 0 && ( -
-
- تصاویر نسخه -
-
- {variant.file_ids.map((image, imgIndex) => ( - {image.alt - ))} -
-
- )} -
- ))} + ))} +
-
- )} -
- - {/* اطلاعات جانبی */} -
- {/* آمار */} -
- - - آمار محصول - -
-
-
- - - شناسه محصول - -
- - {product.id} - -
- -
-
- - - تعداد فروش - -
- - {formatNumber(product.total_sold || 0)} - -
- - {variants.length > 0 && ( -
-
- - - تعداد نسخه‌ها - -
- - {formatNumber(variants.length)} - -
- )} - - {images.length > 0 && ( -
-
- - - تعداد تصاویر - -
- - {formatNumber(images.length)} - -
- )} - - {product.categories && product.categories.length > 0 && ( -
-
- - - تعداد دسته‌بندی‌ها - -
- - {formatNumber(product.categories.length)} - -
- )} -
+ )}
- {/* دسته‌بندی‌ها */} - {product.categories && product.categories.length > 0 && ( + {/* اطلاعات جانبی */} +
+ {/* آمار */}
- - دسته‌بندی‌ها + + آمار محصول -
- {product.categories.map((category) => ( -
- - - {category.name} +
+
+
+ + + شناسه محصول - {category.description && ( - - - {category.description} - - )}
- ))} -
-
- )} + + {product.id} + +
- {/* گزینه محصول */} - {product.product_option && ( -
- - - گزینه محصول - -
-

- {product.product_option.title} -

- {product.product_option.description && ( -

- {product.product_option.description} -

+
+
+ + + تعداد فروش + +
+ + {formatNumber(product.total_sold || 0)} + +
+ + {variants.length > 0 && ( +
+
+ + + تعداد نسخه‌ها + +
+ + {formatNumber(variants.length)} + +
+ )} + + {images.length > 0 && ( +
+
+ + + تعداد تصاویر + +
+ + {formatNumber(images.length)} + +
+ )} + + {product.categories && product.categories.length > 0 && ( +
+
+ + + تعداد دسته‌بندی‌ها + +
+ + {formatNumber(product.categories.length)} + +
)}
- )} - {/* اطلاعات زمانی */} -
- - - اطلاعات زمانی - -
-
-
- - - تاریخ ایجاد - + {/* دسته‌بندی‌ها */} + {product.categories && product.categories.length > 0 && ( +
+ + + دسته‌بندی‌ها + +
+ {product.categories.map((category) => ( +
+ + + {category.name} + + {category.description && ( + + - {category.description} + + )} +
+ ))}
-

- {new Date(product.created_at).toLocaleDateString('fa-IR')} -

+ )} -
-
- - - آخرین به‌روزرسانی - + {/* گزینه محصول */} + {product.product_option && ( +
+ + + گزینه محصول + +
+

+ {product.product_option.title} +

+ {product.product_option.description && ( +

+ {product.product_option.description} +

+ )} +
+
+ )} + + {/* اطلاعات زمانی */} +
+ + + اطلاعات زمانی + +
+
+
+ + + تاریخ ایجاد + +
+

+ {new Date(product.created_at).toLocaleDateString('fa-IR')} +

+
+ +
+
+ + + آخرین به‌روزرسانی + +
+

+ {new Date(product.updated_at).toLocaleDateString('fa-IR')} +

-

- {new Date(product.updated_at).toLocaleDateString('fa-IR')} -

-
- + + {previewImage && ( + setPreviewImage(null)} title="پیش‌نمایش تصویر" size="xl"> + تصویر + + )} + ); }; diff --git a/src/pages/products/product-form/ProductFormPage.tsx b/src/pages/products/product-form/ProductFormPage.tsx index 62f62aa..32786b9 100644 --- a/src/pages/products/product-form/ProductFormPage.tsx +++ b/src/pages/products/product-form/ProductFormPage.tsx @@ -100,8 +100,8 @@ const ProductFormPage = () => { console.log('✅ Successfully extracted category IDs:', categoryIds); // تبدیل variants از ProductVariant به ProductVariantFormData - handle both variants and product_variants - const variants = product.variants || product.product_variants || []; - const formVariants = variants.map(variant => ({ + const variants = product.variants && product.variants.length > 0 ? product.variants : ((product as any).product_variants || []); + const formVariants = variants.map((variant: any) => ({ id: variant.id, enabled: variant.enabled, fee_percentage: variant.fee_percentage, @@ -113,7 +113,7 @@ const ProductFormPage = () => { product_option_id: variant.product_option_id || undefined, attributes: variant.attributes || {}, meta: variant.meta || {}, - file_ids: variant.file_ids || [] + file_ids: (variant.file_ids && variant.file_ids.length > 0 ? variant.file_ids : (variant as any).files || []) })); console.log('✅ Successfully processed variants:', formVariants.length); @@ -128,10 +128,12 @@ const ProductFormPage = () => { category_ids: categoryIds, product_option_id: product.product_option_id || undefined, attributes: product.attributes || {}, - file_ids: product.file_ids || [], + file_ids: (product.file_ids && product.file_ids.length > 0 ? product.file_ids : (product as any).files || []), variants: formVariants }); - setUploadedImages(product.file_ids || []); + const initialImages = (product.file_ids && product.file_ids.length > 0 ? product.file_ids : (product as any).files || []); + setUploadedImages(initialImages); + setValue('file_ids', initialImages, { shouldValidate: true, shouldDirty: false }); setAttributes(product.attributes || {}); } }, [isEdit, product, reset]);