up
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-07 18:57:22 +03:30
parent bdc61398d7
commit aeea803198
8 changed files with 113 additions and 8 deletions
+1 -1
View File
@@ -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>
+48
View File
@@ -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
+3 -2
View File
@@ -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'>
+24 -2
View File
@@ -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>
+8 -1
View File
@@ -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'>
<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}
+4
View File
@@ -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'>
<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'>
+18
View File
@@ -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;