+1
-1
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>negareh-console</title>
|
||||
<title>پنل مدیریت</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Copy } from 'iconsax-react'
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import ModalConfrim from './ModalConfrim'
|
||||
|
||||
interface Props {
|
||||
onDuplicate: () => void,
|
||||
isloading?: boolean,
|
||||
colorIcon?: string,
|
||||
}
|
||||
|
||||
const CopyWithConfirm: FC<Props> = ({ onDuplicate, isloading = false, colorIcon }) => {
|
||||
const [isConfirm, setIsConfirm] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting && !isloading) {
|
||||
setIsConfirm(false)
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}, [isloading, isSubmitting])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setIsConfirm(true)}
|
||||
disabled={isloading}
|
||||
className='disabled:opacity-50'
|
||||
>
|
||||
<Copy size={20} color={colorIcon || '#0037FF'} />
|
||||
</button>
|
||||
|
||||
<ModalConfrim
|
||||
isOpen={isConfirm}
|
||||
close={() => setIsConfirm(false)}
|
||||
onConfrim={() => {
|
||||
setIsSubmitting(true)
|
||||
onDuplicate()
|
||||
}}
|
||||
isloading={isloading}
|
||||
title_header='کپی محصول'
|
||||
label='آیا از کپی این محصول مطمئن هستید؟'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CopyWithConfirm
|
||||
@@ -9,7 +9,8 @@ type Props = {
|
||||
isOpen: boolean,
|
||||
onConfrim: (text?: string) => void,
|
||||
label?: string,
|
||||
isHasDescription?: boolean
|
||||
isHasDescription?: boolean,
|
||||
title_header?: string,
|
||||
}
|
||||
|
||||
const ModalConfrim: FC<Props> = (props: Props) => {
|
||||
@@ -20,7 +21,7 @@ const ModalConfrim: FC<Props> = (props: Props) => {
|
||||
<DefaulModal
|
||||
open={props.isOpen}
|
||||
close={props.close}
|
||||
title_header={'حذف'}
|
||||
title_header={props.title_header ?? 'حذف'}
|
||||
isHeader
|
||||
>
|
||||
<div className='mt-6'>
|
||||
|
||||
@@ -2,11 +2,12 @@ import Button from '@/components/Button'
|
||||
import Filters from '@/components/Filters'
|
||||
import Table from '@/components/Table'
|
||||
import { AddSquare, Edit, More2 } from 'iconsax-react'
|
||||
import { type FC } from 'react'
|
||||
import { useDeleteProduct, useGetProducts } from './hooks/useProductData'
|
||||
import { type FC, useState } from 'react'
|
||||
import { useDeleteProduct, useDuplicateProduct, useGetProducts } from './hooks/useProductData'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import TrashWithConfrim from '@/components/TrashWithConfrim'
|
||||
import CopyWithConfirm from '@/components/CopyWithConfirm'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
|
||||
@@ -14,6 +15,23 @@ const ProductList: FC = () => {
|
||||
|
||||
const { data: products, refetch } = useGetProducts()
|
||||
const { mutate: deleteProduct, isPending: isDeleting } = useDeleteProduct()
|
||||
const { mutate: duplicateProduct, isPending: isDuplicating } = useDuplicateProduct()
|
||||
const [duplicatingId, setDuplicatingId] = useState<string | null>(null)
|
||||
|
||||
const handleDuplicate = (id: string) => {
|
||||
setDuplicatingId(id)
|
||||
duplicateProduct(id, {
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
toast.success('محصول با موفقیت کپی شد')
|
||||
setDuplicatingId(null)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
setDuplicatingId(null)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
deleteProduct(id, {
|
||||
@@ -97,6 +115,10 @@ const ProductList: FC = () => {
|
||||
render: (item) => {
|
||||
return (
|
||||
<div className='flex gap-2 items-center'>
|
||||
<CopyWithConfirm
|
||||
onDuplicate={() => handleDuplicate(item.id)}
|
||||
isloading={isDuplicating && duplicatingId === item.id}
|
||||
/>
|
||||
<Link to={Paths.product.update + item.id}>
|
||||
<Edit size={20} color='#0037FF' />
|
||||
</Link>
|
||||
|
||||
@@ -12,9 +12,13 @@ import SwitchComponent from '@/components/Switch'
|
||||
import { useSingleUpload } from '@/pages/uploader/hooks/useUploader'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import BackButton from '@/components/BackButton'
|
||||
|
||||
const ProductCategory: FC = () => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { data } = useGetCategory()
|
||||
const { mutate, isPending } = useCreateCategory()
|
||||
const { mutate: upload, isPending: isUploading } = useSingleUpload()
|
||||
@@ -29,6 +33,7 @@ const ProductCategory: FC = () => {
|
||||
mutate(values, {
|
||||
onSuccess: () => {
|
||||
toast.success('با موفقیت ذخیره شد')
|
||||
navigate(Paths.product.category.list)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
@@ -64,8 +69,10 @@ const ProductCategory: FC = () => {
|
||||
return (
|
||||
<div className='mt-5'>
|
||||
<div className='flex justify-between items-center'>
|
||||
|
||||
<h1 className='text-lg font-light'>دسته جدید</h1>
|
||||
<div className='flex items-center gap-4'>
|
||||
<BackButton to={Paths.product.category.list} />
|
||||
<h1 className='text-lg font-light'>دسته جدید</h1>
|
||||
</div>
|
||||
<Button
|
||||
className='w-fit px-6'
|
||||
isLoading={isPending || isUploading}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import BackButton from '@/components/BackButton'
|
||||
|
||||
const CategoryList: FC = () => {
|
||||
|
||||
@@ -82,7 +83,10 @@ const CategoryList: FC = () => {
|
||||
return (
|
||||
<div className='mt-5'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<h1 className='text-lg font-light'>دسته بندی محصولات</h1>
|
||||
<div className='flex items-center gap-4'>
|
||||
<BackButton to={Paths.product.list} />
|
||||
<h1 className='text-lg font-light'>دسته بندی محصولات</h1>
|
||||
</div>
|
||||
|
||||
<Link to={Paths.product.category.create}>
|
||||
<Button className='w-fit px-6'>
|
||||
|
||||
@@ -20,8 +20,14 @@ export const useGetCategory = () => {
|
||||
};
|
||||
|
||||
export const useCreateCategory = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.createCategory,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["category"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,6 +98,18 @@ export const useDeleteProduct = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useDuplicateProduct = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.duplicateProduct(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["products"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateAttribute = () => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
||||
@@ -51,6 +51,11 @@ export const deleteProduct = async (id: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const duplicateProduct = async (id: string) => {
|
||||
const { data } = await axios.post(`/admin/products/${id}/duplicate`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createAttribute = async (id: number, params:CreateAttributeType) => {
|
||||
const { data } = await axios.post(`/admin/product/${id}/attribute`, params);
|
||||
return data;
|
||||
|
||||
Reference in New Issue
Block a user