From a71d521c7cc03c923e3ec68ad0aa756bfb0d4fdb Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 23 Sep 2025 15:44:14 +0330 Subject: [PATCH] manage variant producy --- src/components/DatePicker.tsx | 20 +- src/components/DefaulModal.tsx | 4 +- src/config/Pages.ts | 1 + src/index.css | 17 + src/pages/products/List.tsx | 15 + src/pages/products/ProductVariant.tsx | 136 +++++++ .../products/components/CreateVariant.tsx | 382 ++++++++++++++++++ .../products/components/UpdateVariant.tsx | 322 +++++++++++++++ src/pages/products/hooks/useProductData.ts | 39 +- src/pages/products/service/ProductService.ts | 32 +- src/pages/products/types/Types.ts | 175 ++++++++ src/pages/warranty/hooks/useWarrantyData.ts | 10 + src/pages/warranty/service/WarrantyService.ts | 9 + src/pages/warranty/types/Types.ts | 35 ++ src/router/Main.tsx | 2 + src/shared/SideBar.tsx | 2 +- 16 files changed, 1186 insertions(+), 15 deletions(-) create mode 100644 src/pages/products/ProductVariant.tsx create mode 100644 src/pages/products/components/CreateVariant.tsx create mode 100644 src/pages/products/components/UpdateVariant.tsx create mode 100644 src/pages/warranty/hooks/useWarrantyData.ts create mode 100644 src/pages/warranty/service/WarrantyService.ts create mode 100644 src/pages/warranty/types/Types.ts diff --git a/src/components/DatePicker.tsx b/src/components/DatePicker.tsx index 555d4f8..3598f56 100644 --- a/src/components/DatePicker.tsx +++ b/src/components/DatePicker.tsx @@ -3,7 +3,6 @@ import DatePicker from 'react-multi-date-picker'; import persian from 'react-date-object/calendars/persian'; import persian_fa from 'react-date-object/locales/persian_fa'; import DateObject from 'react-date-object'; -import CalenderIcon from '../assets/images/calendar.svg' import { clx } from '../helpers/utils'; type Props = { @@ -14,7 +13,8 @@ type Props = { reset?: boolean; isDateTime?: boolean; className?: string; - label?: string + label: string, + readOnly?: boolean; }; const DatePickerComponent: FC = (props: Props) => { @@ -47,13 +47,12 @@ const DatePickerComponent: FC = (props: Props) => { return (
- {props.label && -
- {props.label} -
} +
+ {props.label} +
= (props: Props) => { calendar={persian} locale={persian_fa} calendarPosition="bottom-right" - className={`rmdp-mobile ${props.className}`} + className={props.className} + readOnly={props.readOnly} /> {props.error_text && props.error_text !== '' && (
@@ -70,7 +70,7 @@ const DatePickerComponent: FC = (props: Props) => {
)} - + {/* */}
); diff --git a/src/components/DefaulModal.tsx b/src/components/DefaulModal.tsx index cad832a..f321697 100644 --- a/src/components/DefaulModal.tsx +++ b/src/components/DefaulModal.tsx @@ -26,8 +26,8 @@ const DefaulModal: FC = (props: Props) => { { props.open && ( -
-
+
+
diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 0a0d7d9..01eeaed 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -127,5 +127,6 @@ export const Pages = { products: { create: "/products/create", list: "/products/list", + variants: "/products/variants/", }, }; diff --git a/src/index.css b/src/index.css index cb700bd..98fe4c7 100644 --- a/src/index.css +++ b/src/index.css @@ -179,3 +179,20 @@ textarea::placeholder { @apply bg-background text-foreground; } } + +.rmdp-input { + min-height: 40px; + border-radius: 12px !important; + border: 1px solid #d0d0d0 !important; + font-size: 12px !important; + width: 100% !important; + margin: 0px; + padding-right: 16px !important; +} +.readOny .rmdp-input { + background-color: #f5f5f5 !important; + border: none !important; +} +.rmdp-container { + width: 100%; +} diff --git a/src/pages/products/List.tsx b/src/pages/products/List.tsx index 92dd5e8..2e0c451 100644 --- a/src/pages/products/List.tsx +++ b/src/pages/products/List.tsx @@ -9,6 +9,9 @@ import StatusWithText from '../../components/StatusWithText'; import Td from '../../components/Td'; import TrashWithConfrim from '../../components/TrashWithConfrim'; import { Edit, Eye } from 'iconsax-react'; +import Button from '@/components/Button'; +import { Link } from 'react-router-dom'; +import { Pages } from '@/config/Pages'; const List: FC = () => { const [currentPage, setCurrentPage] = useState(1); @@ -64,6 +67,7 @@ const List: FC = () => { + @@ -101,6 +105,17 @@ const List: FC = () => { + + + + + { + + const { id } = useParams() + const { data, isLoading, refetch } = useGetProductVariants(id!) + const { data: productData } = useGetProductById(id!) + + const categoryTheme = productData?.results?.product?.category?.theme + + const getVariantColumnHeader = () => { + switch (categoryTheme) { + case 'Size': return 'سایز' + case 'Color': return 'رنگ' + case 'Meterage': return 'متریاژ' + default: return 'تنوع' + } + } + + const getVariantValue = (variant: ProductVariantDetailType) => { + switch (categoryTheme) { + case 'Size': return variant.size?.value || '-' + case 'Color': return 'رنگ' // TODO: باید نام رنگ را نمایش دهیم + case 'Meterage': return '-' // TODO: باید فیلد meterage اضافه شود + default: return '-' + } + } + + const getMarketStatusVariant = (status: string) => { + switch (status) { + case 'Marketable': return 'success'; + case 'out_of_stock': return 'error'; + case 'stopProduction': return 'warning'; + default: return 'warning'; + } + }; + + const getMarketStatusText = (status: string) => { + switch (status) { + case 'Marketable': return 'قابل فروش'; + case 'out_of_stock': return 'ناموجود'; + case 'stopProduction': return 'توقف تولید'; + default: return status; + } + }; + + const renderVariantRow = (variant: ProductVariantDetailType) => ( + + +
+
+ {variant.title_fa} +
+
+ {variant.title_en} +
+
+ + + + + + + + + + + {variant.is_variant_active ? 'فعال' : 'غیرفعال'} + + + + + {variant.isFreeShip ? 'بله' : 'خیر'} + + + + + + + ) + + return ( +
+ +
+ + + + + + + {isLoading ? ( + + + + ) : data?.results?.productVariants && data.results.productVariants.length > 0 ? ( + data.results.productVariants.map((variant) => renderVariantRow(variant)) + ) : ( + + + + )} + +
+ + + + + + + + +
+ +
+ هیچ variant ای یافت نشد +
+
+
+ ) +} + +export default ProductVariant \ No newline at end of file diff --git a/src/pages/products/components/CreateVariant.tsx b/src/pages/products/components/CreateVariant.tsx new file mode 100644 index 0000000..61193c0 --- /dev/null +++ b/src/pages/products/components/CreateVariant.tsx @@ -0,0 +1,382 @@ +import Button from '@/components/Button' +import DefaulModal from '@/components/DefaulModal' +import Input from '@/components/Input' +import Select from '@/components/Select' +import { useState, type FC } from 'react' +import { useParams } from 'react-router-dom' +import { useCreateProductVariant, useGetProductById } from '../hooks/useProductData' +import { useGetCategoryVariants } from '@/pages/category' +import type { CreateProductVariantRequestType } from '../types/Types' +import { useGetWarranties } from '@/pages/warranty/hooks/useWarrantyData' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' +import { Formik, Form, Field, ErrorMessage } from 'formik' +import type { FieldInputProps } from 'formik' +import * as Yup from 'yup' + +interface FormikFieldProps { + field: FieldInputProps +} + +const CreateVariant: FC = () => { + + const { id } = useParams() + + const { data: productData } = useGetProductById(id!) + const { data: categoryData } = useGetCategoryVariants(productData?.results?.product?.category?._id ?? '') + const createProductVariant = useCreateProductVariant() + const { data: warrantiesData } = useGetWarranties() + + const [showModal, setShowModal] = useState(false) + + const categoryTheme = productData?.results?.product?.category?.theme + const categoryVariants = categoryData?.results?.data?.category?.categoryVariants + + const validationSchema = Yup.object().shape({ + variantId: Yup.string().required('انتخاب تنوع الزامی است'), + warrantyId: Yup.string().required('انتخاب گارانتی الزامی است'), + order_limit: Yup.number().required('محدودیت سفارش الزامی است').min(1, 'حداقل ۱ عدد'), + sellerSpecialCode: Yup.string().required('کد ویژه فروشنده الزامی است'), + retail_price: Yup.number().required('قیمت فروش الزامی است').min(0, 'قیمت نمی‌تواند منفی باشد'), + package_weight: Yup.number().required('وزن بسته الزامی است').min(0.1, 'وزن باید حداقل ۰.۱ کیلوگرم باشد'), + package_height: Yup.number().required('ارتفاع بسته الزامی است').min(1, 'ارتفاع باید حداقل ۱ سانتی‌متر باشد'), + package_length: Yup.number().required('طول بسته الزامی است').min(1, 'طول باید حداقل ۱ سانتی‌متر باشد'), + package_width: Yup.number().required('عرض بسته الزامی است').min(1, 'عرض باید حداقل ۱ سانتی‌متر باشد'), + postingTime: Yup.number().required('زمان ارسال الزامی است').min(1, 'زمان ارسال باید حداقل ۱ روز باشد'), + stock: Yup.number().required('موجودی انبار الزامی است').min(0, 'موجودی نمی‌تواند منفی باشد') + }) + + const initialValues = { + variantId: '', + warrantyId: '', + order_limit: '', + sellerSpecialCode: '', + retail_price: '', + package_weight: '', + package_height: '', + package_length: '', + package_width: '', + postingTime: '', + stock: '' + } + + const getVariantOptions = () => { + switch (categoryTheme) { + case 'Size': + return categoryVariants?.sizes?.map(size => ({ + value: size._id.toString(), + label: size.value + })) || [] + case 'Color': + return categoryVariants?.colors?.map((color, index) => ({ + value: index.toString(), // استفاده از index به عنوان ID موقت + label: color.name + })) || [] + case 'Meterage': + return categoryVariants?.meterages?.map(meterage => ({ + value: meterage._id.toString(), + label: meterage.value + })) || [] + default: + return [] + } + } + + const getVariantLabel = () => { + switch (categoryTheme) { + case 'Size': return 'سایز' + case 'Color': return 'رنگ' + case 'Meterage': return 'متریاژ' + default: return 'تنوع' + } + } + + const getWarrantyOptions = () => { + return warrantiesData?.results?.warranties?.map(warranty => ({ + value: warranty._id.toString(), + label: `${warranty.name} (${warranty.duration})` + })) || [] + } + + const handleSubmit = (values: typeof initialValues) => { + const baseData: CreateProductVariantRequestType = { + productId: id!, + warrantyId: parseInt(values.warrantyId), + order_limit: parseInt(values.order_limit), + sellerSpecialCode: values.sellerSpecialCode, + retail_price: parseFloat(values.retail_price), + package_weight: parseFloat(values.package_weight), + package_height: parseFloat(values.package_height), + package_length: parseFloat(values.package_length), + package_width: parseFloat(values.package_width), + postingTime: parseInt(values.postingTime), + stock: parseInt(values.stock) + } + + // اضافه کردن variant بر اساس theme + let variantData: CreateProductVariantRequestType + + if (categoryTheme === 'Size') { + variantData = { ...baseData, sizeId: parseInt(values.variantId) } + } else if (categoryTheme === 'Color') { + // برای رنگ‌ها از index به عنوان ID استفاده می‌کنیم + variantData = { ...baseData, colorId: parseInt(values.variantId) + 1 } // +1 برای اینکه ID از 1 شروع شود + } else if (categoryTheme === 'Meterage') { + variantData = { ...baseData, meterageId: parseInt(values.variantId) } + } else { + variantData = baseData + } + + createProductVariant.mutate(variantData, { + onSuccess: () => { + setShowModal(false) + toast.success('تنوع جدید با موفقیت ایجاد شد') + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + + return ( +
+
+ +
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('sellerSpecialCode', e.target.value)} + /> + )} + + +
+ +
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('retail_price', e.target.value)} + /> + )} + + +
+ + {/* اطلاعات بسته‌بندی */} +
+

اطلاعات بسته‌بندی

+
+
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('package_weight', e.target.value)} + /> + )} + + +
+ +
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('package_height', e.target.value)} + /> + )} + + +
+ +
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('package_length', e.target.value)} + /> + )} + + +
+ +
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('package_width', e.target.value)} + /> + )} + + +
+
+
+ + {/* اطلاعات انبارداری */} +
+

اطلاعات انبارداری

+
+
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('postingTime', e.target.value)} + /> + )} + + +
+ +
+ + {({ field }: FormikFieldProps) => ( + ) => setFieldValue('stock', e.target.value)} + /> + )} + + +
+
+
+ + {/* دکمه‌ها */} +
+ + +
+ + )} + + +
+ ) +} + +export default CreateVariant \ No newline at end of file diff --git a/src/pages/products/components/UpdateVariant.tsx b/src/pages/products/components/UpdateVariant.tsx new file mode 100644 index 0000000..c21a475 --- /dev/null +++ b/src/pages/products/components/UpdateVariant.tsx @@ -0,0 +1,322 @@ +import Button from '@/components/Button' +import DefaulModal from '@/components/DefaulModal' +import Input from '@/components/Input' +import Select from '@/components/Select' +import DatePickerComponent from '@/components/DatePicker' +import { useState, type FC, useMemo } from 'react' +import { useParams } from 'react-router-dom' +import { useGetVariantById, useUpdateVariant } from '../hooks/useProductData' +import type { ProductVariantDetailType, UpdateVariantRequestType } from '../types/Types' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' +import { Formik, Form, Field, ErrorMessage } from 'formik' +import type { FieldInputProps } from 'formik' +import * as Yup from 'yup' + +interface FormikFieldProps { + field: FieldInputProps + form: { values: { discount_type: string } } +} + +interface UpdateVariantProps { + variant: ProductVariantDetailType + onUpdate?: () => void +} + +const UpdateVariant: FC = ({ variant, onUpdate }) => { + + const { id } = useParams() + const { data: variantData, isLoading, error } = useGetVariantById(id!, variant._id) + + const updateVariant = useUpdateVariant() + + const [showModal, setShowModal] = useState(false) + + const discountTypeOptions = [ + { value: 'percent', label: 'درصدی (%)' }, + { value: 'fixed', label: 'مبلغ ثابت (تومان)' } + ] + + const validationSchema = Yup.object().shape({ + retailPrice: Yup.number().required('قیمت فروش الزامی است').min(0, 'قیمت نمی‌تواند منفی باشد'), + order_limit: Yup.number().required('محدودیت سفارش الزامی است').min(1, 'حداقل ۱ عدد'), + sellerSpecialCode: Yup.string().required('کد ویژه فروشنده الزامی است'), + stock: Yup.number().required('موجودی انبار الزامی است').min(0, 'موجودی نمی‌تواند منفی باشد'), + discount_type: Yup.string().oneOf(['fixed', 'percent'], 'نوع تخفیف باید fixed یا percent باشد').required('نوع تخفیف الزامی است'), + discount_value: Yup.number().when('discount_type', { + is: 'fixed', + then: (schema) => schema.min(0, 'مقدار تخفیف نمی‌تواند منفی باشد'), + otherwise: (schema) => schema.min(0, 'تخفیف نمی‌تواند منفی باشد').max(100, 'تخفیف نمی‌تواند بیش از ۱۰۰٪ باشد') + }), + specialSale_order_limit: Yup.number().min(0, 'محدودیت فروش ویژه نمی‌تواند منفی باشد'), + specialSale_quantity: Yup.number().min(0, 'تعداد فروش ویژه نمی‌تواند منفی باشد'), + specialSale_endDate: Yup.string() + }) + + const initialValues = useMemo(() => ({ + retailPrice: variantData?.results?.productVariant?.price?.retailPrice?.toString() || '', + order_limit: variantData?.results?.productVariant?.price?.order_limit?.toString() || '', + sellerSpecialCode: variantData?.results?.productVariant?.sellerSpecialCode, + stock: variantData?.results?.productVariant?.stock?.toString() || '', + discount_type: 'percent', + discount_value: variantData?.results?.productVariant?.price?.discount_percent?.toString() || '0', + specialSale_order_limit: variantData?.results?.productVariant?.price?.specialSale_order_limit?.toString() || '', + specialSale_quantity: variantData?.results?.productVariant?.price?.specialSale_quantity?.toString() || '', + specialSale_endDate: variantData?.results?.productVariant?.price?.specialSale_endDate || '' + }), [variantData]) + + const handleSubmit = (values: typeof initialValues) => { + const discountValue = parseFloat(values.discount_value as string) || 0 + + const baseData = { + productId: id!, + variantId: variant._id, + retailPrice: parseFloat(values.retailPrice as string), + order_limit: parseInt(values.order_limit as string), + sellerSpecialCode: values.sellerSpecialCode!, + stock: parseInt(values.stock as string), + saleFormat: { wholeSale: [] }, // فعلاً خالی می‌گذاریم + discount_type: values.discount_type as "fixed" | "percent", + ...(values.discount_type === 'fixed' ? { + discount_value: discountValue + } : { + discount_percent: discountValue + }), + specialSale_order_limit: parseInt(values.specialSale_order_limit as string) || 0, + specialSale_quantity: parseInt(values.specialSale_quantity as string) || 0, + specialSale_endDate: values.specialSale_endDate || '' + } as UpdateVariantRequestType + + updateVariant.mutate(baseData, { + onSuccess: () => { + setShowModal(false) + toast.success('تنوع با موفقیت بروزرسانی شد') + onUpdate?.() + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + + if (isLoading) { + return ( +
+
+

در حال بارگذاری...

+
+ ) + } + + if (error) { + return ( +
+

خطا در بارگذاری اطلاعات تنوع

+

{extractErrorMessage(error)}

+
+ ) + } + + return ( +
+ + +
+ + )} + + + )} +
+ ) +} + +export default UpdateVariant diff --git a/src/pages/products/hooks/useProductData.ts b/src/pages/products/hooks/useProductData.ts index 3f751aa..6261657 100644 --- a/src/pages/products/hooks/useProductData.ts +++ b/src/pages/products/hooks/useProductData.ts @@ -3,7 +3,8 @@ import * as api from "../service/ProductService"; import { type CreateProductDetailRequestType, type CreateProductAttributeRequestType, - type SaveProductRequestType + type SaveProductRequestType, + type UpdateVariantRequestType } from "../types/Types"; export const useCreateProductDetail = () => { @@ -30,3 +31,39 @@ export const useGetProducts = (page: number = 1, limit: number = 10) => { queryFn: () => api.getProducts(page, limit), }); }; + +export const useGetProductVariants = (productId: string) => { + return useQuery({ + queryKey: ['product-variants', productId], + queryFn: () => api.getProductVariants(productId), + enabled: !!productId, + }); +}; + +export const useCreateProductVariant = () => { + return useMutation({ + mutationFn: api.createProductVariant, + }); +}; + +export const useGetProductById = (id: string) => { + return useQuery({ + queryKey: ['product-by-id', id], + queryFn: () => api.getProductById(id), + enabled: !!id, + }); +}; + +export const useUpdateVariant = () => { + return useMutation({ + mutationFn: (variables: UpdateVariantRequestType) => api.updateVariant(variables), + }); +}; + +export const useGetVariantById = (productId: string, variantId: string) => { + return useQuery({ + queryKey: ['variant-by-id', productId, variantId], + queryFn: () => api.getVariantById(productId, variantId), + enabled: !!productId && !!variantId, + }); +}; \ No newline at end of file diff --git a/src/pages/products/service/ProductService.ts b/src/pages/products/service/ProductService.ts index 8458b79..b326b3a 100644 --- a/src/pages/products/service/ProductService.ts +++ b/src/pages/products/service/ProductService.ts @@ -6,7 +6,12 @@ import { type CreateProductAttributeResponseType, type SaveProductRequestType, type SaveProductResponseType, - type GetProductsResponseType + type GetProductsResponseType, + type GetProductVariantsResponseType, + type CreateProductVariantRequestType, + type GetProductByIdResponseType, + type UpdateVariantRequestType, + type GetVariantByIdResponseType, } from "../types/Types"; export const createProductDetail = async (params: CreateProductDetailRequestType): Promise => { @@ -30,3 +35,28 @@ export const getProducts = async (page: number = 1, limit: number = 10): Promise }); return data; }; + +export const getProductVariants = async (productId: string): Promise => { + const { data } = await axios.get(`/admin/products/${productId}/variants`); + return data; +}; + +export const createProductVariant = async (params: CreateProductVariantRequestType)=> { + const { data } = await axios.post(`/admin/products/${params.productId}/variants`, params); + return data; +}; + +export const getProductById = async (id: string): Promise => { + const { data } = await axios.get(`/admin/products/${id}/details`); + return data; +}; + +export const updateVariant = async (params: UpdateVariantRequestType) => { + const { data } = await axios.patch(`/admin/products/${params.productId}/variants`, params); + return data; +}; + +export const getVariantById = async (productId: string, variantId: string): Promise => { + const { data } = await axios.get(`/admin/products/${productId}/variants/${variantId}`); + return data; +}; \ No newline at end of file diff --git a/src/pages/products/types/Types.ts b/src/pages/products/types/Types.ts index 429647e..ca2f1c8 100644 --- a/src/pages/products/types/Types.ts +++ b/src/pages/products/types/Types.ts @@ -197,3 +197,178 @@ export type GetProductsResponseType = { categories: CategoryListType[]; }; }; + +// تایپ‌های مربوط به variant های محصول +export type ShipmentMethodType = { + _id: number; + name: string; + description: string; + deliveryTime: number; + deliveryType: string; +}; + +export type SizeType = { + _id: number; + value: string; +}; + +export type ProductVariantDetailType = { + _id: string; + product_id: number; + title_fa: string; + title_en: string; + model: string; + market_status: string; + productCode: string; + category_title_fa: string; + is_variant_active: boolean; + coverImage: string; + price: ProductPriceType; + stock: number; + sellerSpecialCode?: string; + shipmentMethod: ShipmentMethodType[]; + product_status: string; + isFreeShip: boolean; + isWholeSale: boolean; + size: SizeType; +}; + +export type GetProductVariantsResponseType = { + status: number; + success: boolean; + results: { + count: number; + productVariants: ProductVariantDetailType[]; + }; +}; + +export type GetVariantByIdResponseType = { + status: number; + success: boolean; + results: { + productVariant: ProductVariantDetailType; + }; +}; + +export type CreateProductVariantRequestType = { + productId: string; + colorId?: number; + sizeId?: number; + meterageId?: number; + warrantyId: number; + order_limit: number; + sellerSpecialCode: string; + retail_price: number; + package_weight: number; + package_height: number; + package_length: number; + package_width: number; + postingTime: number; + stock: number; +}; + +// تایپ‌های مربوط به جزئیات محصول +export type ProductSpecificationType = { + title: string; + values: string[]; +}; + +export type ProductCategoryType = { + _id: string; + title_fa: string; + title_en: string; + icon: string; + imageUrl: string; + description: string; + variants: number[]; + hierarchy: string[]; + leaf: boolean; + parent: string; + deleted: boolean; + createdAt: string; + updatedAt: string; + theme: string; + url: string; + children: ProductCategoryType[]; +}; + +export type ProductBrandType = { + _id: string; + title_fa: string; + title_en: string; + category: string | null; + status: string; + description: string; + logoUrl: string; + images: string[]; + deleted: boolean; + createdAt: string; + updatedAt: string; + url: string; +}; + +export type ProductDetailsType = { + _id: number; + title_fa: string; + title_en: string; + seoTitle: string | null; + seoDescription: string | null; + model: string; + source: string; + description: string; + metaDescription: string; + tags: string[]; + advantages: string[]; + disAdvantages: string[]; + totalRate: number; + category: ProductCategoryType; + shop: string; + brand: ProductBrandType; + status: string; + adminComments: string | null; + step: number; + isFake: boolean; + variants: string[]; + voice: string | null; + deleted: boolean; + specifications: ProductSpecificationType[]; + createdAt: string; + updatedAt: string; + imagesUrl: ProductImagesType; + url: string; +}; + +export type GetProductByIdResponseType = { + status: number; + success: boolean; + results: { + product: ProductDetailsType; + }; +}; + +// تایپ‌های مربوط به آپدیت variant +export type WholeSaleItemType = { + from: number; + to: number; + price: number; +}; + +export type SaleFormatType = { + wholeSale: WholeSaleItemType[]; +}; + +export type UpdateVariantRequestType = { + productId: string; + variantId: string; + retailPrice: number; + order_limit: number; + sellerSpecialCode: string; + stock: number; + saleFormat: SaleFormatType; + discount_type: "fixed" | "percent"; + discount_value?: number; + discount_percent?: number; + specialSale_order_limit: number; + specialSale_quantity: number; + specialSale_endDate: string; +}; diff --git a/src/pages/warranty/hooks/useWarrantyData.ts b/src/pages/warranty/hooks/useWarrantyData.ts new file mode 100644 index 0000000..f309d0c --- /dev/null +++ b/src/pages/warranty/hooks/useWarrantyData.ts @@ -0,0 +1,10 @@ +import { useQuery } from "@tanstack/react-query"; +import * as api from "../service/WarrantyService"; +import type { WarrantyResponse } from "../types/Types"; + +export const useGetWarranties = () => { + return useQuery({ + queryKey: ["warranties"], + queryFn: () => api.getWarranties(), + }); +}; diff --git a/src/pages/warranty/service/WarrantyService.ts b/src/pages/warranty/service/WarrantyService.ts new file mode 100644 index 0000000..707ff0a --- /dev/null +++ b/src/pages/warranty/service/WarrantyService.ts @@ -0,0 +1,9 @@ +import axios from "../../../config/axios"; +import type { GetWarrantiesParams, WarrantyResponse } from "../types/Types"; + +export const getWarranties = async ( + params?: GetWarrantiesParams +): Promise => { + const { data } = await axios.get("/warranty", { params }); + return data; +}; diff --git a/src/pages/warranty/types/Types.ts b/src/pages/warranty/types/Types.ts new file mode 100644 index 0000000..34c1630 --- /dev/null +++ b/src/pages/warranty/types/Types.ts @@ -0,0 +1,35 @@ +export interface Warranty { + _id: number; + name: string; + status: string; + duration: string; + logoUrl: string; + deleted: boolean; + createdAt: string; + updatedAt: string; +} + +export interface Pager { + page: number; + limit: number; + totalItems: number; + totalPages: number; + prevPage: boolean; + nextPage: boolean; +} + +export interface WarrantyResults { + warranties: Warranty[]; + pager: Pager; +} + +export interface WarrantyResponse { + status: number; + success: boolean; + results: WarrantyResults; +} + +export interface GetWarrantiesParams { + page?: number; + limit?: number; +} diff --git a/src/router/Main.tsx b/src/router/Main.tsx index fff96c0..15f18b4 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -13,6 +13,7 @@ import Variant from '../pages/category/Variant' import Attributes from '../pages/category/Attributes' import CreateProduct from '../pages/products/Create' import ListProduct from '../pages/products/List' +import ProductVariant from '@/pages/products/ProductVariant' const MainRouter: FC = () => { @@ -38,6 +39,7 @@ const MainRouter: FC = () => { } /> } /> } /> + } />
diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index 111b22f..fcbc719 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -62,7 +62,7 @@ const SideBar: FC = () => { return ( <> { - openSidebar &&
setOpenSidebar(false)} /> + openSidebar &&
setOpenSidebar(false)} /> }