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