@@ -8,8 +8,8 @@ export const Paths = {
|
|||||||
create: '/product/create',
|
create: '/product/create',
|
||||||
update: '/product/update/',
|
update: '/product/update/',
|
||||||
category: {
|
category: {
|
||||||
create: '/product/category/create',
|
|
||||||
list: '/product/category/list',
|
list: '/product/category/list',
|
||||||
|
children: '/product/category/list/',
|
||||||
update: '/product/category/update/',
|
update: '/product/category/update/',
|
||||||
},
|
},
|
||||||
attribute: {
|
attribute: {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { toast } from 'react-toastify';
|
|||||||
import type { ErrorType } from '@/helpers/types';
|
import type { ErrorType } from '@/helpers/types';
|
||||||
import { extractErrorMessage } from '@/config/func';
|
import { extractErrorMessage } from '@/config/func';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Paths } from '@/config/Paths'
|
||||||
|
import BackButton from '@/components/BackButton'
|
||||||
|
|
||||||
const CreateProduct: FC = () => {
|
const CreateProduct: FC = () => {
|
||||||
|
|
||||||
@@ -71,8 +73,10 @@ const CreateProduct: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='mt-5'>
|
<div className='mt-5'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center'>
|
||||||
|
<div className='flex items-center gap-4'>
|
||||||
<h1 className='text-lg font-light'>محصول جدید</h1>
|
<BackButton to={Paths.product.list} />
|
||||||
|
<h1 className='text-lg font-light'>محصول جدید</h1>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className='w-fit px-6'
|
className='w-fit px-6'
|
||||||
onClick={() => formik.handleSubmit()}
|
onClick={() => formik.handleSubmit()}
|
||||||
|
|||||||
@@ -152,6 +152,10 @@ const ProductList: FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'order',
|
||||||
|
title: 'ترتیب',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
title: '',
|
title: '',
|
||||||
|
|||||||
@@ -3,39 +3,45 @@ import { useFormik } from 'formik'
|
|||||||
import { useState, type FC } from 'react'
|
import { useState, type FC } from 'react'
|
||||||
import type { CreateCategoryType } from '../types/Types'
|
import type { CreateCategoryType } from '../types/Types'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useCreateCategory, useGetCategory } from '../hooks/useProductData'
|
import { useCreateCategory } from '../hooks/useProductData'
|
||||||
import Select from '@/components/Select'
|
|
||||||
import Button from '@/components/Button'
|
import Button from '@/components/Button'
|
||||||
import { AddSquare } from 'iconsax-react'
|
import { AddSquare, TickCircle } from 'iconsax-react'
|
||||||
import UploadBox from '@/components/UploadBox'
|
import UploadBox from '@/components/UploadBox'
|
||||||
import SwitchComponent from '@/components/Switch'
|
import SwitchComponent from '@/components/Switch'
|
||||||
import { useSingleUpload } from '@/pages/uploader/hooks/useUploader'
|
import { useSingleUpload } from '@/pages/uploader/hooks/useUploader'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { extractErrorMessage } from '@/config/func'
|
import { extractErrorMessage } from '@/config/func'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import DefaulModal from '@/components/DefaulModal'
|
||||||
import { Paths } from '@/config/Paths'
|
|
||||||
import BackButton from '@/components/BackButton'
|
|
||||||
|
|
||||||
const ProductCategory: FC = () => {
|
type Props = {
|
||||||
|
parentId?: string
|
||||||
|
refetch: () => void
|
||||||
|
}
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const CreateCategory: FC<Props> = ({ parentId, refetch }) => {
|
||||||
const { data } = useGetCategory()
|
|
||||||
const { mutate, isPending } = useCreateCategory()
|
const { mutate, isPending } = useCreateCategory()
|
||||||
const { mutate: upload, isPending: isUploading } = useSingleUpload()
|
const { mutate: upload, isPending: isUploading } = useSingleUpload()
|
||||||
|
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [file, setFile] = useState<File>()
|
const [file, setFile] = useState<File>()
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setShowModal(false)
|
||||||
|
setFile(undefined)
|
||||||
|
formik.resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
const handleSave = (values: CreateCategoryType) => {
|
const handleSave = (values: CreateCategoryType) => {
|
||||||
const payload: CreateCategoryType = {
|
const payload: CreateCategoryType = {
|
||||||
...values,
|
...values,
|
||||||
parentId: values.parentId || undefined,
|
parentId: parentId || undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
mutate(payload, {
|
mutate(payload, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('با موفقیت ذخیره شد')
|
toast.success('با موفقیت ذخیره شد')
|
||||||
navigate(Paths.product.category.list)
|
refetch()
|
||||||
|
handleClose()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(extractErrorMessage(error))
|
toast.error(extractErrorMessage(error))
|
||||||
@@ -69,55 +75,39 @@ const ProductCategory: FC = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-5'>
|
<div>
|
||||||
<div className='flex justify-between items-center'>
|
<Button
|
||||||
<div className='flex items-center gap-4'>
|
className='w-fit px-6'
|
||||||
<BackButton to={Paths.product.category.list} />
|
onClick={() => setShowModal(true)}
|
||||||
<h1 className='text-lg font-light'>دسته جدید</h1>
|
>
|
||||||
|
<div className='flex gap-1.5'>
|
||||||
|
<AddSquare size={18} color='black' />
|
||||||
|
<div className='text-[13px] font-light'>دستهبندی جدید</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
</Button>
|
||||||
className='w-fit px-6'
|
|
||||||
isLoading={isPending || isUploading}
|
|
||||||
onClick={() => formik.handleSubmit()}
|
|
||||||
>
|
|
||||||
<div className='flex gap-1.5'>
|
|
||||||
<AddSquare size={18} color='black' />
|
|
||||||
<div className='text-[13px] font-light'>ساخت دسته</div>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className='mt-8 bg-white p-6 rounded-3xl '>
|
|
||||||
|
|
||||||
<div>
|
<DefaulModal
|
||||||
|
open={showModal}
|
||||||
|
close={handleClose}
|
||||||
|
isHeader
|
||||||
|
title_header='دسته جدید'
|
||||||
|
>
|
||||||
|
<div className='mt-5'>
|
||||||
<UploadBox
|
<UploadBox
|
||||||
label='تصویر دسته (اختیاری)'
|
label='تصویر دسته (اختیاری)'
|
||||||
onChange={(files) => setFile(files[0])}
|
onChange={(files) => setFile(files[0])}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='rowTwoInput mt-6'>
|
<div className='mt-5'>
|
||||||
<Input
|
<Input
|
||||||
label='عنوان'
|
label='عنوان'
|
||||||
{...formik.getFieldProps('title')}
|
{...formik.getFieldProps('title')}
|
||||||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
|
||||||
label='انتخاب والد درسته'
|
|
||||||
placeholder='اختیاری'
|
|
||||||
items={data?.data?.map((item) => {
|
|
||||||
return {
|
|
||||||
label: item.title,
|
|
||||||
value: item.id + ''
|
|
||||||
}
|
|
||||||
}) || []}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
name='parentId'
|
|
||||||
value={formik.values.parentId || ''}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className='mt-5'>
|
||||||
<Input
|
<Input
|
||||||
label='ترتیب اولویت'
|
label='ترتیب اولویت'
|
||||||
{...formik.getFieldProps('order')}
|
{...formik.getFieldProps('order')}
|
||||||
@@ -125,15 +115,31 @@ const ProductCategory: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className='mt-5'>
|
||||||
<SwitchComponent
|
<SwitchComponent
|
||||||
active={formik.values.isActive}
|
active={formik.values.isActive}
|
||||||
onChange={(value) => formik.setFieldValue('isActive', value)}
|
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className='mt-6 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
className='w-fit px-6'
|
||||||
|
isLoading={isPending || isUploading}
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle
|
||||||
|
size={18}
|
||||||
|
color='black'
|
||||||
|
/>
|
||||||
|
<div>ذخیره</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProductCategory
|
export default CreateCategory
|
||||||
|
|||||||
@@ -1,24 +1,34 @@
|
|||||||
import { type FC, useMemo } from 'react'
|
import { type FC, useMemo } from 'react'
|
||||||
import PresignedImage from '@/components/PresignedImage'
|
import PresignedImage from '@/components/PresignedImage'
|
||||||
import { useDeleteCategory, useGetCategory } from '../hooks/useProductData'
|
import { useDeleteCategory, useGetCategory, useGetCategoryDetail } from '../hooks/useProductData'
|
||||||
import Table from '@/components/Table'
|
import Table from '@/components/Table'
|
||||||
import type { ColumnType, RowDataType } from '@/components/types/TableTypes'
|
import type { ColumnType, RowDataType } from '@/components/types/TableTypes'
|
||||||
import type { CategoryType } from '../types/Types'
|
import type { CategoryType } from '../types/Types'
|
||||||
import Button from '@/components/Button'
|
import { ArrowLeft2, Edit } from 'iconsax-react'
|
||||||
import { AddSquare, Edit } from 'iconsax-react'
|
|
||||||
import TrashWithConfrim from '@/components/TrashWithConfrim'
|
import TrashWithConfrim from '@/components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { extractErrorMessage } from '@/config/func'
|
import { extractErrorMessage } from '@/config/func'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link, useParams } from 'react-router-dom'
|
||||||
import { Paths } from '@/config/Paths'
|
import { Paths } from '@/config/Paths'
|
||||||
import BackButton from '@/components/BackButton'
|
import BackButton from '@/components/BackButton'
|
||||||
|
import CreateCategory from './Create'
|
||||||
|
|
||||||
const CategoryList: FC = () => {
|
const CategoryList: FC = () => {
|
||||||
|
const { parentId } = useParams<{ parentId?: string }>()
|
||||||
|
|
||||||
const { data, refetch } = useGetCategory()
|
const { data, refetch } = useGetCategory({
|
||||||
|
limit: 1000,
|
||||||
|
parentId: parentId ?? 'null',
|
||||||
|
})
|
||||||
|
const { data: parentDetail } = useGetCategoryDetail(parentId ?? '')
|
||||||
const { mutate, isPending } = useDeleteCategory()
|
const { mutate, isPending } = useDeleteCategory()
|
||||||
const categories = (data?.data ?? []) as (CategoryType & RowDataType)[]
|
const categories = (data?.data ?? []) as (CategoryType & RowDataType)[]
|
||||||
|
|
||||||
|
const parentCategory = parentDetail?.data
|
||||||
|
const backTo = parentCategory?.parent?.id
|
||||||
|
? Paths.product.category.children + parentCategory.parent.id
|
||||||
|
: Paths.product.category.list
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
mutate(id, {
|
mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -39,19 +49,34 @@ const CategoryList: FC = () => {
|
|||||||
{
|
{
|
||||||
title: 'عنوان',
|
title: 'عنوان',
|
||||||
key: 'title',
|
key: 'title',
|
||||||
|
render: (item) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={Paths.product.category.children + item.id}
|
||||||
|
className='inline-flex items-center gap-1.5 text-sm text-black hover:opacity-70'
|
||||||
|
>
|
||||||
|
<span>{item.title}</span>
|
||||||
|
<ArrowLeft2 size={16} color='#8C90A3' />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'والد',
|
title: 'زیر دستهها',
|
||||||
key: 'parent',
|
|
||||||
render: (item) => item.parent?.title || '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'تعداد زیر دسته',
|
|
||||||
key: 'children',
|
key: 'children',
|
||||||
render: (item) => <div>{item.children?.length || 0}</div>
|
render: (item) => {
|
||||||
|
if (!item.children?.length) return '0'
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-0.5'>
|
||||||
|
{item.children.map((child) => (
|
||||||
|
<span key={child.id}>{child.title}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'اولویت',
|
title: 'ترتیب',
|
||||||
key: 'order',
|
key: 'order',
|
||||||
render: (item) => item.order ?? '-',
|
render: (item) => item.order ?? '-',
|
||||||
},
|
},
|
||||||
@@ -75,33 +100,30 @@ const CategoryList: FC = () => {
|
|||||||
}
|
}
|
||||||
], [isPending])
|
], [isPending])
|
||||||
|
|
||||||
|
const title = parentCategory
|
||||||
|
? `زیر دستههای «${parentCategory.title}»`
|
||||||
|
: 'دسته بندی محصولات'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-5'>
|
<div className='mt-5'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center'>
|
||||||
<div className='flex items-center gap-4'>
|
<div className='flex items-center gap-4'>
|
||||||
<BackButton to={Paths.product.list} />
|
<BackButton to={parentId ? backTo : Paths.product.list} />
|
||||||
<h1 className='text-lg font-light'>دسته بندی محصولات</h1>
|
<h1 className='text-lg font-light'>{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link to={Paths.product.category.create}>
|
<CreateCategory parentId={parentId} refetch={refetch} />
|
||||||
<Button className='w-fit px-6'>
|
|
||||||
<div className='flex gap-1.5'>
|
|
||||||
<AddSquare size={18} color='black' />
|
|
||||||
<div className='text-[13px] font-light'>دستهبندی جدید</div>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className='mt-8'>
|
||||||
<Table<CategoryType & RowDataType>
|
<Table<CategoryType & RowDataType>
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={categories}
|
data={categories}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CategoryList
|
export default CategoryList
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ export const useGetCategory = (params?: api.GetCategoryParams) => {
|
|||||||
const page = params?.page ?? 1;
|
const page = params?.page ?? 1;
|
||||||
const limit = params?.limit;
|
const limit = params?.limit;
|
||||||
const search = params?.search;
|
const search = params?.search;
|
||||||
|
const parentId = params?.parentId;
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["category", page, limit, search],
|
queryKey: ["category", page, limit, search, parentId],
|
||||||
queryFn: () => api.getCategory({ page, limit, search }),
|
queryFn: () => api.getCategory({ page, limit, search, parentId }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type GetCategoryParams = {
|
|||||||
page?: number;
|
page?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
search?: string;
|
search?: string;
|
||||||
|
parentId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCategory = async (params?: GetCategoryParams) => {
|
export const getCategory = async (params?: GetCategoryParams) => {
|
||||||
@@ -13,6 +14,7 @@ export const getCategory = async (params?: GetCategoryParams) => {
|
|||||||
page: params?.page,
|
page: params?.page,
|
||||||
limit: params?.limit,
|
limit: params?.limit,
|
||||||
search: params?.search || undefined,
|
search: params?.search || undefined,
|
||||||
|
parentId: params?.parentId || undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import LearningList from "@/pages/learning/List";
|
|||||||
import LearningCreate from "@/pages/learning/Create";
|
import LearningCreate from "@/pages/learning/Create";
|
||||||
import LearningCategory from "@/pages/learning/Category";
|
import LearningCategory from "@/pages/learning/Category";
|
||||||
import CategoryList from "@/pages/product/category/List";
|
import CategoryList from "@/pages/product/category/List";
|
||||||
import CreateCategory from "@/pages/product/category/Create";
|
|
||||||
import UpdateCategory from "@/pages/product/category/Update";
|
import UpdateCategory from "@/pages/product/category/Update";
|
||||||
import UpdateProduct from "@/pages/product/Update";
|
import UpdateProduct from "@/pages/product/Update";
|
||||||
import CreateAttribute from "@/pages/product/attribute/Create";
|
import CreateAttribute from "@/pages/product/attribute/Create";
|
||||||
@@ -108,7 +107,7 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Paths.product.attribute.update + ":id"} element={withPermission(Permissions.MANAGE_PRODUCTS, <UpdateAttribute />)} />
|
<Route path={Paths.product.attribute.update + ":id"} element={withPermission(Permissions.MANAGE_PRODUCTS, <UpdateAttribute />)} />
|
||||||
<Route path={Paths.product.attributeValue.list + ":id"} element={withPermission(Permissions.MANAGE_PRODUCTS, <AttributeValues />)} />
|
<Route path={Paths.product.attributeValue.list + ":id"} element={withPermission(Permissions.MANAGE_PRODUCTS, <AttributeValues />)} />
|
||||||
<Route path={Paths.product.category.list} element={withPermission(Permissions.MANAGE_PRODUCTS, <CategoryList />)} />
|
<Route path={Paths.product.category.list} element={withPermission(Permissions.MANAGE_PRODUCTS, <CategoryList />)} />
|
||||||
<Route path={Paths.product.category.create} element={withPermission(Permissions.MANAGE_PRODUCTS, <CreateCategory />)} />
|
<Route path={Paths.product.category.children + ":parentId"} element={withPermission(Permissions.MANAGE_PRODUCTS, <CategoryList />)} />
|
||||||
<Route path={Paths.product.category.update + ":id"} element={withPermission(Permissions.MANAGE_PRODUCTS, <UpdateCategory />)} />
|
<Route path={Paths.product.category.update + ":id"} element={withPermission(Permissions.MANAGE_PRODUCTS, <UpdateCategory />)} />
|
||||||
|
|
||||||
<Route path={Paths.formBuilder.list + ":id/:type"} element={withPermission(Permissions.MANAGE_PRODUCTS, <FormBuilderList />)} />
|
<Route path={Paths.formBuilder.list + ":id/:type"} element={withPermission(Permissions.MANAGE_PRODUCTS, <FormBuilderList />)} />
|
||||||
|
|||||||
Reference in New Issue
Block a user