225 lines
8.3 KiB
TypeScript
225 lines
8.3 KiB
TypeScript
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";
|
||
import { buildProductPayload } from "./utils/productPayload";
|
||
|
||
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 product = productData?.data;
|
||
const isSubmitting = isPending || isUploading;
|
||
|
||
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.required("حداقل مقدار الزامی است").positive("حداقل مقدار باید مثبت باشد"),
|
||
}),
|
||
maxPurchaseQuantity: Yup.number().required("حداکثر مقدار الزامی است").positive("حداکثر مقدار باید مثبت باشد"),
|
||
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) => {
|
||
if (!id) {
|
||
toast.error("شناسه کالا یافت نشد");
|
||
return;
|
||
}
|
||
|
||
const submitProduct = (newImageUrls: string[] = []) => {
|
||
const allImages = [...values.images, ...newImageUrls];
|
||
const productData = buildProductPayload({
|
||
values,
|
||
isActive,
|
||
isSpecialOffer: isSpecial,
|
||
images: allImages,
|
||
});
|
||
|
||
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));
|
||
},
|
||
},
|
||
);
|
||
};
|
||
|
||
if (imageFiles.length > 0) {
|
||
multipleUpload(imageFiles, {
|
||
onSuccess: (response) => {
|
||
const imageUrls = response?.data?.map((item) => item.url) || [];
|
||
submitProduct(imageUrls);
|
||
},
|
||
onError: (error: ErrorType) => {
|
||
toast.error(extractErrorMessage(error, "خطا در آپلود تصاویر"));
|
||
},
|
||
});
|
||
} else {
|
||
submitProduct();
|
||
}
|
||
},
|
||
});
|
||
|
||
useEffect(() => {
|
||
if (product) {
|
||
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 || "",
|
||
variants,
|
||
desc: product.desc || "",
|
||
price: priceFromVariant ?? product.price ?? 0,
|
||
isActive: product.isActive || false,
|
||
images: product.images || [],
|
||
discount: product.discount || 0,
|
||
isSpecialOffer: product.isSpecialOffer || false,
|
||
order: product.order ?? 0,
|
||
stock: product.variants?.[0]?.stock,
|
||
maxPurchaseQuantity: product.maxPurchaseQuantity,
|
||
minPurchaseQuantity: product.minPurchaseQuantity,
|
||
purchasePitch: product.purchasePitch,
|
||
pricingType: product.pricingType ?? PricingType.FIXED,
|
||
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 || []);
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [product]);
|
||
|
||
useEffect(() => {
|
||
formik.setFieldValue("images", existingImages);
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [existingImages]);
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<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>کالا یافت نشد</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>
|
||
<Button className="px-5 w-full sm:w-auto" onClick={() => formik.handleSubmit()} isloading={isSubmitting}>
|
||
<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}
|
||
initialHasVariants={(product?.variants?.length ?? 0) > 1 || (product?.variants?.length === 1 && (product.variants[0].value ?? "") !== "")}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="w-full lg:w-auto">
|
||
<CreateProductSidebar
|
||
isActive={isActive}
|
||
isSpecial={isSpecial}
|
||
onChangeActive={setIsActive}
|
||
onChangeSpecial={setIsSpecial}
|
||
onChangeImages={setImageFiles}
|
||
existingImages={existingImages}
|
||
onChangeExistingImages={setExistingImages}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default UpdateProduct;
|