navigate back
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-29 10:43:01 +03:30
parent d12b36d8dc
commit 901a1b5dd2
3 changed files with 246 additions and 270 deletions
+136 -153
View File
@@ -1,164 +1,147 @@
import { type FC, useState } from 'react'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { TickCircle } from 'iconsax-react'
import { toast } from 'react-toastify'
import Button from '@/components/Button'
import CreateProductSidebar from './components/CreateProductSidebar'
import BasicInfoSection from './components/BasicInfoSection'
import type { CreateProductType } from './types/Types'
import { useCreateProduct, useGetCategories } from './hooks/useProductData'
import { useMultipleUpload } from '../uploader/hooks/useUploaderData'
import { Pages } from '@/config/Pages'
import type { ErrorType } from '@/helpers/types'
import { PaymentType, PricingType, PurchaseUnit } from './enum/Enum'
import Button from "@/components/Button";
import type { ErrorType } from "@/helpers/types";
import { useFormik } from "formik";
import { TickCircle } from "iconsax-react";
import { type FC, useState } from "react";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import * as Yup from "yup";
import { useMultipleUpload } from "../uploader/hooks/useUploaderData";
import BasicInfoSection from "./components/BasicInfoSection";
import CreateProductSidebar from "./components/CreateProductSidebar";
import { PaymentType, PricingType, PurchaseUnit } from "./enum/Enum";
import { useCreateProduct, useGetCategories } from "./hooks/useProductData";
import type { CreateProductType } from "./types/Types";
const CreateProduct: FC = () => {
const navigate = useNavigate();
const { data } = useGetCategories();
const [isActive, setIsActive] = useState<boolean>(true);
const [isSpecial, setIsSpecial] = useState<boolean>(false);
const [imageFiles, setImageFiles] = useState<File[]>([]);
const { mutate: createProduct, isPending } = useCreateProduct();
const { mutate: multipleUpload, isPending: isUploading } = useMultipleUpload();
const { data } = useGetCategories();
const [isActive, setIsActive] = useState<boolean>(true)
const [isSpecial, setIsSpecial] = useState<boolean>(false)
const [imageFiles, setImageFiles] = useState<File[]>([])
const { mutate: createProduct, isPending } = useCreateProduct()
const { mutate: multipleUpload, isPending: isUploading } = useMultipleUpload()
const categories = data?.data ?? [];
const categories = data?.data ?? []
const formik = useFormik<CreateProductType>({
initialValues: {
categoryId: '',
title: '',
attribute: '',
variants: [],
desc: '',
price: 0,
isActive: true,
images: [],
discount: 0,
isSpecialOffer: false,
order: 0,
stock: undefined,
maxPurchaseQuantity: undefined,
minPurchaseQuantity: undefined,
purchasePitch: undefined,
pricingType: PricingType.FIXED,
paymentType: PaymentType.INSTANT,
purchaseUnit: PurchaseUnit.NUMBER,
needAdminAcceptance: false,
},
validationSchema: Yup.object().shape({
title: Yup.string().required('نام کالا الزامی است'),
categoryId: Yup.string().required('دسته‌بندی الزامی است'),
price: Yup.number().required('قیمت الزامی است').min(0, 'قیمت باید مثبت باشد'),
discount: Yup.number().min(0, 'تخفیف باید مثبت باشد'),
attribute: Yup.string(),
pricingType: Yup.string().required(),
paymentType: Yup.string().required(),
minPurchaseQuantity: Yup.number().when('pricingType', {
is: PricingType.VARIABLE,
then: (schema) => schema.min(0, 'حداقل مقدار باید مثبت باشد'),
}),
maxPurchaseQuantity: Yup.number().min(0, 'حداکثر مقدار باید مثبت باشد'),
purchasePitch: Yup.number().when('pricingType', {
is: PricingType.VARIABLE,
then: (schema) => schema.min(0, 'گام تغییر باید مثبت باشد'),
}),
variants: Yup.array().of(
Yup.object().shape({
id: Yup.string(),
value: Yup.string(),
price: Yup.number().min(0)
})
)
const formik = useFormik<CreateProductType>({
initialValues: {
categoryId: "",
title: "",
attribute: "",
variants: [],
desc: "",
price: 0,
isActive: true,
images: [],
discount: 0,
isSpecialOffer: false,
order: 0,
stock: undefined,
maxPurchaseQuantity: undefined,
minPurchaseQuantity: undefined,
purchasePitch: undefined,
pricingType: PricingType.FIXED,
paymentType: PaymentType.INSTANT,
purchaseUnit: PurchaseUnit.NUMBER,
needAdminAcceptance: false,
},
validationSchema: Yup.object().shape({
title: Yup.string().required("نام کالا الزامی است"),
categoryId: Yup.string().required("دسته‌بندی الزامی است"),
price: Yup.number().required("قیمت الزامی است").min(0, "قیمت باید مثبت باشد"),
discount: Yup.number().min(0, "تخفیف باید مثبت باشد"),
attribute: Yup.string(),
pricingType: Yup.string().required(),
paymentType: Yup.string().required(),
minPurchaseQuantity: Yup.number().when("pricingType", {
is: PricingType.VARIABLE,
then: (schema) => schema.min(0, "حداقل مقدار باید مثبت باشد"),
}),
maxPurchaseQuantity: Yup.number().min(0, "حداکثر مقدار باید مثبت باشد"),
purchasePitch: Yup.number().when("pricingType", {
is: PricingType.VARIABLE,
then: (schema) => schema.min(0, "گام تغییر باید مثبت باشد"),
}),
variants: Yup.array().of(
Yup.object().shape({
id: Yup.string(),
value: Yup.string(),
price: Yup.number().min(0),
}),
onSubmit: (values) => {
const submitProduct = (imageUrls: string[] = []) => {
const variants =
values.variants.length > 0
? values.variants
: [{ value: '', price: values.price, ...(values.stock !== undefined && values.stock !== null && { stock: values.stock }) }]
const productData: CreateProductType = {
...values,
variants,
isActive,
isSpecialOffer: isSpecial,
images: imageUrls,
needAdminAcceptance: values.paymentType === PaymentType.AFTER_PREPARATION,
...(values.pricingType === PricingType.FIXED && {
purchaseUnit: values.purchaseUnit ?? PurchaseUnit.NUMBER,
minPurchaseQuantity: values.minPurchaseQuantity ?? 1,
purchasePitch: undefined,
}),
}
),
}),
onSubmit: (values) => {
const submitProduct = (imageUrls: string[] = []) => {
const variants = values.variants.length > 0 ? values.variants : [{ value: "", price: values.price, ...(values.stock !== undefined && values.stock !== null && { stock: values.stock }) }];
const productData: CreateProductType = {
...values,
variants,
isActive,
isSpecialOffer: isSpecial,
images: imageUrls,
needAdminAcceptance: values.paymentType === PaymentType.AFTER_PREPARATION,
...(values.pricingType === PricingType.FIXED && {
purchaseUnit: values.purchaseUnit ?? PurchaseUnit.NUMBER,
minPurchaseQuantity: values.minPurchaseQuantity ?? 1,
purchasePitch: undefined,
}),
};
createProduct(productData, {
onSuccess: () => {
toast.success('کالا با موفقیت ایجاد شد')
window.location.href = Pages.products.list
},
onError: (error: ErrorType) => {
toast.error(error?.response?.data?.error?.message[0] || 'خطا در ایجاد کالا')
}
})
}
createProduct(productData, {
onSuccess: () => {
toast.success("کالا با موفقیت ایجاد شد");
navigate(-1);
},
onError: (error: ErrorType) => {
toast.error(error?.response?.data?.error?.message[0] || "خطا در ایجاد کالا");
},
});
};
if (imageFiles.length > 0) {
multipleUpload(imageFiles, {
onSuccess: (response) => {
const imageUrls = response?.data?.map(item => item.url) || []
submitProduct(imageUrls)
},
onError: (error: ErrorType) => {
toast.error(error?.response?.data?.error?.message[0] || 'خطا در آپلود تصاویر')
}
})
} else {
submitProduct()
}
}
})
if (imageFiles.length > 0) {
multipleUpload(imageFiles, {
onSuccess: (response) => {
const imageUrls = response?.data?.map((item) => item.url) || [];
submitProduct(imageUrls);
},
onError: (error: ErrorType) => {
toast.error(error?.response?.data?.error?.message[0] || "خطا در آپلود تصاویر");
},
});
} else {
submitProduct();
}
},
});
return (
<div className='w-full mt-4'>
<div className='flex flex-col sm:flex-row w-full justify-between items-start sm:items-center gap-4'>
<div className='text-lg font-light'>
کالای جدید
</div>
<div>
<Button
className='px-5 w-full sm:w-auto'
onClick={() => formik.handleSubmit()}
isloading={isPending || isUploading}
>
<div className='flex gap-2 items-center'>
<TickCircle className='size-5' color='white' />
<div>ثبت کالا</div>
</div>
</Button>
</div>
</div>
<div className='flex flex-col lg:flex-row gap-6 mt-6'>
<div className='flex-1'>
<div className='bg-white py-6 sm:py-8 xl:px-10 px-4 rounded-3xl'>
<BasicInfoSection formik={formik} categories={categories} />
</div>
</div>
<div className='w-full lg:w-auto'>
<CreateProductSidebar
isActive={isActive}
isSpecial={isSpecial}
onChangeActive={setIsActive}
onChangeSpecial={setIsSpecial}
onChangeImages={setImageFiles}
/>
</div>
return (
<div className="w-full mt-4">
<div className="flex flex-col sm:flex-row w-full justify-between items-start sm:items-center gap-4">
<div className="text-lg font-light">کالای جدید</div>
<div>
<Button className="px-5 w-full sm:w-auto" onClick={() => formik.handleSubmit()} isloading={isPending || isUploading}>
<div className="flex gap-2 items-center">
<TickCircle className="size-5" color="white" />
<div>ثبت کالا</div>
</div>
</Button>
</div>
)
}
</div>
export default CreateProduct
<div className="flex flex-col lg:flex-row gap-6 mt-6">
<div className="flex-1">
<div className="bg-white py-6 sm:py-8 xl:px-10 px-4 rounded-3xl">
<BasicInfoSection formik={formik} categories={categories} />
</div>
</div>
<div className="w-full lg:w-auto">
<CreateProductSidebar isActive={isActive} isSpecial={isSpecial} onChangeActive={setIsActive} onChangeSpecial={setIsSpecial} onChangeImages={setImageFiles} />
</div>
</div>
</div>
);
};
export default CreateProduct;
+108 -115
View File
@@ -1,44 +1,43 @@
import { type FC, useState, useEffect } from 'react'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { TickCircle } from 'iconsax-react'
import { toast } from 'react-toastify'
import { useNavigate, useParams } from 'react-router-dom'
import Button from '@/components/Button'
import CreateProductSidebar from './components/CreateProductSidebar'
import BasicInfoSection from './components/BasicInfoSection'
import type { CreateProductType } from './types/Types'
import { useQueryClient } from '@tanstack/react-query'
import { useUpdateProduct, useGetProductDetails, useGetCategories } from './hooks/useProductData'
import { useMultipleUpload } from '../uploader/hooks/useUploaderData'
import { Pages } from '@/config/Pages'
import type { ErrorType } from '@/helpers/types'
import { extractErrorMessage } from '@/config/func'
import { PaymentType, PricingType, PurchaseUnit } from './enum/Enum'
import Button from "@/components/Button";
import { extractErrorMessage } from "@/config/func";
import type { ErrorType } from "@/helpers/types";
import { useQueryClient } from "@tanstack/react-query";
import { useFormik } from "formik";
import { TickCircle } from "iconsax-react";
import { type FC, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import * as Yup from "yup";
import { useMultipleUpload } from "../uploader/hooks/useUploaderData";
import BasicInfoSection from "./components/BasicInfoSection";
import CreateProductSidebar from "./components/CreateProductSidebar";
import { PaymentType, PricingType, PurchaseUnit } from "./enum/Enum";
import { useGetCategories, useGetProductDetails, useUpdateProduct } from "./hooks/useProductData";
import type { CreateProductType } from "./types/Types";
const UpdateProduct: FC = () => {
const { id } = useParams<{ id: string }>()
const queryClient = useQueryClient()
const { data: productData, isLoading } = useGetProductDetails(id || '')
const { data: categoriesData } = useGetCategories()
const [isActive, setIsActive] = useState<boolean>(true)
const [isSpecial, setIsSpecial] = useState<boolean>(false)
const [imageFiles, setImageFiles] = useState<File[]>([])
const [existingImages, setExistingImages] = useState<string[]>([])
const { mutate: updateProduct, isPending } = useUpdateProduct()
const { mutate: multipleUpload, isPending: isUploading } = useMultipleUpload()
const navigate = useNavigate()
const categories = categoriesData?.data ?? []
const { id } = useParams<{ id: string }>();
const queryClient = useQueryClient();
const { data: productData, isLoading } = useGetProductDetails(id || "");
const { data: categoriesData } = useGetCategories();
const [isActive, setIsActive] = useState<boolean>(true);
const [isSpecial, setIsSpecial] = useState<boolean>(false);
const [imageFiles, setImageFiles] = useState<File[]>([]);
const [existingImages, setExistingImages] = useState<string[]>([]);
const { mutate: updateProduct, isPending } = useUpdateProduct();
const { mutate: multipleUpload, isPending: isUploading } = useMultipleUpload();
const navigate = useNavigate();
const categories = categoriesData?.data ?? [];
const product = productData?.data
const product = productData?.data;
const formik = useFormik<CreateProductType>({
initialValues: {
categoryId: '',
title: '',
attribute: '',
categoryId: "",
title: "",
attribute: "",
variants: [],
desc: '',
desc: "",
price: 0,
isActive: true,
images: [],
@@ -55,47 +54,47 @@ const UpdateProduct: FC = () => {
needAdminAcceptance: false,
},
validationSchema: Yup.object().shape({
title: Yup.string().required('نام کالا الزامی است'),
categoryId: Yup.string().required('دسته‌بندی الزامی است'),
price: Yup.number().required('قیمت الزامی است').min(0, 'قیمت باید مثبت باشد'),
discount: Yup.number().min(0, 'تخفیف باید مثبت باشد'),
title: Yup.string().required("نام کالا الزامی است"),
categoryId: Yup.string().required("دسته‌بندی الزامی است"),
price: Yup.number().required("قیمت الزامی است").min(0, "قیمت باید مثبت باشد"),
discount: Yup.number().min(0, "تخفیف باید مثبت باشد"),
attribute: Yup.string(),
pricingType: Yup.string().required(),
paymentType: Yup.string().required(),
minPurchaseQuantity: Yup.number().when('pricingType', {
minPurchaseQuantity: Yup.number().when("pricingType", {
is: PricingType.VARIABLE,
then: (schema) => schema.min(0, 'حداقل مقدار باید مثبت باشد'),
then: (schema) => schema.min(0, "حداقل مقدار باید مثبت باشد"),
}),
maxPurchaseQuantity: Yup.number().min(0, 'حداکثر مقدار باید مثبت باشد'),
purchasePitch: Yup.number().when('pricingType', {
maxPurchaseQuantity: Yup.number().min(0, "حداکثر مقدار باید مثبت باشد"),
purchasePitch: Yup.number().when("pricingType", {
is: PricingType.VARIABLE,
then: (schema) => schema.min(0, 'گام تغییر باید مثبت باشد'),
then: (schema) => schema.min(0, "گام تغییر باید مثبت باشد"),
}),
variants: Yup.array().of(
Yup.object().shape({
id: Yup.string(),
value: Yup.string(),
price: Yup.number().min(0)
})
)
price: Yup.number().min(0),
}),
),
}),
onSubmit: (values) => {
if (!id) {
toast.error('شناسه کالا یافت نشد')
return
toast.error("شناسه کالا یافت نشد");
return;
}
const submitProduct = (newImageUrls: string[] = []) => {
const allImages = [...values.images, ...newImageUrls]
const allImages = [...values.images, ...newImageUrls];
const variants =
values.variants.length > 0
? values.variants.map((v) => ({
...(v.id && { id: v.id }),
value: v.value ?? '',
price: v.price ?? 0,
...(v.stock !== undefined && v.stock !== null && { stock: v.stock })
}))
: [{ value: '', price: values.price, ...(values.stock !== undefined && values.stock !== null && { stock: values.stock }) }]
...(v.id && { id: v.id }),
value: v.value ?? "",
price: v.price ?? 0,
...(v.stock !== undefined && v.stock !== null && { stock: v.stock }),
}))
: [{ value: "", price: values.price, ...(values.stock !== undefined && values.stock !== null && { stock: values.stock }) }];
const productData: CreateProductType = {
...values,
variants,
@@ -108,47 +107,50 @@ const UpdateProduct: FC = () => {
minPurchaseQuantity: values.minPurchaseQuantity ?? 1,
purchasePitch: undefined,
}),
}
};
updateProduct({ id, params: productData }, {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['product', id] })
queryClient.invalidateQueries({ queryKey: ['products'] })
toast.success('کالا با موفقیت به‌روزرسانی شد')
navigate(Pages.products.list)
updateProduct(
{ id, params: productData },
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["product", id] });
queryClient.invalidateQueries({ queryKey: ["products"] });
toast.success("کالا با موفقیت به‌روزرسانی شد");
navigate(-1);
},
onError: (error: ErrorType) => {
toast.error(extractErrorMessage(error));
},
},
onError: (error: ErrorType) => {
toast.error(extractErrorMessage(error))
}
})
}
);
};
if (imageFiles.length > 0) {
multipleUpload(imageFiles, {
onSuccess: (response) => {
const imageUrls = response?.data?.map(item => item.url) || []
submitProduct(imageUrls)
const imageUrls = response?.data?.map((item) => item.url) || [];
submitProduct(imageUrls);
},
onError: (error: ErrorType) => {
toast.error(extractErrorMessage(error, 'خطا در آپلود تصاویر'))
}
})
toast.error(extractErrorMessage(error, "خطا در آپلود تصاویر"));
},
});
} else {
submitProduct()
submitProduct();
}
}
})
},
});
useEffect(() => {
if (product) {
const variants = product.variants || []
const priceFromVariant = variants.length > 0 ? variants[0].price : product.price
const variants = product.variants || [];
const priceFromVariant = variants.length > 0 ? variants[0].price : product.price;
formik.setValues({
categoryId: product.category?.id || '',
title: product.title || '',
attribute: product.attribute || '',
categoryId: product.category?.id || "",
title: product.title || "",
attribute: product.attribute || "",
variants,
desc: product.desc || '',
desc: product.desc || "",
price: priceFromVariant ?? product.price ?? 0,
isActive: product.isActive || false,
images: product.images || [],
@@ -163,70 +165,61 @@ const UpdateProduct: FC = () => {
paymentType: product.paymentType ?? (product.needAdminAcceptance ? PaymentType.AFTER_PREPARATION : PaymentType.INSTANT),
purchaseUnit: product.purchaseUnit ?? PurchaseUnit.NUMBER,
needAdminAcceptance: product.needAdminAcceptance ?? false,
})
setIsActive(product.isActive || false)
setIsSpecial(product.isSpecialOffer || false)
setExistingImages(product.images || [])
});
setIsActive(product.isActive || false);
setIsSpecial(product.isSpecialOffer || false);
setExistingImages(product.images || []);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [product])
}, [product]);
useEffect(() => {
formik.setFieldValue('images', existingImages)
formik.setFieldValue("images", existingImages);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [existingImages])
}, [existingImages]);
if (isLoading) {
return (
<div className='w-full mt-4 flex justify-center items-center'>
<div className="w-full mt-4 flex justify-center items-center">
<div>در حال بارگذاری...</div>
</div>
)
);
}
if (!product) {
return (
<div className='w-full mt-4 flex justify-center items-center'>
<div className="w-full mt-4 flex justify-center items-center">
<div>کالا یافت نشد</div>
</div>
)
);
}
return (
<div className='w-full mt-4'>
<div className='flex flex-col sm:flex-row w-full justify-between items-start sm:items-center gap-4'>
<div className='text-lg font-light'>
ویرایش کالا
</div>
<div className="w-full mt-4">
<div className="flex flex-col sm:flex-row w-full justify-between items-start sm:items-center gap-4">
<div className="text-lg font-light">ویرایش کالا</div>
<div>
<Button
className='px-5 w-full sm:w-auto'
onClick={() => formik.handleSubmit()}
isloading={isPending || isUploading}
>
<div className='flex gap-2 items-center'>
<TickCircle className='size-5' color='white' />
<Button className="px-5 w-full sm:w-auto" onClick={() => formik.handleSubmit()} isloading={isPending || isUploading}>
<div className="flex gap-2 items-center">
<TickCircle className="size-5" color="white" />
<div>ذخیره تغییرات</div>
</div>
</Button>
</div>
</div>
<div className='flex flex-col lg:flex-row gap-6 mt-6'>
<div className='flex-1'>
<div className='bg-white py-6 sm:py-8 xl:px-10 px-4 rounded-3xl'>
<div className="flex flex-col lg:flex-row gap-6 mt-6">
<div className="flex-1">
<div className="bg-white py-6 sm:py-8 xl:px-10 px-4 rounded-3xl">
<BasicInfoSection
formik={formik}
categories={categories}
initialHasVariants={
(product?.variants?.length ?? 0) > 1 ||
((product?.variants?.length === 1) && (product.variants[0].value ?? '') !== '')
}
initialHasVariants={(product?.variants?.length ?? 0) > 1 || (product?.variants?.length === 1 && (product.variants[0].value ?? "") !== "")}
/>
</div>
</div>
<div className='w-full lg:w-auto'>
<div className="w-full lg:w-auto">
<CreateProductSidebar
isActive={isActive}
isSpecial={isSpecial}
@@ -239,7 +232,7 @@ const UpdateProduct: FC = () => {
</div>
</div>
</div>
)
}
);
};
export default UpdateProduct
export default UpdateProduct;