diff --git a/src/config/Pages.ts b/src/config/Pages.ts
index 01eeaed..e5ab3be 100644
--- a/src/config/Pages.ts
+++ b/src/config/Pages.ts
@@ -128,5 +128,6 @@ export const Pages = {
create: "/products/create",
list: "/products/list",
variants: "/products/variants/",
+ update: "/products/update/",
},
};
diff --git a/src/index.css b/src/index.css
index 98fe4c7..632390c 100644
--- a/src/index.css
+++ b/src/index.css
@@ -16,7 +16,7 @@ body {
overflow-x: hidden !important;
background: #eceef6;
font-weight: 300;
- overflow: hidden;
+ overflow: hidden !important;
}
html,
body,
diff --git a/src/pages/products/List.tsx b/src/pages/products/List.tsx
index 2e0c451..b875ac8 100644
--- a/src/pages/products/List.tsx
+++ b/src/pages/products/List.tsx
@@ -8,7 +8,7 @@ import PaginationUi from '../../components/PaginationUi';
import StatusWithText from '../../components/StatusWithText';
import Td from '../../components/Td';
import TrashWithConfrim from '../../components/TrashWithConfrim';
-import { Edit, Eye } from 'iconsax-react';
+import { Edit } from 'iconsax-react';
import Button from '@/components/Button';
import { Link } from 'react-router-dom';
import { Pages } from '@/config/Pages';
@@ -126,9 +126,11 @@ const List: FC = () => {
|
-
-
-
+ {product.status === ProductStatus.Draft && (
+
+
+
+ )}
{
diff --git a/src/pages/products/Update.tsx b/src/pages/products/Update.tsx
new file mode 100644
index 0000000..f25fc42
--- /dev/null
+++ b/src/pages/products/Update.tsx
@@ -0,0 +1,209 @@
+import { type FC, useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { useGetProductById } from './hooks/useProductData';
+import { useUpdateProductDetail, useUpdateProductAttribute, useUpdateProduct } from './hooks/useProductData';
+import { useGetBrands } from '../brand/hooks/useBrandData';
+import { useGetCategories } from '../category/hooks/useCategoryData';
+import { StepIndicator, Step1Form, Step2Form, Step3Form } from './components';
+import { type CreateProductDetailRequestType, type CreateProductAttributeRequestType, type SaveProductRequestType } from './types/Types';
+import { toast } from 'react-toastify';
+import { Pages } from '@/config/Pages';
+import { extractErrorMessage } from '@/helpers/utils';
+
+const UpdateProduct: FC = () => {
+ const { id: productId } = useParams<{ id: string }>();
+ const productIdNum = parseInt(productId!, 10);
+ const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1);
+ const [productData, setProductData] = useState>({});
+ const navigate = useNavigate();
+
+ // Fetch existing product data
+ const { data: productDetails, isLoading: productLoading, error: productError } = useGetProductById(productId!);
+
+ // API hooks
+ const updateProductDetailMutation = useUpdateProductDetail();
+ const updateProductAttributeMutation = useUpdateProductAttribute();
+ const updateProductMutation = useUpdateProduct();
+ const { data: brandsData } = useGetBrands();
+ const { data: categoriesData, isLoading: categoriesLoading } = useGetCategories();
+
+ // Initialize product data when product details are loaded
+ useEffect(() => {
+ if (productDetails?.results?.product) {
+ const product = productDetails.results.product;
+ setProductData({
+ productId: product._id,
+ category: product.category._id,
+ brand: product.brand._id,
+ model: product.model,
+ title_fa: product.title_fa,
+ title_en: product.title_en,
+ seoTitle: product.seoTitle || '',
+ source: product.source,
+ isFake: product.isFake,
+ attributes: product.specifications?.map(spec => ({
+ id: 0, // This might need to be adjusted based on API
+ values: spec.values.map(() => 0) // This might need to be adjusted based on API
+ })) || [],
+ description: product.description,
+ metaDescription: product.metaDescription,
+ tags: product.tags,
+ advantages: product.advantages,
+ disAdvantages: product.disAdvantages,
+ imagesList: product.imagesUrl?.list || [],
+ coverImage: product.imagesUrl?.cover || '',
+ });
+ }
+ }, [productDetails]);
+
+ if (productLoading) {
+ return (
+
+ در حال بارگذاری اطلاعات محصول...
+
+ );
+ }
+
+ if (productError) {
+ return (
+
+ خطا در بارگذاری اطلاعات محصول
+
+ );
+ }
+
+ const handleStep1Submit = (data: CreateProductDetailRequestType) => {
+ const fullData = {
+ ...data,
+ productId: productIdNum
+ };
+
+ updateProductDetailMutation.mutate(fullData, {
+ onSuccess: () => {
+ setProductData(prev => ({ ...prev, ...data }));
+ setCurrentStep(2);
+ },
+ onError: (error) => {
+ toast.error(extractErrorMessage(error));
+ }
+ });
+ };
+
+ const handleStep2Submit = (data: Omit) => {
+ const fullData = {
+ ...data,
+ productId: productIdNum
+ };
+
+ updateProductAttributeMutation.mutate(fullData, {
+ onSuccess: () => {
+ setProductData(prev => ({ ...prev, ...data }));
+ setCurrentStep(3);
+ },
+ onError: (error) => {
+ toast.error(extractErrorMessage(error));
+ }
+ });
+ };
+
+ const handleStep3Submit = (data: Omit) => {
+ const fullData = {
+ ...data,
+ productId: productIdNum
+ };
+
+ updateProductMutation.mutate(fullData, {
+ onSuccess: () => {
+ toast.success('محصول با موفقیت بروزرسانی شد');
+ navigate(Pages.products.list);
+ },
+ onError: (error) => {
+ toast.error(extractErrorMessage(error));
+ }
+ });
+ };
+
+ const handlePreviousStep = () => {
+ if (currentStep > 1) {
+ setCurrentStep((prev) => (prev - 1) as 1 | 2 | 3);
+ }
+ };
+
+ const renderStep1 = () => {
+ const categories = categoriesData?.results?.data || [];
+ const brands = brandsData?.results?.brands || [];
+
+ if (categoriesLoading) {
+ return در حال بارگذاری دستهبندیها... ;
+ }
+
+ return (
+
+ );
+ };
+
+ const renderStep2 = () => (
+
+ );
+
+ const renderStep3 = () => (
+
+ );
+
+ return (
+
+
+ بروزرسانی محصول
+
+
+
+
+
+
+
+
+ {currentStep === 1 && renderStep1()}
+ {currentStep === 2 && renderStep2()}
+ {currentStep === 3 && renderStep3()}
+
+
+ );
+};
+
+export default UpdateProduct;
\ No newline at end of file
diff --git a/src/pages/products/components/Step1Form.tsx b/src/pages/products/components/Step1Form.tsx
index fbc2f8a..a29dbc0 100644
--- a/src/pages/products/components/Step1Form.tsx
+++ b/src/pages/products/components/Step1Form.tsx
@@ -1,4 +1,4 @@
-import { type FC, useState, useMemo } from 'react';
+import { type FC, useState, useMemo, useEffect } from 'react';
import Button from '../../../components/Button';
import Input from '../../../components/Input';
import Select from '../../../components/Select';
@@ -12,6 +12,7 @@ interface Step1FormProps {
brands: { _id: string; title_fa: string }[];
categories: CategoryTreeNode[];
loading: boolean;
+ initialData?: Partial;
}
// تابع برای پیدا کردن همه دستهبندیهای leaf از تمام سطحها
@@ -35,7 +36,7 @@ const findAllLeafCategories = (categories: CategoryTreeNode[]): CategoryTreeNode
};
-const Step1Form: FC = ({ onSubmit, brands, categories, loading }) => {
+const Step1Form: FC = ({ onSubmit, brands, categories, loading, initialData }) => {
const [formData, setFormData] = useState({
category: '',
brand: '',
@@ -47,6 +48,12 @@ const Step1Form: FC = ({ onSubmit, brands, categories, loading }
isFake: false
});
+ useEffect(() => {
+ if (initialData) {
+ setFormData(prev => ({ ...prev, ...initialData }));
+ }
+ }, [initialData]);
+
// پیدا کردن همه دستهبندیهای leaf
const leafCategories = useMemo(() => {
if (!categories || categories.length === 0) {
diff --git a/src/pages/products/components/Step2Form.tsx b/src/pages/products/components/Step2Form.tsx
index 42fbdc7..c29f708 100644
--- a/src/pages/products/components/Step2Form.tsx
+++ b/src/pages/products/components/Step2Form.tsx
@@ -1,4 +1,4 @@
-import { type FC, useState } from 'react';
+import { type FC, useState, useEffect } from 'react';
import Button from '../../../components/Button';
import Input from '../../../components/Input';
import Textarea from '../../../components/Textarea';
@@ -66,6 +66,7 @@ interface Step2FormProps {
loading: boolean;
onPrevious: () => void;
categoryId?: string;
+ initialData?: Partial>;
}
// کامپوننت مدیریت تگها
@@ -197,7 +198,7 @@ const ListInput: FC = ({ items, onChange, placeholder, label })
);
};
-const Step2Form: FC = ({ onSubmit, loading, onPrevious, categoryId }) => {
+const Step2Form: FC = ({ onSubmit, loading, onPrevious, categoryId, initialData }) => {
const [formData, setFormData] = useState>({
attributes: [],
description: '',
@@ -207,6 +208,12 @@ const Step2Form: FC = ({ onSubmit, loading, onPrevious, category
disAdvantages: []
});
+ useEffect(() => {
+ if (initialData) {
+ setFormData(prev => ({ ...prev, ...initialData }));
+ }
+ }, [initialData]);
+
const { data: categoryAttributesData } = useGetCategoryAttributes(categoryId || '');
const handleAttributeChange = (attributeId: number, values: number[]) => {
diff --git a/src/pages/products/components/Step3Form.tsx b/src/pages/products/components/Step3Form.tsx
index a756dde..3efee33 100644
--- a/src/pages/products/components/Step3Form.tsx
+++ b/src/pages/products/components/Step3Form.tsx
@@ -1,4 +1,4 @@
-import { type FC, useState } from 'react';
+import { type FC, useState, useEffect } from 'react';
import Button from '../../../components/Button';
import UploadBoxDraggble from '../../../components/UploadBoxDraggble';
import UploadBox from '../../../components/UploadBox';
@@ -9,15 +9,23 @@ interface Step3FormProps {
onSubmit: (data: Omit) => void;
loading: boolean;
onPrevious: () => void;
+ initialData?: Partial>;
}
-const Step3Form: FC = ({ onSubmit, loading, onPrevious }) => {
+const Step3Form: FC = ({ onSubmit, loading, onPrevious, initialData }) => {
const [uploadedImages, setUploadedImages] = useState([]);
const [selectedFiles, setSelectedFiles] = useState([]);
const [coverImage, setCoverImage] = useState('');
const [selectedCoverFile, setSelectedCoverFile] = useState(null);
+ useEffect(() => {
+ if (initialData) {
+ setUploadedImages(initialData.imagesList || []);
+ setCoverImage(initialData.coverImage || '');
+ }
+ }, [initialData]);
+
const uploadMultipleMutation = useUploadMultiple();
const handleFileSelect = (files: File[]) => {
@@ -149,7 +157,7 @@ const Step3Form: FC = ({ onSubmit, loading, onPrevious }) => {
-
+
diff --git a/src/pages/products/hooks/useProductData.ts b/src/pages/products/hooks/useProductData.ts
index 6261657..62bb75b 100644
--- a/src/pages/products/hooks/useProductData.ts
+++ b/src/pages/products/hooks/useProductData.ts
@@ -4,7 +4,10 @@ import {
type CreateProductDetailRequestType,
type CreateProductAttributeRequestType,
type SaveProductRequestType,
- type UpdateVariantRequestType
+ type UpdateVariantRequestType,
+ type UpdateProductDetailRequestType,
+ type UpdateProductAttributeRequestType,
+ type UpdateProductRequestType
} from "../types/Types";
export const useCreateProductDetail = () => {
@@ -66,4 +69,22 @@ export const useGetVariantById = (productId: string, variantId: string) => {
queryFn: () => api.getVariantById(productId, variantId),
enabled: !!productId && !!variantId,
});
+};
+
+export const useUpdateProductDetail = () => {
+ return useMutation({
+ mutationFn: (variables: UpdateProductDetailRequestType) => api.updateProductDetail(variables),
+ });
+};
+
+export const useUpdateProductAttribute = () => {
+ return useMutation({
+ mutationFn: (variables: UpdateProductAttributeRequestType) => api.updateProductAttribute(variables),
+ });
+};
+
+export const useUpdateProduct = () => {
+ return useMutation({
+ mutationFn: (variables: UpdateProductRequestType) => api.updateProduct(variables),
+ });
};
\ No newline at end of file
diff --git a/src/pages/products/service/ProductService.ts b/src/pages/products/service/ProductService.ts
index b326b3a..2c0b45a 100644
--- a/src/pages/products/service/ProductService.ts
+++ b/src/pages/products/service/ProductService.ts
@@ -12,6 +12,12 @@ import {
type GetProductByIdResponseType,
type UpdateVariantRequestType,
type GetVariantByIdResponseType,
+ type UpdateProductDetailRequestType,
+ type UpdateProductDetailResponseType,
+ type UpdateProductAttributeRequestType,
+ type UpdateProductAttributeResponseType,
+ type UpdateProductRequestType,
+ type UpdateProductResponseType,
} from "../types/Types";
export const createProductDetail = async (params: CreateProductDetailRequestType): Promise => {
@@ -59,4 +65,19 @@ export const updateVariant = async (params: UpdateVariantRequestType) => {
export const getVariantById = async (productId: string, variantId: string): Promise => {
const { data } = await axios.get(`/admin/products/${productId}/variants/${variantId}`);
return data;
+};
+
+export const updateProductDetail = async (params: UpdateProductDetailRequestType): Promise => {
+ const { data } = await axios.patch(`/admin/products/update/detail`, params);
+ return data;
+};
+
+export const updateProductAttribute = async (params: UpdateProductAttributeRequestType): Promise => {
+ const { data } = await axios.patch(`/admin/products/update/attribute`, params);
+ return data;
+};
+
+export const updateProduct = async (params: UpdateProductRequestType): Promise => {
+ const { data } = await axios.patch(`/admin/products/update/save`, params);
+ return data;
};
\ No newline at end of file
diff --git a/src/pages/products/types/Types.ts b/src/pages/products/types/Types.ts
index ca2f1c8..b95f2fc 100644
--- a/src/pages/products/types/Types.ts
+++ b/src/pages/products/types/Types.ts
@@ -372,3 +372,48 @@ export type UpdateVariantRequestType = {
specialSale_quantity: number;
specialSale_endDate: string;
};
+
+// تایپهای مربوط به آپدیت محصول سه مرحلهای
+
+// مرحله اول: آپدیت جزئیات محصول
+export type UpdateProductDetailRequestType = {
+ productId: number;
+} & Omit & {
+ category?: string;
+ brand?: string;
+ };
+
+export type UpdateProductDetailResponseType = {
+ status: number;
+ success: boolean;
+ results: {
+ message: string;
+ product: DraftProductType;
+ };
+};
+
+// مرحله دوم: آپدیت ویژگیها و توضیحات
+export type UpdateProductAttributeRequestType = {
+ productId: number;
+} & CreateProductAttributeRequestType;
+
+export type UpdateProductAttributeResponseType = {
+ status: number;
+ success: boolean;
+ results: {
+ success: boolean;
+ };
+};
+
+// مرحله سوم: آپدیت تصاویر و ذخیره نهایی
+export type UpdateProductRequestType = {
+ productId: number;
+} & SaveProductRequestType;
+
+export type UpdateProductResponseType = {
+ status: number;
+ success: boolean;
+ results: {
+ success: boolean;
+ };
+};
diff --git a/src/router/Main.tsx b/src/router/Main.tsx
index 15f18b4..a20e3b3 100644
--- a/src/router/Main.tsx
+++ b/src/router/Main.tsx
@@ -13,6 +13,7 @@ import Variant from '../pages/category/Variant'
import Attributes from '../pages/category/Attributes'
import CreateProduct from '../pages/products/Create'
import ListProduct from '../pages/products/List'
+import UpdateProduct from '../pages/products/Update'
import ProductVariant from '@/pages/products/ProductVariant'
const MainRouter: FC = () => {
@@ -39,6 +40,7 @@ const MainRouter: FC = () => {
} />
} />
} />
+ } />
} />
|