create and list + delete brand
This commit is contained in:
Generated
+18
@@ -39,6 +39,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@types/node": "^24.5.2",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
@@ -2387,6 +2388,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
|
||||||
|
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.12",
|
"version": "19.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
|
||||||
@@ -5468,6 +5479,13 @@
|
|||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||||
|
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@types/node": "^24.5.2",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
|||||||
+2
-1
@@ -56,7 +56,8 @@ const queryClient = new QueryClient({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const refreshTokenValue = await getRefreshToken();
|
const refreshTokenValue = await getRefreshToken();
|
||||||
const { data } = await refreshToken({ refreshToken: refreshTokenValue || '' });
|
const response = await refreshToken({ token: refreshTokenValue || '' });
|
||||||
|
const data = response.results.data;
|
||||||
|
|
||||||
if (data?.accessToken?.token) {
|
if (data?.accessToken?.token) {
|
||||||
// Save the new token
|
// Save the new token
|
||||||
|
|||||||
@@ -129,5 +129,10 @@ export const Pages = {
|
|||||||
list: "/products/list",
|
list: "/products/list",
|
||||||
variants: "/products/variants/",
|
variants: "/products/variants/",
|
||||||
update: "/products/update/",
|
update: "/products/update/",
|
||||||
|
brands: {
|
||||||
|
list: "/products/brands",
|
||||||
|
update: "/products/brands/update/",
|
||||||
|
create: "/products/brands/create",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { type FC, useState } from 'react'
|
||||||
|
import { useGetCategories, type CategoryTreeNode } from '../category'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { type CreateBrandType } from './types/Types'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { useCreateBrand } from './hooks/useBrandData'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import Input from '../../components/Input'
|
||||||
|
import Select from '../../components/Select'
|
||||||
|
import Textarea from '../../components/Textarea'
|
||||||
|
import Button from '../../components/Button'
|
||||||
|
import { TickCircle } from 'iconsax-react'
|
||||||
|
import { useUploadMultiple, useUploadSingle } from '../uploader/hooks/useUploaderData'
|
||||||
|
import UploadBoxDraggble from '@/components/UploadBoxDraggble'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { extractErrorMessage } from '@/helpers/utils'
|
||||||
|
|
||||||
|
const Create: FC = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { data: categoriesData } = useGetCategories()
|
||||||
|
const uploadSingle = useUploadSingle()
|
||||||
|
const uploadMultiple = useUploadMultiple()
|
||||||
|
const createBrandMutation = useCreateBrand()
|
||||||
|
|
||||||
|
const [logoFile, setLogoFile] = useState<File[]>([])
|
||||||
|
const [imageFiles, setImageFiles] = useState<File[]>([])
|
||||||
|
|
||||||
|
const formik = useFormik<CreateBrandType>({
|
||||||
|
initialValues: {
|
||||||
|
title_fa: '',
|
||||||
|
title_en: '',
|
||||||
|
category: '',
|
||||||
|
description: '',
|
||||||
|
images: [],
|
||||||
|
logoUrl: '',
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
title_fa: Yup.string().required('عنوان فارسی برند الزامی است'),
|
||||||
|
title_en: Yup.string().required('عنوان انگلیسی برند الزامی است'),
|
||||||
|
category: Yup.string().required('دسته بندی برند الزامی است'),
|
||||||
|
description: Yup.string().required('توضیحات برند الزامی است'),
|
||||||
|
}),
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
let logoUrl = values.logoUrl
|
||||||
|
let images = values.images
|
||||||
|
|
||||||
|
if (logoFile.length > 0) {
|
||||||
|
const logoResponse = await uploadSingle.mutateAsync(logoFile[0])
|
||||||
|
logoUrl = logoResponse.results?.url?.url || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageFiles.length > 0) {
|
||||||
|
const imagesResponse = await uploadMultiple.mutateAsync(imageFiles)
|
||||||
|
images = imagesResponse.results?.urls?.map(file => file.url) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitData = {
|
||||||
|
...values,
|
||||||
|
logoUrl,
|
||||||
|
images
|
||||||
|
}
|
||||||
|
|
||||||
|
createBrandMutation.mutate(submitData, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('برند با موفقیت ایجاد شد')
|
||||||
|
navigate('/brands')
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(extractErrorMessage(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const categoryOptions = categoriesData?.results?.data?.map((category: CategoryTreeNode) => ({
|
||||||
|
value: category._id,
|
||||||
|
label: category.title_fa
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-4'>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
<h1>ساخت برند جدید</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
className='px-6'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={createBrandMutation.isPending || uploadSingle.isPending || uploadMultiple.isPending}
|
||||||
|
disabled={!formik.isValid || createBrandMutation.isPending || uploadSingle.isPending || uploadMultiple.isPending}
|
||||||
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle size={16} color='#fff' />
|
||||||
|
<div>ثبت برند</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-6 xl:mt-8 mt-6'>
|
||||||
|
{/* اطلاعات اصلی */}
|
||||||
|
<div className='flex-1 text-sm bg-white py-8 xl:px-10 px-6 rounded-3xl'>
|
||||||
|
<h2 className='text-lg font-medium mb-6'>اطلاعات اصلی</h2>
|
||||||
|
|
||||||
|
<div className='space-y-6'>
|
||||||
|
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
|
||||||
|
<Input
|
||||||
|
label='عنوان فارسی برند'
|
||||||
|
placeholder='مثال: سامسونگ'
|
||||||
|
{...formik.getFieldProps('title_fa')}
|
||||||
|
error_text={formik.touched.title_fa && formik.errors.title_fa ? formik.errors.title_fa : ''}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label='عنوان انگلیسی برند'
|
||||||
|
placeholder='مثال: Samsung'
|
||||||
|
{...formik.getFieldProps('title_en')}
|
||||||
|
error_text={formik.touched.title_en && formik.errors.title_en ? formik.errors.title_en : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label='دستهبندی'
|
||||||
|
placeholder='انتخاب دستهبندی برند'
|
||||||
|
items={categoryOptions}
|
||||||
|
{...formik.getFieldProps('category')}
|
||||||
|
error_text={formik.touched.category && formik.errors.category ? formik.errors.category : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
label='توضیحات برند'
|
||||||
|
placeholder='توضیحات کامل برند را وارد کنید...'
|
||||||
|
{...formik.getFieldProps('description')}
|
||||||
|
error_text={formik.touched.description && formik.errors.description ? formik.errors.description : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* فایلها */}
|
||||||
|
<div className='w-80 space-y-6'>
|
||||||
|
<div className='text-sm bg-white py-8 px-6 rounded-3xl'>
|
||||||
|
<h2 className='text-lg font-medium mb-6'>فایلها</h2>
|
||||||
|
|
||||||
|
<div className='space-y-6'>
|
||||||
|
<div>
|
||||||
|
<UploadBoxDraggble
|
||||||
|
label='لوگو برند'
|
||||||
|
isMultiple={false}
|
||||||
|
onChange={(files) => setLogoFile(files)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<UploadBoxDraggble
|
||||||
|
label='تصاویر برند'
|
||||||
|
isMultiple={true}
|
||||||
|
onChange={(files) => setImageFiles(files)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Create
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { type FC, useState } from 'react'
|
||||||
|
import { useGetBrands, useDeleteBrand } from './hooks/useBrandData'
|
||||||
|
import { type BrandType } from './types/Types'
|
||||||
|
import PageLoading from '../../components/PageLoading'
|
||||||
|
import Error from '../../components/Error'
|
||||||
|
import PaginationUi from '../../components/PaginationUi'
|
||||||
|
import Td from '../../components/Td'
|
||||||
|
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
||||||
|
import { Edit } from 'iconsax-react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Pages } from '@/config/Pages'
|
||||||
|
import Button from '@/components/Button'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
const BrandList: FC = () => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const { data: brandsData, isLoading, error, refetch } = useGetBrands(currentPage)
|
||||||
|
const deleteBrandMutation = useDeleteBrand();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[400px]">
|
||||||
|
<PageLoading />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[400px]">
|
||||||
|
<Error errorText="خطا در بارگذاری برندها" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteBrand = async (brandId: string) => {
|
||||||
|
await deleteBrandMutation.mutateAsync(brandId);
|
||||||
|
refetch()
|
||||||
|
};
|
||||||
|
|
||||||
|
const brands = brandsData?.results?.brands || [];
|
||||||
|
const pager = brandsData?.results?.pager;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='flex justify-end mt-5'>
|
||||||
|
<Button
|
||||||
|
label='افزودن برند'
|
||||||
|
onClick={() => navigate(`${Pages.products.brands.create}`)}
|
||||||
|
className='w-fit'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
|
||||||
|
<table className='w-full text-sm'>
|
||||||
|
<thead className='thead'>
|
||||||
|
<tr>
|
||||||
|
<Td text={'لوگو'} />
|
||||||
|
<Td text={'عنوان فارسی'} />
|
||||||
|
<Td text={'عنوان انگلیسی'} />
|
||||||
|
<Td text={'دستهبندی'} />
|
||||||
|
<Td text={'وضعیت'} />
|
||||||
|
<Td text={'عملیات'} />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{brands.length === 0 ? (
|
||||||
|
<tr className='tr'>
|
||||||
|
<td colSpan={6} className="text-center py-8 text-gray-500">
|
||||||
|
هیچ برندی یافت نشد
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
brands.map((brand: BrandType) => (
|
||||||
|
<tr key={brand._id} className='tr'>
|
||||||
|
<Td text="">
|
||||||
|
<div className="w-12 h-12 rounded-lg overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={brand.logoUrl || '/placeholder-image.png'}
|
||||||
|
alt={brand.title_fa}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text={brand.title_fa} />
|
||||||
|
<Td text={brand.title_en} />
|
||||||
|
<Td text={brand.category?.name || 'بدون دستهبندی'} />
|
||||||
|
<Td text="">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className={`px-2 py-1 rounded-full text-xs`}>
|
||||||
|
{brand.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Link to={`${Pages.products.brands.update}${brand._id}`}>
|
||||||
|
<Edit color='#8C90A3' size={16} className="cursor-pointer hover:text-blue-500" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<TrashWithConfrim
|
||||||
|
onDelete={() => handleDeleteBrand(brand._id)}
|
||||||
|
isLoading={deleteBrandMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PaginationUi
|
||||||
|
pager={pager}
|
||||||
|
onPageChange={(page) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BrandList
|
||||||
@@ -1,10 +1,27 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import * as api from "../service/BrandService";
|
import * as api from "../service/BrandService";
|
||||||
|
|
||||||
// Hook برای دریافت لیست برندها
|
export const useGetBrands = (page: number = 1, limit: number = 10) => {
|
||||||
export const useGetBrands = () => {
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["brands"],
|
queryKey: ["brands", page, limit],
|
||||||
queryFn: api.getBrands,
|
queryFn: () => api.getBrands(page, limit),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteBrand = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (id: string) => api.deleteBrand(id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreateBrand = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.createBrand,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateBrand = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.updateBrand,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
import axios from "../../../config/axios";
|
import axios from "../../../config/axios";
|
||||||
import { type BrandsResponseType } from "../types/Types";
|
import { type BrandsResponseType, type CreateBrandType } from "../types/Types";
|
||||||
|
|
||||||
// دریافت لیست برندها
|
export const getBrands = async (page: number = 1, limit: number = 10): Promise<BrandsResponseType> => {
|
||||||
export const getBrands = async (): Promise<BrandsResponseType> => {
|
const { data } = await axios.get(`/brand`, {
|
||||||
const { data } = await axios.get(`/brand`);
|
params: { page, limit }
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteBrand = async (id: string) => {
|
||||||
|
const { data } = await axios.delete(`/admin/brand/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createBrand = async (params: CreateBrandType) => {
|
||||||
|
const { data } = await axios.post(`/admin/brand`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateBrand = async (params: { id: string, params: CreateBrandType }) => {
|
||||||
|
const { data } = await axios.patch(`/admin/brand/${params.id}`, params.params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -4,7 +4,10 @@ export type BrandType = {
|
|||||||
_id: string;
|
_id: string;
|
||||||
title_fa: string;
|
title_fa: string;
|
||||||
title_en: string;
|
title_en: string;
|
||||||
category: string | null;
|
category: {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
} | null;
|
||||||
status: string;
|
status: string;
|
||||||
description: string;
|
description: string;
|
||||||
logoUrl: string;
|
logoUrl: string;
|
||||||
@@ -12,13 +15,14 @@ export type BrandType = {
|
|||||||
deleted: boolean;
|
deleted: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// تایپهای صفحهبندی
|
||||||
export type PagerType = {
|
export type PagerType = {
|
||||||
page: number;
|
page: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
totalItems: number;
|
totalItems: number;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
prevPage: boolean | string;
|
prevPage: boolean | null;
|
||||||
nextPage: string;
|
nextPage: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrandsResponseType = {
|
export type BrandsResponseType = {
|
||||||
@@ -29,3 +33,12 @@ export type BrandsResponseType = {
|
|||||||
pager: PagerType;
|
pager: PagerType;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateBrandType = {
|
||||||
|
title_fa: string;
|
||||||
|
title_en: string;
|
||||||
|
category: string; // category ID
|
||||||
|
description: string;
|
||||||
|
logoUrl: string;
|
||||||
|
images: string[];
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import CreateProduct from '../pages/products/Create'
|
|||||||
import ListProduct from '../pages/products/List'
|
import ListProduct from '../pages/products/List'
|
||||||
import UpdateProduct from '../pages/products/Update'
|
import UpdateProduct from '../pages/products/Update'
|
||||||
import ProductVariant from '@/pages/products/ProductVariant'
|
import ProductVariant from '@/pages/products/ProductVariant'
|
||||||
|
import BrandList from '@/pages/brand/List'
|
||||||
|
import BrandCreate from '@/pages/brand/Create'
|
||||||
|
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
@@ -42,6 +44,8 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.products.list} element={<ListProduct />} />
|
<Route path={Pages.products.list} element={<ListProduct />} />
|
||||||
<Route path={`${Pages.products.update}:id`} element={<UpdateProduct />} />
|
<Route path={`${Pages.products.update}:id`} element={<UpdateProduct />} />
|
||||||
<Route path={`${Pages.products.variants}:id`} element={<ProductVariant />} />
|
<Route path={`${Pages.products.variants}:id`} element={<ProductVariant />} />
|
||||||
|
<Route path={Pages.products.brands.list} element={<BrandList />} />
|
||||||
|
<Route path={Pages.products.brands.create} element={<BrandCreate />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ const ProductsSubMenu: FC = () => {
|
|||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
title={'برند ها'}
|
title={'برند ها'}
|
||||||
isActive={isActive('brands')}
|
isActive={isActive('brands')}
|
||||||
link="/products/brands"
|
link={Pages.products.brands.list}
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
title={'تنوع ها'}
|
title={'تنوع ها'}
|
||||||
|
|||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import path from "path";
|
import { fileURLToPath } from "node:url";
|
||||||
import { fileURLToPath, URL } from "node:url";
|
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|||||||
Reference in New Issue
Block a user