update product

This commit is contained in:
hamid zarghami
2026-01-24 16:27:54 +03:30
parent 480fc056f9
commit 1691be2664
5 changed files with 304 additions and 4 deletions
+261
View File
@@ -0,0 +1,261 @@
import Button from '@/components/Button'
import Input from '@/components/Input';
import { clx } from '@/helpers/utils';
import { useFormik } from 'formik';
import { AddSquare } from 'iconsax-react'
import { useEffect, useState, type FC } from 'react'
import type { CreateProductType } from './types/Types';
import * as Yup from 'yup'
import Textarea from '@/components/Textarea';
import CategoriesSelect from './components/CategoriesSelect';
import SwitchComponent from '@/components/Switch';
import UploadBoxDraggble from '@/components/UploadBoxDraggble';
import { useMultiUpload } from '../uploader/hooks/useUploader';
import { useGetProductDetail, useUpdateProduct } from './hooks/useProductData';
import { toast } from 'react-toastify';
import type { ErrorType } from '@/helpers/types';
import { extractErrorMessage } from '@/config/func';
import { useNavigate, useParams } from 'react-router-dom';
const UpdateProduct: FC = () => {
const navigate = useNavigate()
const { id } = useParams()
const [files, setFiles] = useState<File[]>([])
const { data: product, refetch } = useGetProductDetail(Number(id))
const { mutate: upload, isPending: isUploading } = useMultiUpload()
const { mutate: updateProduct, isPending } = useUpdateProduct()
const handleSave = (values: CreateProductType) => {
values.categoryId = Number(values.categoryId)
updateProduct({ id: Number(id), params: values }, {
onSuccess: () => {
toast.success('عملیات با موفقیت انجام شد')
refetch()
formik.resetForm()
navigate(-1)
},
onError: (error: ErrorType) => {
toast.error(extractErrorMessage(error))
}
})
}
const formik = useFormik<CreateProductType>({
initialValues: {
categoryId: undefined,
desc: '',
images: [],
isActive: true,
linkUrl: '',
order: 1,
quantities: [],
title: ''
},
validationSchema: Yup.object({
categoryId: Yup.string().required('این فیلد اجباری می باشد.'),
title: Yup.string().required('این فیلد اجباری می باشد.'),
}),
onSubmit: (values) => {
if (files.length > 0) {
upload(files, {
onSuccess: (data) => {
const imagesUrl: string[] = [...values.images]
data.data?.map((item) => {
imagesUrl.push(item.url)
})
values.images = imagesUrl
handleSave(values)
}
})
} else {
handleSave(values)
}
}
})
const handleQuantities = (value: number) => {
const array = [...formik.values.quantities]
const index: number = array.findIndex(o => o === value)
if (index > -1) {
array.splice(index, 1)
} else {
array.push(value)
}
formik.setFieldValue('quantities', array)
}
useEffect(() => {
if (product) {
formik.setValues({
title: product?.data?.title,
desc: product?.data?.title,
images: product?.data?.images,
isActive: product?.data?.isActive,
linkUrl: product?.data?.linkUrl,
order: product?.data?.order,
quantities: product?.data?.quantities,
categoryId: product?.data?.category?.id
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [product])
return (
<div className='mt-5'>
<div className='flex justify-between items-center'>
<h1 className='text-lg font-light'>ویرایش جدید</h1>
<Button
className='w-fit px-6'
onClick={() => formik.handleSubmit()}
isLoading={isUploading || isPending}
>
<div className='flex gap-1.5'>
<AddSquare size={18} color='black' />
<div className='text-[13px] font-light'>ویرایش محصول</div>
</div>
</Button>
</div>
<div className='flex gap-6'>
<div className='mt-8 bg-white p-6 rounded-3xl flex-1'>
<div className='font-light'>
اطلاعات محصول
</div>
<div className='mt-6 rowTwoInput'>
<Input
label='عنوان محصول'
{...formik.getFieldProps('title')}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
/>
<Input
label='لینک در وبسایت'
{...formik.getFieldProps('linkUrl')}
/>
</div>
<div className='mt-6 rowTwoInput'>
<CategoriesSelect
{...formik.getFieldProps('categoryId')}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
/>
<Input
label='ترتیب'
{...formik.getFieldProps('order')}
/>
</div>
<div className='mt-6'>
<Textarea
label='توضیحات'
{...formik.getFieldProps('desc')}
/>
</div>
<div className='mt-6'>
<div>بسته تعداد</div>
<div className='mt-2.5 flex gap-4'>
{
[100, 150, 200, 250, 300, 350, 500, 1000].map((item) => {
return (
<div key={item} className={clx(
'h-8 w-[72px] cursor-pointer text-sm rounded-lg border border-[#C3C7DD] flex items-center justify-center',
formik.values.quantities.includes(item) && 'border-primary'
)}
onClick={() => handleQuantities(item)}
>
{item}
</div>
)
})
}
</div>
</div>
</div>
<div className='bg-white p-6 mt-8 rounded-3xl w-[250px]'>
<div className='flex justify-between items-center'>
<div>وضعیت</div>
<SwitchComponent
active={formik.values.isActive}
onChange={(value) => formik.setFieldValue('isActive', value)}
/>
</div>
<div className='mt-6'>
<div className='text-sm mb-4'>
تصاویر محصول
</div>
<UploadBoxDraggble
label='آپلود تصاویر'
onChange={setFiles}
isMultiple
preview={product?.data?.images?.map((item) => {
return item
})}
/>
</div>
</div>
</div>
{/* <div className='mt-8 bg-white p-6 rounded-3xl '>
<h1 className='font-light text-lg'>
ویژگی های محصول
</h1>
<div className='mt-6'>
<GridWrapper
desktop={5}
mobile={2}
>
<div className='flex gap-2 items-center'>
<Checkbox
/>
<div className='text-sm font-light'>نوع روکش</div>
</div>
<div className='flex gap-2 items-center'>
<Checkbox
/>
<div className='text-sm font-light'>نوع روکش</div>
</div>
<div className='flex gap-2 items-center'>
<Checkbox
/>
<div className='text-sm font-light'>نوع روکش</div>
</div>
<div className='flex gap-2 items-center'>
<Checkbox
/>
<div className='text-sm font-light'>نوع روکش</div>
</div>
<div className='flex gap-2 items-center'>
<Checkbox
/>
<div className='text-sm font-light'>نوع روکش</div>
</div>
<div className='flex gap-2 items-center'>
<Checkbox
/>
<div className='text-sm font-light'>نوع روکش</div>
</div>
<div className='flex gap-2 items-center'>
<Checkbox
/>
<div className='text-sm font-light'>نوع روکش</div>
</div>
</GridWrapper>
</div>
</div> */}
</div>
);
};
export default UpdateProduct;
+28 -2
View File
@@ -1,6 +1,11 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
QueryClient,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import * as api from "../service/ProductService";
import type { CreateCategoryType } from "../types/Types";
import type { CreateCategoryType, CreateProductType } from "../types/Types";
export const useGetCategory = () => {
return useQuery({
@@ -54,3 +59,24 @@ export const useCreateProduct = () => {
},
});
};
export const useGetProductDetail = (id: number) => {
return useQuery({
queryKey: ["product", id],
queryFn: () => api.getProductDetail(id),
enabled: !!id,
});
};
export const useUpdateProduct = () => {
const queryClient = new QueryClient();
return useMutation({
mutationFn: ({ id, params }: { id: number; params: CreateProductType }) =>
api.updateProduct(id, params),
onSuccess: () => {
queryClient.refetchQueries({
queryKey: ["products"],
});
},
});
};
+11 -1
View File
@@ -1,5 +1,5 @@
import axios from "@/config/axios";
import { type CategoriesResponse, type CategoryResponse, type CreateCategoryType, type CreateProductType, type ProductResponeType } from "../types/Types";
import { type CategoriesResponse, type CategoryResponse, type CreateCategoryType, type CreateProductType, type ProductDetailResponeType, type ProductResponeType } from "../types/Types";
export const getCategory = async () => {
const { data } = await axios.get<CategoriesResponse>("/admin/category");
@@ -31,7 +31,17 @@ export const getProducts = async () => {
return data;
};
export const getProductDetail = async (id:number) => {
const { data } = await axios.get<ProductDetailResponeType>(`/admin/products/${id}`);
return data;
};
export const createProduct = async (params:CreateProductType) => {
const { data } = await axios.post("/admin/product", params);
return data;
};
export const updateProduct = async (id: number, params:CreateProductType) => {
const { data } = await axios.patch(`/admin/products/${id}`, params);
return data;
};
+2 -1
View File
@@ -6,7 +6,7 @@ export interface CategoryType extends RowDataType {
children: CategoryType[];
createdAt: string;
deletedAt: string | null;
id: string;
id: number;
isActive: boolean;
order: number | null;
parent: CategoryType | null;
@@ -49,3 +49,4 @@ export type ProductType = {
}
export type ProductResponeType = BaseResponse<ProductType[]>;
export type ProductDetailResponeType = BaseResponse<ProductType>;
+2
View File
@@ -28,6 +28,7 @@ import LearningCategory from '@/pages/learning/Category'
import CategoryList from '@/pages/product/category/List'
import CreateCategory from '@/pages/product/category/Create'
import UpdateCategory from '@/pages/product/category/Update'
import UpdateProduct from '@/pages/product/Update'
const MainRouter: FC = () => {
return (
@@ -47,6 +48,7 @@ const MainRouter: FC = () => {
<Route path={Paths.product.list} element={<ProductList />} />
<Route path={Paths.product.create} element={<CreateProduct />} />
<Route path={Paths.product.update + ':id'} element={<UpdateProduct />} />
<Route path={Paths.product.category.list} element={<CategoryList />} />
<Route path={Paths.product.category.create} element={<CreateCategory />} />
<Route path={Paths.product.category.update + ':id'} element={<UpdateCategory />} />